• shinyformica

    @mcriley821 yeah, that's what I felt as well...just unfortunate that we don't have a way to access the font descriptors they want us to use instead of names. Well, no way via normal Pythonista python APIs, I'm sure we could do it via objc.

    posted in Pythonista read more
  • shinyformica

    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?

    posted in Pythonista read more
  • shinyformica

    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:

    ('.SFUI-Regular', 17.0)
    ('.SFUI-Regular', 15.0)
    ('TimesNewRomanPSMT', 15.0)

    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.

    posted in Pythonista read more
  • shinyformica

    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.

    posted in Pythonista read more
  • shinyformica

    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[3] < 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")
    

    posted in Pythonista read more
  • shinyformica

    @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.

    posted in Pythonista read more
  • shinyformica

    @BapeHiks thanks! I didn't know how to do that.

    posted in Pythonista read more
  • shinyformica

    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.

    IMG

    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.

    IMG

    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?)

    posted in Pythonista read more
  • shinyformica

    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.

    posted in Pythonista read more
  • shinyformica

    @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.

    posted in Pythonista read more

Internal error.

Oops! Looks like something went wrong!