Chris Pim 40 Posted Monday at 02:35 PM Hi everyone Does anyone know if it's possible to force the time picker of TTimeEdit to be 12 or 24 hours? I mean the actual picker in iOS itself, not the editor box (which you can manipulate using the "Format" property). By default, the picker matches the time format set in the phone settings, but I have a separate time format setting in my app which determines whether we show am/pm or 24 hour format in the app and I'm trying to make the experience consistent for my users. Thanks Share this post Link to post
Dave Nottage 631 Posted Monday at 09:55 PM 7 hours ago, Chris Pim said: Does anyone know if it's possible to force the time picker of TTimeEdit to be 12 or 24 hours? Since you mention iOS: uses Macapi.ObjCRuntime, Macapi.Helpers, iOSapi.UIKit, iOSapi.Helpers, iOSapi.Foundation; function FindUIDatePicker(const AView: UIView; out ADatePicker: UIDatePicker): Boolean; var I: Integer; LSubView: UIView; begin Result := False; if AView.subviews.count > 0 then begin for I := 0 to AView.subviews.count - 1 do begin LSubView := TUIView.Wrap(AView.subviews.objectAtIndex(I)); if LSubView.isKindOfClass(objc_getClass('UIDatePicker')) then begin Result := True; ADatePicker := TUIDatePicker.Wrap(AView.subviews.objectAtIndex(I)); Break; end else if FindUIDatePicker(LSubView, ADatePicker) then begin Result := True; Break; end; end; end; end; function GetLocale(const AIs24Hour: Boolean): NSLocale; var LIdent: NSString; begin if AIs24Hour then LIdent := StrToNSStr('en_GB') // I say, old chap else LIdent := StrToNSStr('en_US'); // Yeehaw! Result := TNSLocale.Wrap(TNSLocale.OCClass.localeWithLocaleIdentifier(LIdent)); end; procedure TForm1.TimeEdit1OpenPicker(Sender: TObject); var LDatePicker: UIDatePicker; begin if FindUIDatePicker(TiOSHelper.SharedApplication.keyWindow.rootViewController.view, LDatePicker) then LDatePicker.setLocale(GetLocale(True)); // or False, for 12 hour // else it was not found end; FindUIDatePicker iterates the native view hierarchy looking for the UIDatePicker. The alternative would have been to "hack into" FMX.Pickers.iOS. Note: By default, the locale property of UIDatePicker is nil, which means it uses whatever the device setting is (Settings > General > Date & Time). If a locale is assigned, it uses the default time mode for that locale. As long as the default time mode does not change for the two used in the code, it should remain working. Share this post Link to post
Chris Pim 40 Posted Tuesday at 04:43 PM That’s an interesting solution! Thanks Dave, I’ll give that a go for iOS. For Android, the time picker native component supports a Boolean for the time format so I just need to check how I can get access without hacking the sources. Share this post Link to post
Dave Nottage 631 Posted Tuesday at 08:27 PM (edited) 3 hours ago, Chris Pim said: For Android, the time picker native component supports a Boolean for the time format so I just need to check how I can get access without hacking the sources. Sadly, for Android you'd need to "reinvent" the TimePickerFragment class in the Java source (for fmx.jar) located in source\rtl\androiddex\java\fmx\src\com\embarcadero\firemonkey\pickers\defaults\TimePickerFragment.java. In onCreateDialog, it returns a new instance of TimePickerDialog, passing DateFormat.is24HourFormat for the is24HourView flag. This value cannot be changed afterwards, so it'd be pointless to even find the TimePickerDialog instance at runtime (if it were possible), and it won't be possible to change what value is passed without altering the Java source, which means rebuilding fmx.jar (ugh). If it were me, for Android I'd forget TTimeEdit, and implement my own time picker in Java. EDIT: Actually, I've since discovered there is a TimePicker involved, so it might be possible to traverse the view hierarchy as was done for iOS, to find it and call setIs24HourView. Edited Tuesday at 08:42 PM by Dave Nottage Share this post Link to post
Dave Nottage 631 Posted Tuesday at 10:03 PM 1 hour ago, Dave Nottage said: Actually, I've since discovered there is a TimePicker involved, so it might be possible to traverse the view hierarchy as was done for iOS, to find it and call setIs24HourView. I was right, but it took a bit of hoop jumping: uses Androidapi.JNIBridge, Androidapi.JNI.Widget, Androidapi.Helpers, Androidapi.JNI.GraphicsContentViewText, Androidapi.JNI.JavaTypes, Androidapi.JNI.App; function FindTimePickerFragment(out AFragment: JDialogFragment): Boolean; var LFragmentManager: JFragmentManager; I: Integer; LFragment: JFragment; begin LFragmentManager := TAndroidHelper.Activity.getFragmentManager; for I := 0 to LFragmentManager.getFragments.size - 1 do begin LFragment := TJFragment.Wrap(LFragmentManager.getFragments.get(I)); if JStringToString(LFragment.getClass.getName).EndsWith('.TimePickerFragment') then begin AFragment := TJDialogFragment.Wrap(LFragment); Result := True; Break; end; end; end; function FindTimePicker(const AView: JView; out ATimePicker: JTimePicker): Boolean; var I: Integer; LSubView: JView; LViewGroup: JViewGroup; begin Result := False; if TJNIResolver.IsInstanceOf(AView, TJViewGroup.GetClsID) then begin LViewGroup := TJViewGroup.Wrap(AView); for I := 0 to LViewGroup.getChildCount - 1 do begin LSubView := LViewGroup.getChildAt(I); if TJNIResolver.IsInstanceOf(LSubView, TJTimePicker.GetClsID) then begin Result := True; ATimePicker := TJTimePicker.Wrap(LSubView); Break; end else if FindTimePicker(LSubView, ATimePicker) then begin Result := True; Break; end; end; end; end; procedure TForm1.TimeEdit1OpenPicker(Sender: TObject); var LTimePicker: JTimePicker; LFragment: JDialogFragment; begin if FindTimePickerFragment(LFragment) and FindTimePicker(LFragment.getDialog.getWindow.getDecorView, LTimePicker) then LTimePicker.setIs24HourView(TJBoolean.JavaClass.init(True)); // or False end; Share this post Link to post
Chris Pim 40 Posted Wednesday at 06:14 AM Thanks Dave! As always you come up with the best solutions! 1 Share this post Link to post
Chris Pim 40 Posted yesterday at 08:07 AM I've just put your code into practice and it seems to work very well. Thanks again Dave. The only oddity I noticed, is that if the phone (Android) is set to 24 hour format, and I use the above to show my picker in 12 hour format, the AM/PM toggle doesn't appear in the picker dialog, even though the dialog is in 12 hours format. If the phone is set to 12 hour before showing the dialog, then it does show the AM/PM toggle correctly. Weird! I suspect this is a bug in Android as there aren't any APIs to directly impact the AM/PM option so just noting here in case anyone else needs this functionality. Thanks again Dave 1 Share this post Link to post
Dave Nottage 631 Posted 13 hours ago 12 hours ago, Chris Pim said: I suspect this is a bug in Android as there aren't any APIs to directly impact the AM/PM option so just noting here in case anyone else needs this functionality. Sounds like it. Thanks for the heads up! Share this post Link to post