• mcriley821

    I came across a weird difference of colors by passing in an un-normalized RGB tuple to the ui.set_color function. I was wanting just regular green (#00ff00) but instead I got a neon green.

    It happens anytime the green value is greater than 1 in the tuple. It stays the same after 2, so it’s almost as if it’s brightness is mapped from 1-> 2.

    What color space is this? I want to take full advantage of this in the color picker!

    import ui
    class Colors (ui.View):
        def __init__(self, *args, **kwargs):
            return super().__init__(self, *args, **kwargs)
        def draw(self):
            neon = ui.Path.rect(5, 5, 100, 100)
            ui.set_color((0, 2, 0))
            normal = ui.Path.rect(110, 5, 100, 100)
    example = Colors(bg_color="white")

    posted in Pythonista read more
  • mcriley821

    @JonB @cvp
    Thank you for the suggestions! I ended up going the easy way and requiring to pass in the master view. Kept the TableView hidden on the master (and thus un-touchable) until the combobox is touched.

    posted in Pythonista read more
  • mcriley821


    I’m trying to make a custom UI view that allows the user to select an option from a drop down box (like a custom ComboBox).

    Everything seems to be working okay, until I actually try to select a row in the TableView. I know it has to do with the way that iOS does hit-testing for views, but I can’t think of an elegant way to get around this. The only thing I can think of is to customize the root view also (but I want to avoid that so I can simply import it to work with any view ootb).

    Essentially, the root view receives the location of the touch, and it then asks its subviews if they contain that touch in their bounds. The custom view then returns False since the touch isn’t in its bounds, although visually the touch is on the TableView.

    Apologies for any formatting confusion/differences (I’m on iPhone), and for any mistakes/issues I’m not completely finished yet!

    Here’s the code:

    import ui
    """ComboBox is a ui View class that allows a
    string to be chosen from a list of choices by
    tapping on the dropbox button."""
    class ComboBox (ui.View):
        """Initializing of ComboBox:
            Accepts all kwargs of ui.View, as well as:
                font -> Chosen string font
                choices -> List of choice strings
                selected_index -> Initial choice index
                padding -> Internal padding around the label and dropdown button
                            button_tint -> Color of the dropbox button"""
        def __init__(self, *args, **kwargs):
            self.font = kwargs.pop(
                'font', ('<System>', 20))
            self.text_color = kwargs.pop(
                'text_color', "black")
            self.choices = kwargs.pop(
                'choices', [""])
            self.selected_index = kwargs.pop(
                'selected_index', 0)
            self.padding = kwargs.pop(
                'padding', 3)
            self.button_tint = kwargs.pop(
                'button_tint', "grey")
            kwargs.setdefault('border_width', 1.0)
                'border_color', '#e5e5e5')
            kwargs.setdefault('corner_radius', 3.5)
            ui.View.__init__(self, *args, **kwargs)
            # button for the dropbox
            _x = self.width - self._padding - 30
            _h = self.height - self._padding * 2
            self.drop_button = ui.Button(
                frame=(_x, self._padding, 
                             self.height, _h),
            # label for selected item
            # default to item 0
            _w = self.width - self.drop_button.width - self._padding * 2
            self.selected_label = ui.Label(
                frame=(self._padding, self._padding, 
                             _w, _h),
            # dropbox
            _row_h = ui.measure_string(
                self.choices[0], font=self.font)[1]
            _row_h += self._padding
            _h *= 5 if len(self.choices) > 5 else len(self.choices)
            self.dropbox = ui.TableView(
                frame=(self._padding, self.height - 1,
            # draw tableview although out of bounds
            obj = self.objc_instance
            # add subviews
        def selected_index(self):
            return self._selected_index
        def selected_index(self, value):
            if value < len(self.choices):
                self._selected_index = value
                if hasattr(self, 'selected_label'):
                    self.selected_label.text = self.choices[value]
        def selected_text(self):
            return self.choices[self._selected_index]
        def padding(self):
            return self._padding
        def padding(self, value):
            if value < self.height / 2:
                self._padding = value
        def choices(self):
            return self._data_source.items
        def choices(self, value):
            if type(value) is list and len(value) > 0:
                ds = ui.ListDataSource(value)
                ds.delete_enabled = False
                ds.move_enabled = False
                self._data_source = ds
        def layout(self):
            # selected label layout
            self.selected_label.width = self.width - self.height - self._padding * 2
            self.selected_label.height = self.height - self._padding * 2
            # drop button layout
            self.drop_button.x = self.width - self.height
            self.drop_button.width = self.height - self._padding
            self.drop_button.height = self.height - self._padding * 2
            # dropbox layout
            self.dropbox.width = self.selected_label.width
            self.dropbox.y = self.height
            _h = ui.measure_string(
                self.choices[0], font=self.font)[1]
            _h += self._padding
            _h *= 5 if len(self.choices) > 5 else len(self.choices)
            self.dropbox.height = _h    
        def touch_began(self, touch):
            if self._touch_in_frame(touch, self.selected_label.frame) and touch.phase == "began":
                if self.dropbox.hidden:
        def _touch_in_frame(touch, frame):
            x, y, w, h = frame
            xmin, xmax = x, x + w
            ymin, ymax = y, y + h
            x, y = touch.location
            if xmin < x < xmax:
                if ymin < y < ymax:
                    return True
            return False
        def draw(self):
            # draw the splitter border
            p = ui.Path()
                self.selected_label.width + self._padding * 1.5, 0)
                self.selected_label.width + self._padding * 1.5,
            p.line_width = self.border_width
        def dropbox_should_open(self, sender):
            # animate drop box
            if sender:
                sender.action = self.dropbox_should_close
        def do_dropbox(self):
            self.dropbox.hidden = not self.dropbox.hidden
        def dropbox_should_close(self, sender):
            if sender:
                sender.action = self.dropbox_should_open
        def index_changed(self, sender):
            new_index = sender.selected_row
            if new_index != self.selected_index and new_index != -1:
                self.selected_index = new_index
    if __name__ == "__main__":
        root = ui.View(bg_color="white")
        combo = ComboBox(bg_color="white",
                                         choices=['test', 'test2'],
        combo.frame = (50, 50, 275, 40)
        root.present('sheet', hide_title_bar=True)

    posted in Pythonista read more
  • mcriley821

    Hey all,

    I’m modifying the keyboard example “Special Characters.py” to include all Unicode characters. I did this by scraping the Unicode code point range and category name from Wikipedia. It has a navigation view with all the categories in a scroll view, and then presents all the Unicode characters of that category.

    Anyhow, the problem is that I get spotty printability, but since the character is a valid Unicode character I can’t prevent it from displaying as 🠂. I don’t understand the problem since sys.maxunicode (1114111) suggests this shouldn’t be a problem...

    Is it due to the possibility that the iOS system font doesn’t have a rendering of these glyphs? How can I install Google Noto and access these glyphs (if that’s the issue).


    #! python3
    import keyboard
    import ui
    with open('uni_blocks.txt', 'r') as block_file:
        blocks = block_file.readlines()
    class BlocksView (ui.View):
        def __init__(self, *args, **kwargs):
            ui.View.__init__(self, *args, **kwargs)
            self.glyph_sv = None
            self.cat_sv = ui.ScrollView(
            self.buttons = []
            for block in blocks:
                cells = block.split(' ')
                start = cells[0]
                end = cells[1]
                name = ' '.join(cells[2:])
                btn = self.create_button(name)
                btn.action = self.cat_selected
                btn.start = start
                btn.end = end
        def layout(self):
            if self.cat_sv.on_screen:
                bw = ui.get_screen_size()[0]
                x, y = 2, 2
                for button in self.buttons:
                    button.frame = (x, y, bw, 20)
                    y += 24
                self.cat_sv.content_size = (0, (len(self.buttons) + 1) * 24 + 40)
        def cat_selected(self, sender):
            # present another scroll view of unicode titled buttons
            self.glyph_sv = ui.ScrollView(
            w = ui.get_screen_size()[0]
            x = y = 2
            rows = 1
            for i in range(int(sender.start, 16), int(sender.end, 16)):
                char = chr(i)
                if char.isprintable() and not char.isspace():
                    btn = self.create_button(chr(i))
                    btn.action = self.insert_char
                    btn.number = i
                    btn.frame = (x, y, 35, 35)
                    x += 39
                    if x > w - 39:
                        x = 2
                        y += 39
                        rows += 1
            self.glyph_sv.content_size = (w, (rows + 1) * 39 + 40)
            self.navigation_view.push_view(self.glyph_sv, False)
        def create_button(self, name):
            btn = ui.Button(title=name)
            btn.font = ('<System>', 18)
            btn.background_color = (1, 1, 1, 0.1)
            btn.tint_color = 'white'
            btn.corner_radius = 4
            return btn
        def insert_char(self, sender):
            if keyboard.is_keyboard():
                print(sender.title, sender.number)
    def main():
        w = ui.get_screen_size()[0]
        v = BlocksView(
            frame=(0, 0, w, 133),
        nav_view = ui.NavigationView(v)
        nav_view.title_color = 'white'
        if keyboard.is_keyboard():
            keyboard.set_view(nav_view, 'expanded')
            # For debugging in the main app:
            nav_view.bg_color = 'black'
            nav_view.present('fullscreen', hide_title_bar=True)
    if __name__ == '__main__':

    Source .txt:


    posted in Pythonista read more
  • mcriley821

    @mikael @JonB

    I think I figured out a solution that will work. Since the doubletap gesture was recognizing double-taps and gain access to self by inlining the objc method, I realized that’s all I really needed. I can initialize the TextView with editable=False, enable editing inside the objc method, and start editing. Then in the textview’s delegate, when editing is done, disable editing!

    Thank you guys for the help!

    Here’s the code if anyone is interested:

    from objc_util import *
    import ui
    UITap = ObjCClass('UITapGestureRecognizer')
    class Node (ui.View):
        class TextViewDelegate (object):
            def textview_did_change(self, tv):
                    if '\n' == tv.text[-1]:
                        tv.text = tv.text[:-1]
                except IndexError:
            def textview_did_end_editing(self, tv):
                # do whatever with the text
                tv.editable = False
        def __init__(self, *args, **kwargs):
            ui.View.__init__(self, *args, **kwargs)
            self.frame = (0,0,*(ui.get_screen_size()))
            self.tv = ui.TextView(
                frame=(100, 100, 50, 50),
            tvObj = self.tv.objc_instance
            def handleTap_(_slf, _cmd, _gesture):
                self.tv.editable = True
            self.target = create_objc_class(
            self.doubletap = UITap.alloc().initWithTarget_action_(
        def will_close(self):
    if __name__ == '__main__':
        w, h = ui.get_screen_size()
        view = Node(bg_color='white')

    posted in Pythonista read more
  • mcriley821

    @cvp TextField is even odder, having no gestureRecognizers. It probably has a view hierarchy with a TextView. Maybe I need to remove gestures from all underlying views of my TextView?

    posted in Pythonista read more
  • mcriley821

    @JonB I’ve seen @mikael’s repo, but based on personal reasons I’d rather not use it. I want to understand how things are working instead of importing.

    Anyhow, inlining the method did not fix the issue. Making tv a self attribute, and changing the method to:

    def handleTap_(_slf, _cmd, _gesture):

    After the first double tap, a single tap suffices to start editing again. I also retained target (self.target).

    posted in Pythonista read more
  • mcriley821

    I'm doing a project with ui and objc_util (typical). I'm getting this strange behavior when overriding a ui.TextView's gestures; the gesture's view is somehow re-overriding the gestures.
    The simplest code example I can muster is as follows:

    from objc_util import *
    import ui
    UITap = ObjCClass("UITapGestureRecognizer")
    class Node (ui.View):
        class TextViewDelegate (object):
            def textview_did_change(self, tv):
                if '\n' == tv.text[-1]:
                    tv.text = tv.text[:-1]
        def __init__(self, *args, **kwargs):
            ui.View.__init__(self, *args, **kwargs)
            self.frame = (0, 0, *(ui.get_screen_size()))
            tv = ui.TextView(
                frame=(100, 100, 50, 50),
            # this is where the interesting bit starts
            tvObj = tv.objc_instance
            # remove all the gestures of tv
            for gesture in tvObj.gestureRecognizers():
            # for a new gesture, we need a target and action
            # how to make self a target? not really sure
            # maybe making a "blank" target with the method 
            # would be enough? since self carries?
            # I tried making a target of self, but crashes
            target = create_objc_class(
            # now we have a target and action
            # let's make the actual gesture
            doubletap = UITap.alloc().initWithTarget_action_(
            # make into an actual doubletap, and add to view
            # add the tv subview with a single gesture: doubletap
            # can confirm only one gesture by uncommenting below
            #print(self.objc_instance.gestureRecognizers())  # None
            #print(tvObj.gestureRecognizers())  # doubletap
    # Now, without @static_method, everything is fine up until
    # the below function is called -> results in TypeError of passing
    # 4 args to a 3 arg function, since the first arg is self. However,
    # with @static_method, we have to do some round-about trickery
    # to do what we want. 
        def handleTap_(_slf, _cmd, _gesture):
            gesture = ObjCInstance(_gesture)
            view = gesture.view()
    # More interesting stuff happens now. On the first call of handleTap_,
    # the next line prints only doubletap. On next calls, all the gestures 
    # have been reset
    # we can only start editing now by becoming the first responder,
    # since we can't access self
    # I assume here that the call to becomeFirstResponder instantiates
    # a new TextView object somehow, yet this new object still contains
    # a doubletap gesture. Re-tapping the TextView in the UI will start
    # editing - no double tapping needed. 
    if __name__ == "__main__":
        w, h = ui.get_screen_size()
        view = Node(bg_color="white")

    What can I do to make self my target? Or how can I pass self to my method? Can I create a wrapper for the @static_method wrapper and pass self still? I'm stuck on what to do, since any direction I go seems to be a dead-end:

    • make self into a target = crash
    • @static_method = no reference to the true self instance
    • no @static_method = TypeError
    • can't use global variables, since I hope to have ~10 of these Nodes at a time

    Also, I'd prefer to not use a TextField since they have that awful bounding box. I also think this issue would carry to a TextField anyhow.

    Any ideas are greatly appreciated! I'm stumped!

    posted in Pythonista read more
  • mcriley821

    No. You can’t turn off WiFi via Pythonista, unless you use Shortcuts.

    What problems are you having with Shortcuts?

    posted in Pythonista read more
  • mcriley821

    /bin contains:
    bash bunzip2 bzcat bzip2 bzip2recover cat chgrp chmod chown cp date dd df dir echo egrep false fgrep grep gunzip gzexe gzip kill launchctl ln ls mkdir mknod mktemp mv ps pwd readlink rm rmdir sed sh sleep stty su sync tar touch true uname uncompress vdir zcat zcmp zdiff zegrep zfgrep zforce zgrep zless zmore znew

    /sbin contains:
    dmesg dynamic_pager fsck fsck.sbin fsck_apfs fsck_apfs.sbin fsck_exfat fsck_exfat.sbin fsck_hfs fsck_hfs.sbin fsck_msdos fsck_msdos.sbin fstyp fstyp_msdos fstyp_ntfs fstyp_udf halt launchctl launchd launchd.sbin mount mount.sbin mount_apfs mount_apfs.sbin mount_devfs mount_fdesc mount_hfs mount_hfs.sbin mount_nfs newfs_apfs newfs_apfs.sbin newfs_hfs newfs_hfs.sbin pfctl pfctl.sbin quotacheck umount umount.distrib

    /usr/bin contains:
    7z 7za DumpBasebandCrash IOMFB_FDR_Loader PerfPowerServicesExtended [ abmlite appsearch apt apt-cache apt-cdrom apt-config apt-extracttemplates apt-ftparchive apt-get apt-key apt-mark apt-sortpkgs arch asn1Coding asn1Decoding asn1Parser autopoint awk b2sum base32 base64 basename basenc bashbug brctl c_rehash cap_mkdb captoinfo certtool cfversion chcon chflags chfn chown chsh cksum clear cmp comm compress csplit cut cycc cynject db_archive db_checkpoint db_convert db_deadlock db_dump db_hotbackup db_load db_log_verify db_printlog db_recover db_replicate db_stat db_tuner db_upgrade db_verify deviceinfo diff diff3 dircolors dirmngr dirmngr-client dirname doNotify dpkg dpkg-deb dpkg-divert dpkg-maintscript-helper dpkg-query dpkg-split dpkg-statoverride dpkg-trigger dselect du dumpsexp ecidecid env envsubst event_rpcgen.py expand expr factor faker file fileproviderctl find finger fmt fold footprint funzip gawk getconf getopt gettext gettext.sh gettextize getty gnupg2 gnutls-cli gnutls-cli-debug gnutls-serv gpg gpg-agent gpg-connect-agent gpg-error gpg-error-config gpg-wks-server gpgconf gpgparsemail gpgrt-config gpgscm gpgsm gpgtar gpgv groups gssc head hidutil hmac256 hostid hostinfo hpmdiagnose id idn2 infocmp infotocap install iomfsetgamma ip-print ipcrm ipcs join kbdebug kbxutil killall ksba-config last ldid ldrestart libassuan-config libgcrypt-config link locale locate login logname lsdiagnose lsvfs lz4 lz4c lz4cat lzcat lzcmp lzdiff lzegrep lzfgrep lzgrep lzless lzma lzmadec lzmainfo lzmore md5sum mkfifo mktemp mpicalc msgattrib msgcat msgcmp msgcomm msgconv msgen msgexec msgfilter msgfmt msggrep msginit msgmerge msgunfmt msguniq ncurses6-config ncursesw6-config nettle-hash nettle-lfib-stream nettle-pbkdf2 nfsstat ngettext nice nl nohup notificationWatcher nproc npth-config numfmt ocsptool od openURL openssl p11-kit p11tool pager pagesize passwd paste pathchk pax pbcopy pbpaste pinky pkcs1-conv play plistutil plutil powerlogHelperd pr printenv printf psktool ptx quota realpath recode-sr-latin renice reset restart runcon say sbdidlaunch sbreload scp script sdiff seq sexp-conv sftp sha1sum sha224sum sha256sum sha384sum sha512sum shred shuf snappy sort split srptool ssh ssh-add ssh-agent ssh-keygen ssh-keyscan stat stdbuf sum sw_vers sysdiagnose tabs tac tail tailspin tar taskinfo tee test tic time timeout toe toggleTether tput tr truncate trust tset tsort tty uicache uiduid uiopen unexpand uniq unlink unlz4 unlzma unrar unxz unzip unzipsfx update-alternatives updatedb uptime urlclip users vm_stat watchgnupg wc wget which who whoami xargs xgettext xz xzcat xzcmp xzdec xzdiff xzegrep xzfgrep xzgrep xzless xzmore yat2m yes zip zipcloak zipnote zipsplit zprint

    /usr/sbin contains:
    BTAvrcp BTAvrcp.sbin BTLEServer BTLEServer.sbin BTMap BTMap.sbin BTPbap BTPbap.sbin BlueTool BlueTool.sbin WiFiNetworkStoreModel.momd WirelessRadioManagerd WirelessRadioManagerd.sbin absd absd.sbin ac accton addNetworkInterface addNetworkInterface.sbin addgnupghome applecamerad applygnupgdefaults aslmanager aslmanager.sbin bluetoothd bluetoothd.sbin cfprefsd cfprefsd.sbin chown chroot ckksctl dev_mkdb distnoted distnoted.sbin edquota fairplayd.H2 fdisk filecoordinationd filecoordinationd.sbin hdik ioreg ioreg.sbin iostat ipconfig ipconfig.sbin mDNSResponder mDNSResponder.sbin mDNSResponderHelper mDNSResponderHelper.sbin mediaserverd mediaserverd.sbin mkfile nologin notifyd notifyd.sbin nvram nvram.sbin otctl pppd
    pppd.sbin pwd_mkdb quotaon racoon racoon.sbin reboot repquota rtadvd rtadvd.sbin scutil scutil.sbin spindump spindump.sbin sshd startupfiletool sysctl syslogd syslogd.sbin vifs vipw vsdbutil wifid wifid.sbin zdump zic

    Would that be the best way to do background cli commands to StaSh and report the stdout? Is there another way without launching a Web server?

    posted in Pythonista read more
  • mcriley821

    @ccc I can attest that I can navigate the entire file system with StaSh now.

    Is there a way to use StaSh in a headless manner? I want to make a file browser and editor, since similar packages in Cydia are not yet supported for iOS 13

    posted in Pythonista read more
  • mcriley821

    I recently jailbroke my device running iOS 13.5.1 to allow for tethering (data should be free imo). Anyhow, I really love Pythonista and I'm wondering if the jailbreak gives me any extra capabilities now in Pythonista? I'm really curious to start changing the plist file since that's what stunted a lot of my projects. Excited to hear what you guys can think of!

    posted in Pythonista read more
  • mcriley821

    I’ve been doing something similar. Here’s a list of GATT characteristics that helped me. https://www.bluetooth.com/specifications/gatt/characteristics/

    posted in Pythonista read more
  • mcriley821

    @Bumbo-Cactoni iOS is based on Unix so the commands are very similar to what you’d use on Linux. Idk where you could find an actual list of commands though (try googling ‘iOS terminal commands’).

    posted in Pythonista read more
  • mcriley821

    It’s likely that the module has dependencies that you haven’t installed, or that the module isn’t pure python. Both these problems will prevent you from using your module, but only the second one will prevent files from being downloaded to your phone.

    In the first case, find out the dependencies and install them too. In the second case, you’re kinda sol. You could try figuring out what the code does and rewrite it in python.

    More information like the error message and what module you’re trying to pip install would be helpful!

    posted in Pythonista read more
Internal error.

Oops! Looks like something went wrong!