Swift封装 - 计算器开发

2018-05-22 17:50:56 浏览数 (1)

image.png

前言:

师弟要毕业设计,就敲了swift版的计算器给他参考下。现在把代码放上来,通过这个计算器,可以学习简单的封装:将逻辑与界面分离并提供接口的编程方式,这也是我们学习面向对象的必要点。

基于 xcode 9.0 swift4.0

一、先引用SnapKit框架 SnapKit自己看git引入 利用其来约束组件

二、新建一个继承UIButton类的类文件,命名为DWFuncButton,对其设置字体、颜色、风格代码如下:

代码语言:javascript复制
class DWFuncButton: UIButton {

    init() {
        super.init(frame: CGRect.zero)
        //为按钮添加边框
        self.layer.borderWidth = 0.5;
        self.layer.borderColor = UIColor(red: 219/255.0, green: 219/255.0, blue: 219/255.0, alpha: 1).cgColor
        //设置字体与字体颜色
        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")
    }
    
}

三、创建一个继承UIView的类,命名为DWBoard,将其用作计算器的操作面板 首先引入SnapKit框架 import SnapKit 先创建一个数组属性,存放操作面板上的所有功能按钮标题

代码语言:javascript复制
var dataArray = ["0", ".", "%", "="
                , "1", "2", "3", " "
                , "4", "5", "6", "-"
                , "7", "8", "9", "*"
                 , "AC", "DEL", "^", "/"]

重写父类的构造方法,在其中进行界面的加载操作:

代码语言:javascript复制
override init(frame: CGRect) {
        super.init(frame: frame)
        setupUI()
    }
