iOS 9th Class

๐Ÿฉบ ํ”„๋กœ์ ํŠธ ๊ณ„ํš์„œ โ€“ MyBMI (์Šคํ† ๋ฆฌ๋ณด๋“œ ๊ธฐ๋ฐ˜)

1๏ธโƒฃ ํ”„๋กœ์ ํŠธ ๊ฐœ์š”

  • ์•ฑ ์ด๋ฆ„: MyBMI โ€“ ๋‚˜์˜ ๊ฑด๊ฐ• ์ง€์ˆ˜
  • ๊ฐœ๋ฐœ ํ™˜๊ฒฝ:
    • Xcode ์ตœ์‹  ๋ฒ„์ „ (์˜ˆ: Xcode 16 ๊ธฐ์ค€)
    • iOS 17 ์ด์ƒ ์ง€์›
    • UIKit + Storyboard ๊ธฐ๋ฐ˜
    • Swift ์–ธ์–ด ์‚ฌ์šฉ
  • ๋ชฉํ‘œ:
    ์Šคํ† ๋ฆฌ๋ณด๋“œ๋ฅผ ์ด์šฉํ•ด UI๋ฅผ ์ง๊ด€์ ์œผ๋กœ ๊ตฌ์„ฑํ•˜๊ณ , ๊ฐ„๋‹จํ•œ BMI ๊ณ„์‚ฐ๊ณผ ์ฒด์ค‘ ์ถ”์  ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜๋Š” ์ดˆ๋ณด์ž ์นœํ™”ํ˜• iOS ์•ฑ ๊ฐœ๋ฐœ.

2๏ธโƒฃ ์•ฑ์˜ ์ฃผ์š” ๊ธฐ๋Šฅ

๊ตฌ๋ถ„๊ธฐ๋Šฅ๋ช…์„ค๋ช…
๐Ÿ’ก ๊ธฐ๋ณธ ๊ณ„์‚ฐBMI ๊ณ„์‚ฐํ‚ค(cm)์™€ ๋ชธ๋ฌด๊ฒŒ(kg) ์ž…๋ ฅ โ†’ BMI ๊ณ„์‚ฐ ํ›„ ๊ฒฐ๊ณผ ํ‘œ์‹œ
โš™๏ธ ๋‹จ์œ„ ์ „ํ™˜๋‹จ์œ„ ๋ณ€๊ฒฝ ๋ฒ„ํŠผkg/cm โ†” lbs/inch ์ „ํ™˜ (Segmented Control ์‚ฌ์šฉ)
๐ŸŽจ ์‹œ๊ฐํ™”BMI ๊ฒฐ๊ณผ ์ƒ‰์ƒ ํ‘œ์‹œBMI ๋ฒ”์œ„์— ๋”ฐ๋ผ ๋ผ๋ฒจ ์ƒ‰์ƒ ๋ณ€๊ฒฝ (์˜ˆ: ์ •์ƒ=์ดˆ๋ก, ๊ณผ์ฒด์ค‘=์ฃผํ™ฉ)
๐Ÿ“ˆ ๊ธฐ๋ก์ฒด์ค‘ ์ €์žฅ/๊ธฐ๋ก ๋ณด๊ธฐCore Data or UserDefaults๋ฅผ ์ด์šฉํ•ด ์ž…๋ ฅ ๊ธฐ๋ก ์ €์žฅ ํ›„ TableView๋กœ ๋ณด์—ฌ์ฃผ๊ธฐ
๐Ÿ”” ์•Œ๋ฆผ (์„ ํƒ)๋ฆฌ๋งˆ์ธ๋” ๊ธฐ๋ŠฅUNUserNotificationCenter ์‚ฌ์šฉ โ†’ ์ฒด์ค‘ ์ž…๋ ฅ ์•Œ๋ฆผ (๊ธฐ๋ณธ ๋„๊ธฐ, ์„ค์ •์—์„œ ์ผœ๊ธฐ ๊ฐ€๋Šฅ)
๐ŸŒ ๋‹ค๊ตญ์–ดํ•œ๊ตญ์–ด/์˜์–ด ์ง€์›Localizable.strings ํ™œ์šฉ
โš™๏ธ ์„ค์ •์„ค์ • ํ™”๋ฉด๋‹จ์œ„ ์ „ํ™˜, ๋ฐ์ดํ„ฐ ์ดˆ๊ธฐํ™”, ์•Œ๋ฆผ ์„ค์ • ๋“ฑ

