Skip to content

Commit 90836cd

Browse files
Merge pull request #6 from SimformSolutionsPvtLtd/UNT-T20406-time-picker-ui
UNT-T20406: Time picker
2 parents 009e826 + 2ed4321 commit 90836cd

23 files changed

Lines changed: 812 additions & 42 deletions

File tree

DateTimePicker/DateTimePicker.xcodeproj/project.pbxproj

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@
2626
650720CB2B181E5400AC1FB6 /* View+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 650720CA2B181E5400AC1FB6 /* View+Extension.swift */; };
2727
650720CD2B18257600AC1FB6 /* CornerRadiusStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 650720CC2B18257600AC1FB6 /* CornerRadiusStyle.swift */; };
2828
650720EF2B19FD7A00AC1FB6 /* ThemeButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 650720EE2B19FD7A00AC1FB6 /* ThemeButton.swift */; };
29+
650720F52B1DAFFB00AC1FB6 /* SSTimePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 650720F42B1DAFFB00AC1FB6 /* SSTimePicker.swift */; };
30+
650720F72B1DDF3600AC1FB6 /* SSTimePickerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 650720F62B1DDF3600AC1FB6 /* SSTimePickerManager.swift */; };
31+
650720F92B1DE9FD00AC1FB6 /* SSTimeTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 650720F82B1DE9FD00AC1FB6 /* SSTimeTextField.swift */; };
32+
650720FB2B1DEF8300AC1FB6 /* SSTimePickerConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 650720FA2B1DEF8300AC1FB6 /* SSTimePickerConfiguration.swift */; };
33+
654A7F952B1F04DF00EB9B33 /* SSClockPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 654A7F942B1F04DF00EB9B33 /* SSClockPicker.swift */; };
2934
65E058102B0E2B260049A7BA /* DateTimePicker.docc in Sources */ = {isa = PBXBuildFile; fileRef = 65E0580F2B0E2B260049A7BA /* DateTimePicker.docc */; };
3035
65E058162B0E2B260049A7BA /* DateTimePicker.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 65E0580B2B0E2B260049A7BA /* DateTimePicker.framework */; };
3136
65E0581B2B0E2B260049A7BA /* DateTimePickerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65E0581A2B0E2B260049A7BA /* DateTimePickerTests.swift */; };
@@ -62,6 +67,11 @@
6267
650720CA2B181E5400AC1FB6 /* View+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Extension.swift"; sourceTree = "<group>"; };
6368
650720CC2B18257600AC1FB6 /* CornerRadiusStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CornerRadiusStyle.swift; sourceTree = "<group>"; };
6469
650720EE2B19FD7A00AC1FB6 /* ThemeButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeButton.swift; sourceTree = "<group>"; };
70+
650720F42B1DAFFB00AC1FB6 /* SSTimePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSTimePicker.swift; sourceTree = "<group>"; };
71+
650720F62B1DDF3600AC1FB6 /* SSTimePickerManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSTimePickerManager.swift; sourceTree = "<group>"; };
72+
650720F82B1DE9FD00AC1FB6 /* SSTimeTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSTimeTextField.swift; sourceTree = "<group>"; };
73+
650720FA2B1DEF8300AC1FB6 /* SSTimePickerConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSTimePickerConfiguration.swift; sourceTree = "<group>"; };
74+
654A7F942B1F04DF00EB9B33 /* SSClockPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSClockPicker.swift; sourceTree = "<group>"; };
6575
65E0580B2B0E2B260049A7BA /* DateTimePicker.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DateTimePicker.framework; sourceTree = BUILT_PRODUCTS_DIR; };
6676
65E0580E2B0E2B260049A7BA /* DateTimePicker.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DateTimePicker.h; sourceTree = "<group>"; };
6777
65E0580F2B0E2B260049A7BA /* DateTimePicker.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = DateTimePicker.docc; sourceTree = "<group>"; };
@@ -96,18 +106,20 @@
96106
650720B22B10880900AC1FB6 /* MonthSelectionView.swift */,
97107
650720B42B10881900AC1FB6 /* YearSelectionView.swift */,
98108
650720C52B13BE4D00AC1FB6 /* SSDatePicker.swift */,
109+
650720F42B1DAFFB00AC1FB6 /* SSTimePicker.swift */,
99110
);
100111
path = Views;
101112
sourceTree = "<group>";
102113
};
103114
650720A42B0F645600AC1FB6 /* Common */ = {
104115
isa = PBXGroup;
105116
children = (
117+
654A7F972B1F2CF400EB9B33 /* Time picker */,
118+
654A7F962B1F2CEC00EB9B33 /* Date picker */,
106119
6507209D2B0F538400AC1FB6 /* SSPickerConstants.swift */,
107-
6507209B2B0F4D4500AC1FB6 /* SSDatePickerConfiguration.swift */,
108120
650720A92B0F819F00AC1FB6 /* ImageConstant.swift */,
109-
650720C72B146E9500AC1FB6 /* SSDatePickerManager.swift */,
110121
650720CC2B18257600AC1FB6 /* CornerRadiusStyle.swift */,
122+
650720F82B1DE9FD00AC1FB6 /* SSTimeTextField.swift */,
111123
650720EE2B19FD7A00AC1FB6 /* ThemeButton.swift */,
112124
);
113125
path = Common;
@@ -127,6 +139,25 @@
127139
path = Extensions;
128140
sourceTree = "<group>";
129141
};
142+
654A7F962B1F2CEC00EB9B33 /* Date picker */ = {
143+
isa = PBXGroup;
144+
children = (
145+
650720C72B146E9500AC1FB6 /* SSDatePickerManager.swift */,
146+
6507209B2B0F4D4500AC1FB6 /* SSDatePickerConfiguration.swift */,
147+
);
148+
path = "Date picker";
149+
sourceTree = "<group>";
150+
};
151+
654A7F972B1F2CF400EB9B33 /* Time picker */ = {
152+
isa = PBXGroup;
153+
children = (
154+
654A7F942B1F04DF00EB9B33 /* SSClockPicker.swift */,
155+
650720FA2B1DEF8300AC1FB6 /* SSTimePickerConfiguration.swift */,
156+
650720F62B1DDF3600AC1FB6 /* SSTimePickerManager.swift */,
157+
);
158+
path = "Time picker";
159+
sourceTree = "<group>";
160+
};
130161
65E058012B0E2B260049A7BA = {
131162
isa = PBXGroup;
132163
children = (
@@ -288,10 +319,15 @@
288319
650720AA2B0F819F00AC1FB6 /* ImageConstant.swift in Sources */,
289320
650720BE2B10CB7300AC1FB6 /* DateFormatter+Extension.swift in Sources */,
290321
650720B52B10881900AC1FB6 /* YearSelectionView.swift in Sources */,
322+
654A7F952B1F04DF00EB9B33 /* SSClockPicker.swift in Sources */,
323+
650720F72B1DDF3600AC1FB6 /* SSTimePickerManager.swift in Sources */,
291324
650720C82B146E9500AC1FB6 /* SSDatePickerManager.swift in Sources */,
325+
650720F92B1DE9FD00AC1FB6 /* SSTimeTextField.swift in Sources */,
292326
6507209E2B0F538400AC1FB6 /* SSPickerConstants.swift in Sources */,
327+
650720FB2B1DEF8300AC1FB6 /* SSTimePickerConfiguration.swift in Sources */,
293328
65E058102B0E2B260049A7BA /* DateTimePicker.docc in Sources */,
294329
650720BC2B10CB5000AC1FB6 /* Date+Extension.swift in Sources */,
330+
650720F52B1DAFFB00AC1FB6 /* SSTimePicker.swift in Sources */,
295331
650720EF2B19FD7A00AC1FB6 /* ThemeButton.swift in Sources */,
296332
650720C22B10CBC500AC1FB6 /* Color+Extension.swift in Sources */,
297333
6507209C2B0F4D4500AC1FB6 /* SSDatePickerConfiguration.swift in Sources */,
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"colors" : [
3+
{
4+
"color" : {
5+
"color-space" : "srgb",
6+
"components" : {
7+
"alpha" : "1.000",
8+
"blue" : "255",
9+
"green" : "251",
10+
"red" : "243"
11+
}
12+
},
13+
"idiom" : "universal"
14+
}
15+
],
16+
"info" : {
17+
"author" : "xcode",
18+
"version" : 1
19+
}
20+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"colors" : [
3+
{
4+
"color" : {
5+
"color-space" : "srgb",
6+
"components" : {
7+
"alpha" : "1.000",
8+
"blue" : "213",
9+
"green" : "209",
10+
"red" : "244"
11+
}
12+
},
13+
"idiom" : "universal"
14+
}
15+
],
16+
"info" : {
17+
"author" : "xcode",
18+
"version" : 1
19+
}
20+
}

DateTimePicker/DateTimePicker/Common/SSDatePickerConfiguration.swift renamed to DateTimePicker/DateTimePicker/Common/Date picker/SSDatePickerConfiguration.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,13 @@ public struct SSDatePickerConfiguration {
5555

5656
}
5757

58-
protocol ConfigurationDirectAccess {
58+
protocol DatePickerConfigurationDirectAccess {
5959

6060
var configuration: SSDatePickerConfiguration { get }
6161

6262
}
6363

64-
extension ConfigurationDirectAccess {
64+
extension DatePickerConfigurationDirectAccess {
6565

6666
var calendar: Calendar {
6767
configuration.calendar

DateTimePicker/DateTimePicker/Common/SSDatePickerManager.swift renamed to DateTimePicker/DateTimePicker/Common/Date picker/SSDatePickerManager.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public extension DatePickerDataSource {
3030

3131
}
3232

33-
public final class SSDatePickerManager: ObservableObject, ConfigurationDirectAccess {
33+
public final class SSDatePickerManager: ObservableObject, DatePickerConfigurationDirectAccess {
3434

3535
//MARK: - Property
3636

DateTimePicker/DateTimePicker/Common/SSPickerConstants.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,16 @@ struct SSPickerConstants {
2828
static let paddingFive: CGFloat = 5
2929
static let monthYearGridRows: Int = 3
3030

31+
// Time picker constant
32+
33+
static let timeFieldPadding: CGFloat = 8
34+
static let timeFieldCornerRadius: CGFloat = 8
35+
static let circleSize: CGFloat = 40
36+
static let clockPadding: CGFloat = 50
37+
static let clockHeight: CGFloat = 300
38+
static let clockNumberRotationDegree: CGFloat = 30 // To create clock on 360 degree angle, use 30 degree (12*30 = 360)
39+
static let minuteRotationDegree: CGFloat = 6 // 12*5*6 = 360
40+
3141
}
3242

3343
public enum SelectionView {
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
//
2+
// SSTimeTextField.swift
3+
// DateTimePicker
4+
//
5+
// Created by Rizwana Desai on 04/12/23.
6+
//
7+
8+
import SwiftUI
9+
10+
struct SSTimeTextField: View {
11+
12+
//MARK: - Property
13+
14+
@Binding var time: String
15+
var placeHolder: String
16+
var backgroundColor: Color
17+
var foregrondColor: Color
18+
private let charLimit = 2
19+
20+
//MARK: - Body
21+
22+
var body: some View {
23+
timeLabel
24+
}
25+
26+
//MARK: - Sub views
27+
28+
var timeLabel: some View {
29+
Text(time.count == 1 ? "0\(time)" : time)
30+
.keyboardType(.numberPad)
31+
.multilineTextAlignment(.center)
32+
.font(.system(size: 20, weight: .semibold))
33+
.padding(SSPickerConstants.timeFieldPadding)
34+
.foregroundColor(foregrondColor)
35+
.background(backgroundColor)
36+
.fixedSize()
37+
.cornerRadius(SSPickerConstants.timeFieldCornerRadius)
38+
.onChange(of: time) { newValue in
39+
if time.count > charLimit {
40+
time = String(time.prefix(charLimit))
41+
}
42+
}
43+
}
44+
45+
var txtField: some View {
46+
TextField(placeHolder, text: $time)
47+
.keyboardType(.numberPad)
48+
.multilineTextAlignment(.center)
49+
.font(.system(size: 20, weight: .semibold))
50+
.padding(SSPickerConstants.timeFieldPadding)
51+
.foregroundColor(foregrondColor)
52+
.background(backgroundColor)
53+
.fixedSize()
54+
.cornerRadius(SSPickerConstants.timeFieldCornerRadius)
55+
.onChange(of: time) { newValue in
56+
if time.count > charLimit {
57+
time = String(time.prefix(charLimit))
58+
}
59+
}
60+
}
61+
62+
}
63+
64+
struct SSTimeTextField_Previews: PreviewProvider {
65+
static var previews: some View {
66+
SSTimeTextField(time: .constant("12"), placeHolder: "HH", backgroundColor: Color.peach, foregrondColor: Color.darkPink)
67+
}
68+
}
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
//
2+
// SSClockPicker.swift
3+
// DateTimePicker
4+
//
5+
// Created by Rizwana Desai on 04/12/23.
6+
//
7+
8+
import SwiftUI
9+
10+
struct SSClockPicker: View, TimePickerConfigurationDirectAccess {
11+
12+
//MARK: - Property
13+
14+
@ObservedObject var timePickerManager: SSTimePickerManager
15+
private var threeSixtyDegree: CGFloat = 360
16+
private var oneEightyDegree: CGFloat = 180
17+
var configuration: SSTimePickerConfiguration {
18+
timePickerManager.configuration
19+
}
20+
21+
//MARK: - init
22+
23+
init(timePickerManager: SSTimePickerManager) {
24+
self.timePickerManager = timePickerManager
25+
}
26+
27+
//MARK: - Body
28+
29+
var body: some View {
30+
GeometryReader { reader in
31+
ZStack {
32+
let widthGeo = reader.frame(in: .global).width/2
33+
selectionCircle(widthGeo)
34+
clockFace(widthGeo)
35+
clockHand(widthGeo)
36+
clockFace(widthGeo)
37+
}
38+
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
39+
}
40+
.frame(height: SSPickerConstants.clockHeight)
41+
}
42+
43+
//MARK: - Sub views
44+
45+
func selectionCircle(_ widthGeo: CGFloat) -> some View {
46+
Circle()
47+
.fill(clockHandColor)
48+
.frame(width: SSPickerConstants.circleSize, height: SSPickerConstants.circleSize)
49+
.offset(x: widthGeo - SSPickerConstants.clockPadding)
50+
.rotationEffect(.init(degrees: timePickerManager.angle))
51+
.gesture(DragGesture().onChanged(onChanged(value:)).onEnded(onEnd(value:)))
52+
.rotationEffect(.init(degrees: -90))
53+
}
54+
55+
func clockFace(_ widthGeo: CGFloat) -> some View {
56+
ForEach(1...12, id: \.self) { index in
57+
VStack {
58+
Text("\(timePickerManager.isMinuteClock ? index*5 : index)")
59+
.font(.system(size: 15))
60+
.fontWeight(.semibold)
61+
.foregroundColor(clockNumberTextColor)
62+
.rotationEffect(.init(degrees: Double(-index)*SSPickerConstants.clockNumberRotationDegree))
63+
}
64+
.offset(y: -widthGeo+SSPickerConstants.clockPadding)
65+
.rotationEffect(.init(degrees: Double(index)*SSPickerConstants.clockNumberRotationDegree)) // rotating view : 12*30 = 360
66+
}
67+
}
68+
69+
func clockHand(_ widthGeo: CGFloat) -> some View {
70+
Circle()
71+
.fill(clockHandColor)
72+
.frame(width: SSPickerConstants.paddingTen, height: SSPickerConstants.paddingTen)
73+
.overlay (
74+
Rectangle()
75+
.fill(clockHandColor)
76+
.frame(width: 2, height: clockHandHeight(widthGeo))
77+
, alignment: .bottom
78+
)
79+
.rotationEffect(.init(degrees: timePickerManager.angle))
80+
}
81+
82+
func clockHandHeight(_ width: CGFloat) -> CGFloat {
83+
width-SSPickerConstants.clockPadding
84+
}
85+
86+
}
87+
88+
//MARK: - Drag gesture methods
89+
90+
extension SSClockPicker {
91+
92+
func onChanged(value: DragGesture.Value) {
93+
// getting angle
94+
let vector = CGVector(dx: value.location.x
95+
, dy: value.location.y)
96+
let radians = atan2(vector.dy, vector.dx)
97+
var angle = radians * oneEightyDegree / .pi
98+
if angle < 0 {
99+
angle = threeSixtyDegree + angle
100+
}
101+
timePickerManager.angle = angle
102+
if !timePickerManager.isMinuteClock {
103+
// updating angle for hour if the angle is in between hours
104+
let roundValue = Int(SSPickerConstants.clockNumberRotationDegree) * Int(round(angle/SSPickerConstants.clockNumberRotationDegree))
105+
timePickerManager.angle = Double(roundValue)
106+
} else {
107+
// updating minutes
108+
let progress = angle / threeSixtyDegree
109+
timePickerManager.minutes = "\(progress*60)"
110+
}
111+
}
112+
113+
func onEnd(value: DragGesture.Value) {
114+
if !timePickerManager.isMinuteClock {
115+
// updating hour value
116+
timePickerManager.hour = "\(Int(timePickerManager.angle / SSPickerConstants.clockNumberRotationDegree))"
117+
// updating picker to minutes
118+
withAnimation {
119+
timePickerManager.angle = Double((Int(timePickerManager.minutes) ?? 1) * Int(SSPickerConstants.minuteRotationDegree))
120+
timePickerManager.isMinuteClock = true
121+
}
122+
}
123+
}
124+
125+
}
126+
127+
128+
struct SSClockPicker_Previews: PreviewProvider {
129+
static var previews: some View {
130+
SSClockPicker(timePickerManager: SSTimePickerManager(configuration: SSTimePickerConfiguration()))
131+
}
132+
}

0 commit comments

Comments
 (0)