C44.跟著老師動吃動!BMR、TDEE計算機

AI越來越會畫惹

練習成果

沒有瘦不下來的理由了!

練習發想

  • 平常有運動的習慣,剛好可藉由這次的練習,來製作一個基礎代謝率(BMR)以及 每日總熱量消耗(TDEE)的計算機。
  • 而關於BMR以及TDEE的計算流程方式流程:
  1. 男性女性的BMR計算公式不同,因此先將它們兩個的BMR值計算出來。
  2. 在用BMR的值來計算TDEE的值
  3. BMR值不變的狀態下,TDEE 會因為不同運動頻率的計算公式,使得有不同的值產生。

練習項目

這次的練習除了下面幾個項目,同時在PETER的建議下,也強調自己在寫程式時去加強註解 ,以及print的使用。

不得不說這次雖然註解相比以往的有點多,但是當我遇到一些程式碼位置的問題,或是修改時,可以更好的去查找釐清出問題!

  • 數字與字串間的轉換。(TextField中的字串轉數字)
  • Slider 數值轉換為整數索引值
  • if else 的運用,if else在加入 if else。(條件式判斷運動頻率)
  • optional binding的練習,確保 height、weight、age 不為 nil。(與if else搭配 處理TextField可能為 nil 的值)
  • function的使用(使程式碼更整齊)

佈局配置圖

  • 紅框:Segmented(性別) / TextField(身高、體重、年紀),紅框內部的部分即可算出BMR。
  • 藍框:Slider(調整運動頻率:不運動、每週運動1–3次、每週運動3–4次、每週運動6–7次、無時無刻都在動),搭配BMR可算出TDEE的值。
  • 黃框:計算、清除鈕。
  • 灰框:顯示BMR、TDEE的計算結果。

BMR的計算(IBOutlet)

  • 主要需要計算BMR時,所需要提供的資料。
  • 性別SegmentedControl、身高、體重、年齡 TextField 的 IBOutlet。
    // 性別選項
@IBOutlet weak var genderSegmentedControl: UISegmentedControl!
// 身高輸入: cm
@IBOutlet weak var heightInputField: UITextField!
// 體重輸入: kg
@IBOutlet weak var weightInputField: UITextField!
// 年齡輸入
@IBOutlet weak var ageInputField: UITextField!

BMR的計算(Button IBAction)

  • calculateButtonTapped中進行計算
  • Optional 表示該值可以是一個非nil,也可以是 nil
  • DoubleInt 則是非Optional的數字類型(一定要有值)。
TextField 的 text 也是 optional,但會有值(空字串),可以放心加!
  • 使用強制解包 (!) 將 heightInputField.textweightInputField.textageInputField.text 的 Optional值 解包為非可選型的值。表示假設這些值不為 nil,並將它們分別指派給 heightTextweightTextageText。但如果這些可選型值為 nil,則會在執行時觸發錯誤。
  • 將這些文字值轉換為 Double,以進行計算。
有問號代表optional,可能會得到nil,之後要加!才有辦法讀出裡面的值
  • 如果文字無法轉換為 Double,則會得到 nil。
    
// 將 UITextField 的 text 屬性的值指派給相應的變數。保證這些 Optional的值 不是nil,以確保它們的值存在。
let heightText = heightInputField.text!
let weightText = weightInputField.text!
let ageText = ageInputField.text!

// 字串轉數字用於計算
let height = Double(heightText)
let weight = Double(weightText)
let age = Double(ageText)

if else 運用 (Optional Binding)

當初在調整時忘記順序,將BMR計算公式放置在if else外部,導致在測試計算時閃退,還好有註解才發現 XD

  • 使用 Optional Binding 檢查身高、體重、年紀 的值是否不為 nil
  • 當先前條件為不等於nil時,則進行計算。在此已經確認身高體重年紀皆有值,因此要加入(!),將 Optional 進行解包。
  • 我這邊透過SegmentedControl來分別辨識男性女性的BMR的計算公式。
  • 計算出來後再將BMR值字串化,取小數點後一位。
        // 計算BMR之前,先檢查 height、weight 和age 是否為 nil,如果有值(不為nil)則進行計算,selectedSegmentIndex == 0 為男性