ํ™”๋ฉด ๊ตฌ์„ฑ (Storyboard ๊ตฌ์กฐ)

โ–ถ 1. ๋ฉ”์ธ ํ™”๋ฉด โ€“ BMI ๊ณ„์‚ฐ๊ธฐ

  • ViewController: MainViewController
  • ๊ตฌ์„ฑ ์š”์†Œ:
    • UILabel: โ€œ๋‚˜์˜ BMI ๊ณ„์‚ฐ๊ธฐโ€
    • UITextField (2๊ฐœ): ํ‚ค ์ž…๋ ฅ, ๋ชธ๋ฌด๊ฒŒ ์ž…๋ ฅ
    • UISegmentedControl: ๋‹จ์œ„ ์„ ํƒ (cm/kg, inch/lbs)
    • UIButton: โ€œ๊ณ„์‚ฐํ•˜๊ธฐโ€
    • UILabel: ๊ฒฐ๊ณผ ํ‘œ์‹œ (BMI ์ˆ˜์น˜ + ์ƒํƒœ)
    • UIView (๊ฒŒ์ด์ง€ ์ƒ‰์ƒ์šฉ): BMI ์ƒํƒœ์— ๋”ฐ๋ผ ์ƒ‰์ƒ ๋ณ€๊ฒฝ
    • UIButton: โ€œ๊ธฐ๋ก ๋ณด๊ธฐ๋กœ ์ด๋™โ€

๐Ÿ’ก ๋กœ์ง ๊ฐœ์š”

let height = Double(heightTextField.text ?? "") ?? 0
let weight = Double(weightTextField.text ?? "") ?? 0
let bmi = weight / pow(height/100, 2
)
resultLabel.text = String(format: "BMI: %.1f", bmi)

BMI ๋ฒ”์œ„์— ๋”ฐ๋ผ ์ƒ‰์ƒ๊ณผ ๋ฌธ๊ตฌ๋ฅผ ๋ณ€๊ฒฝ (์˜ˆ: <18.5 ์ €์ฒด์ค‘, 18.5~24.9 ์ •์ƒ ๋“ฑ)


โ–ถ 2. ๊ธฐ๋ก ํ™”๋ฉด โ€“ Weight Record

  • ViewController: RecordViewController
  • ๊ตฌ์„ฑ ์š”์†Œ:
    • UITableView: ๋‚ ์งœ๋ณ„ ์ฒด์ค‘ ํ‘œ์‹œ
    • UIButton: โ€œ์ƒˆ ๊ธฐ๋ก ์ถ”๊ฐ€โ€
    • Navigation Bar Title: โ€œ์ฒด์ค‘ ๊ธฐ๋กโ€

๐Ÿ’ก ๋ฐ์ดํ„ฐ ์ €์žฅ ๋ฐฉ์‹

  • ์ดˆ๋ณด์ž์šฉ: UserDefaults ์‚ฌ์šฉ
    (์˜ˆ: UserDefaults.standard.set(records, forKey: "weightRecords"))
  • ์ถ”ํ›„ ํ™•์žฅ: Core Data๋กœ ์ „ํ™˜ ๊ฐ€๋Šฅ

โ–ถ 3. ์ƒˆ ๊ธฐ๋ก ์ถ”๊ฐ€ ํ™”๋ฉด โ€“ Add Record

  • ViewController: AddRecordViewController
  • ๊ตฌ์„ฑ ์š”์†Œ:
    • UITextField: ์ฒด์ค‘ ์ž…๋ ฅ
    • UIDatePicker: ๋‚ ์งœ ์„ ํƒ
    • UIButton: โ€œ์ €์žฅํ•˜๊ธฐโ€

