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.


    Using a scroll view to prevent a form being obscured by the keyboard

    Pythonista
    ui.view
    6
    14
    12859
    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.
    • db2
      db2 last edited by

      I've been dabbling with Pythonista on my iPad, and I put together a little regex testing tool (much like the .NET one I made for my Windows dev machine) using the ui module. It's just a single form with some fields on it. The obvious problem is that the touch keyboard pops up and obscures the bottom half of the form. Works fine when I'm running with my bluetooth keyboard on my iPad, not so much on my iPhone.

      I'm pretty sure I need to use a scroll view and dynamically resize either the scroll view or the window itself, but I'm not quite sure how to put all the pieces together for that. I figure I need to subclass View so I can handle keyboard_frame_did_change.

      Anybody have any good sample code for a form that's got a handful of fields on it, and which shrinks/allows scrolling when the keyboard gets in the way?

      Related question: In the UI designer, is it possible to move existing controls into/out of the subview of a scroll view? I don't see any obvious cut/paste operation.

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

        Can you use these techniques to achieve what you want or do you really need editable text fields underneath where the onscreen keyboard will pop up? If your editable fields could be above the keyboard and uneditable results could be underneath the keyboard then when it is time to show the results, you could use the techniques above to hide the keyboard.

        You will probably want code to enable/disable a "Show me" button when the user has entered text.

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

          @ccc said:

          Can you use these techniques to achieve what you want or do you really need editable text fields underneath where the onscreen keyboard will pop up? If your editable fields could be above the keyboard and uneditable results could be underneath the keyboard then when it is time to show the results, you could use the techniques above to hide the keyboard.

          Might be possible in this particular case, but I can see a need for having more fields in the future if I think of other things I want it to do, and having nice, tall text areas is a plus when mucking with multi-line regexes. If I shrink the main input textarea, I could probably keep the other inputs above the keyboard. Of course, keyboard size isn't always the same, depending on what keyboard you're using, whether prediction is on, etc. I'll probably do that for now as a stop-gap, but having the form scrollable would be the ideal solution.

          Here's a couple screenshots of what happens when you try to enter text in the replacement field (the one with '28' in it).

          Screenshots

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

            I think a scroll view might work. The other approach would be to use the animate function to have everything slide up when you began editing.

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

              Here's a very basic example of a custom view that uses keyboard_frame_will_change to adjust the content inset of a scroll view. Scroll all the way down until you see the "Bottom" label. When you then start editing in the text field, the label will be underneath the keyboard, but you can still make it visible by scrolling.

              import ui
              
              class MyView (ui.View):
              	def __init__(self, *args, **kwargs):
              		ui.View.__init__(self, *args, **kwargs)
              		sv = ui.ScrollView()
              		sv.content_size = (2000, 2000)
              		# At a label at the bottom of the scroll view:
              		demo_label = ui.Label(frame=(0, 1980, 200, 20))
              		demo_label.text = 'Bottom'
              		sv.add_subview(demo_label)
              		sv.background_color = 'silver'
              		sv.frame = self.bounds
              		sv.flex = 'wh'
              		self.add_subview(sv)
              		self.scroll_view = sv
              		# Add a text field, so that it's possible to bring up the keyboard:
              		self.add_subview(ui.TextField(frame=(0, 10, 200, 32)))
              	
              	def keyboard_frame_will_change(self, frame):
              		# NOTE: This only works correctly for full-screen presentation.
              		insets = (0, 0, frame[3], 0)
              		self.scroll_view.content_inset = insets
              		self.scroll_view.scroll_indicator_insets = insets
              
              v = MyView()
              v.present()
              
              1 Reply Last reply Reply Quote 1
              • Phuket2
                Phuket2 last edited by

                I did my crude example

                # coding: utf-8
                
                import ui
                
                class TestClass(ui.View):
                	def __init__(self):
                		#self.frame=(0,0,540,576)
                		self.sv = ui.ScrollView(flex = 'WH')
                		self.add_subview(self.sv)
                		
                		self.tf = ui.TextField()
                		self.tf.delegate = self
                		
                		self.sv.add_subview(self.tf)
                		
                	def layout(self):
                		self.tf.frame = (0, self.sv.height - 44, 300, 44)
                
                	def textfield_should_begin_editing(self, textfield):
                		self.sv.content_offset = (0, textfield.y)
                		return True
                		
                if __name__ == '__main__':
                	tc = TestClass()
                	tc.present()
                
                1 Reply Last reply Reply Quote 0
                • db2
                  db2 last edited by

                  Awesome, thanks for the samples. That should give me a push in the right direction. I'll do some more experimenting and see where I can go from there. I'm presenting the form as "sheet" so it shows up as a nice popup window on the iPad, so I'm guessing I can just change the window dimensions on the fly and let the scroll view deal with its larger content area.

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

                    You can't really change the window dimensions for sheets on the fly, but if you make the sheet small enough, the keyboard shouldn't be a problem on the iPad.

                    The sample code I've posted above only works for full-screen views because it just uses the height of the keyboard as the inset. If the view isn't full-screen, that doesn't work correctly because only a part of the keyboard would actually cover the view....

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

                      Lol, of course @omz knows better than me. But I will still try :)
                      Enter a field for editing, it will go to the top, when you are out it will restore the scroll value.

                      # coding: utf-8
                      
                      import ui
                      
                      class TestClass(ui.View):
                      	def __init__(self):
                      		self.old_scroll = 0
                      		self.frame=(0,0,540,576)
                      		self.sv = ui.ScrollView(flex = 'WH')
                      		self.add_subview(self.sv)
                      		
                      		self.tf = ui.TextField()
                      		self.tf.delegate = self
                      		
                      		self.sv.add_subview(self.tf)
                      		
                      	def layout(self):
                      		self.tf.frame = (0, self.sv.height - 44, 300, 44)
                      
                      	def textfield_should_begin_editing(self, textfield):
                      		self.old_scroll = self.sv.content_offset[1]
                      		self.sv.content_offset = (0, textfield.y)
                      		return True
                      	
                      	def textfield_did_end_editing(self, textfield):
                      		self.sv.content_offset = (0, self.old_scroll)
                      		
                      if __name__ == '__main__':
                      	tc = TestClass()
                      	tc.present('')
                      
                      1 Reply Last reply Reply Quote 0
                      • Phuket2
                        Phuket2 @db2 last edited by

                        @db2, but really don't listen to me. I really know nothing. I had written my reply before I seen omz had replied to you. I spent some time on it so I posted it anyway. But I like to help if I can. Does not mean I will be helpful in reality.

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

                          This seems kind of on the right track, but I'm obviously doing some things wrong with the sizing. Functionally, it's behaving more or less how I want, but the scroll distances aren't being calculated properly.

                          import ui
                          
                          #Get current screen size, taking orientation into account.
                          def screen_size():
                          	size = ui.get_screen_size()
                          	if int(ui.WebView().eval_js('window.orientation')) % 180 != 0:
                          		return (size[1], size[0])
                          	return size
                          
                          class DynamicScrollViewTest (ui.View):
                          	def __init__(self):
                          		self.frame = (0, 0, 320, 504)
                          		sv = self.scroll_view = ui.ScrollView()
                          		self.add_subview(sv)
                          		sv.frame = self.bounds
                          		print "self.frame: " + str(self.frame)
                          		print "scroll_view.frame: " + str(sv.frame)
                          		sv.content_size = (320, 504)
                          		sv.flex = "wh"
                          		sv.background_color = "silver"
                          		self.fields = []
                          		for i in range(13):
                          			field = ui.TextField(frame=(6, 10 + 42 * i, 308, 32))
                          			field.placeholder = "Field " + str(i + 1)
                          			field.flex = "w"
                          			sv.add_subview(field)
                          			self.fields.append(field)
                          		
                          	def keyboard_frame_will_change(self, frame):
                          		print("Keyboard frame: " + str(frame))
                          		print("Window frame: " + str(main_form.frame))
                          		screen = screen_size()
                          		print("Screen size: {} x {}".format(screen[0], screen[1]))
                          		y_overlap = max([main_form.frame[1] + main_form.frame[3] + frame[1] - screen[1], 0])
                          		print("Y Overlap: " + str(y_overlap))
                          		self.scroll_view.content_inset = (0, 0, y_overlap, 0)
                          
                          main_form = DynamicScrollViewTest()
                          main_form.present("sheet")
                          
                          1 Reply Last reply Reply Quote 0
                          • JonB
                            JonB last edited by

                            also, keep in mind that the screen size is orientation dependant.

                            you can also use convert_rect to convert betwen screen coords and the scrollview frame. That way, you shouldnt need to worry about screen size or orientation at all.
                            when you present as a panel, or some other types, the top of your view is not the top of the screen.
                            convert_point or convert_rect take care of that for you -- although in 1.5 it was broken for full_screen. Use None for the from view.

                            db2 1 Reply Last reply Reply Quote 0
                            • db2
                              db2 @JonB last edited by

                              @JonB said:

                              also, keep in mind that the screen size is orientation dependant.

                              you can also use convert_rect to convert betwen screen coords and the scrollview frame. That way, you shouldnt need to worry about screen size or orientation at all.
                              when you present as a panel, or some other types, the top of your view is not the top of the screen.
                              convert_point or convert_rect take care of that for you -- although in 1.5 it was broken for full_screen. Use None for the from view.

                              Seems to be working more or less as intended on my iPad, but I'm getting pretty weird numbers (mostly for the y coordinate) out of my iPhone where it runs in full screen. Is there a way to check at run-time how a view is presented (sheet vs. full-screen) so I can either pass the view or None as appropriate when calling convert_rect?

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

                                fullscreen was broken in 1.5. i created a fix as a subclassable RootView, with static convert_point and convert_rect methods which work the way they were supposed to.
                                i only ever tested on ipad, iphone might behave a little differently, but sounds like you had similar issues.

                                https://github.com/jsbain/uicomponents/blob/master/RootView.py

                                I apologize this is poorly commented, but the example should show how to use it. I also found the first time the kb appears, the frame is sometimes wong, so you may need to show/hide/show the kb, see near line 95. Also, if memory serves, the frame passed to the callbacks was broken in fullscreen for some orientations, so you have to use the get_keyboard_frame method in this class.

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