omz:forum

    • Register
    • Login
    • Search
    • Recent
    • Popular
    1. Home
    2. mikael

    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.


    • Profile
    • Following 0
    • Followers 20
    • Topics 97
    • Posts 1623
    • Best 353
    • Controversial 0
    • Groups 0

    mikael

    @mikael

    467
    Reputation
    13644
    Profile views
    1623
    Posts
    20
    Followers
    0
    Following
    Joined Last Online
    Website github.com/mikaelho Location Espoo, Finland

    mikael Unfollow Follow

    Best posts made by mikael

    • [Share code] Gestures for Pythonista

      This is a convenience class for enabling gestures in Pythonista ui applications, including built-in views. Main intent here has been to make them Python friendly, hiding all the Objective-C stuff.

      Get it from GitHub.

      For example, do something when user swipes left on a TextView:

      def swipe_handler(view, swipe_start_location):
          print ‘I was swiped, starting from ‘ + str(swipe_start_location)
       
      tv = ui.TextView()
      gestures = Gestures()
      gestures.add_swipe(tv, swipe_handler, direction = Gestures.LEFT)
      

      These gestures and methods are provided:

      *add_tap(view, action, number_of_taps_required, number_of_touches_required)

      • add_long_press(view, action, number_of_taps_required, number_of_touches_required, minimum_press_duration, allowable_movement)
      • add_pan(view, action, minimum_number_of_touches, maximum_number_of_touches, set_translation)
      • add_screen_edge_pan(view, action, edges) (see below for possible edges values)
      • add_pinch(view, action)
      • add_rotation(view, action)
      • add_swipe(view, action, direction, number_of_touches_required) (see below for possible direction values)

      In all cases, only the view and action (event handler function like the swipe_handler in the example) are required. Refer to the UIKit UIGestureRecognizer documentation on usage and default values.

      Corresponding handler signatures are (representative names only, use whatever is convenient for you):

      • tap(view, location) - same for long presses and swipes (where the location is the where the swipe began)
      • pan(view, location, absolute_translation, velocity) - same for pans from the screen edges
      • pinch(view, location, scale, velocity)
      • rotation(view, location, rotation, velocity)

      scale, rotation and velocity are numbers. location and velocity values are ui.Point instances with x and y members.

      Possible screen edge pan edges values are (only one of these): Gestures.EDGE_NONE, Gestures.EDGE_TOP, Gestures.EDGE_LEFT, Gestures.EDGE_BOTTOM, Gestures.EDGE_RIGHT, Gestures.EDGE_ALL

      Possible swipe direction values are (one or a list of these; if you need to know the actual swipe direction, add different directions as separate gestures):Gestures.RIGHT, Gestures.LEFT, Gestures.UP,Gestures.DOWN

      All of the add_x methods return a recognizer object that can be used to remove or disable the gesture as needed:

      • remove(view, recognizer)
      • disable(recognizer)
      • enable(recognizer)

      You can also remove all gestures from a view with remove_all_gestures(view).

      NOTES:

      • To bridge the Objective-C and Pyyhon worlds, all the gestures depend on the Gestures instance used to create them being live, so retain the reference to it or Pythonista will crash when the gesture is detected.
      • Single Gestures instance can be used to add any number of gestures to any number of views.
      • If you need to create a lot of dynamic gestures in a long-running, make sure to explicitly remove them when no longer needed, to avoid a memory leak.
      posted in Pythonista
      mikael
      mikael
    • WKWebView - modern webview for Pythonista

      The underlying component used to implement ui.WebView in Pythonista is UIWebView, which has been deprecated since iOS 8. This module implements a Python webview API using the current iOS-provided view, WKWebView. Besides being Apple-supported, WKWebView brings other benefits such as better Javascript performance and an official communication channel from Javascript to Python. This implementation of a Python API also has the additional benefit of being inheritable.

      Available as a single file on GitHub. Run the file as-is to try out some of the capabilities; check the end of the file for demo code.

      Credits: This would not exist without @JonB and @mithrendal.

      Basic usage

      WKWebView matches ui.WebView API as defined in Pythonista docs. For example:

      v = WKWebView()
      v.present()
      v.load_html('<body>Hello world</body>')
      v.load_url('http://omz-software.com/pythonista/')
      

      For compatibility, there is also the same delegate API that ui.WebView has, with webview_should_start_load etc. methods.

      Mismatches with ui.WebView

      eval_js – synchronous vs. asynchronous JS evaluation

      Apple's WKWebView only provides an async Javascript evaliation function. This is available as an eval_js_async, with an optional callback argument that will called with a single argument containing the result of the JS evaluation (or None).

      Here we also provide a synchronous eval_js method, which essentially waits for the callback behind the scenes before returning the result. For this to work, you have to call the method outside the main UI thread, e.g. from a method decorated with ui.in_background.

      scales_page_to_fit

      UIWebView had such a property, WKWebView does not. It is likely that I will implement an alternative with script injection.

      Additional features and notes

      http allowed

      Looks like Pythonista has the specific plist entry required to allow fetching non-secure http urls.

      Swipe navigation

      There is a new property, swipe_navigation, False by default. If set to True, horizontal swipes navigate backwards and forwards in the browsing history.

      Note that browsing history is only updated for calls to load_url - load_html is ignored (Apple feature that has some security justification).

      Data detection

      By default, no data detectors are active for WKWebView. If there is demand, it is easy to add support for activating e.g. turning phone numbers automatically into links.

      Messages from JS to Python

      WKWebView comes with support for JS to container messages. Use this by subclassing WKWebView and implementing methods that start with on_ and accept one message argument. These methods are then callable from JS with the pithy window.webkit.messageHandler.<name>.postMessage call, where <name> corresponds to whatever you have on the method name after the on_ prefix.

      Here's a minimal yet working example:

      class MagicWebView(WKWebView):
        
        def on_magic(self, message):
          print('WKWebView magic ' + message)
          
      html = '<body><button onclick="window.webkit.messageHandlers.magic.postMessage(\'spell\')">Cast a spell</button></body>'
      
      v = MagicWebView()
      v.load_html(html)
      

      Note that the message argument is always a string. For structured data, you need to use e.g. JSON at both ends.

      User scripts a.k.a. script injection

      WKWebView supports defining JS scripts that will be automatically loaded with every page.

      Use the add_script(js_script, add_to_end=True) method for this.

      Scripts are added to all frames. Removing scripts is currently not implemented.

      Javascript exceptions

      WKWebView uses both user scripts and JS-to-Python messaging to report Javascript errors to Python, where the errors are simply printed out.

      Customize Javascript popups

      Javascript alert, confirm and prompt dialogs are now implemented with simple Pythonista equivalents. If you need something fancier or e.g. internationalization support, subclass WKWebView and re-implement the following methods as needed:

      def _javascript_alert(self, host, message):
        console.alert(host, message, 'OK', hide_cancel_button=True)
        
      def _javascript_confirm(self, host, message):
        try:
          console.alert(host, message, 'OK')
          return True
        except KeyboardInterrupt:
          return False
        
      def _javascript_prompt(self, host, prompt, default_text):
        try:
          return console.input_alert(host, prompt, default_text, 'OK')
        except KeyboardInterrupt:
          return None
      
      posted in Pythonista
      mikael
      mikael
    • RE: Can’t get present full screen to work in the latest beta

      @superrune, yes, it is a bit of a pain.

      The correct full screen argument is fullscreen, not full_sceen. Earlier the default was full screen, so it did not really matter which way you wrote it, but now it seems to be sheet (which I think is a bug).

      But anyway, a quick fix is to remove the underscore.

      posted in Pythonista
      mikael
      mikael
    • [Share code] ReminderStore - not what Apple intended

      Logo

      Link to Github

      From the readme:

      ReminderStore

      Key-value store using iOS Reminders for persistence and distribution across iOS devices.

      Introduction

      ReminderStore is a Pythonista persistence provider. It uses the reminders module to store the values in a specific list in the iOS Reminders app. If you set that list to be distributed across your iOS devices, you get a free cloud-based storage for your app data, suitable for prototyping different distributed use cases.

      API

      Create a store object providing the name of the Reminders list to be created/used:

      store = ReminderStore(namespace, to_json=False, cache=False)

      Use the store object to store, retrieve, list and delete items:

      • Store: store['key'] = 'value'
      • Retrieve: store['key']
      • List: for key in store: print store[key]
      • Delete: del store['key']

      If you want to store structures instead of plain strings, set to_json=True. Store and retrieval operations will then serialize and restore the values to and from JSON.

      Setting cache=True reduces direct access to the Reminders app. Use the refresh_cache() method to refresh the cache and get information on any background changes. See Notes below for more details.

      Notes

      • Key is stored as the title of the reminder, and value in the notes field.
      • Reminder titles are not unique, but ReminderStore uses an intermediate dict to enforce uniqueness. Results are likely to be unproductive if you create duplicate titles manually.
      • Key is used as-is if it is a plain or unicode string. Otherwise str() is called on key before using it.
      • By default, ReminderStore goes to the Reminders app to get the latest information. This is somewhat inefficient since checking for new items always requires loading the full list. There are two cases where you might to prefer to activate the intermediate caching instead:
        • If you have no-one making any updates remotely, cache will be more efficient and will never need manual refreshing.
        • If you want to get more control over when any background changes are applied in your app, use the caching and call refresh_cache to check and process changes. The method returns None if there are no changes, or a dict with added, deleted and changed sets if there are changes. Each set contains the keys of the stored items that were inserted, deleted or had their contents modified, respectively. You can use this info to e.g. remove deleted items from the UI, or inform the user of a conflict and let them decide which version to keep.
      • Note that iOS Reminders app provides no support for complex atomic transactions or referential integrity between items.
      posted in Pythonista
      mikael
      mikael
    • iOS 14 UIMenu for buttons

      A wrapper around the button UIMenu introduced in iOS 14.

      Installation

      pip install ui3
      

      Usage

      Simplest way to set it up is to use a list defining a title and a handler function for each menu item:

      from ui3.menu import set_menu
      
      def handler(sender, action):
          print(action.title)
      
      set_menu(button, [
          ('First', handler),
          ('Second', handler),
          ('Third', handler),
      ])
      

      First menu with 3 simple items

      Handler gets the button as sender, and the selected action.

      By default, the menu is displayed by a single tap, but you can set it to be activated with a long press, which enables you to use the regular button action for something else:

      set_menu(button, [
          ('First', handler),
          ('Second', handler),
          ('Third', handler),
      ], long_press=True)
      
      button.action = something_else
      

      For slightly more complex menus, you can define Actions:

      from ui3.menu import set_menu, Action
      from ui3.sfsymbol import SymbolImage
      
      placeholder = print
      
      set_menu(button, [
          Action(
              'Verbose menu item gets the space it needs', placeholder,
          ),
          Action(
              'Regular Pythonista icon', placeholder,
              image=ui.Image('iob:close_32'),
          ),
      
          Action(
              'SFSymbol', placeholder,
              image=SymbolImage('photo.on.rectangle'),
          ),
          Action(
              'Destructive', placeholder,
              image=SymbolImage('tornado'),
              attributes=Action.DESTRUCTIVE,
          ),
          Action(
              'Disabled', placeholder,
              attributes=Action.DISABLED,
          ),
      ])
      

      More complex menu

      Actions have the following attributes:

      • title
      • handler - function or method
      • image - if you set the destructive attribute, image is tinted system red automatically
      • attributes - summed combination of Action.HIDDEN, DESTRUCTIVE and DISABLED- by default none of these are active
      • state - either Action.REGULAR (default) or SELECTED
      • discoverability_title

      ... and some convenience boolean properties (read/write):

      • selected
      • hidden
      • disabled
      • destructive

      (Note that there is nothing inherently destructive by an action marked as destructive, it's just visuals.)

      Changing the Action's attributes automatically updates the menu that it is included in. See this example that shows both the selection visual and updating a hidden action:

      expert_action = Action(
          "Special expert action",
          print,
          attributes=Action.HIDDEN,
      )
      
      def toggle_handler(sender, action):
          action.selected = not action.selected
          expert_action.hidden = not action.selected
      
      set_menu(button2, [
          ('Expert mode', toggle_handler),
          expert_action,
      ])
      

      Toggling and hiding

      Still something to be thought about is a way to keep the menu visible as actions are selected/hidden/shown/added/removed.

      posted in Pythonista
      mikael
      mikael
    • pip install gestures, WKWebView, UI animations and more

      I finally got around to publishing some of my Pythonista libraries to PyPi, so
      now you can just pip install the following:

      • pythonista-gestures - multi-touch gestures (readme)
      • pythonista-wkwebview - non-obsolete webview with a JS console (readme)
      • pythonista-scripter - easy UI animations (readme)
      • pythonista-multipeer - ad hoc networking between Apple devices (readme)
      • pythonista-anchor - UI layouts with constraints instead of coordinates (readme)
      • pythonista-docgen - generate a README from the docstrings in the code (readme)

      I also took this opportunity to revisit the gestures module, and updated it to be more in line with what I have learned in the intervening years about ObjC/Python interfaces and nicer APIs. This resulted in a not backwards-compatible change, so if you are using the earlier version, you might consider not installing this one.

      Basic use of the new API:

      import gestures
      
      def swipe_handler(data):
          print(f‘I was swiped left, starting from {data.location}')
      
      label = ui.Label()
      gestures.swipe(label, swipe_handler, direction=gestures.LEFT)
      

      Coordinating several gestures is now a bit more straight-forward. For example, if you want to give the swipe gesture priority over a pan:

      panner = pan(view, pan_handler)
      swiper = swipe(view, swipe_handler, direction=RIGHT)
      swiper.before(panner)
      
      posted in Pythonista
      mikael
      mikael
    • RE: New Beta for Pythonista 3.3

      @omz, my sincere thanks to you, as well.

      Pythonista and this incredible community have enabled me to keep up and develop my Python knowledge, squeezed in between a non-coding job and a busy family life.

      I am not exaggerating when I say that that was the primary thing that enabled me to get a new job, starting a new career next month as a full-time senior Python developer.

      posted in Pythonista
      mikael
      mikael
    • Pure Python gestures

      Python gesture implementation on Github for those situations where you cannot or do not want to use the ObjC gestures.

      Simple usage example:

      import pygestures
      
      class MyTouchableView(pygestures.GestureView):
        
        def on_swipe(self, data):
          if data.direction in (data.UP, data.DOWN):
            print('I was swiped vertically')
      

      Run the file as-is to play around with the gestures. (Green circles track your touches, crosshairs show the centroid, red circle reflects pan, pinch and rotation.)

      Demo

      In your subclass, implement any or all the methods below to handle gestures. All methods get an information object with attributes including:

      • state - one of BEGAN, CHANGED, ENDED
      • location - location of the touch, or the centroid of all touches, as a scene.Point
      • no_of_touches - use this if you want to filter for e.g. only taps with 2 fingers

      Methods:

      • on_tap
      • on_long_press
      • on_swipe - data includes direction, one of UP, DOWN, LEFT, RIGHT
      • on_swipe_up, on_swipe_down, on_swipe_left, on_swipe_right
      • on_pan - data includes translation, the distance from the start of the gesture, as a scene.Point. For most purposes this is better than location, as it does not jump around if you add more fingers.
      • on_pinch - data includes scale
      • on_rotate - data includes rotation in degrees, negative for counterclockwise rotation

      There are also prev_translation, prev_scale and prev_rotation, if you need them.

      If it is more convenient to you, you can inherit GestureMixin together with ui.View or some other custom view class. In that case, if you want to use e.g. rotate, you need to make sure you have set multitouch_enabled = True.

      posted in Pythonista
      mikael
      mikael
    • RE: Wish list for next release

      One more thing:

      Could we please remove or have an option to silence the various clicking sounds made by the editor auxiliary keyboard keys?

      Currently they do not respect the system-wide setting, and are really annoying in a quiet environment.

      posted in Pythonista
      mikael
      mikael
    • [Share code] MarkdownView

      MarkdownView

      MarkdownView is a Pythonista UI library component. It is a drop-in replacement for ui.TextView that supports both editing markdown tagged text and viewing it as HTML.

      Demo

      Features

      • Integrated markdown editing and HTML viewing modes - end editing and HTML is shown, click somewhere on the HTML text and markdown editing starts in the same position.
      • Markdown editing supported by additional keys. (Thanks JonB for help on Obj-C.)
      • Implements ui.TextView API for easy integration to existing code.
      • Customizable through attributes or CSS.

      ##Quick start

      Download (from Github) both the MarkdownView.py script and this readme.md file into same directory, and you can run the script in Pythonista to view and try out editing this readme file.

      Import MarkdownView as a module and use wherever you would use a TextView. Markdown text can be set and accessed with the text attribute. Implement a delegate with textview_did_end_editing or textview_did_change method to handle saving the contents - see the end of the MarkdownView.py file for an example.

      posted in Pythonista
      mikael
      mikael

    Latest posts made by mikael

    • RE: Tic Tac Toe - Ui help

      Does this help?

      import console
      
      def restart():
          print("Restarting")
          
      console.alert("Game Over", "Do you want to start a new game?", "OK")
      
      # Cancel throws an interrupt so no need
      # to check the result
      
      restart()
      
      posted in Pythonista
      mikael
      mikael
    • RE: App crashes on a long running button action

      @ZinoSama, if you want to do a lot of little animations, you could take a look at scripter (blows dust off the link). With it, you can have several simultaneous and long-running animations without having to worry about the UI thread.

      An example of a button click handler running a counter to 100:

      @script
      def start(sender):
          label = sender.superview["label1"]
          set_value(label, "text", range(1, 101), lambda count: f"Count: {count}")
      
      posted in Pythonista
      mikael
      mikael
    • RE: PySimpleGUI wrapper for Pythonista...?

      While it would be an intriguing and feasible project to wrap the Pythonista UI components in PySimpleGUI, it would still not be trivial, and I am afraid the results might not meet the needs of most user groups.

      For students, taking a course exercise and trying to run it on Pythonista could very easily fail due to unsupported syntax, given that Pythonista runs the now-unsupported Python 3.6.

      For Pythonista users, given that a lot of the power of Pythonista is in its access to iPhone-native functionality, it is very unlikely that an existing significant piece of code would run anywhere else.

      Thinking of your requirements, if PySimpleGUI really runs reliably on the web, the students with iPads could maybe use repl.it to run the code, sync code with GitHub, and do the editing on Pythonista.

      You probably already have all the materials built around PySimpleGUI. Otherwise I would suggest you to take a look at flet, which has the benefit of running a native or web Flutter app without additional platform-specific wrappers.

      posted in Pythonista
      mikael
      mikael
    • RE: Displaying html text rendered, not as plain text

      @cvp, have been following along again now that the update emails work again. Thank you, or anyone else who might have made that happen.

      posted in Pythonista
      mikael
      mikael
    • RE: Displaying html text rendered, not as plain text

      And if you just need the text directly:

      from bs4 import BeautifulSoup
      
      html = '''
      <!doctype html>
      <html>
          <head>
              <meta charset="utf-8">
              <title>Title</title>
          <style type="text/css">
          /* CSS styles here... */
          </style>
          </head>
          
          <body>
          
          <h1>Hello</h1>
          
          </body>
          
      </html>
      '''
      
      soup = BeautifulSoup(html, "html5lib")
      
      # Strip empty lines and extra white space
      text = '\n'.join(
          line.strip() for line in soup.text.splitlines()
          if line.strip()
      )
      
      print(text)
      

      There’s also a library you could install to get a nicer text representation as markdown, where h1 titles are underlined etc.

      posted in Pythonista
      mikael
      mikael
    • RE: Coords/edge detect 2 colour image

      @cvp, thanks! Have been hacking more on the laptop lately.

      posted in Pythonista
      mikael
      mikael
    • RE: Coords/edge detect 2 colour image

      @rb, if we are talking something like a photo with rectangles, you could also use the iOS Vision framework, see here.

      I have a Python implementation lying around somewhere, let me know if that seems interesting.

      posted in Pythonista
      mikael
      mikael
    • RE: Coords/edge detect 2 colour image

      @rb, are the rectangles exactly "squared" or can they be in any position in the image? Are the rectangles solid colour and the background another colour, or is the background (say) white with (say) black rectangular outlines? Do you know what the colours are or can they be anything?

      posted in Pythonista
      mikael
      mikael
    • RE: Outliner with drag/drop reordering

      @cvp, impressive! Know how hard these seemingly simple things can be.

      posted in Pythonista
      mikael
      mikael
    • RE: Running camera in slow motion mode

      @samsonantony, as one possible building block. here is ObjC code that can be used to save a video clip slowed down without audio:
      https://stackoverflow.com/questions/17296999/how-to-do-slow-motion-video-in-ios

      posted in Pythonista
      mikael
      mikael