Skip to content

Commit 3c2a8f5

Browse files
committed
Add TimePickerDataSource
1 parent 209acbc commit 3c2a8f5

3 files changed

Lines changed: 231 additions & 23 deletions

File tree

WatchKitTimePicker.xcodeproj/project.pbxproj

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@
77
objects = {
88

99
/* Begin PBXBuildFile section */
10-
2E45404B2190B851004D8CEE /* WatchKitTimePicker.h in Headers */ = {isa = PBXBuildFile; fileRef = 2E4540492190B851004D8CEE /* WatchKitTimePicker.h */; settings = {ATTRIBUTES = (Public, ); }; };
10+
2E4540522190B878004D8CEE /* TimePickerDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E4540512190B878004D8CEE /* TimePickerDataSource.swift */; };
1111
/* End PBXBuildFile section */
1212

1313
/* Begin PBXFileReference section */
1414
2E4540462190B851004D8CEE /* WatchKitTimePicker.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = WatchKitTimePicker.framework; sourceTree = BUILT_PRODUCTS_DIR; };
15-
2E4540492190B851004D8CEE /* WatchKitTimePicker.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WatchKitTimePicker.h; sourceTree = "<group>"; };
1615
2E45404A2190B851004D8CEE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
16+
2E4540512190B878004D8CEE /* TimePickerDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimePickerDataSource.swift; sourceTree = "<group>"; };
1717
/* End PBXFileReference section */
1818

