You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI中基于15分钟取整时长计算费用及代码架构问题

SwiftUI 时长计算与架构优化解决方案

一、实现金额计算逻辑

你不需要手动维护amountDue的@State变量,直接用计算属性就能自动响应时间变化,完成费率计算:

  1. 删掉原代码中@State private var amountDue = 0.0这一行
  2. 添加以下计算属性:
var amountDue: Double {
    // 将15分钟取整后的时长转为小时,乘以每小时220美元费率
    let hours = duration15 / 60
    return hours * 220
}
  1. 视图中直接使用这个计算属性即可,当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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 11:01:58