Visual Format Language calculator UI improvements

I know that it's woefully inefficient and doesn't quite visually match up, but I really like the idea of VFL and wanted to try to recreate a standard simplified calculator UI with it.


The code below does sort of resemble the UI (no logic to the buttons) but I would care to know any improvements to scale better without using hard coded points, just constraints so that it scales devices independently (I am especially looking for a loop to cut down on UI control declarations):



@IBDesignable class calcButtons: UIButton {
    
        override init(frame: CGRect) {
            super.init(frame: frame)
            calcButtons()
        }
        
        required init?(coder: NSCoder) {
            super.init(coder: coder)
            calcButtons()
        }
        
        
        private func calcButtons() {
            
            let calcPlus = UIButton()
            calcPlus.translatesAutoresizingMaskIntoConstraints = false
            calcPlus.setTitle("+", for: .normal)
            calcPlus.setTitleColor(UIColor.black, for: .normal)
            calcPlus.setTitleColor(UIColor.white, for: .highlighted)
            calcPlus.backgroundColor = UIColor.orange
            addSubview(calcPlus)
            
            let calcSubtract = UIButton()
            calcSubtract.translatesAutoresizingMaskIntoConstraints = false
            calcSubtract.setTitle("-", for: .normal)
            calcSubtract.setTitleColor(UIColor.black, for: .normal)
            calcSubtract.setTitleColor(UIColor.white, for: .highlighted)
            calcSubtract.backgroundColor = UIColor.orange
            addSubview(calcSubtract)
            
            let calcMultiply = UIButton()
            calcMultiply.translatesAutoresizingMaskIntoConstraints = false
            calcMultiply.setTitle("x", for: .normal)
            calcMultiply.setTitleColor(UIColor.black, for: .normal)
            calcMultiply.setTitleColor(UIColor.white, for: .highlighted)
            calcMultiply.backgroundColor = UIColor.orange
            addSubview(calcMultiply)
            
            let calcDivide = UIButton()
            calcDivide.translatesAutoresizingMaskIntoConstraints = false
            calcDivide.setTitle("÷", for: .normal)
            calcDivide.setTitleColor(UIColor.black, for: .normal)
            calcDivide.setTitleColor(UIColor.white, for: .highlighted)
            calcDivide.backgroundColor = UIColor.orange
            addSubview(calcDivide)

            let calcEquals = UIButton()
            calcEquals.translatesAutoresizingMaskIntoConstraints = false
            calcEquals.setTitle("=", for: .normal)
            calcEquals.setTitleColor(UIColor.black, for: .normal)
            calcEquals.setTitleColor(UIColor.white, for: .highlighted)
            calcEquals.backgroundColor = UIColor.orange
            addSubview(calcEquals)
            
            let calcDot = UIButton()
            calcDot.translatesAutoresizingMaskIntoConstraints = false
            calcDot.setTitle(".", for: .normal)
            calcDot.setTitleColor(UIColor.black, for: .normal)
            calcDot.setTitleColor(UIColor.white, for: .highlighted)
            calcDot.backgroundColor = UIColor.orange
            addSubview(calcDot)
            
            let calcDisplay = UIButton()
            calcDisplay.translatesAutoresizingMaskIntoConstraints = false
            calcDisplay.setTitle("1234567890", for: .normal)
            calcDisplay.setTitleColor(UIColor.black, for: .normal)
            calcDisplay.setTitleColor(UIColor.white, for: .highlighted)
            calcDisplay.backgroundColor = UIColor.orange
            addSubview(calcDisplay)
            
            let calcSpacer1 = UIButton()
            calcSpacer1.translatesAutoresizingMaskIntoConstraints = false
            calcSpacer1.setTitle("§", for: .normal)
            calcSpacer1.setTitleColor(UIColor.black, for: .normal)
            calcSpacer1.setTitleColor(UIColor.white, for: .highlighted)
            calcSpacer1.backgroundColor = UIColor.orange
            addSubview(calcSpacer1)

            let calcSpacer2 = UIButton()
            calcSpacer2.translatesAutoresizingMaskIntoConstraints = false
            calcSpacer2.setTitle("§", for: .normal)
            calcSpacer2.setTitleColor(UIColor.black, for: .normal)
            calcSpacer2.setTitleColor(UIColor.white, for: .highlighted)
            calcSpacer2.backgroundColor = UIColor.orange
            addSubview(calcSpacer2)
            
            let calcSpacer3 = UIButton()
            calcSpacer3.translatesAutoresizingMaskIntoConstraints = false
            calcSpacer3.setTitle("§", for: .normal)
            calcSpacer3.setTitleColor(UIColor.black, for: .normal)
            calcSpacer3.setTitleColor(UIColor.white, for: .highlighted)
            calcSpacer3.backgroundColor = UIColor.orange
            addSubview(calcSpacer3)
            
            let calcSpacer4 = UIButton()
            calcSpacer4.translatesAutoresizingMaskIntoConstraints = false
            calcSpacer4.setTitle("§", for: .normal)
            calcSpacer4.setTitleColor(UIColor.black, for: .normal)
            calcSpacer4.setTitleColor(UIColor.white, for: .highlighted)
            calcSpacer4.backgroundColor = UIColor.orange
            addSubview(calcSpacer4)
            
            let calc1 = UIButton()
            calc1.translatesAutoresizingMaskIntoConstraints = false
            calc1.setTitle("1", for: .normal)
            calc1.setTitleColor(UIColor.black, for: .normal)
            calc1.setTitleColor(UIColor.white, for: .highlighted)
            calc1.backgroundColor = UIColor.orange
            addSubview(calc1)

            let calc2 = UIButton()
            calc2.translatesAutoresizingMaskIntoConstraints = false
            calc2.setTitle("2", for: .normal)
            calc2.setTitleColor(UIColor.black, for: .normal)
            calc2.setTitleColor(UIColor.white, for: .highlighted)
            calc2.backgroundColor = UIColor.orange
            addSubview(calc2)

            let calc3 = UIButton()
            calc3.translatesAutoresizingMaskIntoConstraints = false
            calc3.setTitle("3", for: .normal)
            calc3.setTitleColor(UIColor.black, for: .normal)
            calc3.setTitleColor(UIColor.white, for: .highlighted)
            calc3.backgroundColor = UIColor.orange
            addSubview(calc3)

            let calc4 = UIButton()
            calc4.translatesAutoresizingMaskIntoConstraints = false
            calc4.setTitle("4", for: .normal)
            calc4.setTitleColor(UIColor.black, for: .normal)
            calc4.setTitleColor(UIColor.white, for: .highlighted)
            calc4.backgroundColor = UIColor.orange
            addSubview(calc4)

            let calc5 = UIButton()
            calc5.translatesAutoresizingMaskIntoConstraints = false
            calc5.setTitle("5", for: .normal)
            calc5.setTitleColor(UIColor.black, for: .normal)
            calc5.setTitleColor(UIColor.white, for: .highlighted)
            calc5.backgroundColor = UIColor.orange
            addSubview(calc5)

            let calc6 = UIButton()
            calc6.translatesAutoresizingMaskIntoConstraints = false
            calc6.setTitle("6", for: .normal)
            calc6.setTitleColor(UIColor.black, for: .normal)
            calc6.setTitleColor(UIColor.white, for: .highlighted)
            calc6.backgroundColor = UIColor.orange
            addSubview(calc6)

            let calc7 = UIButton()
            calc7.translatesAutoresizingMaskIntoConstraints = false
            calc7.setTitle("7", for: .normal)
            calc7.setTitleColor(UIColor.black, for: .normal)
            calc7.setTitleColor(UIColor.white, for: .highlighted)
            calc7.backgroundColor = UIColor.orange
            addSubview(calc7)

            let calc8 = UIButton()
            calc8.translatesAutoresizingMaskIntoConstraints = false
            calc8.setTitle("8", for: .normal)
            calc8.setTitleColor(UIColor.black, for: .normal)
            calc8.setTitleColor(UIColor.white, for: .highlighted)
            calc8.backgroundColor = UIColor.orange
            addSubview(calc8)

            let calc9 = UIButton()
            calc9.translatesAutoresizingMaskIntoConstraints = false
            calc9.setTitle("9", for: .normal)
            calc9.setTitleColor(UIColor.black, for: .normal)
            calc9.setTitleColor(UIColor.white, for: .highlighted)
            calc9.backgroundColor = UIColor.orange
            addSubview(calc9)

            let calc0 = UIButton()
            calc0.translatesAutoresizingMaskIntoConstraints = false
            calc0.setTitle("0", for: .normal)
            calc0.setTitleColor(UIColor.black, for: .normal)
            calc0.setTitleColor(UIColor.white, for: .highlighted)
            calc0.backgroundColor = UIColor.orange
            addSubview(calc0)

            
            
            
            let views = ["calcPlus": calcPlus, "calcSubtract": calcSubtract, "calcMultiply": calcMultiply, "calcDivide": calcDivide, "calcEquals": calcEquals, "calcDot": calcDot, "calcDisplay": calcDisplay, "calcSpacer1": calcSpacer1, "calcSpacer2": calcSpacer2, "calcSpacer3": calcSpacer3, "calcSpacer4": calcSpacer4, "calc1": calc1, "calc2": calc2, "calc3": calc3, "calc4": calc4, "calc5": calc5, "calc6": calc6, "calc7": calc7, "calc8": calc8, "calc9": calc9, "calc0": calc0]
            
            
            //Display
            NSLayoutConstraint.activate(NSLayoutConstraint.constraints(withVisualFormat: "H:|-0-[calcDisplay]-0-|", options: .alignAllBottom, metrics: nil, views: views))
            //NSLayoutConstraint.activate(NSLayoutConstraint.constraints(withVisualFormat: "V:[calcDisplay]-0-|", options: .alignAllTop, metrics: nil, views: views))
            
            //0 . =
            NSLayoutConstraint.activate(NSLayoutConstraint.constraints(withVisualFormat: "H:|-0-[calc0]-[calcDot]-[calcEquals]-0-|", options: .alignAllBottom, metrics: nil, views: views))
            NSLayoutConstraint.activate(NSLayoutConstraint.constraints(withVisualFormat: "V:[calc0]-|", options: .alignAllBottom, metrics: nil, views: views))
            
            
            //1 2 3 +
            NSLayoutConstraint.activate(NSLayoutConstraint.constraints(withVisualFormat: "H:|-0-[calc1]-[calc2]-[calc3]-[calcPlus]-0-|", options: .alignAllBottom, metrics: nil, views: views))
            NSLayoutConstraint.activate(NSLayoutConstraint.constraints(withVisualFormat: "V:[calc1]-[calc0]-|", options: .alignAllLeading, metrics: nil, views: views))

    
            //4 5 6 -
            NSLayoutConstraint.activate(NSLayoutConstraint.constraints(withVisualFormat: "H:|-0-[calc4]-[calc5]-[calc6]-[calcSubtract]-0-|", options: .alignAllBottom, metrics: nil, views: views))
            NSLayoutConstraint.activate(NSLayoutConstraint.constraints(withVisualFormat: "V:[calc4]-[calc1]", options: .alignAllLeading, metrics: nil, views: views))
            
            
            //7 8 9 x
            NSLayoutConstraint.activate(NSLayoutConstraint.constraints(withVisualFormat: "H:|-0-[calc7]-[calc8]-[calc9]-[calcMultiply]-0-|", options: .alignAllBottom, metrics: nil, views: views))
            NSLayoutConstraint.activate(NSLayoutConstraint.constraints(withVisualFormat: "V:[calc7]-[calc4]", options: .alignAllLeading, metrics: nil, views: views))

            
            //spacerx3 ÷
            NSLayoutConstraint.activate(NSLayoutConstraint.constraints(withVisualFormat: "H:|-0-[calcSpacer2]-[calcSpacer3]-[calcSpacer4]-[calcDivide]-0-|", options: .alignAllBottom, metrics: nil, views: views))
            NSLayoutConstraint.activate(NSLayoutConstraint.constraints(withVisualFormat: "V:[calcSpacer2]-[calc7]", options: .alignAllLeading, metrics: nil, views: views))
            
            
        }

}

