카테고리 없음

iOS 기초 프로그래밍 13주차

sw0913 2024. 11. 27. 16:33

Video Scene의 Custom Class를 지정해줘야해요

VideoViewController를 지정할 것이라면 V만 쳐도 자동으로 나타나야 하는데

저는 나타나질 않습니다 ㅠㅠ

commed + R 로 한번 실행하고 되니깐 갑자기 됩니다!

 

iOS 함수중에 중요한 함수에요.

# present 함수

iOS에서 present 함수는 새로운 뷰 컨트롤러를 화면에 표시하는 데 사용되는 메서드입니다. 보통 새로운 화면을 띄울 때 사용하며, 특히 **모달 방식(modal)**으로 뷰 컨트롤러를 표시할 때 유용합니다. 모달 방식은 새로운 화면이 기존 화면 위에 덮어져서 나타나는 형태를 말합니다.

 

이 메서드는 다음과 같은 세 가지 파라미터를 가집니다:

  1. viewControllerToPresent (UIViewController)
    • 이 파라미터는 현재 화면에 표시될 새로운 뷰 컨트롤러입니다.
    • 예를 들어, AVPlayerViewController, UIAlertController, 혹은 사용자 정의 뷰 컨트롤러가 될 수 있습니다.
    • 이 뷰 컨트롤러는 현재 화면 위에 "모달" 방식으로 표시됩니다. 모달 방식은 새로운 화면이 기존 화면 위에 나타나는 방식으로, 사용자가 새로운 화면을 닫을 때까지 원래 화면으로 돌아갈 수 없습니다.
  2. animated (Bool)
    • Bool 타입으로, 화면 전환이 애니메이션을 통해 이루어질지 여부를 결정합니다.
      • true: 화면 전환이 애니메이션을 통해 이루어집니다.
      • false: 화면 전환이 즉시 이루어지며 애니메이션 없이 새로운 뷰가 나타납니다.
    • 일반적으로 화면 전환 시 애니메이션을 사용하는 것이 자연스러워 true로 설정합니다.
  3. completion ((() -> Void)?)
    • completion은 화면 전환이 완료된 후 실행될 클로저입니다.
    • 이 파라미터는 옵셔널이며 기본값은 nil입니다. 즉, 특별히 처리할 일이 없다면 생략할 수 있습니다.
    • 예를 들어, 화면 전환 후 비디오를 재생하거나, 어떤 처리를 완료했음을 알리는 작업을 할 수 있습니다.

present의 동작 방식

  • 모달 방식: present 메서드는 새로운 뷰 컨트롤러를 현재 화면 위에 모달 방식으로 표시합니다. 즉, 새로운 뷰가 기존 화면을 덮어씁니다. 사용자가 모달 화면을 닫을 때까지 원래 화면으로 돌아갈 수 없습니다.
  • 뒤로 가기: 모달 방식으로 띄운 화면은 dismiss 메서드를 호출하여 닫을 수 있습니다. 보통 화면을 닫을 때는 dismiss(animated: true)를 사용하여 애니메이션을 추가하여 원래 화면으로 돌아갑니다.

 

동영상을 재생시킬 수 있는 코드입니다. 하지만 완벽한 코드는 아니에요

import UIKit
import AVKit

class VideoViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
    }
    
    @IBAction func playVideo(_ sender: UIButton) {
        let videoPath : String? = Bundle.main.path(forResource: "bmi", ofType: "mp4")
        let videoURL = URL(filePath: videoPath!)
        let player = AVPlayer(url: videoURL)
        let playerController = AVPlayerViewController()
        playerController.player = player
        present(playerController, animated: true)
        player.play(

 

1. URL(filePath:) 사용 오류

  • URL(filePath:)는 파일 경로를 나타내는 문자열을 사용하여 URL을 생성하는 초기화 방식입니다. 하지만 Bundle.main.path(forResource:ofType:) 메서드는 String?을 반환하므로, 이를 URL(filePath:)에 그대로 전달하는 것은 안전하지 않습니다.
  • URL(filePath:) 대신 URL(fileURLWithPath:)를 사용하는 것이 더 안전합니다.

2. 강제 언래핑 (!)

  • videoPath! 부분에서 강제로 언래핑을 하고 있습니다. 이는 만약 파일이 존재하지 않으면 앱이 크래시를 일으킬 수 있습니다. 이를 안전하게 처리하려면 옵셔널 바인딩(if let 또는 guard let)을 사용하여 안전하게 값을 처리해야 합니다.

3. present 후 player.play() 호출 순서

  • present 메서드는 비동기적으로 작동하므로, player.play()가 present 호출 이후에 실행되어야 합니다. present가 완료된 후에 비디오가 재생되도록 하기 위해서는 completion 블록을 사용해야 합니다.

수정본

import UIKit
import AVKit
import AVFoundation

class VideoViewController: UIViewController {

    @IBAction func playVideo(_ sender: UIButton) {
        // 비디오 파일 경로 가져오기 (Bundle에서)
        if let videoPath = Bundle.main.path(forResource: "bmi", ofType: "mp4") {
            let videoURL = URL(fileURLWithPath: videoPath)  // 올바른 URL 생성
            
            // AVPlayer 인스턴스 생성
            let player = AVPlayer(url: videoURL)
            
            // AVPlayerViewController 인스턴스 생성
            let playerController = AVPlayerViewController()
            playerController.player = player
            
            // 모달로 AVPlayerViewController 표시
            present(playerController, animated: true, completion: {
                // 플레이어가 화면에 나타난 후 비디오 재생
                player.play()
            })
        } else {
            print("비디오 파일을 찾을 수 없습니다.")
        }
    }
}

 

# 첫번째 영상 재생 코드에 대한 자세한 설명

import UIKit
import AVKit
import AVFoundation

class VideoViewController: UIViewController {

    // 비디오를 재생할 때 호출되는 액션 메서드
    @IBAction func playVideo(_ sender: UIButton) {
        
        // 1. Bundle에서 비디오 파일 경로를 안전하게 가져옵니다.
        if let videoPath = Bundle.main.path(forResource: "bmi", ofType: "mp4") {
            
            // 2. videoPath가 유효한 경로일 경우, 이를 사용해 URL을 생성합니다.
            // Bundle에서 가져온 경로를 통해 fileURLWithPath()로 URL을 생성
            let videoURL = URL(fileURLWithPath: videoPath)
            
            // 3. AVPlayer 객체를 생성하여 비디오 파일을 로드합니다.
            // AVPlayer는 AVPlayerViewController에서 재생할 수 있는 미디어를 처리하는 객체입니다.
            let player = AVPlayer(url: videoURL)
            
            // 4. AVPlayerViewController 객체를 생성합니다.
            // AVPlayerViewController는 비디오 재생 화면을 제공하는 컨트롤러로, 플레이어를 연결할 수 있습니다.
            let playerController = AVPlayerViewController()
            playerController.player = player  // AVPlayer를 AVPlayerViewController에 연결
            
            // 5. AVPlayerViewController를 화면에 모달로 표시합니다.
            // present() 메서드는 현재 뷰 컨트롤러에서 다른 뷰 컨트롤러를 표시할 때 사용됩니다.
            // 모달로 표시되며, animated: true로 애니메이션을 추가하고,
            // completion 블록에서 비디오 재생을 시작합니다.
            present(playerController, animated: true, completion: {
                // 화면 전환 후 AVPlayer가 준비되면 비디오를 재생 시작합니다.
                // player.play()는 AVPlayer 객체에서 비디오 재생을 시작하는 메서드입니다.
                player.play()
            })
        } else {
            // 6. 만약 비디오 파일이 존재하지 않는 경우, 콘솔에 오류 메시지를 출력합니다.
            // Bundle에서 해당 파일을 찾을 수 없는 경우를 처리합니다.
            print("비디오 파일을 찾을 수 없습니다.")
        }
    }
}

 

# guard let 을 이용한 방법

@IBAction func playVideo(_ sender: UIButton) {
        guard let videoPath = Bundle.main.path(forResource: "bmi", ofType: "mp4") else{ return }
        
        let videoURL = URL(filePath: videoPath)
        let player = AVPlayer(url: videoURL)
        let playerController = AVPlayerViewController()
        playerController.player = player
        present(playerController, animated: true)
        player.play()
  
    }

1. 명확한 오류 처리 및 흐름 제어

  • guard let은 주로 필수 조건을 확인하고, 그 조건을 만족하지 않으면 즉시 메서드를 종료하도록 하는 데 사용됩니다.
  • guard를 사용하면 **"if 조건을 만족하지 않으면 즉시 종료"**라는 의도가 명확하게 표현됩니다.
  • 반면, if let은 조건을 만족하는 경우에만 이후 코드가 실행되기 때문에, 조건을 만족하지 않았을 때의 흐름이 덜 명확합니다.

guard let의 특징:

  • 조건이 충족되지 않으면 메서드나 함수에서 **즉시 return**하거나 throw 등으로 흐름을 종료시킵니다.
  • 조건이 성공했을 때만 이후 코드를 계속 실행합니다.
  • 코드 흐름이 명확하고 깔끔해집니다. 조건을 체크하고, 조건을 만족하지 않으면 바로 return하기 때문에, 그 후의 코드가 조건을 만족하는 경우에만 실행될 것이라는 보장이 생깁니다.

if let의 특징:

  • 조건이 만족되면 실행되지만, 만족하지 않을 때 추가 코드가 실행됩니다. if 블록 안에서 추가 작업을 해야 하므로, 흐름이 좀 더 복잡해집니다.
  • if let을 사용할 때 조건이 만족하지 않으면 나머지 코드를 계속 진행할 수 있기 때문에, 코드 흐름을 더 어렵게 만들 수 있습니다.

2. 코드 가독성 및 유지보수성

  • guard let은 조건이 만족하지 않으면 메서드의 실행을 중단시키므로, 메서드 바깥에서 어떤 작업을 해야 하는지 명확하게 보여줍니다.
  • 이를 통해 코드의 가독성유지보수성이 높아집니다. 특히, 조건이 중요한 경우(예: 비디오 파일 경로가 없을 때), 그 처리를 guard로 명확하게 보여주면 코드를 빠르게 이해할 수 있습니다.

 

 

여기서 Enter를 치면 { code } 로 바뀌어요

 

 

이거는 url만 있으면 보여주는 내장 Web view에요

 

 

강제 언래핑을 하여 사용 할수 있지만 권장하는 방법은 아니기때문에 guard let을 이용해보도록 해요

 

중복되는 부분을 제거하여 리펙토링 했습니다

 

@ WebViewController를 문서화

import UIKit
import WebKit

/**
  `WebViewController`는 웹 페이지를 로드하고 표시하는 뷰 컨트롤러입니다.
  이 클래스는 초기 로드 시 기본 웹사이트를 로드하고, 버튼을 클릭하여 다른 웹사이트로 이동하는 기능을 제공합니다.
*/
class WebViewController: UIViewController {

    // MARK: - IBOutlet

    /// 웹 페이지를 표시하는 `WKWebView` 객체
    /// 이 IBOutlet을 통해 스토리보드에서 연결된 웹 뷰를 사용할 수 있습니다.
    @IBOutlet weak var webView: WKWebView!
    
    // MARK: - Lifecycle Methods
    
    /**
      뷰가 로드될 때 호출되는 메서드입니다.
      기본적으로 `viewDidLoad()`에서 설정한 URL (https://m.youtube.com)로 첫 번째 웹 페이지를 로드합니다.
    */
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 기본 웹사이트 로드 (Tistory)
        loadWebsite(urlString: "https://sw0913.tistory.com/")
    }
    
    // MARK: - Actions
    
    /**
      'Naver' 버튼이 클릭되었을 때 호출되는 메서드입니다.
      클릭 시, 네이버 모바일 웹사이트 (`m.naver.com`)를 로드합니다.
      
      - Parameter sender: 버튼 클릭 이벤트
    */
    @IBAction func goNaver(_ sender: UIButton) {
        // 네이버 웹사이트 로드
        loadWebsite(urlString: "https://m.naver.com")
    }
    
    // MARK: - Helper Methods
    
    /**
      주어진 URL 문자열을 사용하여 웹 페이지를 로드하는 메서드입니다.
      `URL`이 유효한 경우, 해당 URL을 `WKWebView`에서 로드합니다.
      
      - Parameter urlString: 로드할 웹 페이지의 URL 문자열
    */
    private func loadWebsite(urlString: String) {
        // URL 객체를 생성하고 유효성 검사
        guard let url = URL(string: urlString) else {
            print("Invalid URL: \(urlString)") // URL이 유효하지 않으면 콘솔에 오류 메시지 출력
            return
        }
        
        // URLRequest 객체 생성
        let request = URLRequest(url: url)
        
        // 웹 뷰에서 URL 요청 로드
        webView.load(request)
    }
}

문서화된 코드 설명:

1. 클래스 설명 (WebViewController):

  • WebViewController는 UIViewController를 상속받는 뷰 컨트롤러 클래스입니다. 이 클래스는 WKWebView를 사용하여 웹 페이지를 로드하고 표시합니다. 뷰가 로드될 때 기본 URL을 표시하고, 버튼 클릭 시 다른 웹사이트로 이동하는 기능을 제공합니다.

2. 변수 설명:

  • @IBOutlet weak var webView: WKWebView!:
    • WKWebView 객체는 웹 페이지를 표시하는 데 사용됩니다. 이 객체는 스토리보드에서 연결되어 있으며, 뷰가 로드되면 지정된 URL을 표시합니다.
    • IBOutlet을 사용하여 스토리보드와 연결된 UI 요소를 코드에서 접근할 수 있게 합니다.

3. 메서드 설명:

  • viewDidLoad():
    • viewDidLoad()는 뷰가 메모리에 로드된 후 한 번만 호출되는 메서드입니다. 이 메서드 내에서 기본 웹사이트를 로드하는 작업을 합니다.
    • loadWebsite(urlString:) 메서드를 호출하여 기본 웹사이트 (https://sw0913.tistory.com)를 웹 뷰에 로드합니다.
  • goNaver(_ sender: UIButton):
    • 네이버 버튼을 클릭했을 때 호출되는 액션 메서드입니다.
    • 네이버 모바일 웹사이트 (https://m.naver.com)를 로드하는 기능을 수행합니다.
    • loadWebsite(urlString:) 메서드를 호출하여 네이버 사이트를 웹 뷰에 로드합니다.
  • loadWebsite(urlString:):
    • urlString을 매개변수로 받아, 해당 URL을 웹 뷰에 로드하는 기능을 수행하는 헬퍼 메서드입니다.
    • URL 문자열이 유효한지 확인한 후, 유효한 URL이면 URLRequest를 생성하고 이를 WKWebView에 로드합니다.
    • 만약 URL이 잘못되었을 경우, guard let으로 처리하여 안전하게 오류를 처리하고, 유효하지 않은 URL에 대해서는 콘솔에 오류 메시지를 출력합니다.

4. MARK:

  • // MARK: 주석을 사용하여 코드의 섹션을 구분하고, 가독성을 높였습니다. 각 섹션은 다음과 같습니다:
    • IBOutlet: 인터페이스 요소와 연결된 변수들.
    • Lifecycle Methods: 뷰 컨트롤러의 생명 주기와 관련된 메서드들 (예: viewDidLoad).
    • Actions: 사용자 인터페이스의 이벤트(버튼 클릭 등)에 반응하는 메서드들.
    • Helper Methods: 재사용 가능한 보조 메서드들 (예: loadWebsite).

@ ViewController를 문서화

import UIKit

/**
 `ViewController` 클래스는 BMI(체질량지수)를 계산하는 뷰 컨트롤러입니다.
 사용자로부터 키와 체중을 입력받아 BMI를 계산하고, 결과를 화면에 표시합니다.
 */
class ViewController: UIViewController {
    
    // MARK: - IBOutlet
    
    /// 사용자로부터 입력받은 키 텍스트 필드
    @IBOutlet weak var txtHeight: UITextField!
    
    /// 사용자로부터 입력받은 체중 텍스트 필드
    @IBOutlet weak var txtWeight: UITextField!
    
    /// 계산된 BMI 결과를 표시할 라벨
    @IBOutlet weak var lblResult: UILabel!
    
    // MARK: - Actions
    
    /**
     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)
            
            // 소수점 1자리까지 표시
            let shortenedBmi = String(format: "%.1f", bmi)
            
            // BMI에 따라 체중 상태를 판단하고 색상 및 메시지 설정
            var body = ""
            var color = UIColor.white
            
            if bmi >= 40 {
                // 3단계 비만 (BMI 40 이상)
                color = UIColor(displayP3Red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0)
                body = "3단계 비만"
            } else if bmi >= 30 && bmi < 40 {
                // 2단계 비만 (BMI 30 이상 40 미만)
                color = UIColor(displayP3Red: 0.7, green: 0.0, blue: 0.0, alpha: 1.0)
                body = "2단계 비만"
            } else if bmi >= 25 && bmi < 30 {
                // 1단계 비만 (BMI 25 이상 30 미만)
                color = UIColor(displayP3Red: 0.4, green: 0.0, blue: 0.0, alpha: 1.0)
                body = "1단계 비만"
            } else if bmi >= 18.5 && bmi < 25 {
                // 정상 체중 (BMI 18.5 이상 25 미만)
                color = UIColor(displayP3Red: 0.0, green: 0.0, blue: 1.0, alpha: 1.0)
                body = "정상"
            } else {
                // 저체중 (BMI 18.5 미만)
                color = UIColor(displayP3Red: 0.0, green: 1.0, blue: 0.0, alpha: 1.0)
                body = "저체중"
            }
            
            // 계산된 결과를 라벨에 표시
            lblResult.backgroundColor = color  // BMI 범위에 맞는 배경색 설정
            lblResult.clipsToBounds = true  // 라벨의 배경색이 라벨 모서리를 넘어가지 않도록 설정
            lblResult.text = "BMI:\(shortenedBmi), 판정:\(body)"  // BMI 값과 체중 상태를 표시
            lblResult.layer.cornerRadius = 10  // 라벨의 모서리를 둥글게 설정
        }
    }
    
    // MARK: - Lifecycle Methods
    
    /**
     뷰가 로드될 때 호출되는 메서드입니다.
     추가적인 초기 설정이 필요할 경우 여기에 작성합니다.
     */
    override func viewDidLoad() {
        super.viewDidLoad()
        // 추가적인 설정이 필요하면 여기에 작성
    }
}

 

 

문서화된 코드 설명:

1. 클래스 설명 (ViewController):

  • ViewController는 사용자가 입력한 체중을 바탕으로 BMI를 계산하고 그 결과를 화면에 표시하는 뷰 컨트롤러입니다. 계산된 BMI 값에 따라 적절한 체중 상태(저체중, 정상, 1단계 비만, 2단계 비만, 3단계 비만)를 출력합니다.
  • UILabel을 사용하여 계산 결과를 화면에 표시하고, BMI 값에 따라 배경색과 텍스트 색상을 다르게 설정하여 시각적으로 명확한 피드백을 제공합니다.

2. 변수 설명:

  • @IBOutlet weak var txtHeight: UITextField!:
    • 사용자가 키를 입력하는 텍스트 필드입니다.
  • @IBOutlet weak var txtWeight: UITextField!:
    • 사용자가 체중을 입력하는 텍스트 필드입니다.
  • @IBOutlet weak var lblResult: UILabel!:
    • BMI 계산 결과와 체중 상태를 표시하는 레이블입니다. 이 레이블의 배경색과 텍스트는 BMI 계산 결과에 따라 동적으로 변경됩니다.

3. 메서드 설명:

  • calcBmi(_ sender: UIButton):
    • 사용자가 BMI 계산 버튼을 클릭하면 호출됩니다.
    • txtHeight와 txtWeight에서 사용자가 입력한 값을 가져와 BMI를 계산하고, 결과를 lblResult에 표시합니다.
    • 만약 입력값이 비어 있으면 경고 메시지를 표시하고 계산을 중단합니다.
    • BMI 값에 따라 적절한 체중 상태를 판별하고, 해당 상태에 맞는 텍스트와 배경색을 설정합니다.
    • bmi 계산식은 weight / (height * height * 0.0001)입니다. 여기서 0.0001은 height가 cm 단위일 때 m 단위로 변환하기 위한 계수입니다.
  • viewDidLoad():
    • 뷰가 로드된 후 호출되는 메서드입니다. 여기서는 특별한 설정이 필요하지 않지만, 나중에 뷰가 로드될 때 초기화 작업이 필요하다면 여기에 작성할 수 있습니다.

4. 동작 설명:

  • BMI 계산:
    • BMI는 체중(kg)을 키(m)의 제곱으로 나눈 값으로 계산됩니다.
    • BMI 공식: BMI = weight / (height * height) (단, height는 m 단위여야 하므로, cm 단위로 입력받은 경우 height * height * 0.0001로 계산합니다.)
  • 체중 상태 분류:
    • BMI 값에 따라 체중 상태를 다섯 가지 범주로 분류합니다:
      • 3단계 비만: BMI ≥ 40
      • 2단계 비만: 30 ≤ BMI < 40
      • 1단계 비만: 25 ≤ BMI < 30
      • 정상 체중: 18.5 ≤ BMI < 25
      • 저체중: BMI < 18.5
  • 결과 표시:
    • lblResult에 BMI 값과 해당 체중 상태를 출력합니다. 또한, BMI 값에 따라 배경색과 텍스트 색상이 변경되며, 라벨은 둥근 모서리를 갖도록 설정됩니다.

5. 배경색 설정:

  • 각 체중 상태에 맞게 배경색을 다르게 설정합니다:
    • 3단계 비만: 빨간색
    • 2단계 비만: 어두운 빨간색
    • 1단계 비만: 어두운 붉은색
    • 정상: 파란색
    • 저체중: 초록색

출처: smile Han https://www.youtube.com/channel/UCM8wseo6DkA-D7yGlCrcrwA