Preface:

The teacher younger brother wants graduation design, knocked swift version calculator to his reference below. Now put the code up and use the calculator to learn simple encapsulation: the programming of separating logic from the interface and providing an interface, which is essential for learning about object orientation.

Based on XCode 9.0 SWIFt4.0

Git uses SnapKit to constrain components

2. Create a class file that inherits UIButton class, named DWFuncButton, and set the font, color and style codes as follows:

class DWFuncButton: UIButton {

    init() {super.init(frame: cgrect.zero) // Add a border to the button self.layer.borderWidth = 0.5; Self.layer.bordercolor = UIColor(red: 219/255.0, green: 219/255.0, blue: 219/255.0, alpha: Self.settitlecolor (uicolor.orange,for: .normal) self.titleLabel? .font = UIFont.systemFont(ofSize: 25) self.setTitleColor(UIColor.black,for: .highlighted) } required init? (coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented")}}Copy the code

Create a class that inherits UIView, name it DWBoard, and use it as the action panel of your calculator

var dataArray = ["0"."."."%"."="
                , "1"."2"."3"."+"
                , "4"."5"."6"."-"
                , "Seven"."8"."9"."*"
                 , "AC"."DEL"."^"."/"]
Copy the code

Override the constructor of the parent class to load the interface:

override init(frame: CGRect) {
        super.init(frame: frame)
        setupUI()
    }
Copy the code
// Layout the interface funcsetupUI() {// Create a variable to save the previous button of the current layout button var frontBtn: DWFuncButton! // Create a function button loopfor index in0.. <20 {// Create a function buttonletBTN = DWFuncButton() self.addSubView (BTN) // Bind btn.snp.makeconstraints ({(make))in// When the button is first in each line, place it to the leftif index%4 == 0 {
                make.left.equalTo(0)
            }elseMake. Left. EqualTo (frontbtn.snp.right)} // When the button is the first row, place it near the bottom of the parent viewif index/4 == 0 {
                make.bottom.equalTo(0)
            }else ifIndex %4 == 0 {// Align the bottom of the button with the top of the previous button if the button is not in the first row and is the first in each row make.bottom.equalto (frontbtn.snp.top) // otherwise align the bottom with the bottom of the previous button}else{make.bottom.equalto (frontbtn.snp.bottom)} // Restrict width to 0.25 times the width of the parent view. .snp.width).multipliedby (0.25) // Constraint height is 0.2 times the width of the superview make.height.equalto (btn.superview! .snp.height).multipliedby (0.2)}) // Set tag value btn.tag = index + 100#selector(btnClick(_:)), for: .touchUpInside)// set the title btn.setTitle(dataArray[index],for// Update the previous button to save frontBtn = BTN}}Copy the code

This is a simple keyboard interface, constraint code you can look at, typesetting 5 rows and 4 columns, layout order is from bottom up, left to right

Click method to create the code above

@objc func btnClick(_ button:DWFuncButton) {
        print(button.currentTitle as Any)
    }
Copy the code

The user inputs the operation on the operation panel, and the input content also needs to be displayed on the display screen of the calculator. At the same time, the display screen also has the function of calculating results. First, create a new class file named DWScreen on the DWCalculator project, which inherits from UIView and acts as a display control for the calculator. The display screen is divided into two parts, one is used to calculate the results, and the other is used to display the calculation process entered by the user, so two Uilabels are used for processing.

class DWScreen: UIView { var inputLabel:UILabel? // Displays historical records varhistoryLabel:UILabel? // User input expression or calculation result string var inputString =""// History expression string varhistoryString = ""// All numeric characters are used for detection matcheslet figureArray:Array<Character> = ["0"."1"."2"."3"."4"."5"."6"."Seven"."8"."9"."."] // All arithmetic function characters are used to detect matcheslet funcArray = ["+"."-"."*"."/"."^"]
    init() {
        super.init(frame: CGRect.zero)
        inputLabel = UILabel()
        historyLabel = UILabel()
        setupUI()
    }
    
    func setupUI() {// Set the text to right-aligned inputLabel? .textAlignment = .righthistoryLabel? .textalignment =.right // Set font inputLabel? .font = UIFont.systemFont(ofSize: 34)historyLabel? .font = uifont. systemFont(ofSize: 30) // Set the text color inputLabel? .textColor = UIColor.orangehistoryLabel? TextColor = uicolor. black // Set the text size to match the word count inputLabel? .adjustsFontSizeToFitWidth =trueinputLabel? . MinimumScaleFactor = 0.5 // The minimum font is half the current fonthistoryLabel? .adjustsFontSizeToFitWidth =true
        historyLabel? . MinimumScaleFactor = 0.5 // Set text to inputLabel? .lineBreakMode = .byTruncatingHeadhistoryLabel? .linebreakMode =.byTruncatingHead // Set the number of lines of text inputLabel? .numberOfLines = 0historyLabel? .numberOfLines = 0 self.addSubview(inputLabel!) self.addSubview(historyLabel!) // Do automatic layout inputLabel? .snp.makeConstraints({ (make)inmake.left.equalTo(10) make.right.equalTo(-10) make.bottom.equalTo(-10) make.height.equalTo(inputLabel! .superview! . SNP. Height). MultipliedBy (0.5). The offset (10)})historyLabel? .snp.makeConstraints({ (make)inmake.left.equalTo(10) make.right.equalTo(-10) make.top.equalTo(10) make.height.equalTo(inputLabel! .superview! .snp.height).multipliedby (0.5).offset(-10)})} // Provide an interface for input information func inputContent(content:String) { inputString.append(content) inputLabel? .text = inputString} // provide a method to refresh history funcrefreshHistory() {
        historyString = inputString
        historyLabel? .text =historyString } required init? (coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented")}}Copy the code

The DWBoard class accepts input from the user, the DWScreen takes input from the user, and the association between them is done through the ViewController class. Use the proxy design pattern to do this.

Add the protocol code in dwboard. swift

protocol DWBoardButtonInputDelegate {
    func boardButtonClick(content:String)
}
Copy the code

Add a proxy property to the DWBoard class:

var delegate:DWBoardButtonInputDelegate?
Copy the code

Modify click events in the DWBoard class

@objc func btnClick(_ button:DWFuncButton) {
    ifdelegate ! = nil {// Pass the value to the delegate via the protocol method? .boardButtonClick(content: button.currentTitle!) }}Copy the code

The ViewController class also needs to call each other with instances of the DWBoard class and DWScreen class as its properties

let board = DWBoard()
let screen = DWScreen()
Copy the code

Add setupUI() to the viewDidLoad() method with the following setupUI code

func setupUI() {self. View. AddSubview (board)/agents/set board. Delegate = self board. The SNP. MakeConstraints {(make)inmake.left.equalTo(0) make.right.equalTo(0) make.bottom.equalTo(0) make.height.equalTo(board.superview! . SNP. Height). MultipliedBy (2/3.0)} the self. The addSubview (screen) screen. SNP. MakeConstraints {(make)in
        make.left.equalTo(0)
        make.right.equalTo(0)
        make.top.equalTo(0)
        make.bottom.equalTo(board.snp.top)
    }
}
Copy the code

ViewController to follow DWBoardButtonInputDelegate protocol:

class ViewController: UIViewController,DWBoardButtonInputDelegate
Copy the code

And implement the protocol method:

func boardButtonClick(content: String) {
    if content == "AC" || content == "DEL" || content == "="{// do the logical processing screen.refreshHistory()}else {
        screen.inputContent(content:content)
    }
}
Copy the code

Run the project as shown below:

We have basically developed the interface part, and then carry out the packaging of the logical processing class.

Third, calculator calculation logic: DWScreen class needs to be improved. For example, when the user hits the clear button, the input computational expression should be cleared. When the user clicks the back button, the last character typed should be wiped out. Add the following code to the DWScreen class:

// Clear the current input information funcclearContent() {
    inputString = ""} // Delete the last entered character func from the displaydeleteInput() {
    ifinputString.characters.count>0 { inputString.remove(at: inputString.index(before: inputString.endIndex)) inputLabel? .text = inputString } }Copy the code

Create a new class file in your project that inherits from NSObject and name it DWCalculatorEngine. As a calculation engine utility class, the code is as follows:

Class DWCalculatorEngine: NSObject {// Set of operatorslet funcArray:CharacterSet = ["+"."-"."*"."/"."^"."%"] func calculatEquation(equation:String)->Double {// Get all numbers with operatorletelementArray = equation.components(separatedBy: Var result:Double = Double(elementArray[0])! // iterate over the evaluated expressionfor char inEquation. Characters {switch char {// Addcase "+":
                tip += 1
                ifelementArray.count>tip { result += Double(elementArray[tip])! } // subtractcase "-":
                tip += 1
                if elementArray.count>tip {
                    result -= Double(elementArray[tip])!
                }
            case "*":
                tip += 1
                ifelementArray.count>tip { result *= Double(elementArray[tip])! } // Perform divisioncase "/":
                tip += 1
                ifelementArray.count>tip { result /= Double(elementArray[tip])! } // Perform the modcase "%":
                tip += 1
                ifelementArray.count>tip { result = Double(Int(result)%Int(elementArray[tip])!) } // Perform exponential operationscase "^":
                tip += 1
                if elementArray.count>tip {
                    let tmp = result
                    for _ in1.. <Int(elementArray[tip])! { result *= tmp } } default:break}}return result
    }
}
Copy the code

Add two properties to the ViewController class:

// Compute engine instanceletCalcalator = DWCalculatorEngine() // Whether this input needs to refresh the display var isNew =false
Copy the code

The isNew attribute is used to mark whether to clear the existing content of the display. When the user completes a calculation, the calculation result is displayed on the screen. At this point, if the user continues to enter, the next round of calculation is carried out, and the last result of the display should be cleared. Modify the protocol methods in the ViewController class

func boardButtonClick(content: String) {
    if content == "AC" || content == "DEL" || content == "="// Switch content {case "AC":
            screen.clearContent()
            screen.refreshHistory()
        case "DEL":
            screen.deleteInput()
        case "=":
            letresult = calcalator.calculatEquation(equation: Screen.inputstring) // Refresh the history screen.refreshHistory() // clear the input screen.clearContent() // enter the result screen.inputContent(content:  String(result)) isNew =true
        default:
            screen.refreshHistory()
        }
        
    }else {
        if isNew {
            screen.clearContent()
            isNew = false
        }
        screen.inputContent(content:content)
    }
}
Copy the code

And you’re done! Code portal