# Weightlifting calculator

I'm building a simple iOS app using Swift that calculates the One Rep Max weight a user can theoretically lift. User inputs a weight lifted, the number of repetitions and the app uses one of several formulas to calculate the maximum weight they should be able to lift one time.

I'm skeptical about the way I'm implementing this whole thing because I'm using enum for the formula names but because I'm also providing the ability to save lift events in a log using Core Data, I have an entity called Formula with a relationship to a LiftEvent.

It works, but I think it smells and I would really appreciate some direction from someone who can tell me if it's a bad idea to be using both an enum and an entity for these formulas. If I'm headed down the wrong path, I want someone to set me straight.

Here's the LiftEvent:

import Foundation
import CoreData

extension LiftEvent {

@NSManaged var date: NSDate
@NSManaged var liftEventUid: NSNumber
@NSManaged var maxAmount: NSNumber
@NSManaged var repetitions: NSNumber
@NSManaged var weightLifted: NSNumber
@NSManaged var calculation: Formula
@NSManaged var lift: Lift
}


and the Formula entity:

import Foundation
import CoreData

extension Formula {

@NSManaged var formulaName: String
@NSManaged var formulaUid: NSNumber
@NSManaged var used: NSSet
}


Here's the CalculatorBrain.swift containing the enum for the formula names and the calculation function:

import Foundation

let formulas = ["Epley", "Brzychi", "Baechle", "Lander", "Lombardi", "Mayhew Et.Al.", "O'Conner Et.Al."]

enum CalculationFormula: Int {
case Epley
case Brzychi
case Baechle
case Lander
case Lombardi
case MayhewEtAl
case OConnerEtAl
case TotalFormulas

var formulaName: String {
switch self {
case .Epley:
return "Epley"
case .Brzychi:
return "Brzychi"
case .Baechle:
return "Baechle"
case .Lander:
return "Lander"
case .Lombardi:
return "Lombardi"
case .MayhewEtAl:
return "Mayhew Et.Al."
case .OConnerEtAl:
return "O'Conner Et.Al."
default:
}
}
}

class CalculatorBrain: NSObject {

var weightLifted: Double?
var repetitions: Double?
var oneRepMax: Double?
var preferredFormula: CalculationFormula

override init() {
let userDefaults = NSUserDefaults.standardUserDefaults()
let tempDefault = userDefaults.objectForKey("preferredFormula") as! Int?
self.preferredFormula = CalculationFormula.Epley
if(tempDefault != nil) {
self.preferredFormula = CalculationFormula(rawValue: tempDefault!)!
}
}

func calculateOneRepMax(weightLifted: Double, repetitions: Double, formula: CalculationFormula ) -> Double {
switch formula {
case .Epley:
oneRepMax = weightLifted * (1 + (repetitions)/30)
return oneRepMax!
case .Brzychi:
oneRepMax = weightLifted * (36/(37 - repetitions))
return oneRepMax!
case .Baechle:
oneRepMax = weightLifted * ( 1 + ( 0.033 * repetitions ) )
return oneRepMax!
case .Lander:
oneRepMax = 100 * weightLifted / (101.3 - (2.67123 * repetitions))
return oneRepMax!
case .Lombardi:
//oneRepMax = weightLifted * Double((Int(repetitions) ^^ Int(0.10)))
//return oneRepMax! //formula doesn't work
return 1
case .MayhewEtAl:
oneRepMax = 100 * weightLifted / (101.3 - (2.67123 * repetitions))
return oneRepMax!
case .OConnerEtAl:
oneRepMax = weightLifted * (1 + 0.025 * repetitions)
return oneRepMax!
default:
return 1
}
}
}


Notice the raw value type of the enum is Int. I've chosen to do that because a) I need to use it to populate the table view below where the users can select one of them as the user default and I could only get the override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell to work as an Int and b) I also was only able to get the loop in the refresh() function to work if I made it an Int. This function is called when the user selects a formula and it places a checkmark on that selected formula. The selected formula is then set as the new default value and will be included in the lift log if the user chooses to save a lift in the lift log (this is done in another view).

