搭建UI
搭建如下图UI:
UI搭建
实现
计算器逻辑
定义Operator枚举表示基本运算符,如下所示:
/// 基本运算符
enum Operator {
case addition // 加
case subtruction // 减
case multiplication // 乘
case division // 除
}
extension Operator {
/// 符号字符串
var sign: String {
switch self {
case .addition:
return "+"
case .subtruction:
return "-"
case .multiplication:
return "x"
case .division:
return "/"
}
}
/// 执行运算
var perform: (Double, Double) -> Double {
switch self {
case .addition:
return (+)
case .subtruction:
return (-)
case .multiplication:
return (*)
case .division:
return (/)
}
}
}
定义CalculatorCommand枚举表示计算器上的所有命令,如下图所示:
/// 计算器命令
enum CalculatorCommand {
case clear // 输入清除号
case changeSign // 输入变换符号
case percent // 输入百分号
case operation(Operator) // 输入基本运算符
case equal // 输入等号
case addNumber(Character) // 输入数字
case addDoc // 输入小数点
}
定义CalculatorState枚举表示计算器状态:
/// 计算器状态
enum CalculatorState {
case oneOperand(screen: String) // 一个操作数
case oneOperandAndOperator(operand: Double, operator: Operator) // 一个操作数和一个操作符
case twoOperandAndOperator(operand: Double, operator: Operator, screen: String) // 两个个操作数和一个操作符
}
为CalculatorState定义一个计算属性screen表示计算结果:
/// 屏幕显示数据
var screen: String {
switch self {
case let .oneOperand(screen: screen):
return screen
case .oneOperandAndOperator(operand: _, operator: _):
return CalculatorState.initalScreen
case let .twoOperandAndOperator(operand: _, operator: _, screen: screen):
return screen
}
}
为CalculatorState定义sign计算属性表示将要执行的操作符:
/// 屏幕显示操作符
var sign: String {
switch self {
case .oneOperand(screen: _):
return ""
case let .oneOperandAndOperator(operand: _, operator: o):
return o.sign
case let .twoOperandAndOperator(operand: _, operator: o, screen: _):
return o.sign
}
}
扩展String
类型,增加一个removedMantisse
计算属性用来去掉字符串末尾的无用字符:
extension String {
var removedMantisse: String {
if self.contains(".") && (self.last == "0" || self.last == ".") {
return String(self[..<self.index(before: self.endIndex)]).removedMantisse
} else {
return self
}
}
}
代码解析:
- 字符串包含
.
并且 字符串最后一位是.
或者0
,那么去掉字符串最后一个字符 - 递归调用去掉小数字符串末尾无效的
0
和.
为CalculatorState定义mapScreen函数做数据转换:
/// 转换屏幕上的数据
/// - Parameter transform: 转换闭包
func mapScreen(transform: (String) -> String) -> CalculatorState {
switch self {
case let .oneOperand(screen: screen):
return .oneOperand(screen: transform(screen))
case let .oneOperandAndOperator(operand: operand, operator: operat):
return .twoOperandAndOperator(operand: operand, operator: operat, screen: transform(CalculatorState.initalScreen))
case let .twoOperandAndOperator(operand: operand, operator: operat, screen: screen):
return .twoOperandAndOperator(operand: operand, operator: operat, screen: transform(screen))
}
}
为CalculatorState定义reduce函数来计算计算器状态、结果:
func reduce(command: CalculatorCommand) -> CalculatorState {
switch command {
case .clear:
return CalculatorState.inital
case .changeSign:
return self.mapScreen { (screen) -> String in
if screen.count == 0 {
return "-"
} else if screen[screen.startIndex] == "-" {
let result = screen[screen.index(after: screen.startIndex)..<screen.endIndex]
return String(result)
} else {
return "-" + screen
}
}
case .percent:
return self.mapScreen { return "\((Double($0) ?? 0.0)/100.0)" }
case .operation(let o):
switch self {
case let .oneOperand(screen: screen):
return .oneOperandAndOperator(operand: Double(screen) ?? 0.0, operator: o)
case let .oneOperandAndOperator(operand: operand, operator: _):
return .oneOperandAndOperator(operand: operand, operator: o)
case let .twoOperandAndOperator(operand: operand, operator: oo, screen: screen):
return .twoOperandAndOperator(operand: oo.perform(operand, Double(screen) ?? 0.0), operator: o, screen: CalculatorState.initalScreen)
}
case .equal:
switch self {
case .oneOperand(screen: _):
return self
case let .oneOperandAndOperator(operand: operand, operator: _):
return .oneOperand(screen: "\(operand)".removedMantisse)
case let .twoOperandAndOperator(operand: operand, operator: oo, screen: screen):
return .oneOperand(screen: "\(oo.perform(operand, Double(screen) ?? 0.0))".removedMantisse)
}
case let .addNumber(c):
return self.mapScreen { (screen) -> String in
if screen == CalculatorState.initalScreen {
return String(c)
} else if screen == "-0" {
return "-" + String(c)
} else {
return screen + String(c)
}
}
case .addDoc:
return self.mapScreen(transform: { $0.contains(".") || $0 == CalculatorState.initalScreen ? $0 : $0 + "." })
}
绑定计算器
代码如下:
/// 所有操作的Observable<CalculatorCommand>数组
let events: [Observable<CalculatorCommand>] = [
clearButton.rx.tap.map({ CalculatorCommand.clear }),
changeSignButton.rx.tap.map({ CalculatorCommand.changeSign }),
percentButton.rx.tap.map({ CalculatorCommand.percent }),
equalButton.rx.tap.map({ CalculatorCommand.equal }),
plusButton.rx.tap.map({ CalculatorCommand.operation(Operator.addition) }),
minusButton.rx.tap.map({ CalculatorCommand.operation(Operator.subtruction) }),
multiplyButton.rx.tap.map({ CalculatorCommand.operation(Operator.multiplication) }),
divideButton.rx.tap.map({ CalculatorCommand.operation(Operator.division) }),
dotButton.rx.tap.map({ CalculatorCommand.addDoc }),
zeroButton.rx.tap.map({ CalculatorCommand.addNumber("0") }),
oneButton.rx.tap.map({ CalculatorCommand.addNumber("1") }),
twoButton.rx.tap.map({ CalculatorCommand.addNumber("2") }),
threeButton.rx.tap.map({ CalculatorCommand.addNumber("3") }),
fourButton.rx.tap.map({ CalculatorCommand.addNumber("4") }),
fiveButton.rx.tap.map({ CalculatorCommand.addNumber("5") }),
sixButton.rx.tap.map({ CalculatorCommand.addNumber("6") }),
sevenButton.rx.tap.map({ CalculatorCommand.addNumber("7") }),
eightButton.rx.tap.map({ CalculatorCommand.addNumber("8") }),
nineButton.rx.tap.map({ CalculatorCommand.addNumber("9") })
]
/// 初始状态
let initState = CalculatorState.inital
/// 操作线程
let scheduler = MainScheduler.instance
/// 绑定
Observable.deferred({
Observable.merge(events)
.scan(initState) { $0.reduce(command: $1) }
.subscribeOn(MainScheduler.instance)
.startWith(initState)
.observeOn(scheduler)
}).subscribe(onNext: { (state) in
self.signLabel.text = state.sign
self.resultLabel.text = state.screen
}).disposed(by: bag)
代码分析:
- 构建一个计算器操作序列的数组,然后使用merge操作符合并为一个序列
- 使用scan扫描序列,传入计算器
初始状态
累积来自reduce
函数的计算结果 - 使用
startWith
设置初始元素,使用observeOn设置线程 - 最后将计算器状态
sign
、screen
绑定到Label
上显示,并清理资源
网友评论