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.


    Spacing of ButtonItem's

    Pythonista
    ui.buttonitem
    2
    11
    8806
    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.
    • marcus67
      marcus67 last edited by marcus67

      Is there a way to influence the spacing of ButtonItem's in a title row bar? They seem to be pretty far apart. In the iPhone layout of my app I have four (one left and three right) of them and now there's hardly any room left for the title itself. Thanks a lot!

      screenshot

      1 Reply Last reply Reply Quote 1
      • omz
        omz last edited by

        Not really... It's possible to get something similar by using objc_util though. This way, you can create a ButtonItem that uses a custom view instead of an image/title, and you could add multiple buttons with custom spacing/size to that view...

        A Button as a custom view of a ButtonItem doesn't behave completely like a normal ButtonItem with an image though. For example, the touch target of regular ButtonItems is much larger (taps don't need to be as precise).

        Anyway, here's a little demo of what I mean. The spacing is very tight in this example, but it's easy to change.

        import ui
        from objc_util import *
        
        v = ui.View(frame=(0, 0, 400, 400), name='Demo')
        v.background_color = 'white'
        
        btn_images = [ui.Image.named(n) for n in ['iob:beaker_32', 'iob:beer_32', 'iob:coffee_32']]
        btn_container = ui.View(frame=(0, 0, len(btn_images)*32, 44))
        for i, img in enumerate(btn_images):
        	btn = ui.Button(image=img)
        	btn.frame = (i*32, 0, 32, 44)
        	btn_container.add_subview(btn)
        
        btn_item = ui.ButtonItem()
        btn_item_objc = ObjCInstance(btn_item)
        btn_item_objc.customView = ObjCInstance(btn_container)
        v.right_button_items = [btn_item]
        v.present('sheet')
        
        marcus67 1 Reply Last reply Reply Quote 1
        • marcus67
          marcus67 @omz last edited by

          @omz Hi there! Thanks a lot for offering this sample code snippet. I'm currently turning it into a utility class. Unfortunately, there seems to be an issue with the action method of the ButtonItem's: they are never called. I can see the icons being pressed but nothing else happens. Do I need a little more ObjC wizadry for this? Thanks! I appreciate your help!

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

            You'd have to use the action of the individual buttons that are added to the container.

            marcus67 1 Reply Last reply Reply Quote 0
            • marcus67
              marcus67 @omz last edited by

              @omz That's exactly what I'm using. The methods of the individual buttons are not called, at least, not in my setup. See my gist.

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

                The first problem is that you cannot pass an action as a keyword argument to the ui.Button constructor. It's a bit unfortunate that this is silently ignored instead of raising an exception... but you have to assign the action attribute separately.

                Unfortunately, your code will crash after you do so. The reason for this is that the container view (and with it, the buttons) get garbage-collected because there are no references to them anymore after get_condensed_list returns. The underlying (ObjC) views still exist, but the Python objects are gone, which leads to garbage pointers and crashes... In short, you have to keep a reference to the btn_container view somehow. I would suggest that you simply assign it as an attribute of v (something like v.button_container = btn_container). This requires some refactoring of your get_condensed_list method. This should work:

                # coding: utf-8
                # This file is part of https://github.com/marcus67/rechtschreibung
                
                import ui
                from objc_util import *
                
                DEFAULT_X_SPACING = 8
                DEFAULT_HEIGHT = 44
                
                class ButtonItemCondenser (object):
                  
                  def __init__(self, button_item_list, x_spacing=DEFAULT_X_SPACING):
                    
                    self.button_item_list = button_item_list
                    self.x_spacing = x_spacing
                    
                  def get_condensed_list(self):
                    
                    # see https://forum.omz-software.com/topic/2724/spacing-of-buttonitem-s
                    i = 0
                    x = 0
                    btn_container = ui.View(name='test')
                    
                    for button_item in self.button_item_list:
                      btn = ui.Button(image=button_item.image, action=button_item.action)
                      #button_item.action(btn_container)
                      width = button_item.image.size[0]
                      btn.frame = (x, 0, width, DEFAULT_HEIGHT)
                      x = x + width + self.x_spacing
                      btn_container.add_subview(btn)
                      i = i + 1
                      
                    x = x - self.x_spacing
                    btn_container.frame = (0, 0, x , DEFAULT_HEIGHT)
                    btn_item = ui.ButtonItem()
                    btn_item_objc = ObjCInstance(btn_item)
                    btn_item_objc.customView = ObjCInstance(btn_container)
                    return [btn_item]    
                  
                def handle_action(sender):
                  #print "handle_action: sender.name=%s" % sender.name  
                  print str(sender)
                    
                #def handle_action():
                #  print "handle_action"
                  
                def test():
                  
                  icon_names = [ 'iob:beaker_32', 'iob:beer_32', 'iob:bag_32' ]
                  
                  button_item_list = map(lambda name : ui.ButtonItem(image=ui.Image.named(name), action=handle_action), icon_names)
                  condenser = ButtonItemCondenser(button_item_list)
                  
                  v = ui.View(frame=(0, 0, 400, 400), name='Demo')
                  v.background_color = 'white'  
                  condensed_list = condenser.get_condensed_list()
                  normal_item = ui.ButtonItem(image=ui.Image.named('iob:checkmark_32'), action=handle_action)
                  condensed_list.append(normal_item)
                  v.right_button_items = condensed_list
                  v.present('sheet')
                  
                if __name__ == '__main__':
                  test()
                
                marcus67 1 Reply Last reply Reply Quote 1
                • marcus67
                  marcus67 @omz last edited by

                  @omz Hi there! I've incorporated your changes and created references to all otherwise dangling instances. It still crashes, usually when pressing the third button. See here. Any idea?

                  marcus67 1 Reply Last reply Reply Quote 1
                  • marcus67
                    marcus67 @marcus67 last edited by

                    @JonB Sorry for addressing you directly, but you seem to have an abundance of experience in this area. Do you have any idea what could be wrong in my implementation of @omz's ObjC approach (see my gist link below)? Thanks a lot!

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

                      @marcus67 A simple one-line fix would be to add a reference to the condenser object to the view before you present it:

                      # ...
                      v.condenser = condenser
                      v.present('sheet')
                      # ...
                      

                      This way, you can make sure that the objects you reference in condenser don't get garbage-collected as long as the view is on screen...

                      marcus67 1 Reply Last reply Reply Quote 1
                      • marcus67
                        marcus67 @omz last edited by

                        @omz Thanks a lot for your help! The latest change did it for me.

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

                          @omz: That's what the title bar looks like now! :-)

                          corrected title bar

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