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