Swift

13. 간단한 BMI 계산 앱 만들기

리버윤 2024. 11. 27. 16:46
728x90

 

- xcode에서 파일 지울 때 항상 뜨는 거

빨간건 삭제되는게 아니라 안보이게 만드는 것 뿐임.

완전히 지우려면 Move to Trash 눌러야함

 

 

- mp4파일 프로젝트에 복사

파일을 끌어다 놓으면 되는데 넣을 때 저 노란 네모 부분 꼭 체크 해줘야 함

 

 

- Video Scene 스토리보드와 VideoViewController 클래스 연결.

VideoViewController 선택 해주어야 함. enter눌러서 확인까지 *identity inspector

 

//
//  VideoViewController.swift
//  BMI_kys
//
//  Created by 소프트웨어컴퓨터 on 2024/11/27.
//

import UIKit
import AVKit

class VideoViewController: UIViewController {
    @IBAction func playVideo(_ sender: UIButton) {
        let videoPath : String? = Bundle.main.path(forResource: "APT", ofType: "mp4")
        let videoURL = URL(filePath: videoPath!)
        let player1 = AVPlayer(url: videoURL)
        
        let playerController = AVPlayerViewController()
        playerController.player = player1
        present(playerController, animated: true) //애니메이션을 줄것인가
        player1.play()
//        guard let videoPath = Bundle.main.path(forResource: "APT", ofType: "mp4")
//        else {
//            print("비디오 파일이 없습니다.")
//            return
//        }
//
//        let videoURL = URL(filePath: videoPath)
//        let player = AVPlayer(url: videoURL)
//        let playerViewController = AVPlayerViewController()
//        playerViewController.player = player
//
//        self.present(playerViewController, animated: true) {
//            player.play() // 비디오 플레이어가 표시된 후 비디오 재생 시작
//        }
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
    }

}

 

 

`present` 메소드란?

`present` 메소드는 iOS의 UIViewController 클래스에서 제공되는 메소드로, 현재의 뷰 컨트롤러 위에 다른 뷰 컨트롤러를 모달 방식으로 표시하는 데 사용됩니다. 이 메소드는 사용자 인터페이스의 전환을 관리하는 중요한 방법입니다. 아래는 `present` 메소드의 상세한 설명입니다.

### 메소드 시그니처

//swift
func present(_ viewControllerToPresent: UIViewController, animated: Bool, completion: (() -> Void)?)

 

  • 매개변수 설명
    1. viewControllerToPresent
         - 표시할 뷰 컨트롤러의 인스턴스입니다. 이 인스턴스가 모달로 표시됩니다.
    2. animated
         - Bool 타입의 값으로, `true`로 설정하면 뷰 컨트롤러가 표시될 때 애니메이션 효과가 적용됩니다. `false`로 설정하면 애니메이션 없이 즉시 표시됩니다.
    3. completion
         - 클로저로, 뷰 컨트롤러가 표시된 후에 실행될 코드를 포함합니다. 이 매개변수는 선택 사항이며, 필요하지 않으면 `nil`로 설정할 수 있습니다.
  •  사용 예시
swift
let newViewController = NewViewController() // 표시할 뷰 컨트롤러 생성
self.present(newViewController, animated: true, completion: {
    print("새로운 뷰 컨트롤러가 표시되었습니다.")
})

 

  • 동작 방식
    - `present` 메소드를 호출하면, 현재의 뷰 컨트롤러가 새로운 뷰 컨트롤러를 모달로 표시합니다.
    - 모달 방식으로 표시된 뷰 컨트롤러는 사용자가 닫거나 (대개는 "취소" 또는 "닫기" 버튼을 통해) 다른 방법으로 다시 돌아갈 때까지 화면에 유지됩니다.
    - 모달 뷰 컨트롤러는 화면의 전체를 차지하거나 일부만 차지할 수 있으며, 이 설정은 뷰 컨트롤러의 `modalPresentationStyle` 속성으로 조정할 수 있습니다.
  • 추가 사항
    - 표시된 뷰 컨트롤러는 `dismiss` 메소드를 사용하여 닫을 수 있습니다. `dismiss`는 현재 표시된 뷰 컨트롤러를 제거하고 이전 뷰 컨트롤러로 돌아갑니다.
    - 모달 전환은 사용자 경험에서 중요한 부분으로, 사용자의 흐름을 방해하지 않도록 적절하게 사용해야 합니다.

 enter를 누르면 중괄호가 나타남

 

 

