• Samer

    From a bit of searching it seems like prefersStatusBarHidden (source) returns true/false if the view controller is requesting the status bar to be hidden. Not sure if this would work, just thought I would through in what I found.

    posted in Pythonista read more
  • Samer

    @stephen There are two problems I encountered with that solution, the first is that when hide_title_bar=True The whole view is shifted down 20px. The second is that on my iPad it still jumps down a few pixels, yet it doesn’t on my iPhone. I wish the solution was that simple however it is unfortunately not.

    posted in Pythonista read more
  • Samer

    I took a few days to think about this bug/feature. I implemented a workaround for it which at the very least hides the problem. I don’t like it, but it works. If anyone has a better solution then overriding the present method, I would be happy to replace my current solution with it.

    posted in Pythonista read more
  • Samer

    @mikael huh, thats very strange I get the same effect using your example on my IPad Pro 11” (2018). It seems like initially the view overlaps with the info bar at the top of the screen (time, wifi, battery, etc) and upon touching the screen the view shifts down to not overlap with it. It’s hard to tell in your example due to the black text on black background. However if you set hide_title_bar to true in the built in example a similar effect happens. I will look into a fix in the next day or two.

    posted in Pythonista read more
  • Samer

    @mikael And with that all the changes are implemented.

    posted in Pythonista read more
  • Samer

    @stephen A bit of both, I have a project in the works that started out as a way to explore the ui module, it then morphed into learning about the objc_utils module a bit (MapView, ProgressView, UIPageControl) and then finally into wanting to create an app for the app store. This is just one part of a larger project. However I have probably had the most fun on this one part then all the others combined.

    posted in Pythonista read more
  • Samer

    I just edited the post with v0.4. Let me know what you people think!

    posted in Pythonista read more
  • Samer

    @mikael Thank you again for this feedback.

    What do you mean by:

    • Exposing a page_changed callback with a delegate parameter for self.

    I have not played around with callbacks very much and would appreciate some guidance on how to implement something like this.

    posted in Pythonista read more
  • Samer

    I have just edited the post with a new version. (v0.3 as i’m calling it).

    posted in Pythonista read more
  • Samer

    I have a quick question about:

    • The color properties are convenient, but return None if the defaults are not changed. I would prefer just to use a "reverse" color function, see below. It would have the additional benefit of removing the extra internal variables.
    def _py_color(self, objc_color):
       return tuple([c.floatValue() for c in objc_color.arrayFromRGBAComponents()])
    

    Specifically what you were referring to with arrayFromRGBAComponents() I wasn’t able to find a reference to it anywhere. Also i’m assuming that by objc_color I would be passing in the UIColor that _objc_color creates.
    Scratch that, Turns out I was passing in the wrong thing.

    Thank you everyone for all the help, I really appreciate it!

    posted in Pythonista read more
  • Samer

    @mikael I really enjoy this type of programming as well, It gives me an external view and makes me aware of certain use cases I was not aware of.

    • Thanks for letting me know of super().add_subview() wish I had known about that earlier :|
    • Could you explain what you mean by a layout method? I’m relatively new to pythonista (not so much python itself) and the UI module.

    posted in Pythonista read more
  • Samer

    After a few week break, I have just updated this post with a new version that implements some of the great feedback I got.

    posted in Pythonista read more
  • Samer

    @mikael Thanks for the feedback! I have a few questions about how I would go about overriding add_subview. How would I add the subview to the view.

    This snippet:

    def add_subview(self, v):
        self.scrollView.add_subview(v)
    

    Gives me the error:
    ValueError: Cannot add self as subview.

    posted in Pythonista read more
  • Samer

    Hello!

    For a project i’m currently working on I needed a UIPageControl view, I thought I would share this here in case anyone else needed it. I apologize for my terrible code style. I am very open to criticism on anything I should change.

    
    import ui
    from objc_util import ObjCClass, CGRect, create_objc_class, ObjCInstance, UIColor
    
    UIPageControl = ObjCClass('UIPageControl')
    
    
    def changePage(_self, _cmd):
        self = ObjCInstance(_self)
        self.page_control.set_page(self.page_control.pageControl.currentPage())
    
    
    ChangePageClass = create_objc_class("ChangePageClass", methods=[changePage])
    
    
    class PageControl(ui.View):
        def __init__(self, **kwargs):
    
            self.scrollView = ui.ScrollView(
                delegate=self,
                paging_enabled=True,
                shows_horizontal_scroll_indicator=False,
                bounces=False,
                frame=self.bounds, flex='WH',
            )
    
            self.pageControl = UIPageControl.alloc().init().autorelease()
            self._target = ChangePageClass.new().autorelease()
            self._target.page_control = self
            self.pageControl.addTarget_action_forControlEvents_(self._target, 'changePage', 1 << 12)  #1<<12 = 4096
            self.pageControl.numberOfPages = len(self.scrollView.subviews)
            self.pageControl.currentPage = 0
            self.pageControl.hidesForSinglePage = True
        
            self._prev_page = 0
    
            super().add_subview(self.scrollView)
            ObjCInstance(self).addSubview_(self.pageControl)
    
            super().__init__(**kwargs)      
        
        def present(self, *args, **kwargs):
            if 'hide_title_bar' in kwargs and kwargs['hide_title_bar']:
                    #Temp work around for possible bug.
                    background = ui.View(background_color=self.background_color)
                    background.present(*args, **kwargs)
                    self.frame = background.bounds
                    background.add_subview(self)
            else:
                super().present(*args, **kwargs)
        
        def layout(self):
            self.scrollView.content_size = (self.scrollView.width * len(self.scrollView.subviews), 0)
            safe_bottom = self.bounds.max_y - self.objc_instance.safeAreaInsets().bottom
            size = self.pageControl.sizeForNumberOfPages_(self.pageControl.numberOfPages())
            self.pageControl.frame = CGRect(
                (self.bounds.center().x - self.bounds.width / 2, safe_bottom - size.height), 
                (self.bounds.width, size.height))
    
            for i, v in enumerate(self.scrollView.subviews):
                v.x = i * self.bounds.width
    
            self.set_page(self.pageControl.currentPage())
    
        def scrollview_did_scroll(self, scrollView):
            pageNumber = round(self.scrollView.content_offset[0] / (self.scrollView.content_size.width/len(self.scrollView.subviews)+1))
            self.pageControl.currentPage = pageNumber
            self._trigger_delegate()
    
        def add_subview(self, page):
            self.pageControl.numberOfPages = len(self.scrollView.subviews) + 1
            page.frame = self.scrollView.bounds
            page.flex = 'WH'
            self.scrollView.add_subview(page)
            self.layout()
    
        def _objc_color(self, color):
            return UIColor.colorWithRed_green_blue_alpha_(*ui.parse_color(color))
    
        def _py_color(self, objc_color):
            return tuple([c.floatValue() for c in objc_color.arrayFromRGBAComponents()]) if objc_color else None
    
        def _trigger_delegate(self):
            try:
                callback = self.delegate.page_changed
            except AttributeError: return
            if self.pageControl.currentPage() is not self._prev_page:
                callback(self, self.pageControl.currentPage())
                self._prev_page = self.pageControl.currentPage()
    
        def set_page(self, page_number):
            if page_number < self.pageControl.numberOfPages() and page_number > -1:
                x = page_number * self.scrollView.width
                self.scrollView.content_offset = (x, 0)
            else:
                raise ValueError("Invalid Page Number. page_number is zero indexing.")
    
        @property
        def page_count(self):
            return self.pageControl.numberOfPages()
    
        @property
        def current_page(self):
            return self.pageControl.currentPage()
    
        @property
        def hide_on_single_page(self):
            return self.pageControl.hidesForSinglePage()
        
        @hide_on_single_page.setter
        def hide_on_single_page(self, val):
            self.pageControl.hidesForSinglePage = val
    
        @property
        def indicator_tint_color(self):
            """Returns un-selected tint color, returns None as default due to .pageIndicatorTintColor() returning that"""
            return self._py_color(self.pageControl.pageIndicatorTintColor())
    
        @indicator_tint_color.setter
        def indicator_tint_color(self, val):
            self.pageControl.pageIndicatorTintColor = self._objc_color(val)
    
        @property
        def indicator_current_color(self):
            """Returns selected tint color, returns None as default due to .currentPageIndicatorTintColor() returning that"""
            return self._py_color(self.pageControl.currentPageIndicatorTintColor())
    
        @indicator_current_color.setter
        def indicator_current_color(self, val):
            self.pageControl.currentPageIndicatorTintColor = self._objc_color(val)
    
    
    if __name__ == '__main__':
        
        class SampleDelegate():
            def __init__(self):
                pass
            
            def page_changed(self, sender, page_number):
                """Gets called every time the page changes."""
                print(f'Sender: {sender}, Delegate: {page_number}')
    
    
                
        pages = PageControl()
    
        sv = ui.View()
        sv.background_color = 'red'
        sv1B = ui.Button()
        sv1B.title = "Go To Page 3 (index 2)"
        sv1B.frame = (100,100,200,100)
        
        def btn_action(sender):
            pages.set_page(2)
        sv1B.action = btn_action
        
        sv.add_subview(sv1B)
    
        sv2 = ui.View()
        sv2.background_color = 'blue'
    
        sv3 = ui.View()
        sv3.background_color = 'yellow'
    
        pages.indicator_tint_color = 'brown'
        pages.indicator_current_color = 'black'
    
        pages.add_subview(sv)
        pages.add_subview(sv2)
        pages.add_subview(sv3)
        
        pages.delegate = SampleDelegate()
    
        print(pages.indicator_tint_color)
        print(pages.indicator_current_color)
        print(pages.page_count)
        print(pages.current_page)
        print(pages.hide_on_single_page)
    
        pages.present('fullscreen')
    

    Gist: https://gist.github.com/samerbam/8062c6075283a2c190812ddc925c015a

    Changelog:

    v0.2:

    • m.scrollView.add_subview(view) -> m.add_subview(view)
    • m.tintColor to set un-selected dot colour
    • m.currentTintColor to set selected dot colour
    • No longer need to manually set x position on each page
      — x and y pos acts as offset from edge of screen (like normal)
    • Moved some objc class creations out of __init__ (still working on this)
    • Mirrored to gist

    v0.3:

    • Finished moving objc class creations out of __init__
    • m.TintColor -> m.tint_color
    • m.currentTintColor -> m.current_tint_color
    • m.current_page returns the current page your on
    • m.page_count returns total amount of pages
    • Switched from ui.get_screen_size() to using self.bounds
      — Should allow for compatibility with more advanced UI’s

    v0.4

    • Fixed bug where tapping on either side of dots to change page would only work on first launch.
    • m.tint_color -> m.indicator_tint_color
    • m.current_tint_color -> m.indicator_current_color
    • Added delegate callback page_changed set delegate to class that includes page_changed(self, page_number)
    • Added set_page method to change the page programmatically
    • Added hide_on_single_page attribute (defaults to true) hides dots with only one page.

    v0.5

    • Changed example slightly to include a weirdly placed button.
    • Added explicit call to self.layout() in add_subview
    • Removed call to _trigger_delegate from set_page, as set_page triggers scrollview_did_scroll which calls _trigger_delegate

    v0.6

    • Switched from hasattr to try: except AttributeError:
    • Implemented a work around for a bug involving the view jumping down ~20 pixels. (Really dislike this, however it seems like the best I could do at the moment)
    • Removed unnecessary attribute pNum
    • Switched from setting width and height of each subview manually to using flex
    • Renamed PageControl instance in example to pages from m

    posted in Pythonista read more
Internal error.

Oops! Looks like something went wrong!