代码语言:javascript复制
//对界面进行布局
func setupUI() {
    //创建一个变量 用于保存当前布局按钮的上一个按钮
    var frontBtn: DWFuncButton!
    //进行功能按钮的循环创建
    for index in 0..<20 {
        //创建一个功能按钮
        let btn = DWFuncButton()
        self.addSubview(btn)
        //约束
        btn.snp.makeConstraints({ (make) in
            //当按钮每一行的第一个时,将其靠左侧摆放
            if index%4 == 0 {
                make.left.equalTo(0)
            }else { //否则将按钮的左边考上一个右侧进行摆放
                make.left.equalTo(frontBtn.snp.right)
            }
            //当按钮为第一行,将其靠父视图底部摆放
            if index/4 == 0 {
                make.bottom.equalTo(0)
            }else if index%4 == 0 { //当按钮不在第一行且为每行的第一个时,将其底部与上一个按钮的顶部对齐
                make.bottom.equalTo(frontBtn.snp.top)
                //否则将其底部与上一个按钮底部对齐整
            }else {
                make.bottom.equalTo(frontBtn.snp.bottom)
            }
            //约束宽度为父视图宽度的0.25倍
            make.width.equalTo(btn.superview!.snp.width).multipliedBy(0.25)
            //约束高度为父视图宽度的0.2倍
            make.height.equalTo(btn.superview!.snp.height).multipliedBy(0.2)
        })
        
        //设置tag值
        btn.tag = index   100
        //添加点击事件
        btn.addTarget(self, action: #selector(btnClick(_:)), for: .touchUpInside)
        //设置标题
        btn.setTitle(dataArray[index], for: .normal)
        //对上一个按钮更新保存
        frontBtn = btn
    }
    
}

上面就构建了一个简单的键盘界面,约束代码大家可以看一下,排版为5行4列,布局顺序为从下向上、从左向右依次布局

创建上述代码的点击方法

代码语言:javascript复制
@objc func btnClick(_ button:DWFuncButton) {
        print(button.currentTitle as Any)
    }

用户在操作面板上进行输入操作,在计算器的显示屏上还需要显示输入的内容,同时,显示屏还兼有计算结果的功能。 首先在DWCalculator工程上新建一个名为DWScreen的类文件,继承自UIView,作为计算器的显示器控件。显示屏分成两部分,一部分用于计算结果,一部分用于显示用户输入的计算过程,所以用两个UILabel来处理。

代码语言:javascript复制
class DWScreen: UIView {

    var inputLabel:UILabel?
    //用于显示历史记录信息
    var historyLabel:UILabel?
    //用户输入表达式或者计算结果字符串
    var inputString = ""
    //历史表达式字符串
    var historyString = ""
    //所有数字字符 用于进行检测匹配
    let figureArray:Array<Character> = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "."]
    //所有运算功能字符 用于进行检测匹配
    let funcArray = [" ", "-", "*", "/", "^"]
    init() {
        super.init(frame: CGRect.zero)
        inputLabel = UILabel()
        historyLabel = UILabel()
        setupUI()
    }
    
    func setupUI() {
        //设置文字的对其方式为右对齐
        inputLabel?.textAlignment = .right
        historyLabel?.textAlignment = .right
        //设置字体
        inputLabel?.font = UIFont.systemFont(ofSize: 34)
        historyLabel?.font = UIFont.systemFont(ofSize: 30)
        //设置文字颜色
        inputLabel?.textColor = UIColor.orange
        historyLabel?.textColor = UIColor.black
        //设置文字大小根据字数进行适配
        inputLabel?.adjustsFontSizeToFitWidth = true
        inputLabel?.minimumScaleFactor = 0.5  //最小字体为当前字体的一半
        historyLabel?.adjustsFontSizeToFitWidth = true
        historyLabel?.minimumScaleFactor = 0.5
        //设置文字的截断方式
        inputLabel?.lineBreakMode = .byTruncatingHead
        historyLabel?.lineBreakMode = .byTruncatingHead
        //设置文字的行数
        inputLabel?.numberOfLines = 0
        historyLabel?.numberOfLines = 0
        
        self.addSubview(inputLabel!)
        self.addSubview(historyLabel!)
        //进行自动布局
        inputLabel?.snp.makeConstraints({ (make) in
            make.left.equalTo(10)
            make.right.equalTo(-10)
            make.bottom.equalTo(-10)
        make.height.equalTo(inputLabel!.superview!.snp.height).multipliedBy(0.5).offset(-10)
        })
        historyLabel?.snp.makeConstraints({ (make) in
            make.left.equalTo(10)
            make.right.equalTo(-10)
            make.top.equalTo(10)
            make.height.equalTo(inputLabel!.superview!.snp.height).multipliedBy(0.5).offset(-10)
        })
    }
    
    //提供一个输入信息的接口
    func inputContent(content:String) {
        inputString.append(content)
        inputLabel?.text = inputString
    }
    
    //提供一个刷新历史记录的方法
    func refreshHistory() {
        historyString = inputString
        historyLabel?.text = historyString
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
}

DWBoard类可以接收用户的输入,DWScreen需要获取用户的输入,他们之间的关联是需要通过ViewController类来完成的。使用代理设计模式完成此功能。

在DWBoard.swift添加协议代码

代码语言:javascript复制
protocol DWBoardButtonInputDelegate {
    func boardButtonClick(content:String)
}

在DWBoard类添加一个代理属性:

代码语言:javascript复制
var delegate:DWBoardButtonInputDelegate?

修改DWBoard类中的点击事件

代码语言:javascript复制
@objc func btnClick(_ button:DWFuncButton) {
    if delegate != nil {
        //通过协议方法将值传递出去
        delegate?.boardButtonClick(content: button.currentTitle!)
    }
}

ViewController类也需要将DWBoard类实例和DWScreen类实例作为自己的属性,相互调用

代码语言:javascript复制
let board = DWBoard()
let screen = DWScreen()

在viewDidLoad()方法添加setupUI(),并且setupUI代码如下

代码语言:javascript复制
func setupUI() {
    self.view.addSubview(board)
    //设置代理
    board.delegate = self
    board.snp.makeConstraints { (make) in
        make.left.equalTo(0)
        make.right.equalTo(0)
        make.bottom.equalTo(0)
        make.height.equalTo(board.superview!.snp.height).multipliedBy(2/3.0)
    }
    
    self.view.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)
    }
}

