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

      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