the making of BMI Calculator app

Sometimes there’s a way to calculate what you are made of…

This app is an exercise mainly focused on performing segue to additional pages and using nil coalescing operator (??) when working with optional value. Surely some other fun discoveries along the way such as converting metric units to imperial units and how to display different decimals, and learning about BMI equations from a few articles is a pleasant bonus! Unlike previous blogs where I had some intense code breakdown notes because I really needed that process of holding my own hand if you will, but I’m now feeling more comfortable moving beyond that process (yay!). So for this blog, I would only individually share some fun and useful code blocks because I think you might benefit from them if you are also just learning like me. They are like glowing mushrooms that bring a mysterious thrill in a dark forest. Alright, let’s reveal these mushrooms!

  • What is nil coalescing operator?

    I figured what’s better than Paul Hudson’s explanation? Check it out here.

  • What to do with optional:

// Force unwrapping

optional!

// Check for nil value

if optional != nil {
    optional!
}

// optional binding

if let safeOptional = optional {
    safeOptional
}

// nil coalescing operator

optional ?? defaultValue

// optional chaining

optional?.property
optional?.method()

// example of optional chaining and nil coalescing operator

 func getBMIValue() -> String {
        
        let bmiTo1DecimalPlace = String(format: "%.1f", bmi?.value ?? 0.0)
        return bmiTo1DecimalPlace
    }

Here are some places where nil coalescing operator was used in this app:

func getBMIValue() -> String {
       
       let bmiTo1DecimalPlace = String(format: "%.1f", bmi?.value ?? 0.0)
       return bmiTo1DecimalPlace
   }
func getAdvice() -> String {
       return bmi?.advice ?? ""
   }
func getColor() -> UIColor {
        return bmi?.color ?? #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
    }

Before moving on to segue, let’s quickly take a look at the MVC pattern structured.

  • For models, we need a BMI struct model so we call for it’s properties we could customize such as BMI result value display, as well as color change and advice given based on the result. Another model is the CalculatorBrain where we dump all the code for the actual calculation equation and advice we want to customize based on the result.

  • For views, we need to make two pages in the storyboard, one is for calculation, the other one is for the result display.

  • For view controllers, We need a controller that does the calculation named CalculateVC for the first page to display the BMI calculator, and a final result view controller which I named ResultVC for displaying the result on the second page.

MVC pattern

Now onto the exciting Segue!

How to add a new view controller page:

  • In the Object Library (bottom right panel), search for "View Controller."

  • Drag and drop the "View Controller" object onto your storyboard canvas.

How to link a new view controller page:

  • Create a new swift file for the new controller: Right-click on your project in the Project Navigator. Choose "New File..." -> "Cocoa Touch Class". Name your new class (I name it “ResultVC” in this app), set the subclass to "UIViewController," and choose Swift as the language.

  • Link the new swift file to storyboard: Select your new view controller in the storyboard, open the Identity Inspector (top-right panel), under "Custom Class," set the class to the name of your Swift file (“ResultVC” in my case). Control-click on the controller icon (see below) and drag it to the new page, when you let go, a dropdown menu appears, select "Present Modally" to display over the current screen.

Don’t forget to give it a name for the Storyboard Segue Identifier!

How to pass data to the new view controller page: (all these codes happen in the first page’s view controller swift file, in my case, it’s the file named “CalculateVC“)

  • We need to first, prepare it in the previous page where the button action happens.

  • Then override that func with our customized method.

// prepare for segue inside of a button action

performSegue(withIdentifier: "goToResult", sender: self) // identifier is the name of the segue in the storyboard 

// function to prepare for segue and pass data to the next view controller

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "goToResult" {
            let destinationVC = segue.destination as! ResultVC // set the destination view controller as ResultVC
            destinationVC.bmiValueResult = calculatorBrain.getBMIValue() 
            destinationVC.advice = calculatorBrain.getAdvice()
            destinationVC.color = calculatorBrain.getColor()
        }

That’s basically the meat of this app, nil coalescing operator and segue.

Here are some useful code snippets I collected along the way:

// rounding float to 2 decimal places

let floatValue: Float = 3.14159
let roundedString = String(format: "%.2f", floatValue) // "3.14"

// convert float to Int

let floatValue: Float = 3.14159
let intValue = Int(floatValue) // 3

// alternative to colorLiteral
// colorLiteral thumbnail display version of code is from older version of Xcode, so the older version of code for that no longer works, so I didn't include that here, but if you still want to see the thumbnail, use this code instead. Just double click on the square thumbnail to select color when it appears as you type out the code.