if height != nil, weight != nil, age != nil, genderSegmentedControl.selectedSegmentIndex == 0 {

// ● 男性:基礎代謝率 = (10 × 公斤體重) + (6.25 × 公分身高) - (5 × 年齡歲數) + 5
let bmrCalculate = (10 * weight!) + (6.25 * height!) - (5 * age!) + 5
// BMR 數值取小數點第一位,並顯示於 bmrResultLabel 上
bmrResultLabel.text = String(format: "%.1f", bmrCalculate)

// selectedSegmentIndex == 1 為女性
} else if height != nil, weight != nil, age != nil, genderSegmentedControl.selectedSegmentIndex == 1 {

// ● 女性:基礎代謝率 = (10 × 公斤體重) + (6.25 × 公分身高) - (5 × 年齡歲數) – 161
let bmrCalculate = (10 * weight!) + (6.25 * height!) - (5 * age!) - 161
bmrResultLabel.text = String(format: "%.1f", bmrCalculate)

}

運動頻率Slider

  • 當初看其他人在做這個作業時都是用UIPickerView,但現階段還不太理解,因此使用Slider來進行操作,先將重心放在Optional binding上XD
滑動時會顯示狀態,同時指定TDEE計算公式
  • 運動頻率以及相對應的計算公式
// 運動頻率以及相對應的計算公式
1.久坐(沒運動)
TDEE = 1.2 x BMR
2.輕量活動量(每周運動1-3天)
TDEE = 1.375 x BMR
3.中度活動量(每周運動3-5天)
TDEE = 1.55 x BMR
4.高度活動量(每周運動6-7天)
TDEE = 1.725 x BMR
5.非常高度活動量(無時無刻都在運動)
TDEE = 1.9 x BMR
  • activityLevelLabel:為顯示當前運動頻率的Label,讓使用者知道當Slider滑動到哪個選項。
  • exerciseSlider:用於選擇運動頻率的值,並在滑動時觸發 exerciseFrequencySliderChanged 的Slider IBAction。
    // 運動頻率
@IBOutlet weak var activityLevelLabel: UILabel!
// 存儲運動頻率 Slider 的值 (TDEE 才會用到)
@IBOutlet weak var exerciseSlider: UISlider!
  • 陣列部分:後續會透過exerciseFrequencySliderChanged來調用,藉以展示在activityLevelLabel上。
  • 我打算用Slider的值來當索引,故先在viewDidLoad()將 exerciseSlider 的最小值0,最大值為4。因為陣列選項有5個。由0開始計算到4,會完整涵蓋5個項目。
 // 運動頻率 Array
let exerciseFrequencyArrays = ["Sedentary or No Exercise", "Exercise 1-3 Times / Week", "Intense Exercise 3-4 Times / Week", "Intense Exercise 6-7 Times / Week", "Very Intense Exercise Daily!"]

// 在view載入後執行的初始化操作
override func viewDidLoad() {
super.viewDidLoad()

// exerciseSlider 的最大值最小值、起始值
exerciseSlider.minimumValue = 0
exerciseSlider.maximumValue = 4
exerciseSlider.value = 0
}

TDEE的運動頻率 Slider的IBAction

  • 因為Slider的值是浮點數,因此使用rounded()將其四捨五入為整數。
  • 將四捨五入後的整數sliderValue 設置回 exerciseSlider,這樣可以確保滑動條的顯示值與實際值保持一致,即顯示整數而不是小數。
此時的 sliderValue 為浮點數
  • 使用 Int(sliderValue)sliderValue 轉換為Int,並將其作為索引從 exerciseFrequencyArrays 中取出對應的運動頻率字串。因此當Slider的值改變時,運動頻率的Label顯示也會相應地更新。
    // 運動頻率Slider:
@IBAction func exerciseFrequencySliderChanged(_ sender: UISlider) {

// rounded() 四捨五入 sender.value
let sliderValue = sender.value.rounded()
// 將 sliderValue 轉換為整數並設置回 exerciseSlider,確保Slider條的值一直保持在整數。
exerciseSlider.setValue(sliderValue, animated: false)

/* 使用 sliderValue 作為索引,從 exerciseFrequencyArrays 中取出對應的運動頻率字串,並將其設置為 activityLevelLabel 的文字。實現當 Slider 滑動時,更新運動頻率文字的顯示。 */
activityLevelLabel.text = exerciseFrequencyArrays[Int(sliderValue)]
}

if else 中在加入if else進行另一個條件式(TDEE運動頻率判斷)

  • 一樣是在calculateButtonTapped 中進行計算,我這邊一樣先放男性來做示範。
exerciseSlider.value是浮點數
  • exerciseFrequencyIndex:因為是要運用Slider的值來給予TDEE不同運動頻率的計算公式,因此將其轉化成Int,用以進行if else 的條件判斷來給予公式。
可以明顯知道運動狀態的改變只會更改TDEE值
  • 由於 TDEE 的公式是基於BMR的值來進行計算。
  • 因此在原先的 BMR 條件式在加入另一個 if else進行運動頻率判斷。
  • 對應exerciseFrequencySliderChanged 我將其值變為整數,並將其用作更新運動頻率Label索引值。
  • 同樣的我透過 exerciseFrequencyIndex 的值來對應運動頻率的計算公式 ,使用者通過Slider選擇的數值會直接影響到 TDEE 的計算結果。
// 將 exerciseSlider 的值轉換為整數,作為運動頻率的索引值。並且用在 if else 條件判斷 TDEE 計算使用。
let exerciseFrequencyIndex = Int(exerciseSlider.value)


// 計算BMR之前,先檢查 height、weight 和age 是否為 nil,如果有值(不為nil)則進行計算,selectedSegmentIndex == 0 為男性
if height != nil, weight != nil, age != nil, genderSegmentedControl.selectedSegmentIndex == 0 {

// 當Slider的值 == 0 時,代表沒有運動習慣時,該條件下所使用的 TDEE計算公式
if exerciseFrequencyIndex == 0 {

// 沒運動習慣:TDEE = 1.2 x BMR
let tdee = bmrCalculate * 1.2
// 數字轉字串: TDEE 數值取小數點第一位,並顯示於 tdeeResultLabel 上
tdeeResultLabel.text = String(format: "%.1f", tdee)

} else if exerciseFrequencyIndex == 1 {

// 輕量活動(每周運動1-3天):TDEE = 1.375 x BMR
let tdee = bmrCalculate * 1.375
tdeeResultLabel.text = String(format: "%.1f", tdee)

} else if exerciseFrequencyIndex == 2 {

// 中度活動量(每周運動3-5天):TDEE = 1.55 x BMR
let tdee = bmrCalculate * 1.55
tdeeResultLabel.text = String(format: "%.1f", tdee)

} else if exerciseFrequencyIndex == 3 {

// 高度活動量(每周運動6-7天):TDEE = 1.725 x BMR
let tdee = bmrCalculate * 1.725
tdeeResultLabel.text = String(format: "%.1f", tdee)

} else if exerciseFrequencyIndex == 4 {

// 非常高度活動量(無時無刻都在運動):TDEE = 1.9 x BMR
let tdee = bmrCalculate * 1.9
tdeeResultLabel.text = String(format: "%.1f", tdee)
}

}