- 최종 비디오 출력 소스 

//
//  VideoViewController.swift
//  BMI_kys
//
//  Created by 소프트웨어컴퓨터 on 2024/11/27.
//

import UIKit
import AVKit

class VideoViewController: UIViewController {
    @IBAction func playVideo(_ sender: UIButton) {
        guard let videoPath = Bundle.main.path(forResource: "APT", ofType: "mp4") //guard let을 쓰면 옵셔널이 풀린 애가 되서 옵셔널 안써줘도 됨
        else {
            return
        }
        let videoURL = URL(filePath: videoPath)
        let player1 = AVPlayer(url: videoURL)
        
        let playerController = AVPlayerViewController()
        playerController.player = player1
        present(playerController, animated: true) {
            player1.play()
        } // 중괄호? : 컨트롤러가 먼저 뜨고 영상이 떠야함
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
    }

}

 

 

- chat gpt. 아래 세 소스가 어떻게 다른가?

present(playerViewController, animated: true)
player.play()

self.present(playerViewController, animated: true)
player.play()

self.present(playerViewController, animated: true) {
player.play()
} //후행 클로저(trailing closure)

 

세 가지 코드 조각은 모두 `present` 메소드를 사용하여 `playerViewController`를 표시하고, 그 이후에 비디오를 재생하는 방식에서 차이가 있다.
  1. `present(playerViewController, animated: true)`

present(playerViewController, animated: true)
player.play()

 - `playerViewController`를 애니메이션과 함께 표시합니다.
  - 이후에 `player.play()`를 호출하여 즉시 비디오를 재생합니다.
   **문제점**: 
  - 이 경우 비디오 플레이어가 화면에 나타나기 전에 `player.play()`가 호출되기 때문에, 사용자에게 플레이어가 보이기 전에 비디오가 재생될 수 있습니다. 따라서 비디오가 재생되는 동안 플레이어 UI가 나타나지 않아 사용자 경험이 좋지 않을 수 있습니다.

  2. `self.present(playerViewController, animated: true)`

self.present(playerViewController, animated: true)
player.play()


 **설명**: 
  - 이 코드 역시 `playerViewController`를 애니메이션과 함께 표시하지만, `self` 키워드는 현재 뷰 컨트롤러를 명시적으로 나타냅니다. 
  - 이후에 `player.play()`를 호출하여 비디오를 재생합니다.
**문제점**: 
  - 첫 번째 경우와 마찬가지로, 비디오 플레이어가 화면에 나타나기 전에 재생이 시작되므로 사용자 경험에 문제가 발생할 수 있습니다.

3. `self.present(playerViewController, animated: true) { player.play() }`

self.present(playerViewController, animated: true) {
    player.play()
}


**설명**: 
  - `playerViewController`를 애니메이션과 함께 표시하고, 그 뷰 컨트롤러가 화면에 나타난 후에 클로저 안의 코드를 실행합니다. 이 경우 `player.play()`가 클로저 안에 있으므로, 플레이어가 화면에 완전히 나타난 후에 비디오가 재생됩니다.
**장점**: 
  - 이 방식은 비디오 플레이어의 UI가 사용자에게 표시된 후에 비디오가 재생되므로, 사용자 경험이 더 매끄럽고 자연스럽습니다.

- 요약
- 첫 번째와 두 번째 방식은 비디오가 재생되기 전에 플레이어 UI가 나타나지 않을 수 있어 사용자 경험이 좋지 않을 수 있습니다.
- 세 번째 방식은 플레이어가 화면에 나타난 후에 비디오를 재생하므로, 사용자에게 더 나은 경험을 제공합니다. 

 

- present

 = nil 은 디폴트 인자.(completion을 쓰지않으면 nil이 자동으로 들어감)

  

 

- 웹 브라우저 띄우기

위에서 영상 재생 띄우는 것과 같이 뷰 컨드롤러 하나 꺼내고 파일 하나 만들고 파일이랑 뷰 컨드롤러랑 연결 시킨 후

웹 씬에 웹 추가

 첫 번째는 사용xx

두 번째꺼 사용하기

 

 

- 코드작성

흐린건 디폴트 값이 있어서 안써도 된다는 거

= 이게 디폴트 인자가 있다는거

 

- ! 풀기 (guard let) *가드렛은 함수 안에서만 사용 가능

 

- 첫 화면엔 내 블로그, 버튼 누르면 네이버

