omz:forum

    • Register
    • Login
    • Search
    • Recent
    • Popular

    Welcome!

    This is the community forum for my apps Pythonista and Editorial.

    For individual support questions, you can also send an email. If you have a very short question or just want to say hello — I'm @olemoritz on Twitter.


    Popover on iPhone?

    Pythonista
    5
    15
    6208
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • mikael
      mikael @Ichicoro last edited by

      @Ichicoro, maybe check @JonB’s code for Overlay?

      1 Reply Last reply Reply Quote 0
      • shinyformica
        shinyformica last edited by shinyformica

        @Ichicoro funnily enough I had to implement this recently. It requires some fiddling with objc view controller stuff, since you have to force the behavior to show as a popover instead of fullscreen view on the iPhone:

        import objc_util
        
        def getUIViewController(cls, view):
                import objc_util
                UIViewController = objc_util.ObjCClass('UIViewController')
                UIView = objc_util.ObjCClass('UIView')
                viewobj = view.objc_instance
                viewResponder = viewobj.nextResponder()
                try:
                    while not viewResponder.isKindOfClass_(UIViewController):
                        viewResponder = viewResponder.nextResponder()
                except AttributeError:
                        return None
                return viewResponder
        
        def adaptivePresentationStyleForPresentationController_(_self, _cmd, controller):
            return UIModalPresentationNone # == -1
        
        def popoverPresentationControllerDidDismissPopover_(_self, _cmd, controller):
            import objc_util
            delegate = objc_util.ObjCInstance(_self)
            if delegate._completion is not None:
                delegate._completion()
        
        PopoverPresentationDelegate = objc_util.create_objc_class(
                        "PopoverPresentationDelegate",
                        methods=[adaptivePresentationStyleForPresentationController_,
                                popoverPresentationControllerDidDismissPopover_],
                        protocols=["UIPopoverPresentationControllerDelegate"])
        
        def popover(sourceview, popview, completion=None):
            def _closer(vc):
                def _f():
                    vc.dismissViewControllerAnimated_completion_(True,None)
                return _f
        
            parentvc = getUIViewController(sourceview)
            UIViewController = objc_util.ObjCClass("UIViewController")
            oldvc = getUIViewController(popview)
            if oldvc is not None: oldvc.view = None
            
            vc = UIViewController.new().autorelease()
            popview.flex = "WH"
            vc.view = popview.objc_instance
            vc.modalPresentationStyle = UIModalPresentationPopover # == 7
            vc.preferredContentSize = objc_util.CGSize(popview.width, popview.height)
            popovervc = vc.popoverPresentationController()
            if popovervc is not None:
                delegate = PopoverPresentationDelegate.new().autorelease()
                delegate._completion = completion
                delegate._popview = popview
                popovervc.delegate = delegate
                popovervc.sourceView = sourceview.objc_instance
                popovervc.sourceRect = objc_util.CGRect(objc_util.CGPoint(0,0),objc_util.CGSize(sourceview.width, sourceview.height))
            parentvc.presentViewController_animated_completion_(vc,True,None)
            return _closer(vc)
        
        BapeHiks 1 Reply Last reply Reply Quote 1
        • BapeHiks
          BapeHiks @shinyformica last edited by

          @shinyformica Sorry, maybe a stupid question, but how to use it :)

          1 Reply Last reply Reply Quote 0
          • shinyformica
            shinyformica last edited by

            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")
            
            mikael 1 Reply Last reply Reply Quote 1
            • mikael
              mikael @shinyformica last edited by

              @shinyformica, some typos in the example, should probably be:

              import ui
              
              def showPopover(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("fullscreen")
              

              But what is ViewHierarchy?

              1 Reply Last reply Reply Quote 0
              • BapeHiks
                BapeHiks last edited by

                I haven't managed to make something work, ViewHierarchy is like I understand a pointer in <popover in ios 13, and how to make it all on 12.

                stephen mikael 3 Replies Last reply Reply Quote 0
                • stephen
                  stephen @BapeHiks last edited by

                  @BapeHiks said:

                  I haven't managed to make something work, ViewHierarchy is like I understand a pointer in <popover in ios 13, and how to make it all on 12.

                  Change:

                  sourcevc = sourceview.objc_instance.ViewHierarchy.getUIViewController(sourceview)
                  

                  to:

                  sourcevc = sourceview.objc_instance._findNearestViewController()
                  
                  1 Reply Last reply Reply Quote 0
                  • stephen
                    stephen @BapeHiks last edited by

                    @BapeHiks

                    
                        
                    
                    import ui
                    import objc_util
                    
                    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 = sourceview.objc_instance._findNearestViewController()
                        #### 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)
                    
                    def showPopover(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")
                    
                    1 Reply Last reply Reply Quote 1
                    • mikael
                      mikael @BapeHiks last edited by

                      @BapeHiks, suggest these tweaks to the popover presentation so that it looks nicer:

                      
                          def showPopover(sender):
                              popview = ui.Label(
                                  text="Hello popover!", 
                                  alignment=ui.ALIGN_CENTER,
                              )
                              popview.size_to_fit()
                              popview.frame = popview.frame.inset(-8, -16)
                              popover(sender, popview)
                      
                      
                      1 Reply Last reply Reply Quote 2
                      • BapeHiks
                        BapeHiks last edited by

                        sourcevc = sourceview.objc_instance._findNearestViewController()
                        
                        AttributeError: No method found for selector ":findNearestViewController"
                        

                        if I understand correctly sourcevc should be a view.

                        stephen 1 Reply Last reply Reply Quote 0
                        • stephen
                          stephen @BapeHiks last edited by

                          @BapeHiks said:

                          sourcevc = sourceview.objc_instance._findNearestViewController()
                          
                          AttributeError: No method found for selector ":findNearestViewController"
                          

                          if I understand correctly sourcevc should be a view.

                          vc in sourcevc stands for view controller i also do not get this error.. what are you passing for your sourceview

                          BapeHiks 1 Reply Last reply Reply Quote 0
                          • BapeHiks
                            BapeHiks @stephen last edited by BapeHiks

                            @stephen
                            I used the examples above, generally in the

                            taking the very first post with code managed to
                            changed

                            def adaptivePresentationStyleForPresentationController_(_self, _cmd, controller):
                                return -1 #UIModalPresentationNone # == -1
                            
                            vc.modalPresentationStyle = 7 #UIModalPresentationPopover # == 7
                            

                            img

                            stephen 1 Reply Last reply Reply Quote 0
                            • stephen
                              stephen @BapeHiks last edited by

                              @BapeHiks said:

                              @stephen
                              I used the examples above, generally in the

                              taking the very first post with code managed to
                              changed

                              def adaptivePresentationStyleForPresentationController_(_self, _cmd, controller):
                                  return -1 #UIModalPresentationNone # == -1
                              
                              vc.modalPresentationStyle = 7 #UIModalPresentationPopover # == 7
                              

                              img

                              Outstanding! im glad you got it working!

                              i didnt realize this was for iPhone not iPad until few hours after my post lol

                              1 Reply Last reply Reply Quote 0
                              • shinyformica
                                shinyformica last edited by

                                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.

                                1 Reply Last reply Reply Quote 0
                                • First post
                                  Last post
                                Powered by NodeBB Forums | Contributors