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
先创建一个数组属性,存放操作面板上的所有功能按钮标题
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
代码语言:javascript复制isNew属性主要作用是标记本次输入是否需要将显示屏已有的内容清除。当用户完成一次计算后,计算结果会显示在显示屏上。此时如果用户继续输入,则进行下一轮的计算,显示屏的上次结果应该被清空。 修改ViewController类中的协议方法
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)
}
}
大功告成!代码传送门