궁금한 내용을 검색해보세요!
이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.
서근 개발노트
티스토리에 팔로잉
SWIFT/Udemy iOS

[Udemy] 섹션11: Segue, Cocoa Touch Class, Optional Binding, BMI계산기

서근
QUOTE THE DAY

-
Written by SeogunSEOGUN

반응형

Slider

- Value  :  슬라이더의 기본 값

- Minimum  :  슬라이더의 최솟값

- Maximum  :  슬라이더의 최댓값

 

Slider의 값 출력 ( 소수점 )

소수점 출력 방법 ( 2 가지 )

첫 번째, String(format: "%.2f", sender.value)

두 번째, sender.valueFloat 정수이기 때문에 Int(sender.value) 로도 가능

 

text 레이블로 연결

소수점 출력 방법을 let 상수로 지정한 후, Label.text로 호출 함.

    @IBAction func heightSliderBar(_ sender: UISlider) {
        let height = String(format: "%.2f", sender.value)
        heightLabel.text = "\(height)m"
        
        print(height)
    }

버튼 연결

UISlider 들은 개별적으로 분리되어있기 때문에 UIButton에 슬라이더 value을 가져오려면 SliderIBOutlet으로 가져와서 버튼에 호출할 수 있다.

import UIKit

class ViewController: UIViewController {
    @IBOutlet weak var heightLabel: UILabel!
    @IBOutlet weak var weightLabel: UILabel!
    
    @IBOutlet weak var heightSlider: UISlider!
    @IBOutlet weak var weightSlider: UISlider!
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        heightSlider.value = 1.50
        weightSlider.value = 100
    }

    @IBAction func heightSliderBar(_ sender: UISlider) {
        let height = String(format: "%.2f", sender.value)
        heightLabel.text = "\(height)m"
        
        print(height)
    }
    
    @IBAction func weightSliderBar(_ sender: UISlider) {
        let weight = String(format: "%.0f", sender.value)
        weightLabel.text = "\(weight)kg"
        
        print(weight)
        
    }
    
    @IBAction func pressedCalculate(_ sender: UIButton) {
        let height = heightSlider.value
        let weight = weightSlider.value
        
        let bmi = weight / (height * height)
        
        print(bmi)
    }
}

체질량(BMI) 계산

UIButton에 사용자의 몸무게와 키를 통해 체질량을 계산한 값을 나타내려면 다음과 같은 공식이 필요하다.

 

BMI : 자신의 무게를 키의 제곱으로 나눈 값 

swift에서의 제곱

// 몸무게 / (신장 * 신장)
let bmi = weight / (height * height)
// pow(double, double)  = height의 2제곱
let bmi = weight / pow(height, 2)
    @IBAction func pressedCalculate(_ sender: UIButton) {
        let height = heightSlider.value
        let weight = weightSlider.value
        
        let bmi = weight / pow(height, 2)
        
        print(bmi)
    }

다른 뷰로 값을 전달하는 방법

1. Main.storyboard에서 object libraryView Controller를 추가

2. Controller폴더 우클릭 > New File > Swift File > SecondViewController

import UIKit

 // SecondViewController의 superView는 UIViewController
class SecondViewController: UIViewController {
	override func viewDidLoad() {
    	super.viewDidLoad()
}

TIP
 
 

UIKitFoundation을 포함하는 더 큰 프레임워크이다.

View를 연결하는 방법

첫 번째, 뷰 컨트롤러에서 다른 뷰 컨트롤러를 호출하여 화면 전환

// ViewController.swift
let secondVC = SecondViewController()
secondVC.bmiValue = String(format: "%.2f", bmi)
self.present(secondVC, animated: true, completion: nil)

// SecondVieWController.swift
import UIKit
class SecondViewController: UIViewController {
	var bmiValue = "0.0"
    override func viewDidLoad() {
    	super.viewDidLoad()
        
        view.backgroundColor = .yellow
        
        let label = UILable()
        label.text = bmiValue
        label.frame = CGRect(x:0, y:0, width:100, heigth: 50)
        view.addSubview(label)
    }

}

SecondViewController에서 UILabel()UIView를 상속받았기 때문에 view.addSubview(label)이 가능하다.

 

두 번째, Cocoa Touch Class : Segue 세그웨이

UIKit, UIViewController, viewDidLoad를 이미 포함하고 있는 프레임워크

 

일단 우리는 위에서 작성한 SecondeVC를 사용하지 않을 것이다. 대충 저런 식으로 main Stroybard 없이 코드를 구현할 수 있다는 것 정도만 일단 알아두자!

ViewController 파일 생성

+ N > Cocoa Touch Class

 

Class : Some ViewController Name

Subclass of: UIViewController

VC와 SecondVC 연결

viewController + 오른쪽 마우스 클릭 > SecondVC로 끌어당김 > Present Modally

SecondVC Class 연결

SecondViewControllerclass를 연결해주고 assistant를 띄울 수 있다.

VC와 SecondVC Segue Indentifier 연결

viewControllerSecondViewControllerindetifier를 정해주어 고유한 코드를 넣어준다.

performSegue & prepare

CalculateVC에서 ResultVC를 연결할 때 준비 과정

1. self.perporm(withIdentifier:String, sender: self)

2. 준비과정인 prepare() 사용

// ResultViewController.swift

import UIKit

class ResultViewController: UIViewController {
    