//
//  WebViewController.swift
//  BMI_kys
//
//  Created by 소프트웨어컴퓨터 on 2024/11/27.
//

import UIKit
import WebKit

class WebViewController: UIViewController {
    
    @IBOutlet weak var webView: WKWebView!
    
    @IBAction func goNaver(_ sender: UIButton) {
        guard let url = URL(string: "https://m.naver.com")  //https 꼭 써줘야함 s빠지면 실행 안됨
        else {
            return
        }  //https 꼭 써줘야함 s빠지면 실행 안됨
        let request = URLRequest(url: url)
        webView.load(request)
    }
    
    override func viewDidLoad() { //앱 실행되자마자 바로 실행되는 부분
        super.viewDidLoad()
        guard let url = URL(string: "https://river-yun28.tistory.com/")  //https 꼭 써줘야함 s빠지면 실행 안됨
        else {
            return
        }  //https 꼭 써줘야함 s빠지면 실행 안됨
        let request = URLRequest(url: url)
        webView.load(request)
        //실행하자마자 내 블로그가 나타나고 아래 네이버 버튼을 누르면 네이버로 넘어감

        // Do any additional setup after loading the view.
    }

}

 

- refactoring 한 코드

//
//  WebViewController.swift
//  BMI_kys
//
//  Created by 소프트웨어컴퓨터 on 2024/11/27.
//

import UIKit
import WebKit

class WebViewController: UIViewController {
    
    @IBOutlet weak var webView: WKWebView!
    
    // URL을 받아서 WKWebView에 로드하는 메서드
    private func loadURL(_ urlString: String) {
        guard let url = URL(string: urlString) else {
            print("유효하지 않은 URL: \(urlString)") // URL이 유효하지 않을 경우 로그 출력
            return
        }
        let request = URLRequest(url: url)
        webView.load(request)
    }
    
    
    @IBAction func goNaver(_ sender: UIButton) {
        loadURL("https://m.naver.com") // 네이버 버튼 클릭 시 네이버 로드
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        loadURL("https://river-yun28.tistory.com/") // 앱 실행 시 내 블로그 로드
    }
    
}

 

- 소스의 문서화

//
//  WebViewController.swift
//  BMI_kys
//
//  Created by 소프트웨어컴퓨터 on 2024/11/27.
//

import UIKit
import WebKit

/// 웹 페이지를 로드하는 뷰 컨트롤러
/// 
/// 이 클래스는 WKWebView를 사용하여 웹 페이지를 표시하고, 
/// 버튼 클릭 시 다른 웹 페이지로 이동할 수 있는 기능을 제공합니다.
class WebViewController: UIViewController {
    
    @IBOutlet weak var webView: WKWebView! // WKWebView 인스턴스 연결

    /// 주어진 URL 문자열을 사용하여 WKWebView에 웹 페이지를 로드합니다.
    /// - Parameter urlString: 로드할 웹 페이지의 URL 문자열
    private func loadURL(_ urlString: String) {
        // URL 문자열을 URL 객체로 변환
        guard let url = URL(string: urlString) else {
            print("유효하지 않은 URL: \(urlString)") // URL이 유효하지 않을 경우 로그 출력
            return
        }
        let request = URLRequest(url: url) // URLRequest 객체 생성
        webView.load(request) // WKWebView에 요청 로드
    }
    
    /// 네이버 웹 페이지로 이동하는 버튼 액션
    /// 
    /// 사용자가 네이버 버튼을 클릭하면, 
    /// 네이버 모바일 웹사이트를 로드합니다.
    @IBAction func goNaver(_ sender: UIButton) {
        loadURL("https://m.naver.com") // 네이버 버튼 클릭 시 네이버 로드
    }
    
    /// 뷰가 메모리에 로드되었을 때 호출되는 메서드
    ///
    /// 이 메서드는 뷰가 로드될 때 초기 URL로 
    /// 사용자의 블로그 페이지를 로드합니다.
    override func viewDidLoad() {
        super.viewDidLoad()
        loadURL("https://river-yun28.tistory.com/") // 앱 실행 시 내 블로그 로드
    }
}
//
//  VideoViewController.swift
//  BMI_kys
//
//  Created by 소프트웨어컴퓨터 on 2024/11/27.
//

import UIKit
import AVKit

