计算类具体实现
新建类 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 : String
switch self {
//如果输入的左侧的数字,直接输出left的数字
case .left(let left): result = left
case .leftOp(let leftOp, _): result = leftOp
case .leftOpRight(let leftOpRight, _, _): result = leftOpRight
case .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#>
@discardableResult
func 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 self
case .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 = 0
f.maximumFractionDigits = 8
f.numberStyle = .decimal
return f
}()
extension String {
///是否包含小数点
var containsDot: Bool {
return contains(".")
}
///开始减去
var startWithNegative: Bool {
return starts(with: "-")
}
//int as str
func apply(num: Int) -> String {
return self == "0" ? "\(num)" : "\(self)\(num)"
}
func applyDot() -> String {
return containsDot ? self : "\(self)."
}
///反转-号
func flipped() -> String {
if startWithNegative {
var s = self
s.removeFirst()
return s
} else {
return "-\(self)"
}
}
///求百分比
func percentaged() -> String {
return String(Double(self)! / 100)
}
}
extension CalculatorButtonItem.Op {
///点击=号的时候计算
func calculate(l: String, r: String) -> String? {
///左右值都不为空继续,为空返回nil
guard let left = Double(l), let right = Double(r) else {
return nil
}
///结果
let result: Double?
switch self {
case .plus: result = left + right
case .minus: result = left - right
case .multipy: result = left * right
case .divide: result = right == 0 ? nil : left / right
case .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 学习笔记