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

SwiftUI如何使用同一时钟视图展示不同时区的时间

实现方案

步骤1:调整Country数据结构,存储时区标识而非固定时间

原来的Country存储的是初始化时一次性生成的固定时间字符串,不会实时更新,替换为存储时区标识符即可:

struct Country: Identifiable {
    let id = UUID()
    let name: String
    let timeZoneIdentifier: String
}

let countryList: [Country] = [
    Country(name: "纽约", timeZoneIdentifier: "America/New_York"),
    Country(name: "巴黎", timeZoneIdentifier: "Europe/Paris")
]

步骤2:扩展Date支持自定义时区的时间/角度计算

原来的Date计算属性默认使用系统当前时区,新增支持传入时区的方法:

extension Date {
    func hour(in timeZone: TimeZone) -> Double {
        var calendar = Calendar.current
        calendar.timeZone = timeZone
        return Double(calendar.component(.hour, from: self))
    }
    func minutes(in timeZone: TimeZone) -> Double {
        var calendar = Calendar.current
        calendar.timeZone = timeZone
        return Double(calendar.component(.minute, from: self))
    }
    func seconds(in timeZone: TimeZone) -> Double {
        var calendar = Calendar.current
        calendar.timeZone = timeZone
        return Double(calendar.component(.second, from: self))
    }
    
    func hourAngle(in timeZone: TimeZone) -> Angle {
        return Angle(degrees: (360 / 12) * (hour(in: timeZone) + minutes(in: timeZone) / 60))
    }
    func minuteAngle(in timeZone: TimeZone) -> Angle {
        return Angle(degrees: (minutes(in: timeZone) * 360 / 60))
    }
    func secondAngle(in timeZone: TimeZone) -> Angle {
        return Angle(degrees: (seconds(in: timeZone) * 360 / 60))
    }
}

你可以删除原Date扩展中不带时区参数的hour/minutes/seconds/角度相关计算属性,避免后续调用混淆。


步骤3:为TickHands增加时区参数,调整角度计算逻辑

给指针组件新增时区入参,替换角度计算逻辑为传入时区的版本:

struct TickHands: View {
    var scale: Double
    var timeZone: TimeZone // 新增时区参数
    @State private var dateTime = Date()
    
    private let timer = Timer.publish(every: 1.0, on: .main, in: .common).autoconnect()
    
    var body: some View {
        ZStack {
            Hand(inset: 65 * scale, angle: dateTime.hourAngle(in: timeZone))
                .stroke(style: StrokeStyle(lineWidth: 3 * scale, lineCap: .round, lineJoin: .round))
            Hand(inset: 40 * scale, angle: dateTime.minuteAngle(in: timeZone))
                .stroke(style: StrokeStyle(lineWidth: 2.5 * scale, lineCap: .round, lineJoin: .round))
            Hand(inset: 20 * scale, angle: dateTime.secondAngle(in: timeZone))
                .stroke(Color.orange, style: StrokeStyle(lineWidth: 1.5 * scale, lineCap: .round, lineJoin: .round))
            Circle().fill(Color.orange).frame(width: 10 * scale)
        }
        .onReceive(timer) { input in
            self.dateTime = input
        }
    }
}

步骤4:为TestView增加时区参数,向下传递

给时钟外层容器新增时区入参,传递给内部的TickHands:

struct TestView: View {
    var clockSize: CGFloat = 100
    var timeZone: TimeZone // 新增时区参数
    
    var body: some View {
        GeometryReader { geometry in
            ZStack {
                let scale = geometry.size.height / 200
                TickHands(scale: scale, timeZone: timeZone)
                ForEach(0..<60*4) { tick in
                    Ticks.tick(at: tick, scale: scale)
                }
            }
        }.frame(width: clockSize, height: clockSize)
    }
}

步骤5:调整ContentView实现,传递时区并实时刷新时间

新增定时器触发页面刷新,实时更新时间字符串并给TestView传递对应时区:

// 新增时间格式化工具方法
func getLocalTime(in timeZone: TimeZone) -> String {
    let f = DateFormatter()
    f.timeZone = timeZone
    f.dateFormat = "HH:mm"
    return f.string(from: Date())
}

struct ContentView: View {
    @State private var currentDate = Date()
    private let timer = Timer.publish(every: 1.0, on: .main, in: .common).autoconnect()
    
    var body: some View {
        NavigationView {
            List(countryList) { countryItem in
                // 异常处理:无效时区标识返回空视图,可自行补充降级逻辑
                guard let timeZone = TimeZone(identifier: countryItem.timeZoneIdentifier) else {
                    EmptyView()
                }
                HStack {
                    Text(countryItem.name + ": " + getLocalTime(in: timeZone))
                        .frame(height: 80.0)
                    Spacer()
                    TestView(timeZone: timeZone)
                }.padding(5)
            }
            .navigationBarTitle("世界时钟")
        }
        .onReceive(timer) { input in
            currentDate = input
        }
    }
}

内容的提问来源于stack exchange,提问作者dwayne1929

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.26 01:36:06