class VideoViewController: UIViewController {
    @IBAction func playVideo(_ sender: UIButton) {
        guard let videoPath = Bundle.main.path(forResource: "APT", ofType: "mp4") //guard let을 쓰면 옵셔널이 풀린 애가 되서 옵셔널 안써줘도 됨
        else {
            return
        }
        let videoURL = URL(filePath: videoPath)
        let player1 = AVPlayer(url: videoURL)
        
        let playerController = AVPlayerViewController()
        playerController.player = player1
        present(playerController, animated: true) {
            player1.play()
        } // 중괄호? : 컨트롤러가 먼저 뜨고 영상이 떠야함
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
    }

}
//
//  ViewController.swift
//  BMI_kys
//
//  Created by 소프트웨어컴퓨터 on 2024/11/13.
//

import UIKit

/// BMI 계산을 위한 뷰 컨트롤러
///
/// 이 클래스는 사용자의 키와 체중을 입력받아 BMI를 계산하고,
/// 그 결과를 화면에 표시하는 기능을 제공합니다.
class ViewController: UIViewController {
    
    @IBOutlet weak var txtHeight: UITextField! // 사용자 입력을 위한 키 텍스트 필드
    @IBOutlet weak var txtWeight: UITextField! // 사용자 입력을 위한 체중 텍스트 필드
    @IBOutlet weak var lblResult: UILabel! // BMI 결과를 표시할 레이블

    /// 스위치 상태 변경 시 호출되는 액션 메서드
    ///
    /// 스위치의 상태에 따라 특정 동작을 수행할 수 있습니다.
    /// - Parameter sender: 상태가 변경된 UISwitch
    @IBAction func aa(_ sender: UISwitch) {
        if sender.isOn {
            // 스위치가 켜질 때의 동작
        } else {
            // 스위치가 꺼질 때의 동작
        }
    }
    
    /// BMI 계산 버튼 클릭 시 호출되는 액션 메서드
    ///
    /// 사용자가 입력한 키와 체중을 바탕으로 BMI를 계산하고,
    /// 그 결과를 레이블에 표시합니다.
    /// - Parameter sender: 버튼 클릭 이벤트
    @IBAction func calcBmi(_ sender: UIButton) {
        // 키와 체중 입력 여부 확인
        if txtHeight.text == "" || txtWeight.text == "" {
            lblResult.textColor = .red
            lblResult.text = "키와 체중을 입력하세요" // 오류 메시지 표시
            return
        } else {
            // 입력된 값을 Double로 변환
            let weight = Double(txtWeight.text!)!
            let height = Double(txtHeight.text!)!
            // BMI 계산 (kg/m²)
            let bmi = weight / (height * height * 0.0001)
            let shortenedBmi = String(format: "%.1f", bmi) // 소수점 1자리로 포맷
            
            // BMI에 따른 판정 및 배경색 설정
            var body = ""
            var color = UIColor.white
            
            if bmi >= 40 {
                color = UIColor(displayP3Red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0) // 3단계 비만
                body = "3단계 비만"
            } else if bmi >= 30 && bmi < 40 {
                color = UIColor(displayP3Red: 0.7, green: 0.0, blue: 0.0, alpha: 1.0) // 2단계 비만
                body = "2단계 비만"
            } else if bmi >= 25 && bmi < 30 {
                color = UIColor(displayP3Red: 0.4, green: 0.0, blue: 0.0, alpha: 1.0) // 1단계 비만
                body = "1단계 비만"
            } else if bmi >= 18.5 && bmi < 25 {
                color = UIColor(displayP3Red: 0.0, green: 1.0, blue: 0.0, alpha: 1.0) // 정상
                body = "정상"
            } else {
                color = UIColor(displayP3Red: 0.0, green: 0.0, blue: 1.0, alpha: 1.0) // 저체중
                body = "저체중"
            }
            
            // 결과 레이블 설정
            lblResult.backgroundColor = color // 배경색 설정
            lblResult.textColor = .white // 텍스트 색상 설정
            
            lblResult.clipsToBounds = true // 클리핑 설정
            lblResult.layer.cornerRadius = 10 // 코너 라운딩 설정
            lblResult.text = "BMI:\(shortenedBmi), 판정:\(body)" // 최종 결과 표시
        }
    }
    
    /// 뷰가 메모리에 로드될 때 호출되는 메서드
    ///
    /// 이 메서드는 뷰가 로드될 때 추가적인 설정을 수행할 수 있는 장소입니다.
    override func viewDidLoad() {
        super.viewDidLoad()
        // 추가적인 설정을 여기에 작성할 수 있습니다.
    }
}

 

 

 

728x90