Skip to content

Conversation

@bbrk24
Copy link
Contributor

@bbrk24 bbrk24 commented Nov 6, 2025

Fixes #233

Each backend has its own shortcomings:

  • UIKitBackend has weird availability constraints on each picker style, doesn't support hourMinuteAndSecond, and doesn't respect font color
    • Furthermore, UIDatePicker doesn't exist on tvOS
  • AppKitBackend doesn't support the wheel picker style
  • WinUIBackend doesn't support hourMinuteAndSecond, doesn't respect font color, and doesn't respect environment.timeZone (instead always using the current time zone)
  • GtkBackend only supports the graphical picker style, doesn't respect environment.timeZone (instead always using UTC), and only supports picking the date, not the time
  • Gtk3Backend is unimplemented as I have no way to test it.

@bbrk24
Copy link
Contributor Author

bbrk24 commented Nov 6, 2025

The thought just occurred to me to observe NSSystemTimeZoneDidChange to properly redraw views that use @Environment(\.timeZone), but I'm unsure how that would work, and regardless I don't see an equivalent for the calendar.

@stackotter
Copy link
Owner

There’s a backend method called setEnvironmentChangeHandler or something like that. That’s where we currently observe system theme changes and would be where timezone observation should live as well.

@bbrk24
Copy link
Contributor Author

bbrk24 commented Nov 7, 2025

Right, I'll go see if I can get that to work in UIKitBackend and AppKitBackend first, and then maybe the other backends. I don't think it'll necessarily work on Linux though

@bbrk24 bbrk24 marked this pull request as draft November 7, 2025 18:53
@bbrk24
Copy link
Contributor Author

bbrk24 commented Nov 7, 2025

Marking this as draft because UIDatePicker (at least on iOS 17) completely misbehaves if the user changes the time zone in settings

@bbrk24 bbrk24 marked this pull request as ready for review November 8, 2025 00:34
Copy link
Owner

@stackotter stackotter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this super detailed PR! Safari was lagging the entire time that I was reviewing your changes lol. I've left a bunch of requests and comments from an initial read through of all of the code.

I haven't had time to test out the changes myself yet, but I'll make sure to do that before merging and will update the PR with any additional feedback I have after testing things out.


var body: some Scene {
WindowGroup("Date Picker") {
VStack {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add #hotReloadable { ... } around this VStack

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added

Comment on lines 18 to 31
allStyles = [.automatic]

if #available(iOS 14, macCatalyst 14, *) {
allStyles.append(.graphical)
}

#if !canImport(GtkBackend)
if #available(iOS 13.4, macCatalyst 13.4, *) {
allStyles.append(.compact)
#if os(iOS) || os(visionOS) || canImport(WinUIBackend)
allStyles.append(.wheel)
#endif
}
#endif
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that the differences in platform support for each style are kinda complicated, it could be a good idea to add a supportedDataPickerStyles property to the environment so that devs can implement runtime compatibility checks a bit more easily than this. The environment variable should be a get-only property with a getter that references some internal property that SwiftCrossUI can write to. Then when creating the default environment values, SwiftCrossUI should query the backend for its list of supported date picker styles (via a new supportedDatePickerStyles property of some sort).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense to me, will implement

)
.datePickerStyle(style ?? .automatic)

Button("Reset date") {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Reset date to now"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough, this button was a last-minute addition when I got lost using calendars and time zones

public var timeZone: TimeZone

#if !os(tvOS)
public var datePickerStyle: DatePickerStyle
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Document this property.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added documentation

// choice for the current calendar means the cursor position is reset after every keystroke. I
// know of no simple way to tell whether NSDatePicker requires or forbids eras for a given
// calendar, so in lieu of that I have hardcoded the calendar identifiers.
private let calendarsWithEras: Set<Calendar.Identifier> = [
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to this thread, the Gregorian calendar also has eras: https://www.reddit.com/r/iOSProgramming/comments/7brq81/what_is_the_importance_of_the_calendar_component/

It could make more sense to name this property to calendarsRequiringEra or something?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that's why I don't just check the length of the eras property on the calendar object. Gregorian has two (AD/BC or CE/BCE depending on your preference), and some of the ones that require it only have one. The suggested rename makes sense to me.

/// - label: The view to be shown next to the date input.
public nonisolated init(
selection: Binding<Date>,
range: ClosedRange<Date> = Date.distantPast...Date.distantFuture,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Label this parameter in to match SwiftUI. (same with the alternative init below)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Forgot to say anything but I already did this

Comment on lines +130 to +143
#if os(tvOS)
preconditionFailure()
#else
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With my comment about making updateDatePicker available on tvOS, I think this conditional compilation should be made redundant.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, already done

var environment = defaultEnvironment

environment.toggleStyle = .switch
environment.timeZone = .current
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like AppKitBackend would also prefer that timeZone is set to .current. Should AppKitBackend be doing that? Or maybe should EnvironmentValues be doing that? I can see reasons for and against;

  • If the default was current, then backends that don't implement timezone change observation (such as GtkBackend) would get out of sync
  • If the default was autoupdatingCurrent, then whenever you accessed the environment property you would technically get the current timezone, but it wouldn't capture the current timezone in the way that you'd expect cause the value's meaning could change out from under you.

Now that I think about it, having it be autoupdatingCurrent could cause issues further down the line when we do more surgical propagation of environment changes. From SwiftCrossUI's perspective, the old timezone and the new one would always seem to be identical.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The entire extent of my logic was that I booted up a Swift REPL and ran

import SwiftUI
EnvironmentValues().timeZone == .autoupdatingCurrent

And seeing that that's true (and knowing .autoupdatingCurrent != .current), I put that as the default in SwiftCrossUI.


customDatePicker.toggleTimeView(shown: components.contains(.hourAndMinute))

if environment.timeZone != .autoupdatingCurrent {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would have to be updated to current if the environment timezone default value gets changed to .current (as discussed in one of my comments on UIKitBackend.

#if compiler(>=6.2)
case .vietnamese: "VietnameseLunarCalendar"
#endif
case let id: fatalError("Unsupported calendar identifier \(id)")
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like this fatalError would be relatively easy to come across when trying to make an app support many locales and calendars. It'd be annoying to find out by getting a bug report from someone softlocked out of using the app because of their locale, so I think it'd be best to print a warning here and fallback on gregorian. That way people can at least use the app even if they try to intentionally (or unintentionally via locale) use an unsupported calendar.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense to me.

Add DatePicker for AppKitBackend

Add DatePickerExample to macOS CI

Fix DatePicker update logic for AppKitBackend

Update argument name to match SwiftUI

Add more availability annotations

Shut up tvOS let me see if the iOS CI will pass

please work.

Fine, here's your view

Initial WinUI implementation

Reformat WinUI code

Implement minYear/maxYear for DatePicker

Improve WinUI sizing code

Fix CalendarDatePicker size

Minor cleanup

Generate GTK classes and improve manual type conversion

oops

Fix casing of calendar name

Saving partial work on GtkBackend

More partial work

Use Gtk.Calendar

Add missing parts to GtkBackend.updateDatePicker

Add DatePickerExample to Linux CI

Fix one Mac availability error

Add availability annotation on unused widget

Add time zone listener for UIKitBackend

Add listener for AppKitBackend
Update some doc comments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Date Picker

2 participants