I'm writing a weight training app that calculates a one rep maximum and it has several options including recording your lifts in pounds or kilograms. I'm trying my best to follow the MVC pattern but I'm not entirely sure where to do some calculating and formatting.

I have a UITableView to display my Lift Log and when I load it, I'm converting the oneRepMaxWeight as needed to display all values in pounds or kilograms, depending on the user's current default units. I also round the numbers if the user's default is set to round (see formatForDisplay(value:):

class LiftLogViewController: UIViewController, NSFetchedResultsControllerDelegate, DismissalDelegate {

    //MARK: IB outlets

    @IBOutlet var tableView: UITableView!
    @IBOutlet weak var navItem: UINavigationItem!

    let coreDataStack = CoreDataStack()
    var liftEvents = [LiftEvent]()
    var preferredUnits = UserDefaultsManager.sharedInstance.preferredUnits

    override func viewDidLoad() {

        let doneButton = UIBarButtonItem(title: "Done", style: .Plain, target: self, action: #selector(self.dismissLog(_:)))

        self.navItem.rightBarButtonItem = doneButton

        tableView.allowsSelectionDuringEditing = true
        tableView.rowHeight = 88.0
        tableView.rowHeight = UITableViewAutomaticDimension
        tableView.estimatedRowHeight = 88.0
        automaticallyAdjustsScrollViewInsets = false

    func selectionDidFinish(controller: LiftSelectionTableViewController) {
        controller.dismissViewControllerAnimated(true, completion: nil)

    func formatForDisplay(value: Double) -> String {

        let roundingIsOn = UserDefaultsManager.sharedInstance.preferredRoundingOption
        if roundingIsOn == true {
            let value = String(format:"%.lf", value)

            return value
        else {
            let value = String(format:"%.1lf", value)

            return value

    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
            let nav = segue.destinationViewController as! UINavigationController
            let vc = nav.topViewController as! LiftSelectionTableViewController
            vc.selectForFilter = true
            vc.dismissalDelegate = self

    func segueToLogFilters(sender: AnyObject) {
        performSegueWithIdentifier("segueToLogFilters", sender: self)

    // MARK: - Table view data source

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

    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return liftEvents.count


    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier("liftEventCell", forIndexPath: indexPath) as! LiftLogTableViewCell
        let liftEvent = liftEvents[indexPath.row]
        cell.viewData = LiftLogTableViewCell.ViewData(liftEvent: liftEvent)

        if liftEvent.units != preferredUnits {
            let from = Unit(rawValue: cell.units.text!)
            let to = Unit(rawValue: preferredUnits!)
            let oneRepMaxWeight = Double(cell.oneRepMaxWeight.text!)
            let convertedMax = calculator.convertUnits(from!, to: to!, value: oneRepMaxWeight!)
            cell.oneRepMaxWeight.text = String(convertedMax)
            cell.units.text = preferredUnits

        let formattedMaxWeight = formatForDisplay(Double(cell.oneRepMaxWeight.text!)!)
        cell.oneRepMaxWeight.text = formattedMaxWeight
        cell.liftName.textColor = UIColor(hexString: "232B35")
        cell.oneRepMaxWeight.textColor = UIColor(hexString: "232B35")
        cell.units.textColor = UIColor(hexString: "232B35")

        return cell

I recently created a separate UITableViewCell class to get the conversion and formatting logic out of cellForRowAtIndexPath because I don't think it belongs there and I can't test it if it's in there. But then I found myself having to put this code into this new class and that seems just as bad:

class LiftLogTableViewCell: UITableViewCell {

    @IBOutlet weak var liftName: UILabel!
    @IBOutlet weak var liftDetails: UILabel!
    @IBOutlet weak var liftDate: UILabel!
    @IBOutlet weak var oneRepMaxWeight: UILabel!
    @IBOutlet weak var units: UILabel!
    @IBOutlet weak var formula: UILabel!

    struct ViewData {
        let liftName: String
        let weightLifted: Double
        let repetitions: Int
        let liftDate: String
        let oneRepMaxWeight: Double
        let units: String
        let formula: String

    var viewData: ViewData? {
        didSet {
            liftName!.text = viewData!.liftName
            liftDetails!.text = "\(viewData!.weightLifted) @ \(viewData!.repetitions)"
            liftDate!.text = "on \(viewData!.liftDate)"
            oneRepMaxWeight!.text = "\(viewData!.oneRepMaxWeight)"
            formula!.text = viewData!.formula
            units!.text = viewData!.units

extension LiftLogTableViewCell.ViewData {
    init(liftEvent: LiftEvent) {
        self.liftName = liftEvent.liftName
        self.weightLifted = Double(liftEvent.weightLifted)
        self.repetitions = Int(liftEvent.repetitions)
        self.formula = liftEvent.calculation.formulaName //change 'calculation' to 'forumla' for heaven's sake
        let dateFormatter = NSDateFormatter()
        dateFormatter.dateFormat = "yyyy-MM-dd"
        let formattedDate = dateFormatter.stringFromDate(liftEvent.liftDate)
        self.liftDate = "\(formattedDate)"
        self.oneRepMaxWeight = Double(liftEvent.maxAmount)
        self.units = liftEvent.units

Ultimately, my question is, if I'm trying to follow MVC, where should I put code to convert these values and format them as I'm creating my tableview cells? Would it make sense to create helper class(es) that handle these things?


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.