UIColor {_ in return #colorLiteral()}

// BMI equation

// Metric Units: BMI = weight (kg) / height² (m²)
// Imperial Units: BMI = BMI = (weight (lb) / height² (in²)) * 703

// metric (kilograms and meters) units version of the BMI calculation

@IBAction func heightSliderChanged(_ sender: UISlider) {
        let height = String(format: "%.2f", sender.value)
        heightLabel.text = "\(height)m"
    }
    
    @IBAction func weightSiderChanged(_ sender: UISlider) {
        let weight = String(format: "%.0f", sender.value)
        weightLabel.text = "\(weight)Kg"
    }

// imperial (pounds and inches) units version of the BMI calculation

 @IBAction func heightSliderChanged(_ sender: UISlider) {
        let totalInches = Int(sender.value)
        let feet = totalInches / 12
        let inches = totalInches % 12
        let height = String(format: "%.0f", sender.value)
        heightNumber.text = "\(feet)'\(inches)\"" // The backslashes before the quotation marks tell the compiler that those quotation marks are part of the string itself, not the start or end of the string. An "escape character" in programming is called that because it allows you to "escape" from the special meaning within a string. It tells the compiler, "Don't treat the next character as special; just take it literally."
    }
    
    @IBAction func weightSiderChanged(_ sender: UISlider) {
        let weight = String(format: "%.0f", sender.value)
        weightNumber.text = "\(weight)lb"
    }

// self.dismiss is used to dismiss the current view controller

    @IBAction func recalculatePressed(_ sender: UIButton) {
        self.dismiss(animated: true, completion: nil) // completion is nil because we don't need to do anything after the view controller is dismissed, otherwise you can add a completion block to perform an action after the view controller is dismissed, for example, updating the UI of the previous view controller
    }

And my assets design:

Just one more thing…in case you are confused about the colorLiteral (colorLiteral thumbnail display version of code is from older version of Xcode, so the older version of code for that no longer works, but if you still want to see the thumbnail, see the alternative code mentioned above) code, since you could only see the display of the thumbnail in Xcode environment, I’m attached a screenshot of how it appears inside of Xcode here:

App Code:

// Models: BMI, CalculatorBrain

// BMI.swift

import UIKit

struct BMI {
    let value: Float
    let advice: String
    let color: UIColor
}

// CalculatorBrain.swift

import UIKit

struct CalculatorBrain {
    
    var bmi: BMI?
    
    func getBMIValue() -> String {
        
        let bmiTo1DecimalPlace = String(format: "%.1f", bmi?.value ?? 0.0)
        return bmiTo1DecimalPlace
    }
    
    mutating func calculateBMI(height: Float, weight: Float) {
        
        // BMI = (weight (lb) / height² (in²)) * 703
        
        let bmiValue = (weight / pow(height, 2)) * 703
        
        if bmiValue < 18.5 {
            bmi = BMI(value: bmiValue, advice: "thin air...", color: UIColor {_ in return #colorLiteral(red: 0.2392156869, green: 0.6745098233, blue: 0.9686274529, alpha: 1) })
        } else if bmiValue < 24.9 {
            bmi = BMI(value: bmiValue, advice: "a bowl of healthy salad", color: UIColor {_ in return #colorLiteral(red: 0.3411764801, green: 0.6235294342, blue: 0.1686274558, alpha: 1) })
        } else {
            bmi = BMI(value: bmiValue, advice: "lots lots of icecream...", color: UIColor {_ in return #colorLiteral(red: 1, green: 0.1491314173, blue: 0, alpha: 1) })
        }
    }
    
    func getAdvice() -> String {
        return bmi?.advice ?? ""
    }
    
    func getColor() -> UIColor {
        return bmi?.color ?? #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
    }
    
}

// Controllers: CalculateVC, ResultVC

// CalculateVC.swift


import UIKit

class CalculateVC: UIViewController {
    
    var calculatorBrain = CalculatorBrain()
    
    @IBOutlet weak var heightNumber: UILabel!
    @IBOutlet weak var weightNumber: UILabel!
    @IBOutlet weak var heightSlider: UISlider!
    @IBOutlet weak var weightSlider: UISlider!
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    @IBAction func heightSliderChanged(_ sender: UISlider) {
        let totalInches = Int(sender.value)
        let feet = totalInches / 12
        let inches = totalInches % 12
        let height = String(format: "%.2f", sender.value)
        heightNumber.text = "\(feet)'\(inches)\""
    }
    
    @IBAction func weightSiderChanged(_ sender: UISlider) {
        let weight = String(format: "%.0f", sender.value)
        weightNumber.text = "\(weight)lb"
    }
    
    @IBAction func calculatePressed(_ sender: UIButton) {
        
        let height = heightSlider.value
        let weight = weightSlider.value
        
        calculatorBrain.calculateBMI(height: height, weight: weight)
        performSegue(withIdentifier: "goToResult", sender: self)
        
    }
    
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "goToResult" {
            let destinationVC = segue.destination as! ResultVC
            destinationVC.bmiValueResult = calculatorBrain.getBMIValue()
            destinationVC.advice = calculatorBrain.getAdvice()
            destinationVC.color = calculatorBrain.getColor()
        }
    }
    
}

// ResultVC.swift


import UIKit

class ResultVC: UIViewController {
    
    var bmiValueResult: String?
    var advice: String?
    var color: UIColor?
    
    @IBOutlet weak var bmiLabel: UILabel!
    @IBOutlet weak var adviceLabel: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        bmiLabel.text = bmiValueResult
        adviceLabel.text = advice
        bmiLabel.textColor = color
    }
    
    @IBAction func recalculatePressed(_ sender: UIButton) {
        self.dismiss(animated: true, completion: nil)
    }
}
Previous
Previous

the making of bill splitter app

Next
Next

the making of interactive storyline app “Distopio“