    var bmiValue: String?

    @IBOutlet weak var bimLabel: UILabel!
    @IBOutlet weak var adviceLabel: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()

        bimLabel.text = bmiValue
    }
    
    @IBAction func recalculatePressed(_ sender: UIButton) {
        self.dismiss(animated: true, completion: nil)
    }
}

AS! 다운 캐스팅

CalculateVC에서 ResultVC를 호출할 때 준비 과정인 prepare을 override 하여 메서드를 가져왔고, 조건문으로는 segue의 indetifier가 맞다면, destinationVC를 다운 캐스팅을 통해 ResultVC와 연결해준다.

// CalculateViewController.swift
var bmiValue = "0.0"

@IBAction func pressedCalculate(_ sender: UIButton) {
let height = heightSlider.value
let weight = weightSlider.value
let bmi = weight / pow(height, 2)
        
bmiValue = String(format: "%.1f", bmi)
        
self.performSegue(withIdentifier: "goToResult", sender: self)
}

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
	if segue.identifier == "goToResult" {
 // 🔥 UIStoryboardSegue의 destination은 UIViewController인데 이걸을 as!(다운 캐스팅)을통해 ResultVC 으로 캐스팅해준다.
    	let destinationVC = segue.destination as! ResultViewController
        destinationVC.bmiValue = bmiValue
    }
}

Dismiss ( back button)

self.dismiss(animated: true, completion: nil)를 사용하여 ResultVC에서 back버튼을 누르면 이전 화면으로 돌아가게 할 수 있다.

// ResultViewController.swift

    @IBAction func recalculatePressed(_ sender: UIButton) {
        self.dismiss(animated: true, completion: nil)
    }

옵셔널 사용

1. Model 폴더 > CalculatorBrian 파일 생성

2. calculateBMI(height:, weight:) 함수 생성

3. 소수점 자리를 나타내는 getBmiValue() 함수 생성.

4. CalculateVCmodel에서 만든 함수 호출 ( CalculatorBrain 초기화 필수 )

// CalculateViewController.swift ( Controller )

class CalculateViewController: UIViewController {

// CalculatorBrain 초기화
 var calculatorBrain = CalculatorBrain()
 
 @IBAction func pressedCalculate(_ sender: UIButton) {
           ...
        // calculateBMI 함수 호출
        calculatorBrain.calculateBMI(height: height, weight: weight)
       ...
    }
    
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
              ...
              // getBmiValue 함수 호출
            destinationVC.bmiValue = calculatorBrain.getBmiValue()
        }
    }
}


// CalculatorBrain.swift ( Model )

struct CalculatorBrain {
    var bmiValue: Float = 0.0
    
    mutating func calculateBMI(height: Float, weight: Float) {
        bmiValue = weight / pow(height, 2)
    }
    
    func getBmiValue() -> String {
        let bmiToOneDecimalPlace = String(format: "%.1f", bmiValue)
        return bmiToOneDecimalPlace
    }
}

문제점

위 코드의 brain을 보면 bmiValue의 기본값이 0.0으로 되어있다. 이것을 Float 혹은 Float?으로 기본값을 주지 않는다면 getBmiValue 메서드의 bmiValueerror가 나타난다. 왜냐? bmiValuenil 이기 때문이지!

 

또, 기본값을 주지 않으면 viewController에 우리가 초기화했었는데 거기에도 값을 주라고 아래와 같이 오류가 난 것을 확인할 수 있다!

 

해결방법

옵셔널을 이해하면 위 문제를 해결할 수 있다. 옵셔널에 대한 문법

 

첫 번째 방법) if let 으로 safe unwrapping 해줌.

두 번째 방법) nil Coalescing Operator을 사용하여 축약할 수 있음. 추천 ⭐️