1919
/* Begin PBXFrameworksBuildPhase section */
@@ -46,7 +46,7 @@
4646
2E4540482190B851004D8CEE /* WatchKitTimePicker */ = {
4747
isa = PBXGroup;
4848
children = (
49-
2E4540492190B851004D8CEE /* WatchKitTimePicker.h */,
49+
2E4540512190B878004D8CEE /* TimePickerDataSource.swift */,
5050
2E45404A2190B851004D8CEE /* Info.plist */,
5151
);
5252
path = WatchKitTimePicker;
@@ -59,7 +59,6 @@
5959
isa = PBXHeadersBuildPhase;
6060
buildActionMask = 2147483647;
6161
files = (
62-
2E45404B2190B851004D8CEE /* WatchKitTimePicker.h in Headers */,
6362
);
6463
runOnlyForDeploymentPostprocessing = 0;
6564
};
@@ -95,6 +94,7 @@
9594
TargetAttributes = {
9695
2E4540452190B851004D8CEE = {
9796
CreatedOnToolsVersion = 10.1;
97+
LastSwiftMigration = 1010;
9898
};
9999
};
100100
};
@@ -130,6 +130,7 @@
130130
isa = PBXSourcesBuildPhase;
131131
buildActionMask = 2147483647;
132132
files = (
133+
2E4540522190B878004D8CEE /* TimePickerDataSource.swift in Sources */,
133134
);
134135
runOnlyForDeploymentPostprocessing = 0;
135136
};
@@ -260,6 +261,7 @@
260261
isa = XCBuildConfiguration;
261262
buildSettings = {
262263
APPLICATION_EXTENSION_API_ONLY = YES;
264+
CLANG_ENABLE_MODULES = YES;
263265
CODE_SIGN_IDENTITY = "";
264266
CODE_SIGN_STYLE = Automatic;
265267
DEFINES_MODULE = YES;
@@ -276,6 +278,7 @@
276278
PRODUCT_BUNDLE_IDENTIFIER = tech.calstephens.WatchKitTimePicker;
277279
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
278280
SKIP_INSTALL = YES;
281+
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
279282
SWIFT_VERSION = 4.2;
280283
TARGETED_DEVICE_FAMILY = 4;
281284
};
@@ -285,6 +288,7 @@
285288
isa = XCBuildConfiguration;
286289
buildSettings = {
287290
APPLICATION_EXTENSION_API_ONLY = YES;
291+
CLANG_ENABLE_MODULES = YES;
288292
CODE_SIGN_IDENTITY = "";
289293
CODE_SIGN_STYLE = Automatic;
290294
DEFINES_MODULE = YES;
Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
//
2+
// TimePickerDataSource.swift
3+
// WatchKitTimePicker
4+
//
5+
// Created by Cal Stephens on 11/5/18.
6+
// Copyright © 2018 Cal Stephens. All rights reserved.
7+
//
8+
9+
import WatchKit
10+
11+
public class TimePickerDataSource {
12+
13+
private weak var hoursPicker: WKInterfacePicker?
14+
private weak var minutesPicker: WKInterfacePicker?
15+
private weak var amPmPicker: WKInterfacePicker?
16+
17+
public var selectedTimeDidUpdate: ((Date) -> Void)?
18+
19+
public init(hoursPicker: WKInterfacePicker, minutesPicker: WKInterfacePicker, amPmPicker: WKInterfacePicker?, initiallySelectedDate: Date?) {
20+
self.hoursPicker = hoursPicker
21+
self.minutesPicker = minutesPicker
22+
self.amPmPicker = amPmPicker
23+
24+
setup(withInitiallySelectedDate: initiallySelectedDate)
25+
}
26+
27+
28+
// MARK: Setup
29+
30+
private lazy var timeFormatter: DateFormatter = {
31+
let formatter = DateFormatter()
32+
formatter.dateStyle = .none
33+
formatter.timeStyle = .short
34+
return formatter
35+
}()
36+
37+
private lazy var userHas24HourTimeEnabled: Bool = {
38+
let timeString = timeFormatter.string(from: Date())
39+
return !(timeString.contains(Locale.current.calendar.amSymbol)
40+
|| timeString.contains(Locale.current.calendar.pmSymbol))
41+
}()
42+
43+
private lazy var hourPickerOptions: [Int] = {
44+
if userHas24HourTimeEnabled {
45+
return Array(0...23)
46+
} else {
47+
return [/* am: */ 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
48+
/* pm: */ 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
49+
}
50+
}()
51+
52+
private lazy var minutePickerOptions: [Int] = {
53+
// create 0-60 minute entries for all 24 hours in the day.
54+
return Array(repeating: Array(stride(from: 0, to: 60, by: 5)), count: 24).flatMap { $0 }
55+
}()
56+
57+
private lazy var amPmPickerOptions: [String]? = {
58+
if userHas24HourTimeEnabled {
59+
return nil
60+
} else {
61+
return [
62+
Locale.current.calendar.amSymbol,
63+
Locale.current.calendar.pmSymbol]
64+
}
65+
}()
66+
67+
private func setup(withInitiallySelectedDate initiallySelectedDate: Date?) {
68+
hoursPicker?.setItems(hourPickerOptions.map { hourValue in
69+
let pickerItem = WKPickerItem()
70+
pickerItem.title = "\(hourValue)"
71+
return pickerItem
72+
})
73+
74+
minutesPicker?.setItems(minutePickerOptions.map { minuteValue in
75+
let pickerItem = WKPickerItem()
76+
if "\(minuteValue)".count == 1 {
77+
pickerItem.title = "0\(minuteValue)"
78+
} else {
79+
pickerItem.title = "\(minuteValue)"
80+
}
81+
return pickerItem
82+
})
83+
84+
if let amPmPickerOptions = amPmPickerOptions {
85+
amPmPicker?.setItems(amPmPickerOptions.map { value in
86+
let pickerItem = WKPickerItem()
87+
pickerItem.title = value
88+
return pickerItem
89+
})
90+
91+
amPmPicker?.setHidden(false)
92+
hoursPicker?.setRelativeWidth(0.333, withAdjustment: 0)
93+
minutesPicker?.setRelativeWidth(0.333, withAdjustment: 0)
94+
amPmPicker?.setRelativeWidth(0.333, withAdjustment: 0)
95+
96+
} else {
97+
amPmPicker?.setHidden(true)
98+
hoursPicker?.setRelativeWidth(0.5, withAdjustment: 0)
99+
minutesPicker?.setRelativeWidth(0.5, withAdjustment: 0)
100+
}
101+
102+
// set the initial values of the pickers
103+
if let initiallySelectedDate = initiallySelectedDate {
104+
let dateComponents = Calendar.current.dateComponents(Set(arrayLiteral: .hour, .minute), from: initiallySelectedDate)
105+
let hours = dateComponents.hour!
106+
let minutes = dateComponents.minute!
107+
108+
hoursPicker?.setSelectedItemIndex(hours)
109+
selectedHour = hourPickerOptions[hours]
110+
111+
let displayedMinutesPerHour = minutePickerOptions.count / 24
112+
let corresponsingMinuteIndex = (minutePickerOptions.firstIndex(where: { $0 >= minutes }) ?? 0) + (displayedMinutesPerHour * hours)
113+
minutesPicker?.setSelectedItemIndex(corresponsingMinuteIndex)
114+
selectedMinute = minutePickerOptions[corresponsingMinuteIndex]
115+
116+
if !userHas24HourTimeEnabled {
117+
if hours < 12 {
118+
amPmPicker?.setSelectedItemIndex(0)
119+
amPm = .am
120+
} else {
121+
amPmPicker?.setSelectedItemIndex(1)
122+
amPm = .pm
123+
}
124+
}
125+
}
126+
}
127+
128+
129+
// MARK: User Interaction
130+
131+
private enum AMPM {
132+
case am, pm
133+
}
134+
135+
private var selectedHour: Int!
136+
private var selectedMinute: Int!
137+
private var amPm: AMPM?
138+
139+
public func hourPickerUpdated(to index: Int) {
140+
selectedHour = hourPickerOptions[index]
141+
142+
// if using 12-hour time, switching between the first half and the last half swaps between AM and PM
143+
if !userHas24HourTimeEnabled {
144+
let selectedHourIsInTheAM = (index < 12)
145+
146+
if selectedHourIsInTheAM, amPm == .pm {
147+
amPmPicker?.setSelectedItemIndex(0)
148+
}
149+
150+
else if !selectedHourIsInTheAM, amPm == .am {
151+
amPmPicker?.setSelectedItemIndex(1)
152+
}
153+
}
154+
155+
// there are minute values for each of the 24 hours in the day,
156+
// so make sure the selected minute corresponds with the selected hour
157+
let displayedMinutesPerHour = minutePickerOptions.count / 24
158+
let corresponsingMinuteIndex = (minutePickerOptions.firstIndex(of: selectedMinute) ?? 0) + (displayedMinutesPerHour * index)
159+
minutesPicker?.setSelectedItemIndex(corresponsingMinuteIndex)
160+
161+
selectedTimeDidUpdate?(selectedTime())
162+
}
163+
164+
public func minutePickerUpdated(to index: Int) {
165+
selectedMinute = minutePickerOptions[index]
166+
167+
// there are minute values for each of the 24 hours in the day,
168+
// so make sure the selected hour corresponds with the selected minute
169+
let displayedMinutesPerHour = minutePickerOptions.count / 24
170+
let expectedHourIndex = index / (displayedMinutesPerHour)
171+
let expectedHour = hourPickerOptions[expectedHourIndex]
172+
if selectedHour != expectedHour {
173+
hoursPicker?.setSelectedItemIndex(expectedHourIndex)
174+
}
175+
176+
selectedTimeDidUpdate?(selectedTime())
177+
}
178+
179+
public func amPmPickerUpdated(to index: Int) {
180+
guard !userHas24HourTimeEnabled else { return }
181+
182+
if index == 0 { amPm = .am }
183+
else { amPm = .pm }
184+
185+
// update the hour picker to inhabit the correct half of the 24 picker options
186+
var selectedIndex = hourPickerOptions.firstIndex(of: selectedHour) ?? 0
187+
if amPm == .pm {
188+
// the PM times inhabit the second half
189+
selectedIndex += 12
190+
}
191+
192+
hoursPicker?.setSelectedItemIndex(selectedIndex)
193+
hourPickerUpdated(to: selectedIndex)
194+
195+
selectedTimeDidUpdate?(selectedTime())
196+
}
197+
198+
public func selectedTime() -> Date {
199+
var hourIn24HourTime = selectedHour ?? 0
200+
201+
// if there's an option for am/pm, the user's in 12 hour time.
202+
// Need to do the necessary corrections.
203+
if let amPm = amPm {
204+
if hourIn24HourTime == 12 && amPm == .am {
205+
hourIn24HourTime = 0
206+
} else if hourIn24HourTime == 12 && amPm == .pm {
207+
hourIn24HourTime = 12
208+
} else if amPm == .pm {
209+
hourIn24HourTime += 12
210+
}
211+
}
212+
213+
return Calendar.current.date(
214+
bySettingHour: hourIn24HourTime,
215+
minute: selectedMinute,
216+
second: 0,
217+
of: Date(),
218+
matchingPolicy: .nextTime,
219+
repeatedTimePolicy: .first,
220+
direction: .forward)!
221+
}
222+
223+
}

WatchKitTimePicker/WatchKitTimePicker.h

Lines changed: 0 additions & 19 deletions
This file was deleted.

0 commit comments

Comments
 (0)