Accepted Reply

IB is not just drawing boxes. It. also allow to set all properties without writing a single line of code, better than a for loop.


you should read some tutorials on how to use Xcode.


if you want to code, replace individual calc1, calc2 ... by an array

var calc : [UIButton]()

// and a for loop

for i in 0...9 {
  let button = UIButton()
  calc.append(button)
}
for i in 0...9 {
  calc[i].setTitle(String(i))
  // all the code you had
}

An even better way is to use a dictionary. Looks like it is your views, used differently


var calc : [String: UIBbutton] = ["0": UIButton(), "1": UIButton(), "+": UIButton()] // include all the keys

// Then your loop becomes
for key in calc.keys { // that will loop with "0", "1" etc ‘....
   calc[key].setTitle(key)
   calc[key].backgroundColor = UIColor.orange
   // other properties here
}


Once again, for the constraints, it is so much simpler nin IB...

Replies

Why not declare it in IB ? That would dramatically cut down on UI control declaration

You mean draw boxes with the UI? I am looking to code everything. I know that a for loop can create several replica boxes, but I don't know how to assign say a corresponding count (0...9) for the number keys to each one, and then place them on the screen, that would be the kind of simplification I am looking to achieve as those are similar.

IB is not just drawing boxes. It. also allow to set all properties without writing a single line of code, better than a for loop.


