I am realizing a list with different clocks that should give me different times. Unfortunately I lack a bit of knowledge how to implement this.
My situation: The clock in my list is generated via TestView(). In the TestView I display a clock with the current time. Now I want to display different clocks and times in my list. For this I created a file "Country", which gives me a city name and a time zone. How would I have to pass the values of the specific time zone to the TestView so that the time is displayed in the clock?
Here is my Code how i create a clock with de current file:
struct Hand: Shape {
let inset: CGFloat
let angle: Angle
func path(in rect: CGRect) -> Path {
let rect = rect.insetBy(dx: inset, dy: inset)
var p = Path()
p.move(to: CGPoint(x: rect.midX, y: rect.midY))
p.addLine(to: position(for: CGFloat(angle.radians), in: rect))
return p
}
private func position(for angle: CGFloat, in rect: CGRect) -> CGPoint {
let angle = angle - (.pi/2)
let radius = min(rect.width, rect.height)/2
let xPos = rect.midX (radius * cos(angle))
let yPos = rect.midY (radius * sin(angle))
return CGPoint(x: xPos, y: yPos)
}
}
struct TickHands: View {
var scale: Double
@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)
.stroke(style: StrokeStyle(lineWidth: 3 * scale, lineCap: .round, lineJoin: .round))
Hand(inset: 40 * scale, angle: dateTime.minuteAngle)
.stroke(style: StrokeStyle(lineWidth: 2.5 * scale, lineCap: .round, lineJoin: .round))
Hand(inset: 20 * scale, angle: dateTime.secondAngle)
.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
}
}
}
extension Date {
var hourAngle: Angle {
return Angle (degrees: (360 / 12) * (self.hour self.minutes / 60))
}
var minuteAngle: Angle {
return Angle(degrees: (self.minutes * 360 / 60))
}
var secondAngle: Angle {
return Angle (degrees: (self.seconds * 360 / 60))
}
}
extension Date {
var hour: Double {
return Double(Calendar.current.component(.hour, from: self))
}
var minutes: Double {
return Double(Calendar.current.component(.minute, from: self))
}
var seconds: Double {
return Double(Calendar.current.component(.second, from: self))
}
}
struct TestView: View {
var clockSize: CGFloat = 100
var body: some View {
GeometryReader { geometry in
ZStack {
let scale = geometry.size.height / 200
TickHands(scale: scale)
ForEach(0..<60*4) { tick in
Ticks.tick(at: tick, scale: scale)
}
}
}.frame(width: clockSize, height: clockSize)
}
}
struct Ticks{
static func tick(at tick: Int, scale: CGFloat) -> some View {
VStack {
Rectangle()
.fill(Color.primary)
.opacity(tick % 20 == 0 ? 1 : 0.4)
.frame(width: 2 * scale, height: (tick % 5 == 0 ? 15 : 7) * scale)
Spacer()
}
.rotationEffect(Angle.degrees(Double(tick)/(60) * 360))
}
}
Here is the code of the list in which I want to display the diffrent clocks
struct ContentView: View {
var body: some View {
NavigationView {
List(countryList) { countryItem in
HStack() {
Text(countryItem.name ": " countryItem.time)
.frame(height: 80.0)
Spacer()
TestView()
}.padding(5)
}
.navigationBarTitle("App")
}
}
}
And here is the code of the countries, where I get the name and the timezone for the clocks
struct Country: Identifiable {
let id = UUID()
let name: String
let time: String
}
let countryList: [Country] = [
Country(
name: "New York",
time: getLocalTimeZone(in: "America/New_York")),
Country(
name: "Paris",
time: getLocalTimeZone(in: "Europe/Paris"))
]
func getLocalTimeZone(in timeZone: String) -> String {
let f = DateFormatter()
f.timeZone = TimeZone(identifier: timeZone)
f.dateFormat = "HH:mm"
return f.string(from: Date())
}
CodePudding user response:
import SwiftUI
struct Country: Identifiable {
let id = UUID()
let name: String
//Since you need the identifier for the string and the clock it is best to have a variable for the identifier vs just the time string
let timeZoneIdentifier: String
}
//You can use an extension to create other variables that use the primary ones
extension Country{
///Returns the TimeZone using the timeZoneIdentifier or the system timezone if not defined
var timeZone: TimeZone{
TimeZone(identifier: timeZoneIdentifier) ?? TimeZone.current
}
///Returns a string with the current time in "HH:mm" format
var time: String{
Date().getLocalTimeZone(in: self.timeZone)
}
}
struct WorldClockView: View {
var countryList: [Country] = [
Country(
name: "New York",
timeZoneIdentifier: "America/New_York"),
Country(
name: "Paris",
timeZoneIdentifier: "Europe/Paris")
]
var body: some View {
NavigationView {
List(countryList) { countryItem in
HStack() {
Text(countryItem.name ": " countryItem.time)
.frame(height: 80.0)
Spacer()
//Your clock will need the timeZone
ClockTestView(timeZone: countryItem.timeZone)
}.padding(5)
}
.navigationBarTitle("App")
}
}
}
struct WorldClockView_Previews: PreviewProvider {
static var previews: some View {
WorldClockView()
}
}
struct TickHands: View {
var scale: Double
@State var dateTime: Date = Date()
//Take in timezone
let timeZone: TimeZone
private let timer = Timer.publish(every: 1.0, on: .main, in: .common).autoconnect()
var body: some View {
ZStack {
//Get the angles in 1 variable using time zone
let angles = dateTime.getHandAngles(timeZone: timeZone)
Hand(inset: 65 * scale, angle: angles.hourAngle)
.stroke(style: StrokeStyle(lineWidth: 3 * scale, lineCap: .round, lineJoin: .round))
Hand(inset: 40 * scale, angle: angles.minuteAngle)
.stroke(style: StrokeStyle(lineWidth: 2.5 * scale, lineCap: .round, lineJoin: .round))
Hand(inset: 20 * scale, angle: angles.secondAngle)
.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
}
}
}
//I made some changes to yout date extension so you can account for the time zone
extension Date {
///Returns the hour, minute and second date component in the provided timeZone
func getComponents(timeZone: TimeZone = TimeZone.current) -> (hour: Double, minutes: Double, seconds: Double){
var calendar = Calendar.current
//Calendar that includes a custom timezone
calendar.timeZone = timeZone
let hour: Double = Double(calendar.component(.hour, from: self))
let minutes: Double = Double(calendar.component(.minute, from: self))
let seconds: Double = Double(calendar.component(.second, from: self))
return (hour, minutes, seconds)
}
///Returns the angles for the hour, minute and second clock hands in the provided timeZone
func getHandAngles(timeZone: TimeZone = TimeZone.current) -> (hourAngle: Angle, minuteAngle: Angle, secondAngle: Angle ){
let components = self.getComponents(timeZone: timeZone)
let hourAngle: Angle = Angle (degrees: (360 / 12) * (components.hour components.minutes / 60))
let minuteAngle: Angle = Angle(degrees: (components.minutes * 360 / 60))
let secondAngle: Angle = Angle (degrees: (components.seconds * 360 / 60))
return(hourAngle,minuteAngle,secondAngle)
}
//Moved this here to make it more resusable
///Returns a string with the current time in "HH:mm" format for the provided time zone
func getLocalTimeZone(in timeZone: TimeZone) -> String {
let f = DateFormatter()
f.timeZone = timeZone
f.dateFormat = "HH:mm"
return f.string(from: self)
}
}
struct ClockTestView: View {
var clockSize: CGFloat = 100
//Take in time zone to be used
var timeZone: TimeZone
var body: some View {
GeometryReader { geometry in
ZStack {
let scale = geometry.size.height / 200
//Pass time zone to be used
TickHands(scale: scale, timeZone: timeZone)
ForEach(0..<60*4) { tick in
Ticks.tick(at: tick, scale: scale)
}
}
}.frame(width: clockSize, height: clockSize)
}
}
CodePudding user response:
The way you calculate hours
, minutes
and seconds
are adopting the same calendar instance, hence the same time zone is always used for such calculation.