Here's the table view controller where I use the enum as just described:

import UIKit

class FormulasTableViewController: UITableViewController {

func refresh() {
for index in 0 ... CalculationFormula.TotalFormulas.rawValue {
let indexPath = NSIndexPath(forItem: index, inSection: 0)
if let cell = tableView.cellForRowAtIndexPath(indexPath) {
cell.accessoryType = calculator.preferredFormula.rawValue == index ? .Checkmark : .None
print(cell.accessoryType.rawValue)
}
}
}

override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
guard let theFormula = CalculationFormula(rawValue: indexPath.row) else {
return
}
calculator.preferredFormula = theFormula
let newPreferredFormula = calculator.preferredFormula
savePreferences(newPreferredFormula)
refresh()
}

override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
refresh()
}

override func viewDidLoad() {
}

override func didReceiveMemoryWarning() {
}

// MARK: - Table view data source

override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}

override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return formulas.count
}

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("formulasCell", forIndexPath: indexPath)
let currentFormula = formulas[indexPath.row]
cell.textLabel?.text = currentFormula
return cell
}

func savePreferences(formula: CalculationFormula) {
let userDefaults = NSUserDefaults.standardUserDefaults()
userDefaults.setObject(formula.formulaName, forKey: "preferredFormula")
}

var currentFormula: String {
get {
let userDefaults = NSUserDefaults.standardUserDefaults()
if let returnValue = userDefaults.objectForKey("preferredFormula") as? String {
return returnValue
} else {
return "Epley"
}
}
set {
NSUserDefaults.standardUserDefaults().setObject(newValue, forKey: "preferredFormula")
NSUserDefaults.standardUserDefaults().synchronize()
}
}
}


This is a perfect "question" for this site!

I see no problem with mapping an enum to an NSManagedObject. In fact I recommend it.

I think it is far better to encapsulate your core data classes, such that none of the rest of the code relies on the fact that core data is used. (Note that this was also expressed by Andy Matuschak in his "Refactor the Mega Controller" talk.)

This isn't duplication, rather it is simply multiple ways to refer to the same thing so that the thing can be more easily referenced in different places.

The value type of the enum need not be an Int. You could just as easily make the value type a String and make a "formulaID" calculated property that returns the int (instead of "formulaName".)

EDIT: To expand on the above a bit... If you had the computed property below, I expect you could figure out how to use the enum values in the cellForRowAtIndexPath method and the refresh method, even if the enum used a String value type:

var formulaID: Int {
switch self {
case .Epley:
return 0
case .Brzychi
return 1
case .Baechle
return 2
case .Lander
return 3
case .Lombardi
return 4
case .MayhewEtAl
return 5
case .OConnerEtAl
return 6
default:
fatalError()
}
}


Based on your comments, the Int value type is being used to establish the position of the formula in a UI element and I think that's a very bad idea. If you later want to re-order the formulas, for e.g. to put the user's most favorite formula at the top of the list, you're in a bit of a pickle. Note, this isn't an argument for making the value type a string. Rather, it's an argument for dis-associating the value type from the position in the UITableView.

Another quick comment about your code... I strongly expect that TotalFormulas is not really a CalculationFormula type. It should be removed.

• Daniel, I made the value type an Int because I couldn't figure out how to use the enum values in the cellForRowAtIndexPath method unless they were Ints. Similarly in the refresh() method, I didn't know how to make cell.accessoryType = calculator.preferredFormula.rawValue == index work without it being an Int. – Jim Jun 20 '16 at 5:03
• Also, about the Core Data stuff... so having an enum with the formula names and also having those formula names in a database 'table' isn't considered duplicating code somehow? – Jim Jun 20 '16 at 5:07
• I edited my answer to cover your additional concerns. – Daniel T. Jun 20 '16 at 13:07
• Your suspicion about TotalFormulas is correct - it is not a real CalculationFormula type. That was a hacky way to set the upper bound in for loop of the refresh() method because I couldn't get a .count on the enum. – Jim Jun 20 '16 at 16:27
• Instead, give the enum a count computed property that returns the count. – Daniel T. Jun 20 '16 at 16:32