์ €์žฅ ์‹œ UserDefaults ๋ฐฐ์—ด์— ์ถ”๊ฐ€ โ†’ Record ํ™”๋ฉด์˜ ํ…Œ์ด๋ธ”๋ทฐ ๋ฆฌ๋กœ๋“œ


โ–ถ 4. ์„ค์ • ํ™”๋ฉด โ€“ Settings

  • ViewController: SettingsViewController
  • ๊ตฌ์„ฑ ์š”์†Œ:
    • UISwitch: ์•Œ๋ฆผ ์ผœ๊ธฐ/๋„๊ธฐ
    • UIButton: ๋ฐ์ดํ„ฐ ์ดˆ๊ธฐํ™”
    • UISegmentedControl: ๋‹จ์œ„ ๋ณ€๊ฒฝ

ํ™”๋ฉด ์ „ํ™˜ ๊ตฌ์กฐ (Storyboard Segue)

MainViewController
โ”œโ”€(Show segue)โ†’ RecordViewController
โ”‚ โ””โ”€(Modal segue)โ†’ AddRecordViewController
โ””โ”€(Tab or modal)โ†’ SettingsViewController

  • Navigation Controller๋ฅผ ๋ฃจํŠธ๋กœ ์‚ฌ์šฉ
  • Segue Identifier๋กœ ๊ฐ ํ™”๋ฉด ์ „ํ™˜ ์ œ์–ด
  • โ€œ๋Œ์•„๊ฐ€๊ธฐโ€ ๋ฒ„ํŠผ์€ ์ž๋™ ์ƒ์„ฑ๋จ

ํŒŒ์ผ ๊ตฌ์กฐ (ํ”„๋กœ์ ํŠธ ํด๋” ๊ตฌ์กฐ ์˜ˆ์‹œ)

MyBMI/
โ”œโ”€ Models/
โ”‚ โ”œโ”€ BMIModel.swift
โ”‚ โ””โ”€ RecordModel.swift

โ”œโ”€ ViewControllers/
โ”‚ โ”œโ”€ MainViewController.swift
โ”‚ โ”œโ”€ RecordViewController.swift
โ”‚ โ”œโ”€ AddRecordViewController.swift
โ”‚ โ””โ”€ SettingsViewController.swift

โ”œโ”€ Storyboard/
โ”‚ โ””โ”€ Main.storyboard
โ”œโ”€ Resources/
โ”‚ โ”œโ”€ Assets.xcassets
โ”‚ โ””โ”€ Localizable.strings
โ””โ”€ AppDelegate.swift


๊ฐœ๋ฐœ ๋‹จ๊ณ„๋ณ„ ์ผ์ • (์Šคํ† ๋ฆฌ๋ณด๋“œ ๋ฒ„์ „)

๋‹จ๊ณ„๊ธฐ๊ฐ„์ฃผ์š” ๋‚ด์šฉ
1๋‹จ๊ณ„1์ฃผStoryboard ํ™”๋ฉด ์„ค๊ณ„ (Main, Record, Add, Settings)
2๋‹จ๊ณ„2์ฃผBMI ๊ณ„์‚ฐ ๋กœ์ง + ๋‹จ์œ„ ๋ณ€ํ™˜ ๊ธฐ๋Šฅ ๊ตฌํ˜„
3๋‹จ๊ณ„3์ฃผ๊ธฐ๋ก ์ €์žฅ (UserDefaults), ํ…Œ์ด๋ธ”๋ทฐ ๊ตฌํ˜„
4๋‹จ๊ณ„4์ฃผ์•Œ๋ฆผ ๊ธฐ๋Šฅ ๋ฐ ์„ค์ • ํ™”๋ฉด ์ถ”๊ฐ€
5๋‹จ๊ณ„5์ฃผUI ๊ฐœ์„  ๋ฐ ๋ฒ„๊ทธ ์ˆ˜์ •
6๋‹จ๊ณ„6์ฃผTestFlight ๋ฐฐํฌ ๋ฐ ์•ฑ์Šคํ† ์–ด ๋“ฑ๋ก ์ค€๋น„

