SwiftUI中基于15分钟取整时长计算费用及代码架构问题
SwiftUI 时长计算与架构优化解决方案
一、实现金额计算逻辑
你不需要手动维护amountDue的@State变量,直接用计算属性就能自动响应时间变化,完成费率计算:
- 删掉原代码中
@State private var amountDue = 0.0这一行 - 添加以下计算属性:
var amountDue: Double { // 将15分钟取整后的时长转为小时,乘以每小时220美元费率 let hours = duration15 / 60 return hours * 220 }
- 视图中直接使用这个计算属性即可,当
startTime或endTime变化时,SwiftUI会自动重新计算并更新UI:
Text(amountDue, format: .currency(code: Locale.current.currencyCode ?? "USD"))
二、架构组织与变量作用域说明
SwiftUI的核心是状态驱动视图,把计算逻辑和视图代码混在一起会导致冗余和难以维护,推荐用ViewModel分离业务逻辑:
重构后的代码结构
1. 创建ViewModel(负责逻辑计算)
import SwiftUI class CalculatorViewModel: ObservableObject { // 用@Published标记需要通知视图更新的状态 @Published var startTime = Date().zeroSeconds @Published var endTime = Date().zeroSeconds // 把费率常量放在这里,便于统一修改 private let hourlyRate = 220.0 // 计算原始时长(分钟) var duration: TimeInterval { guard endTime > startTime else { return 0 } return DateInterval(start: startTime, end: endTime).duration / 60 } // 计算向上取整到15分钟的时长 var duration15: TimeInterval { (duration / 15.0).rounded(.up) * 15 } // 计算应付金额 var amountDue: Double { let hours = duration15 / 60 return hours * hourlyRate } }
2. 修改ContentView(只负责展示和交互)
struct ContentView: View { // 用@ObservedObject监听ViewModel的状态变化 @ObservedObject private var viewModel = CalculatorViewModel() var body: some View { NavigationView { Form { Section(header: Text("输入案件时间:")) { DatePicker("开始时间", selection: $viewModel.startTime, displayedComponents: .hourAndMinute) DatePicker("结束时间", selection: $viewModel.endTime, in: viewModel.startTime..., displayedComponents: .hourAndMinute) } Section(header: Text("案件时长:")) { Text("时长 = \(viewModel.duration) 分钟") Text("时长(按15分钟向上取整) = \(viewModel.duration15) 分钟") } Section(header: Text("应付金额:")) { Text(viewModel.amountDue, format: .currency(code: Locale.current.currencyCode ?? "USD")) } } .navigationTitle("DDA 费率计算器") } } } // 保留原有的Date扩展 extension Date { var zeroSeconds: Date { let calendar = Calendar.current let dateComponents = calendar.dateComponents([.hour, .minute], from: self) return calendar.date(from: dateComponents) ?? self } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }
变量作用域核心说明
- @State:用于视图内部的私有状态,只在当前View范围内生效,适合存储用户输入的原始值(比如原代码中的startTime/endTime)。
- @Published:用于ViewModel中的属性,当属性值变化时,会自动通知监听它的View更新。
- @ObservedObject:让View监听ViewModel的状态变化,ViewModel的状态更新会触发View的重绘。
- 计算属性:不需要存储值,依赖其他状态动态计算,SwiftUI会自动跟踪依赖的状态变化,实时更新计算结果,适合处理像时长、金额这类派生值。
这样拆分后,视图代码更简洁,业务逻辑集中在ViewModel里,便于测试和后续扩展。
内容的提问来源于stack exchange,提问作者mgianzero
相关产品推荐
相关产品推荐