you should read some tutorials on how to use Xcode.


if you want to code, replace individual calc1, calc2 ... by an array

var calc : [UIButton]()

// and a for loop

for i in 0...9 {
  let button = UIButton()
  calc.append(button)
}
for i in 0...9 {
  calc[i].setTitle(String(i))
  // all the code you had
}

An even better way is to use a dictionary. Looks like it is your views, used differently


var calc : [String: UIBbutton] = ["0": UIButton(), "1": UIButton(), "+": UIButton()] // include all the keys

// Then your loop becomes
for key in calc.keys { // that will loop with "0", "1" etc ‘....
   calc[key].setTitle(key)
   calc[key].backgroundColor = UIColor.orange
   // other properties here
}


Once again, for the constraints, it is so much simpler nin IB...

Thanks Claude31, I manaed to refactor my code by a third and by 50% in key area, but NSLayout constraints can only be taken so far with For Loops, and I don't think I will miss VFL.


        //Row: 2
        let fromBottomR2 = calculatriceButtonsII["8", "9", "x"]
        let toBottomR2 = calculatriceButtonsII["7", "7", "7"]
        
        for (_, buttonFrom) in fromBottomR2.enumerated() {
            for (_, buttonTo) in toBottomR2.enumerated() {
                NSLayoutConstraint.activate([
                    buttonFrom.bottomAnchor.constraint(equalTo: buttonTo.bottomAnchor),
                    ])
            }
        }




I will periodically challenge myself as I improve into the future.