function的運用 (return運用)

  • 因為不管男性還是女性,只有在BMR的計算才會有公式上的差異,而TDEE則會依照不同運動頻率來做計算。此外她們分屬不同的 {} 括弧裡,因此在命名上並不會互相影響,都是在各自獨立的括弧裡進行運算。
  • 為了使程式calculateButtonTapped裡的程式碼相對簡潔,因此另外創立一個funtion calculateTdee
  • 定義了一個 calculateTdee 的function,用於計算 TDEE (Total Daily Energy Expenditure)。TDEE 是一個人在一天中所需的總能量消耗量,根據不同的運動頻率進行計算。
  • calculateTdee 方法中,有兩個參數 (bmrfrequencyIndex )。bmr 是之前計算得到的 BMR (Basal Metabolic Rate) 值,而 frequencyIndex 是選定的運動頻率索引值。
  • 根據 frequencyIndex 的不同,calculateTdee 使用不同的條件判斷來計算相應的 TDEE 值。每個條件分支對應一個特定的運動頻率,並根據該運動頻率使用相應的計算公式來計算 TDEE 值。
  • var tdee: Double = 0.0 宣告了一個名為 tdee 的變數,並將其初始化為 0.0。在後續的計算中,根據不同的運動頻率和BMR,tdee 的值會被計算出並存儲在這個變數中。
  • if else 括弧內計算得到的 TDEE 值指派給 var tdee: Double
  • 接著使用 returntdee 的值返回給調用 calculateTdee 的地方。使外部的程式碼就可以獲取到計算得到的 TDEE 值,並進一步處理或顯示該值。
func calculateTdee(bmr:Double, frequencyIndex: Int) -> Double{

// 先宣告一個變數來存儲計算得到的 TDEE 值。
var tdee: Double = 0.0

// 根據運動頻率的 Slider 的值,來決定該條件下所使用的 TDEE計算公式
if frequencyIndex == 0 {
// 沒運動習慣:TDEE = 1.2 x BMR
tdee = 1.2 * bmr

} else if frequencyIndex == 1 {
// 輕量活動(每周運動1-3天):TDEE = 1.375 x BMR
tdee = 1.375 * bmr

} else if frequencyIndex == 2 {
// 中度活動量(每周運動3-5天):TDEE = 1.55 x BMR
tdee = 1.55 * bmr

} else if frequencyIndex == 3 {
// 高度活動量(每周運動6-7天):TDEE = 1.725 x BMR
tdee = 1.725 * bmr

} else if frequencyIndex == 4 {
// 非常高度活動量(無時無刻都在運動):TDEE = 1.9 x BMR
tdee = 1.9 * bmr
}
// 返回計算得到的 TDEE 值
return tdee
}

在 calculateButtonTapped 裡面調用 calculateTdee 方法

  • 其兩個參數:bmr帶入計算出來的BMR值。frequencyIndex帶入運動頻率的索引值。
  • 將使用者的BMR值以及所選的運動頻率,用來進行條件判斷,並且得到TDEE的值。
  • 再將計算得到的 TDEE 值格式化後設置到 tdeeResultLabel 中顯示。
        // 將 exerciseSlider 的值轉換為整數,作為運動頻率的索引值。並且用在 if else 條件判斷 TDEE 計算使用。
let exerciseFrequencyIndex = Int(exerciseSlider.value)

// 計算BMR之前,先檢查 height、weight 和age 是否為 nil,如果有值(不為nil)則進行計算,selectedSegmentIndex == 0 為男性
if height != nil, weight != nil, age != nil, genderSegmentedControl.selectedSegmentIndex == 0 {

let bmrCalculate = (10 * weight!) + (6.25 * height!) - (5 * age!) + 5
bmrResultLabel.text = String(format: "%.1f", bmrCalculate)

// 根據選定的 運動頻率索引值 和 前面計算得到的 BMR,調用 calculateTdee function 計算 TDEE。
let tdee = calculateTdee(bmr: bmrCalculate, frequencyIndex: exerciseFrequencyIndex)
// 將結果設置到 tdeeResultLabel 中。
tdeeResultLabel.text = String(format: "%.1f", tdee)

}

GitHub

參考

--

--

wei Tsao 學習紀錄
彼得潘的 Swift iOS / Flutter App 開發教室

Hi ! 我是wei , 先前未接觸過程式開發設計,想藉此來記錄自己的學習歷程,以利培養自己的程式邏輯 :)