viewController需要遵守DWBoardButtonInputDelegate协议:

代码语言:javascript复制
class ViewController: UIViewController,DWBoardButtonInputDelegate

并且实现协议方法:

代码语言:javascript复制
func boardButtonClick(content: String) {
    if content == "AC" || content == "DEL" || content == "=" {
        //进行逻辑处理
        screen.refreshHistory()
    }else {
        screen.inputContent(content:content)
    }
}

运行项目,如下图:

界面部分我们已经基本开发完,接下来进行逻辑处理类的封装。

三、计算器计算逻辑: DWScreen类需要继续完善。例如当用户点击清空按钮时,输入的计算表达就应该被清空。当用户点击回退按钮时,上一次输入的字符就应该被清空。在DWScreen类添加如下代码:

代码语言:javascript复制
//清空显示屏当前输入的信息
func clearContent() {
    inputString = ""
}

//删除显示屏中上次输入的字符
func deleteInput() {
    if inputString.characters.count>0 {
        inputString.remove(at: inputString.index(before: inputString.endIndex))
        inputLabel?.text = inputString
    }
}

在项目中新建一个继承于NSObject的类文件,并命名为DWCalculatorEngine。将其作为计算引擎工具类,代码如下:

代码语言:javascript复制
class DWCalculatorEngine: NSObject {
    //运算符集合
    let funcArray:CharacterSet = [" ", "-", "*", "/", "^", "%"]
    func calculatEquation(equation:String)->Double {
        //以运算符进行分割获取到所有数字
        let elementArray = equation.components(separatedBy: funcArray)
        //设置一个运算标记游标
        var tip = 0
        //运算结果
        var result:Double = Double(elementArray[0])!
        //遍历计算表达式
        for char in equation.characters {
            switch char {
                //进行加法运算
            case " ":
                tip  = 1
                if elementArray.count>tip {
                    result  = Double(elementArray[tip])!
                }
                //进行减法运算
            case "-":
                tip  = 1
                if elementArray.count>tip {
                    result -= Double(elementArray[tip])!
                }
            case "*":
                tip  = 1
                if elementArray.count>tip {
                    result *= Double(elementArray[tip])!
                }
                //进行除法运算
            case "/":
                tip  = 1
                if elementArray.count>tip {
                    result /= Double(elementArray[tip])!
                }
                //进行取余运算
            case "%":
                tip  = 1
                if elementArray.count>tip {
                    result = Double(Int(result)%Int(elementArray[tip])!)
                }
                //进行指数运算
            case "^":
                tip  = 1
                if elementArray.count>tip {
                    let tmp = result
                    for _ in 1..<Int(elementArray[tip])! {
                        result *= tmp
                    }
                }
            default:
                break
            }
        }
        return result
    }
}

在ViewController类中添加两个属性:一个计算工具类:

代码语言:javascript复制
//计算引擎实例
let calcalator = DWCalculatorEngine()
//这个输入是否需要刷新显示屏
var isNew = false

isNew属性主要作用是标记本次输入是否需要将显示屏已有的内容清除。当用户完成一次计算后,计算结果会显示在显示屏上。此时如果用户继续输入,则进行下一轮的计算,显示屏的上次结果应该被清空。 修改ViewController类中的协议方法

代码语言:javascript复制
func boardButtonClick(content: String) {
    if content == "AC" || content == "DEL" || content == "=" {
        //进行逻辑处理
        switch content {
        case "AC":
            screen.clearContent()
            screen.refreshHistory()
        case "DEL":
            screen.deleteInput()
        case "=":
            let result = calcalator.calculatEquation(equation: screen.inputString)
            //先刷新历史
            screen.refreshHistory()
            //清除输入的内容
            screen.clearContent()
            //将结果输入
            screen.inputContent(content: String(result))
            isNew = true
        default:
            screen.refreshHistory()
        }
        
    }else {
        if isNew {
            screen.clearContent()
            isNew = false
        }
        screen.inputContent(content:content)
    }
}

大功告成!代码传送门

0 人点赞