//CalculatorBrain.swift (model)

struct CalculatorBrain {
    var bmiValue: Float?
    
    //🔥 struct의 bmiValue를 변화시키기 때문에 mutating 키워드를 반드시 사용함
    mutating func calculateBMI(height: Float, weight: Float) {
        bmiValue = weight / pow(height, 2)
    }
    
    func getBmiValue() -> String? {
        
//        if let safeBMI = bmiValue {
//            let bmiToOneDecimalPlace = String(format: "%.1f", safeBMI)
//            return bmiToOneDecimalPlace
//        } else {
//            return "0.0"
//        }
        
        // 🔥 Nil Coalescing Operator를 사용하여 축약할 수 있다.
        let bmiToOneDecimalPlace = String(format: "%.1f", bmiValue ?? 0.0)
        return bmiToOneDecimalPlace
    }
}

MVC 패턴 리팩터링 ( 옵셔널 체이닝 )

이제 여태까지 했던 코드를 MVC 패턴으로 리팩터링 하는 동시에 bmi 수치에 따라 advicebackgroundColor가 변경될 수 있도록 해주겠음! 옵셔널체이닝에 대한 문법

 

1. Bmi model 생성

2. Bmi 구조체에 value, advice, color를 상수로 만듦

3. ResultVCadvicecolor를 옵셔널 상수로 만듦

3. CalculatorBrainBmi model을 옵셔널로 호출과 동시에 코드 수정

4. calculateBMI 메서드에 bmi?.value 를 추가하는 대신 임시 상수를 생성함

5. calculateBMI 메서드에 bmi 지수별 advicebackgroundcolor를 나타내는 if 조건문 생성

6. CalculateVCovrride prepare 메서드의 advicecolor에 함수를 호출함

<hide/>

// Bmi.swift ( Model )

struct Bmi {
    var value: Float
    var color: UIColor
    var advice: String
}
<hide/>

//ResultViewController.swift ( Controller )

import UIKit

class ResultViewController: UIViewController {
    
    var bmiValue: String?
    var advice: String?
    var color: UIColor?

    @IBOutlet weak var bimLabel: UILabel!
    @IBOutlet weak var adviceLabel: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()

        bimLabel.text = bmiValue
        adviceLabel.text = advice
        view.backgroundColor = color
    }
    
    @IBAction func recalculatePressed(_ sender: UIButton) {
        self.dismiss(animated: true, completion: nil)
    }
}
<hide/>

//CalculatorBrain.swift

import UIKit

struct CalculatorBrain {
    
    var bmi: Bmi?
    