ํ–ฅํ›„ ํ™•์žฅ ์•„์ด๋””์–ด

  • ๊ทธ๋ž˜ํ”„ ์ถ”๊ฐ€ (์˜ˆ: Charts ํ”„๋ ˆ์ž„์›Œํฌ๋กœ ์ฒด์ค‘ ๋ณ€ํ™” ๊ทธ๋ž˜ํ”„)
  • HealthKit ์—ฐ๋™ (์ฒด์ค‘ ์ž๋™ ์ž…๋ ฅ)
  • AI ์ถ”์ฒœ (BMI ๊ฒฐ๊ณผ ๊ธฐ๋ฐ˜ ์šด๋™/์‹๋‹จ ํŒ ์ œ์‹œ)
{
    // ์ž…๋ ฅ๊ฐ’ ๊ฒ€์ฆ
    guard let heightText = txtHeight.text,
          let weightText = txtWeight.text,
          !heightText.isEmpty,
          !weightText.isEmpty else {
        lblResult.text = "ํ‚ค์™€ ์ฒด์ค‘์„ ์ž…๋ ฅํ•˜์„ธ์š”!"
        return
    }
    
    // Double ๋ณ€ํ™˜ ๊ฒ€์ฆ
    guard let height = Double(heightText),
          let weight = Double(weightText) else {
        lblResult.text = "์ˆซ์ž๋กœ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”!"
        return
    }

    // BMI ๊ณ„์‚ฐ
    let bmi = weight / pow(height * 0.01, 2)
    let shortenedBmi = String(format: "%.1f", bmi)

    // ํŒ์ • ๊ณ„์‚ฐ
    let body: String
    switch bmi {
    case 40...:
        body = "3๋‹จ๊ณ„ ๋น„๋งŒ"
    case 30..<40:
        body = "2๋‹จ๊ณ„ ๋น„๋งŒ"
    case 25..<30:
        body = "1๋‹จ๊ณ„ ๋น„๋งŒ"
    case 18.5..<25:
        body = "์ •์ƒ"
    default:
        body = "์ €์ฒด์ค‘"
    }

    // ๊ฒฐ๊ณผ ํ‘œ์‹œ
    lblResult.text = "BMI: \(shortenedBmi), ํŒ์ •: \(body)"
    print("BMI: \(shortenedBmi), ํŒ์ •: \(body)")
}
            var color = UIColor.white
            if bmi >= 40 {
                color = UIColor(displayP3Red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0)
                body = "3๋‹จ๊ณ„ ๋น„๋งŒ"
            } else if bmi >= 30 && bmi < 40 {
                color = UIColor(displayP3Red: 1.0, green: 0.7, blue: 0.0, alpha: 1.0)
                body = "2๋‹จ๊ณ„ ๋น„๋งŒ"
            } else if bmi >= 25 && bmi < 30 {
                color = UIColor(displayP3Red: 1.0, green: 1.0, blue: 0.0, alpha: 1.0)
                body = "1๋‹จ๊ณ„ ๋น„๋งŒ"
            } else if bmi >= 18.5 && bmi < 25 {
                color = UIColor(displayP3Red: 0.0, green: 0.8, blue: 0.0, alpha: 1.0)
                body = "์ •์ƒ"
            } else {
                color = UIColor(displayP3Red: 0.0, green: 0.0, blue: 1.0, alpha: 1.0)
                body = "์ €์ฒด์ค‘"
            }

            print("BMI:\(shortenedBmi), ํŒ์ •:\(body)")
            lblResult.backgroundColor = color
            lblResult.clipsToBounds = true
            lblResult.layer.cornerRadius = 20
            lblResult.text = "BMI:\(shortenedBmi), ํŒ์ •:\(body)"