Yeah, that makes sense. I just read a stackoverflow post about something similar to this. This isn't a big deal, overall...I can do something other than setting the widget font to the font of the associated widget.
Does this qualify as a "bug" really? Should the value of the widget.font property be returning something which can't be assigned as a valid value to another widget's font property?
Could someone verify my sanity here...I was noticing some widgets in my UI were getting a different non-system font under Pythonista3.3 on iOS13. And when I investigated, it was only widgets who were setting their font to match another widget.
What I'm seeing is that code like this:
label = ui.Label() print label.font() button = ui.Button() print button.font() label.font = button.font print label.font()
will end up with label.font being set to something other than the button's font. It will instead be set to ('TimesNewRomanPSMT', 15.0). In fact, the output is:
Which is decidedly strange. It seems like it actually boils down to that '.SFUI-Regular' font name...it's being turned into 'TimesNewRomanPSMT', and the size comes across appropriately. If I directly assign a specific font, or the special '<system>' value for the font, it works as expected.
Is this some new iOS13 font name that Pythonista doesn't handle properly? Admittedly this is a rare thing to do, setting one widget's font to equal another's...but I was expecting it to work, and it did under Pythonista3.2 and iOS12, and I think it was still working under Pyhtonista3.3 and iOS12.
Sorry for the typos and things...I didn't really test what I posted, just copy pasted stuff from my own project and test code, and modified it to remove extra utilities and things that I wrote which are in other modules.
Thanks to those who fixed those issues, glad it's working.
The ViewHierarchy thing is a utility class I have which holds a bunch of tricks and things for helping with navigating the views in my projects. The actual definition of getUIViewController is:
def getUIViewController(cls, view): import objc_util UIViewController = objc_util.ObjCClass('UIViewController') UIView = objc_util.ObjCClass('UIView') if isinstance(view,ui.View): viewobj = view.objc_instance elif isinstance(view,objc_util.ObjCInstance) and \ view.isKindOfClass_(UIView): viewobj = view elif isinstance(view,objc_util.ObjCInstance) and \ view.isKindOfClass_(UIViewController): viewobj = view viewResponder = viewobj.nextResponder() try: while not viewResponder.isKindOfClass_(UIViewController): viewResponder = viewResponder.nextResponder() except AttributeError: return None return viewResponder
which crawls the responder chain to find the view controller living above the one controlling the passed-in view, or the view controller if one was given. Anyway...looking at what @stephen posted...I think I should actually just be doing that instead.
First, here's a newer version with some improvements which make it work better on iOS 13, and adds a callback which is called when the popover is presented:
def adaptivePresentationStyleForPresentationController_(_self, _cmd, controller): #### force the popover presentation style to stay as-is return -1 # UIModalPresentationNone def popoverDelegateCompletion(delegate): if delegate._popview is not None and delegate._bgcolor is not None: delegate._popview.background_color = delegate._bgcolor if delegate._completion is not None: delegate._completion() delegate._popview = None delegate._presented_block = None def popoverPresentationControllerDidDismissPopover_(_self, _cmd, controller): import objc_util delegate = objc_util.ObjCInstance(_self) popoverDelegateCompletion(delegate) PopoverPresentationDelegate = objc_util.create_objc_class( "PopoverPresentationDelegate", methods=[adaptivePresentationStyleForPresentationController_, popoverPresentationControllerDidDismissPopover_], protocols=["UIPopoverPresentationControllerDelegate"]) def popover(sourceview, popview, presented=None, dismissed=None): """Create and display a popover modal view containing the given content view, with a little arrow pointing at the given source view. 'presented' is called when the popover is first presented. 'dismissed' will be called when the popover is dismissed.""" def _closer(vc, delegate): #### return a function object which will dismiss the popover #### and restore the popview bg color, as well as call #### any completion function def _f(): vc.dismissViewControllerAnimated_completion_(True,None) popoverDelegateCompletion(delegate) return _f #### find the view controller which is presenting the source view #### this is what the popover arrow will point at sourcevc = ViewHierarchy.getUIViewController(sourceview) #### create a new modal view controller and set its presentation #### style to the popover style, and size it to the content UIViewController = objc_util.ObjCClass("UIViewController") vc = UIViewController.new().autorelease() vc.modalPresentationStyle = 7 # UIModalPresentationPopover vc.preferredContentSize = objc_util.CGSize(popview.width, popview.height) #### As of iOS 13+ popover presentation bounds include the little arrow #### as part of the content area. #### Since our popup content is designed to be displayed only in the #### rectangular portion of the popover view, we constrain it to #### the "safe area", which excludes the arrow. safeView = objc_util.UIView.new().autorelease() safeView.addSubview_(popview.objc_instance) safeView.backgroundColor = objc_util.UIColor.clearColor() vc.view = safeView popview.objc_instance.translatesAutoresizingMaskIntoConstraints = False guide = safeView.safeAreaLayoutGuide() anchor = popview.objc_instance.leadingAnchor() anchor.constraintEqualToAnchor_(guide.leadingAnchor()).active = True anchor = popview.objc_instance.trailingAnchor() anchor.constraintEqualToAnchor_(guide.trailingAnchor()).active = True anchor = popview.objc_instance.topAnchor() anchor.constraintEqualToAnchor_(guide.topAnchor()).active = True anchor = popview.objc_instance.bottomAnchor() anchor.constraintEqualToAnchor_(guide.bottomAnchor()).active = True #### retrieve the popover presentation controller for the new modal #### view controller, bail if that fails for some reason popover = vc.popoverPresentationController() if popover is None: return None #### create and assign the popover presentation delegate delegate = PopoverPresentationDelegate.new().autorelease() delegate._completion = dismissed delegate._popview = popview delegate._bgcolor = None popover.delegate = delegate #### if the popview has a translucent background, make the #### presenting view translucent instead, and the popview transparent, #### save the original color to restore on dismissal. color = popview.background_color if color < 1.0: popview.background_color = "clear" delegate._bgcolor = color popover.backgroundColor = objc_util.UIColor.colorWithRed_green_blue_alpha_(*color) popover.sourceView = sourceview.objc_instance popover.sourceRect = objc_util.CGRect(objc_util.CGPoint(0,0),objc_util.CGSize( sourceview.width, sourceview.height)) #### if a presentation function is provided, it is converted to an #### objective-c block to be called when the popover is displayed objc_f = None if presented is not None: objc_f = objc_util.ObjCBlock(presented, None, [objc_util.c_void_p]) delegate._presented_block = objc_f #### present the view controller containing the popover content #### via the popover presentation controller, using the #### popover delegate we created, and return the close #### function to allow caller to close the popover from code sourcevc.presentViewController_animated_completion_(vc, True, objc_f) return _closer(vc, delegate)
it's simple enough to use. When you wish to display a view as a popover, just call like so:
def popover(sender): popview = ui.View() popview.width = 200 popview.height = 100 popview.add_subview(ui.Label(text="Hello popover!", flex="WH") popover(sender, popview) v = ui.View() v.width = 300 v.height = 300 b = ui.Button() b.title = "Show Popover" b.action = showPopover b.frame = (0,0,100,40) b.center = v.center v.add_subview(b) v.present("sheet")
@JonB I try very hard not to swizzle anything...it sounds and feels dirty. I did do it once, I admit, in order to make the tableview have the tableView_willDisplayHeaderView_forSection_ method, so I could customize header display.
I suspect you are right about why it isn't working. Using the string comparison is working well enough.
So I'm noticing a couple things which are related to the way theming and control appearance works in pythonista 3.3 on iOS 13.
First: using the editor.present_themed() or apply_ui_theme() methods with "Default" or None as the theme name is presenting with whatever theme is stored in the ThemeName user preference, and not respecting the ThemeNameLightMode and ThemeNameDarkMode preference value, which stores the actual theme to use.
No big deal, of course, since it just means I have to check with ui.get_ui_style() and set the theme accordingly.
But I'm also noticing that some of the iOS 13 control appearances are not being presented in the appropriate mode. This is most apparent for the ui.SegmentedControl, as it appears to always be presented in the light mode, even when presented with a dark mode theme.
If I dive into objc and change the UISegmentedControl.overrideUserInterfaceStyle property to explicitly match the current mode, it does change the control to have the correct appearance.
So, I'm guessing this means that the actual light/dark mode is not being applied along with the theme?
For now it looks like if I just force the overrideUserInterfaceStyle property on the view presenting my UI, it propagates.
(anyone know why my imgur images aren't displaying? can anyone see the images if they follow the links by clicking the image?)
That is odd. I haven't tried the isKindOfClass_() call with the .ptr...I don't see it called that way elsewhere in places like objc_util or the Gestures module.
What's disturbing is that I'm relying on calling isKindOfClass_() on objc_util.ObjCClass() objects in a lot of places, so now I'm worried it'll fail elsewhere. Those calls usually check against UIView or UIVIewController, so maybe they're safe...and so far I see no issues.
This was the only place that was suddenly broken...and given the other issues in the logic, I'm actually pretty confused how my old code was working at all...and yet it was.
@cvp so strange...there were definitely some issues in those methods which make it so they honestly shouldn't have been working except by luck. But also, it is definitely now the case that the isKindOfClass_() calls no longer work...which is why these were failing in my tests.
So this works:
def findConsoleView(sender): app = objc_util.UIApplication.sharedApplication() mainwindow = app.keyWindow() mainview = mainwindow.rootViewController().view() visit = list(mainview.subviews()) while visit: v = visit.pop() if str(v._get_objc_classname()) == "OMTextView": if v.superview() and not \ str(v.superview()._get_objc_classname()) == "OMTextEditorView": return v visit += list(v.subviews()) return None
but at some point isKindOfClass_() was returning True for me when comparing an actual OMTextView instance against objc_util.ObjCClass("OMTextView").
So not sure when that stopped working. But this updated version works for my purposes.