    mutating func calculateBMI(height: Float, weight: Float) {
        let bmiValue = weight / pow(height, 2)
        
        switch bmiValue {
        case 0...18.4:
            bmi = Bmi(value: bmiValue, color: #colorLiteral(red: 0.1764705926, green: 0.4980392158, blue: 0.7568627596, alpha: 1), advice: "저체중 입니다.")
        case 18.5...22.9:
            bmi = Bmi(value: bmiValue, color: #colorLiteral(red: 0.3411764801, green: 0.6235294342, blue: 0.1686274558, alpha: 1), advice: "정상체중 입니다.")
        case 23...24.9:
            bmi = Bmi(value: bmiValue, color: #colorLiteral(red: 0.9529411793, green: 0.6862745285, blue: 0.1333333403, alpha: 1), advice: "과체중 입니다.")
        case 25...:
            bmi = Bmi(value: bmiValue, color: #colorLiteral(red: 0.9254902005, green: 0.2352941185, blue: 0.1019607857, alpha: 1), advice: "비만 입니다.")
        default:
            bmi = Bmi(value: bmiValue, color: #colorLiteral(red: 0.1764705926, green: 0.4980392158, blue: 0.7568627596, alpha: 1), advice: "저체중 입니다.")
        }
    }
    
    func getBmiValue() -> String? {
        // 🔥 bmi?.value ?? 0.0 옵셔널 체이닝
        let bmiToOneDecimalPlace = String(format: "%.1f", bmi?.value ?? 0.0)
        return bmiToOneDecimalPlace
    }
    
    func getAdvice() -> String? {
        let bmiAdvice = bmi?.advice ?? "empty"
        return bmiAdvice
    }
    
    func getColor() -> UIColor {
        return bmi?.color ?? #colorLiteral(red: 0, green: 0.46, blue: 0.89, alpha: 1)
    }
}
<hide/>

//CalculateViewController.swift ( Controller )

import UIKit

class CalculateViewController: UIViewController {
    
    var calculatorBrain = CalculatorBrain()
    
    @IBOutlet weak var heightLabel: UILabel!
    @IBOutlet weak var weightLabel: UILabel!
    
    @IBOutlet weak var heightSlider: UISlider!
    @IBOutlet weak var weightSlider: UISlider!
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        heightSlider.value = 1.50
        weightSlider.value = 100
    }

    @IBAction func heightSliderBar(_ sender: UISlider) {
        let height = String(format: "%.2f", sender.value)
        heightLabel.text = "\(height)m"
        
        print(height)
    }
    
    @IBAction func weightSliderBar(_ sender: UISlider) {
        let weight = String(format: "%.0f", sender.value)
        weightLabel.text = "\(weight)kg"
        
        print(weight)
        
    }
    
    @IBAction func pressedCalculate(_ sender: UIButton) {
        let height = heightSlider.value
        let weight = weightSlider.value
        
        calculatorBrain.calculateBMI(height: height, weight: weight)

        self.performSegue(withIdentifier: "goToResult", sender: self)
    }
    
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "goToResult" {
            let destinationVC = segue.destination as! ResultViewController
            destinationVC.bmiValue = calculatorBrain.getBmiValue()
            destinationVC.advice = calculatorBrain.getAdvice()
            destinationVC.color = calculatorBrain.getColor()
        }
    }
}

완성 파일

BMI-Calculator.zip
2.01MB

----------  문법  ----------

Order of operations (Mathematical)

swift에서 계산이 완료되는 순서는 아래와 같다.

 

B > O > D > M > A > S

 

Classes

 

- SuperClass

- SubClass

- Override

 

SuperClass & SubClass

Inheritance(상속)  - override

subClasssuperClass를 상속받아서 superClass에 있는 메서드를 활용할 뿐 아니라 override(변경)하고 추가할 수 있다.

 

superClass의 메서드를 override를 사용하여 재정의 한다.

// superClass

class Seogun {
    func name() {
        print("저의 이름은 서근 입니다")
    }
}

// subClass

class Mijin: Seogun {
    override func name() {
        print("저의 이름은 미진 입니다")
    }
}

subClass에서 superClass의 메서드를 재정의한 메서드와 함께 실행하려면 subClass에서 super 키워드를 사용한다.

// subClass

class Mijin: Seogun {
    override func name() {
        super.name()
        print("저의 이름은 미진 입니다")
    }
}

Class 프로젝트는 macOS의 Command tool로 만들어서 실습한다

File > New > Project > macOs > Command Line Tool > Product Name : Classes Something

Practice 

main

<hide/>

// main

let seogung = Seogun()

seogung.move()
seogung.attack()

let dragon = Dragon()
dragon.power = 500
dragon.move()
dragon.talk(speech: "불꽃 회오리!")
dragon.attack()

superClass

<hide/>

// superClass

class Seogun {
    var power = 50
    var health = 100
    
    func move() {
        print("앞으로 이동한다!")
    }
    
    func attack() {
        print("\(power)의 데미지로 적을 공격했다!")
    }
}

subClass

<hide/>

// subClass

class Dragon: Seogun {
    var dragonFire = 1000
    
    func talk(speech: String) {
        print("드래곤: \(speech)")
    }
    
    override func attack() {
        print("드래곤이 불을 내뿜어 \(dragonFire) 만큼의 데미지를 입혔다!")
    }
    
    override func move() {
        super.move()
        print("드래곤이 하늘을 향해 날아갔다!")
    }
}

Swift 상속 관계

NSObjectUIResponder > UIViewUIControlUIButton/UILabel/UISlider...

Struct & Class

Class참조 타입이다. 클래스는 init()으로 초기화를 필수로 해줘야 한다.

위 이미지를 보면 내가 누군가와 어떠한 파일을 공유할 때, 사본으로 상대방과 공유하는 것이 아닌 정확한 로컬의 위치를 알려주어 모든 사람과 그 파일이 공유될 수 있도록 한다. 

 

이것의 단점으로는 공유받은 어느 한 사람이라도 그 파일을 지우거나 수정하게 된다면 모든 사람들이 이에 영향을 받게 된다.

Structclass와 다르게 값으로 전달하는 타입이다. 즉, 위에 이미지처럼 내가 어떠한 사진을 가지고 다른 사람에게 공유를 할 때 사본을 만들어서 그 이미지를 준다. 

 

그렇기 때문에 다른 사람이 그 사본을 가지고 삭제를 하거나, 무언가를 수정해도 또 다른 사본이나 원본은 아무런 영향이 없다.

 

이 점 때문에 Apple에서는 Class 보다 Struct를 사용하는 것을 권장하고 있다.

Struct와 Class 차이점

    • Structure:
      • init()을 생성하지 않아도 됨.
      • immutable(불변) 하기 때문에 안에 생성되어 있는 변수의 값을 바꾸기 위해서는 mutating 키워드를 사용
      • 상속이 불가능함.
      • Apple은 struct를 사용하기를 권장함.
    • Class:
      • init()을 생성해야 함.
      • 참조값을 가짐.
      • Object-C와 함께 사용할 때 주로 사용함.
class Seogun {
    var health: Int
    
    init(health: Int) {
        self.health = health
    }
    
    func takeDamage(amount: Int) {
        health = health - amount
    }
}

let Character1 = Seogun(health: 100)
let Character2 = Character1

Character1.takeDamage(amount: 20)
Character2.takeDamage(amount: 20)


print("Character1 의 hp : \(Character1.health)")
print("Character2 의 hp : \(Character2.health)")

struct Seogun {
    var health: Int
    
    init(health: Int) {
        self.health = health
    }
    
    // stuct의 속성을 변경했기 때문에 mutation 키워드 사용
    mutating func takeDamage(amount: Int) {
        health = health - amount
    }
}

var Character1 = Seogun(health: 100)
var Character2 = Character1

Character1.takeDamage(amount: 20)
Character2.takeDamage(amount: 40)


print("Character1 의 hp : \(Character1.health)")
print("Character2 의 hp : \(Character2.health)")

Optionals

1. Force Unwrapping ( 강제 언래핑 )

optional!

강제로 언래핑 하는 방법이다. 하지만 이것은 사용하지 않는것이 좋다. 아래 코드에 myOptionalnil이고 이것을 강제로 언래핑 시켰을 때 코드에서는 오류가 생기지 않지만, 앱을 실행하면 Optional 값의 래핑을 해제하는 동안 예기치 않게 nil이 발견되었습니다.라는 오류와 함께 crush 된다.

let myOptional: String?
myOptional = nil

let text: String = myOptional!
print(text) // Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value

2. Check for nil value ( nil 값 확인 )

if myOptional != nil {
    myOptional!
}

myOptionalnil이 아니라면 textmyOptional! 이라고 조건문으로 코드를 구현했는데 이곳에도 반드시 강제언래핑 ! 을 해줘야 한다. 

let myOptional: String?
myOptional = "Check for nil value (nil 값 확인)"

if myOptional != nil {
    let text: String = myOptional!
    print(text)
} else {
    print("이 값은 nil 입니다.")
}

//Check for nil value (nil 값 확인)

3. Optional Binding ( useful ⭐️⭐️⭐️⭐️⭐️ )

myOptionalnil이 아니라면, unwrap(언래핑)된 safeOptional{ } 안에서 바인딩하여 사용한다.
- safeOptional의 데이터 타입은 String
- myOptional의 데이터 타입은 String?

if let safeOptional = myOptional {
	print(safeOptional)
}

let myOptional: String?
myOptional = nil

if let safeOptional = myOptional {
    let text: String = safeOptional
    print(text)
} else {
    print("이 값은 nil 입니다.")
}

//이 값은 nil 입니다.

4. Nil Coalescing Operator ( 추천 )

optionalnil이 아니면 optional 값을 사용하고, nil 이면 기본값으로 정해준 defaultValue를 사용하게 된다.

optional ?? defaultValue

아래는 myOptional이 값이 있기 때문에 textprint 하면 "myOptional" 이 출력된다.

let myOptional: String?
myOptional = "myOptional"

let text:String = myOptional ?? "nil 병합 연산자"
print(text)

5. Optional Chaining ( useful ⭐️⭐️⭐️⭐️⭐️ )

optionalnil이 아니라면 property 또는 method()에 접근할 수 있다.

optional?.property
optional?.method()
// optional이 nil이 아니면 property에 접근한다

Struct에서 사용

struct MyOptional {
    var property = "서근"
    func method() {
        print("MyOptional의 method 입니다.")
    }
}

let myOptional: MyOptional?
myOptional = nil

print(myOptional?.property) // nil
struct MyOptional {
    var property = "서근"
    func method() {
        print("MyOptional의 method 입니다.")
    }
}

let myOptional: MyOptional?
myOptional = MyOptional()

print(myOptional?.property) // "서근"

 

 


잘못된 내용이 있으면 언제든 피드백 부탁드립니다.


서근


위처럼 이미지 와 함께 댓글을 작성할 수 있습니다.