Skip to content

Commit cf8147c

Browse files
fix: 🐛 [IOSSDKBUG-791] DateTimePicker Enhancement (SAP#1154)
* fix: 🐛 [IOSSDKBUG-791] DateTimePicker Enhancement Add date formatter support to DateTimePicker component * fix: 🐛 [IOSSDKBUG-791] DateTimePicker Enhancement Add additional API doc --------- Co-authored-by: dyongxu <61523257+dyongxu@users.noreply.github.com>
1 parent 9923779 commit cf8147c

File tree

5 files changed

+84
-31
lines changed

5 files changed

+84
-31
lines changed

Apps/Examples/Examples/FioriSwiftUICore/Picker/DateTimePickerExample.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ struct DateTimePickerExample: View {
1010
@State var s5: Date = .now
1111
@State var s6: Date = .now
1212
@State var s7: Date = .now
13+
@State var customizedDate: Date = .init()
1314
@State var limitedDate: Date = .now
1415
@State var isRequired = false
1516
@State var showsErrorMessage = false
@@ -20,6 +21,7 @@ struct DateTimePickerExample: View {
2021
@State var pickerVisible3 = false
2122
@State var pickerVisible4 = false
2223
@State var pickerVisible5 = false
24+
@State var customizedPickerVisible = false
2325
@State var limitedDatePickerVisible = false
2426

2527
// Limit the selectable dates from last seven days to next seven days
@@ -61,6 +63,12 @@ struct DateTimePickerExample: View {
6163
return .text(indicator)
6264
}
6365

66+
let customizedDateFormatter: DateFormatter = {
67+
let formatter = DateFormatter()
68+
formatter.dateFormat = "MM/dd/yyyy HH:mm:ss"
69+
return formatter
70+
}()
71+
6472
var body: some View {
6573
List {
6674
Toggle("Mandatory Field", isOn: self.$isRequired)
@@ -87,6 +95,10 @@ struct DateTimePickerExample: View {
8795
DateTimePicker(title: "Custom Style", mandatoryFieldIndicator: self.mandatoryFieldIndicator(), isRequired: self.isRequired, selectedDate: self.$s6, pickerVisible: self.$pickerVisible5)
8896
.titleStyle(CustomTitleStyle())
8997
.valueLabelStyle(CustomValueLabelStyle())
98+
99+
DateTimePicker(title: "Customized Date Formatter, locale and calendar", mandatoryFieldIndicator: self.mandatoryFieldIndicator(), isRequired: self.isRequired, selectedDate: self.$customizedDate, dateFormatter: self.customizedDateFormatter, pickerVisible: self.$customizedPickerVisible)
100+
.environment(\.locale, Locale(identifier: "zh-Hans"))
101+
.environment(\.calendar, Calendar(identifier: .gregorian))
90102
}
91103
Section(header: Text("Disabled")) {
92104
DateTimePicker(title: "In Disabled Mode", controlState: .disabled, selectedDate: self.$s7, pickerVisible: self.$pickerVisible)

Sources/FioriSwiftUICore/_ComponentProtocols/CompositeComponentProtocols.swift

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -580,20 +580,35 @@ protocol _SwitchViewComponent: _TitleComponent, _SwitchComponent {}
580580
///
581581
/// ## Usage
582582
/// ```swift
583-
/// @State var selection: Date = .init(timeIntervalSince1970: 0.0)
583+
/// @State var customizedDate: Date = .init(timeIntervalSince1970: 0.0)
584584
/// @State var isRequired = false
585585
/// @State var showsErrorMessage = false
586+
/// @State var customizedPickerVisible = false
587+
/// let customizedDateFormatter: DateFormatter = {
588+
/// let formatter = DateFormatter()
589+
/// formatter.dateFormat = "MM/dd/yyyy HH:mm:ss"
590+
/// return formatter
591+
/// }()
592+
/// func mandatoryFieldIndicator() -> TextOrIcon {
593+
/// var indicator = AttributedString("*")
594+
/// indicator.font = .fiori(forTextStyle: .title3)
595+
/// indicator.foregroundColor = Color.preferredColor(.indigo7)
596+
/// return .text(indicator)
597+
/// }
598+
/// @State var customizedPickerVisible = false
586599
///
587-
/// DateTimePicker(title: "Default", isRequired: self.isRequired, selectedDate: self.$selection)
588-
/// .informationView(isPresented: self.$showsErrorMessage, description: AttributedString("The Date should be before December."))
589-
/// .informationViewStyle(.informational)
600+
/// DateTimePicker(title: "Customized Date Formatter, locale and calendar", mandatoryFieldIndicator: self.mandatoryFieldIndicator(), isRequired: self.isRequired, selectedDate: self.$customizedDate, dateFormatter: self.customizedDateFormatter, pickerVisible: self.$customizedPickerVisible)
601+
/// .environment(\.locale, Locale(identifier: "zh-Hans"))
602+
/// .environment(\.calendar, Calendar(identifier: .gregorian))
590603
/// ```
591604
// sourcery: CompositeComponent
592605
protocol _DateTimePickerComponent: _TitleComponent, _ValueLabelComponent, _MandatoryField, _FormViewComponent {
593606
// The inclusive range of selectable dates.
594607
var range: ClosedRange<Date>? { get }
595608
// sourcery: @Binding
596609
var selectedDate: Date { get }
610+
/// The `DateFormatter` to be used to display the selected `Date`. Default formatter will use customized dateStyle and timeStyle.
611+
var dateFormatter: DateFormatter? { get }
597612

598613
// sourcery: defaultValue = [.date, .hourAndMinute]
599614
/// The components shown in the date picker, default value shows date and time.

Sources/FioriSwiftUICore/_FioriStyles/DateTimePickerStyle.fiori.swift

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,32 +7,34 @@ public struct DateTimePickerBaseStyle: DateTimePickerStyle {
77
@Environment(\.dynamicTypeSize) var dynamicTypeSize
88

99
public func makeBody(_ configuration: DateTimePickerConfiguration) -> some View {
10-
VStack(spacing: 0) {
11-
Group {
12-
if self.dynamicTypeSize >= .accessibility3 {
13-
self.configureMainStack(configuration, isVertical: true)
14-
} else {
15-
ViewThatFits(in: .horizontal) {
16-
self.configureMainStack(configuration, isVertical: false)
10+
VStack {
11+
VStack(spacing: 0) {
12+
Group {
13+
if self.dynamicTypeSize >= .accessibility3 {
1714
self.configureMainStack(configuration, isVertical: true)
15+
} else {
16+
ViewThatFits(in: .horizontal) {
17+
self.configureMainStack(configuration, isVertical: false)
18+
self.configureMainStack(configuration, isVertical: true)
19+
}
1820
}
1921
}
20-
}
21-
.animation(nil, value: configuration.pickerVisible)
22-
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
23-
.padding(.top, 8)
24-
25-
if configuration.pickerVisible {
26-
LazyVStack {
27-
Divider()
28-
.frame(height: 0.33)
29-
.foregroundStyle(Color.preferredColor(.separatorOpaque))
30-
self.showPicker(configuration)
22+
.animation(nil, value: configuration.pickerVisible)
23+
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
24+
.padding(.top, 8)
25+
26+
if configuration.pickerVisible {
27+
LazyVStack {
28+
Divider()
29+
.frame(height: 0.33)
30+
.foregroundStyle(Color.preferredColor(.separatorOpaque))
31+
self.showPicker(configuration)
32+
}
33+
.transition(.opacity.combined(with: .scale(scale: 1.0, anchor: .top)))
3134
}
32-
.transition(.opacity.combined(with: .scale(scale: 1.0, anchor: .top)))
3335
}
36+
.animation(.easeInOut(duration: 0.3), value: configuration.pickerVisible)
3437
}
35-
.animation(.easeInOut(duration: 0.3), value: configuration.pickerVisible)
3638
}
3739

3840
func configureMainStack(_ configuration: DateTimePickerConfiguration, isVertical: Bool) -> some View {
@@ -63,6 +65,10 @@ public struct DateTimePickerBaseStyle: DateTimePickerStyle {
6365

6466
func getValueLabel(_ configuration: DateTimePickerConfiguration) -> String {
6567
if configuration.selectedDate != Date(timeIntervalSince1970: 0.0) {
68+
if let dateFormatter = configuration.dateFormatter {
69+
return dateFormatter.string(from: configuration.selectedDate)
70+
}
71+
6672
let formattedDate = configuration.selectedDate.formatted(date: configuration.dateStyle, time: .omitted)
6773
let formattedTime = configuration.selectedDate.formatted(date: .omitted, time: configuration.timeStyle)
6874
if configuration.pickerComponents == .date {

Sources/FioriSwiftUICore/_generated/StyleableComponents/DateTimePicker/DateTimePicker.generated.swift

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,26 @@ import SwiftUI
77
///
88
/// ## Usage
99
/// ```swift
10-
/// @State var selection: Date = .init(timeIntervalSince1970: 0.0)
10+
/// @State var customizedDate: Date = .init(timeIntervalSince1970: 0.0)
1111
/// @State var isRequired = false
1212
/// @State var showsErrorMessage = false
13+
/// @State var customizedPickerVisible = false
14+
/// let customizedDateFormatter: DateFormatter = {
15+
/// let formatter = DateFormatter()
16+
/// formatter.dateFormat = "MM/dd/yyyy HH:mm:ss"
17+
/// return formatter
18+
/// }()
19+
/// func mandatoryFieldIndicator() -> TextOrIcon {
20+
/// var indicator = AttributedString("*")
21+
/// indicator.font = .fiori(forTextStyle: .title3)
22+
/// indicator.foregroundColor = Color.preferredColor(.indigo7)
23+
/// return .text(indicator)
24+
/// }
25+
/// @State var customizedPickerVisible = false
1326
///
14-
/// DateTimePicker(title: "Default", isRequired: self.isRequired, selectedDate: self.$selection)
15-
/// .informationView(isPresented: self.$showsErrorMessage, description: AttributedString("The Date should be before December."))
16-
/// .informationViewStyle(.informational)
27+
/// DateTimePicker(title: "Customized Date Formatter, locale and calendar", mandatoryFieldIndicator: self.mandatoryFieldIndicator(), isRequired: self.isRequired, selectedDate: self.$customizedDate, dateFormatter: self.customizedDateFormatter, pickerVisible: self.$customizedPickerVisible)
28+
/// .environment(\.locale, Locale(identifier: "zh-Hans"))
29+
/// .environment(\.calendar, Calendar(identifier: .gregorian))
1730
/// ```
1831
public struct DateTimePicker {
1932
let title: any View
@@ -24,6 +37,8 @@ public struct DateTimePicker {
2437
let errorMessage: AttributedString?
2538
let range: ClosedRange<Date>?
2639
@Binding var selectedDate: Date
40+
/// The `DateFormatter` to be used to display the selected `Date`. Default formatter will use customized dateStyle and timeStyle.
41+
let dateFormatter: DateFormatter?
2742
/// The components shown in the date picker, default value shows date and time.
2843
let pickerComponents: DatePicker.Components
2944
/// The custom style for displaying the date. The default value is `.abbreviated`, showing for example, "Oct 21, 2015".
@@ -47,6 +62,7 @@ public struct DateTimePicker {
4762
errorMessage: AttributedString? = nil,
4863
range: ClosedRange<Date>? = nil,
4964
selectedDate: Binding<Date>,
65+
dateFormatter: DateFormatter? = nil,
5066
pickerComponents: DatePicker.Components = [.date, .hourAndMinute],
5167
dateStyle: Date.FormatStyle.DateStyle = .abbreviated,
5268
timeStyle: Date.FormatStyle.TimeStyle = .shortened,
@@ -60,6 +76,7 @@ public struct DateTimePicker {
6076
self.errorMessage = errorMessage
6177
self.range = range
6278
self._selectedDate = selectedDate
79+
self.dateFormatter = dateFormatter
6380
self.pickerComponents = pickerComponents
6481
self.dateStyle = dateStyle
6582
self.timeStyle = timeStyle
@@ -82,6 +99,7 @@ public extension DateTimePicker {
8299
errorMessage: AttributedString? = nil,
83100
range: ClosedRange<Date>? = nil,
84101
selectedDate: Binding<Date>,
102+
dateFormatter: DateFormatter? = nil,
85103
pickerComponents: DatePicker.Components = [.date, .hourAndMinute],
86104
dateStyle: Date.FormatStyle.DateStyle = .abbreviated,
87105
timeStyle: Date.FormatStyle.TimeStyle = .shortened,
@@ -90,7 +108,7 @@ public extension DateTimePicker {
90108
{
91109
self.init(title: {
92110
TextWithMandatoryFieldIndicator(text: title, isRequired: isRequired, mandatoryFieldIndicator: mandatoryFieldIndicator)
93-
}, valueLabel: { OptionalText(valueLabel) }, controlState: controlState, errorMessage: errorMessage, range: range, selectedDate: selectedDate, pickerComponents: pickerComponents, dateStyle: dateStyle, timeStyle: timeStyle, noDateSelectedString: noDateSelectedString, pickerVisible: pickerVisible)
111+
}, valueLabel: { OptionalText(valueLabel) }, controlState: controlState, errorMessage: errorMessage, range: range, selectedDate: selectedDate, dateFormatter: dateFormatter, pickerComponents: pickerComponents, dateStyle: dateStyle, timeStyle: timeStyle, noDateSelectedString: noDateSelectedString, pickerVisible: pickerVisible)
94112
}
95113
}
96114

@@ -106,6 +124,7 @@ public extension DateTimePicker {
106124
self.errorMessage = configuration.errorMessage
107125
self.range = configuration.range
108126
self._selectedDate = configuration.$selectedDate
127+
self.dateFormatter = configuration.dateFormatter
109128
self.pickerComponents = configuration.pickerComponents
110129
self.dateStyle = configuration.dateStyle
111130
self.timeStyle = configuration.timeStyle
@@ -121,7 +140,7 @@ extension DateTimePicker: View {
121140
if self._shouldApplyDefaultStyle {
122141
self.defaultStyle()
123142
} else {
124-
self.style.resolve(configuration: .init(componentIdentifier: self.componentIdentifier, title: .init(self.title), valueLabel: .init(self.valueLabel), controlState: self.controlState, errorMessage: self.errorMessage, range: self.range, selectedDate: self.$selectedDate, pickerComponents: self.pickerComponents, dateStyle: self.dateStyle, timeStyle: self.timeStyle, noDateSelectedString: self.noDateSelectedString, pickerVisible: self.$pickerVisible)).typeErased
143+
self.style.resolve(configuration: .init(componentIdentifier: self.componentIdentifier, title: .init(self.title), valueLabel: .init(self.valueLabel), controlState: self.controlState, errorMessage: self.errorMessage, range: self.range, selectedDate: self.$selectedDate, dateFormatter: self.dateFormatter, pickerComponents: self.pickerComponents, dateStyle: self.dateStyle, timeStyle: self.timeStyle, noDateSelectedString: self.noDateSelectedString, pickerVisible: self.$pickerVisible)).typeErased
125144
.transformEnvironment(\.dateTimePickerStyleStack) { stack in
126145
if !stack.isEmpty {
127146
stack.removeLast()
@@ -139,7 +158,7 @@ private extension DateTimePicker {
139158
}
140159

141160
func defaultStyle() -> some View {
142-
DateTimePicker(.init(componentIdentifier: self.componentIdentifier, title: .init(self.title), valueLabel: .init(self.valueLabel), controlState: self.controlState, errorMessage: self.errorMessage, range: self.range, selectedDate: self.$selectedDate, pickerComponents: self.pickerComponents, dateStyle: self.dateStyle, timeStyle: self.timeStyle, noDateSelectedString: self.noDateSelectedString, pickerVisible: self.$pickerVisible))
161+
DateTimePicker(.init(componentIdentifier: self.componentIdentifier, title: .init(self.title), valueLabel: .init(self.valueLabel), controlState: self.controlState, errorMessage: self.errorMessage, range: self.range, selectedDate: self.$selectedDate, dateFormatter: self.dateFormatter, pickerComponents: self.pickerComponents, dateStyle: self.dateStyle, timeStyle: self.timeStyle, noDateSelectedString: self.noDateSelectedString, pickerVisible: self.$pickerVisible))
143162
.shouldApplyDefaultStyle(false)
144163
.dateTimePickerStyle(DateTimePickerFioriStyle.ContentFioriStyle())
145164
.typeErased

Sources/FioriSwiftUICore/_generated/StyleableComponents/DateTimePicker/DateTimePickerStyle.generated.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public struct DateTimePickerConfiguration {
2929
public let errorMessage: AttributedString?
3030
public let range: ClosedRange<Date>?
3131
@Binding public var selectedDate: Date
32+
public let dateFormatter: DateFormatter?
3233
public let pickerComponents: DatePicker.Components
3334
public let dateStyle: Date.FormatStyle.DateStyle
3435
public let timeStyle: Date.FormatStyle.TimeStyle

0 commit comments

Comments
 (0)