计算类具体实现
新建类 CalculatorBrain.swift 里面来实现具体的计算:
//// CalculatorBrain.swift// Calculator//// Created by wei on 2021/7/5.//import Foundation/// 计算器 app 的逻辑部分enum CalculatorBrain {///用户正在输入左侧数字ing . 当用户按下加减乘除改变为下一个状态case left(String)///用户输入了左侧数字和计算符号,等待开始输入右侧数字case leftOp(left: String,op: CalculatorButtonItem.Op)///用户已经输入了左侧数字、计算符号、和部分右侧数字.并在等待更多右侧数字的输入case leftOpRight(left: String,op: CalculatorButtonItem.Op,right: String)///输入计算出现了错误.case error/// 输出 字符串的显示结果var output: String{let result : Stringswitch self {//如果输入的左侧的数字,直接输出left的数字case .left(let left): result = leftcase .leftOp(let leftOp, _): result = leftOpcase .leftOpRight(let leftOpRight, _, _): result = leftOpRightcase .error: result = "Error"}guard let value = Double(result) else {return "Error"}//限制结果为8位小数后输出return formatter.string(from: value as NSNumber)!}/// 把item点击事件转变成brain输出/// - Parameter item: <#item description#>/// - Returns: <#description#>@discardableResultfunc apply(item: CalculatorButtonItem) -> CalculatorBrain {switch item {case .digit(let num):return apply(num: num)case .dot:return applyDot()case .op(let op):return apply(op: op)case .command(let command):return apply(command: command)}}/// 把输入的数值(0-9)变成brain/// - Parameter num: 0-9的值/// - Returns: brain对象private func apply(num: Int) -> CalculatorBrain {switch self {case .left(let left):return .left(left.apply(num: num))case .leftOp(let left,let op):return .leftOpRight(left: left, op: op, right: "0".apply(num: num))case .leftOpRight(let left, let op, let right):return .leftOpRight(left: left, op: op, right: right.apply(num: num))case .error:return .left("0".apply(num: num))}}/// 把小数点变成brain对象/// - Returns: brain对象private func applyDot() -> CalculatorBrain {switch self {case .left(let left):return .left(left.applyDot())case .leftOp(let left, let op):return .leftOpRight(left: left, op: op, right: "0".applyDot())case .leftOpRight(let left, let op, let right):return .leftOpRight(left: left, op: op, right: right.applyDot())case .error:return .left("0".applyDot())}}/// 把运算符转变成brain对象/// - Parameter op: 运算符号/// - Returns: brain对象private func apply(op: CalculatorButtonItem.Op) -> CalculatorBrain {switch self {case .left(let left):switch op{case .plus, .minus, .divide, .multipy:return .leftOp(left: left, op: op)case .equal:return self}case .leftOp(let left, let currentop):switch op {case .plus, .minus, .divide, .multipy:return .leftOp(left: left, op: currentop)case .equal:if let result = currentop.calculate(l: left, r: left) {return .leftOp(left: result, op: currentop)}else{return .error}}case .leftOpRight(let left, let currentop, let right):switch op {case .plus, .minus, .divide, .multipy:if let result = currentop.calculate(l: left, r: right) {return .leftOp(left: result, op: currentop)}else{return .error}case .equal:if let result = currentop.calculate(l: left, r: right){return .left(result)}else{return .error}}case .error:return self}}/// 把其他的符号转变成brain对象/// - Parameter command: 情况、反正、取余/// - Returns: brain对象private func apply(command: CalculatorButtonItem.Command) -> CalculatorBrain {switch command {case .clear:return .left("0")case .flip:switch self {case .left(let left):return .left(left.flipped())case .leftOp(let left, let op):return .leftOpRight(left: left, op: op, right: "-0")case .leftOpRight(let left, let op, let right):return .leftOpRight(left: left, op: op, right: right)case .error:return .left("-0")}case .percent:switch self {case .left(let left):return .left(left.percentaged())case .leftOp:return selfcase .leftOpRight(left: let left, let op, let right):return .leftOpRight(left: left, op: op, right: right.percentaged())case .error:return .left("-0")}}}}///将结果的数字小数后位数,限定在八位以内var formatter: NumberFormatter = {let f = NumberFormatter()f.minimumFractionDigits = 0f.maximumFractionDigits = 8f.numberStyle = .decimalreturn f}()extension String {///是否包含小数点var containsDot: Bool {return contains(".")}///开始减去var startWithNegative: Bool {return starts(with: "-")}//int as strfunc apply(num: Int) -> String {return self == "0" ? "\(num)" : "\(self)\(num)"}func applyDot() -> String {return containsDot ? self : "\(self)."}///反转-号func flipped() -> String {if startWithNegative {var s = selfs.removeFirst()return s} else {return "-\(self)"}}///求百分比func percentaged() -> String {return String(Double(self)! / 100)}}extension CalculatorButtonItem.Op {///点击=号的时候计算func calculate(l: String, r: String) -> String? {///左右值都不为空继续,为空返回nilguard let left = Double(l), let right = Double(r) else {return nil}///结果let result: Double?switch self {case .plus: result = left + rightcase .minus: result = left - rightcase .multipy: result = left * rightcase .divide: result = right == 0 ? nil : left / rightcase .equal: fatalError()}print("= result \(String(describing: result))")return result.map { String($0) }}}
使用 @state 关联UI
在主工程里面,初始化brain对象,然后加上修饰@state,初始化值为0
@state 修饰的值,在SwiftUI 内部会被自动转换为一对setter 和getter,对这个属性进行赋值的操作将会触发View 的刷新,它的body 会被再次调用,底层渲染引擎会找出界面上被改变的部分,根据新的属性值计算出新 的View,并进行刷新。 @State 属性值仅只能在属性本身被设置时会触发UI 刷新,这个特性让它非常适合用来声明一个值类型的值
对于@State 修饰的属性的访问,只能发生在body 或者body 所调用的方法中。你不能在外部改变@State 的值,它的所有相关操作和状态改变都应该是和当前View 挂钩的
///主内容视图
struct ContentView: View {
//state 在swiftui中会自动转换为一对 setter 和 getter. 对这个属性进行赋值
//的操作将会触发 view的刷新
@State private var brain: CalculatorBrain = .left("0")
var body: some View {
//垂直布局
VStack(spacing: 12, content: {
Spacer() //为了填充上面的留白
Text(brain.output) //输入显示的区域
.font(.system(size: 76))
.minimumScaleFactor(0.5)
.padding(.trailing, 24 * scale)
.frame(
minWidth: 0,
maxWidth: .infinity, //最大
alignment: .trailing //靠右
)
Button("Test") {
self.brain = .left("1.23")
}
CalculatorButtonPad(brain: $brain) //计算器上面的按键
.padding(.bottom) //底部留白为0
})
}
}
使用@state修饰的值,在对象之间传递的之后会遵循值语义发生复制,这样一层层传递下去的话,text是无法跟新的,怎么才能保持一样呢?我们用的@Binding
使用@Binding保持引用语义
@Binding 也是对属性的修饰,它做的事情是将值语义的属性“转换” 为引用语义。对被声明为@Binding 的属
性进行赋值,改变的将不是属性本身,而是它的引用,这个改变将被向外传递。
我们在 CalculatorButtonPad 和CalculatorButtonRow 的定义,分别为它们加上 @Binding 的brain:
/// 封装的每一行 ,使用foreach
struct CalculatorButtonRow: View {
@Binding var brain: CalculatorBrain
let row: [CalculatorButtonItem] //每一行里面的元素button
var body: some View {
HStack{ //水平布局
ForEach(row, id: \.self) { item in //使用foreach水平布局button
CalculatorButton(
title: item.title,
size: item.size,
backgroundColorName: item.backgroundColorName) {
print("button : \(item.title)")
self.brain = self.brain.apply(item: item)
}
}
}
}
}
/// 封装计算器上的所有按键
struct CalculatorButtonPad: View {
@Binding var brain: CalculatorBrain
/// pad里面是每一行的数据,具体意思看下item类
let pad: [[CalculatorButtonItem]] = [
[.command(.clear), .command(.flip), .command(.percent), .op(.divide)],
[.digit(7), .digit(8), .digit(9), .op(.multipy)],
[.digit(4), .digit(5), .digit(6), .op(.minus)],
[.digit(1), .digit(2), .digit(3), .op(.plus)],
[.digit(0), .dot, .op(.equal)],
]
var body: some View {
//垂直布局类
VStack(spacing: 8, content: {
//使用foreach添加每一行
ForEach(pad, id: \.self) { row in
CalculatorButtonRow(brain: self.$brain, row: row)//每一行的数据
}
})
}
}
在初始化他的地方修改为:
CalculatorButtonPad(brain: $brain) //计算器上面的按键
CalculatorButtonRow(brain: self.$brain, row: row)//每一行的数据
在用@Binding修饰的属性前面我们加上美元$符号.
对一个由@ 符号修饰的属性,在它前面使用$ 所取得的值,被称为投影属性
修改之后ContentView.swift的代码为如下:
//
// ContentView.swift
// Calculator
//
// Created by wei on 2021/7/1.
//
import SwiftUI
///按钮宽度
let size_width = 88
let scale = UIScreen.main.bounds.width / 414
///主内容视图
struct ContentView: View {
//state 在swiftui中会自动转换为一对 setter 和 getter. 对这个属性进行赋值
//的操作将会触发 view的刷新
@State private var brain: CalculatorBrain = .left("0")
var body: some View {
//垂直布局
VStack(spacing: 12, content: {
Spacer() //为了填充上面的留白
Text(brain.output) //输入显示的区域
.font(.system(size: 76))
.minimumScaleFactor(0.5)
.padding(.trailing, 24 * scale)
.frame(
minWidth: 0,
maxWidth: .infinity, //最大
alignment: .trailing //靠右
)
Button("Test") {
self.brain = .left("1.23")
}
CalculatorButtonPad(brain: $brain) //计算器上面的按键
.padding(.bottom) //底部留白为0
})
}
}
///效果显示调用的地方
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
///每个button的封装
struct CalculatorButton : View {
let fontSize: CGFloat = 38
let title: String
let size: CGSize
let backgroundColorName: String
let action: ()->Void
var body: some View {
Button(action: action, label: {
Text(title)
.font(.system(size: fontSize)) //字体
.foregroundColor(.blue) //字色
.frame(width: size.width, height: size.height) //size
.background(Color(backgroundColorName)) //背景色
.cornerRadius(size.width/2) //圆角
})
}
}
/// 封装的每一行 ,使用foreach
struct CalculatorButtonRow: View {
@Binding var brain: CalculatorBrain
let row: [CalculatorButtonItem] //每一行里面的元素button
var body: some View {
HStack{ //水平布局
ForEach(row, id: \.self) { item in //使用foreach水平布局button
CalculatorButton(
title: item.title,
size: item.size,
backgroundColorName: item.backgroundColorName) {
print("button : \(item.title)")
self.brain = self.brain.apply(item: item)
}
}
}
}
}
/// 封装计算器上的所有按键
struct CalculatorButtonPad: View {
@Binding var brain: CalculatorBrain
/// pad里面是每一行的数据,具体意思看下item类
let pad: [[CalculatorButtonItem]] = [
[.command(.clear), .command(.flip), .command(.percent), .op(.divide)],
[.digit(7), .digit(8), .digit(9), .op(.multipy)],
[.digit(4), .digit(5), .digit(6), .op(.minus)],
[.digit(1), .digit(2), .digit(3), .op(.plus)],
[.digit(0), .dot, .op(.equal)],
]
var body: some View {
//垂直布局类
VStack(spacing: 8, content: {
//使用foreach添加每一行
ForEach(pad, id: \.self) { row in
CalculatorButtonRow(brain: self.$brain, row: row)//每一行的数据
}
})
}
}
效果:

以上代码来自猫神swiftUI 和 combine 学习笔记

