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.
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 evaluationApple's WKWebView only provides an async Javascript evaliation function. This is available as an
eval_js_async
, with an optionalcallback
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 withui.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 pithywindow.webkit.messageHandler.<name>.postMessage
call, where<name>
corresponds to whatever you have on the method name after theon_
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
-
Some updates:
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.
Following two convenience methods are also available:
add_style(css)
to add a style tag containing the given CSS style definition.add_meta(name, content)
to add a meta tag with the given name and content.
Making a web page behave more like an app
These methods set various style and meta tags to disable typical web interaction modes:
disable_zoom
disable_user_selection
disable_font_resizing
disable_scrolling
(alias for settingscroll_enabled
to False)
There is also a convenience method,
disable_all
, which calls all of the above. -
Javascript debugging
Javascript errors and console messages are sent to Python side and printed to Pythonista console. Supported JS console methods are
log
,info
,warn
anderror
.For further JS debugging and experimentation, there is a simple convenience command-line utility that can be used to evaluate load URLs and evaluate javascript. If you
present
your app as a 'panel', you can easily switch between the tabs for your web page and this console.Or you can just create a WKWebView manually for quick experimentation, like in the usage example below.
>>> from wkwebview import * >>> v = WKWebView(name='Demo') >>> WKWebView.console() Welcome to WKWebView console. Evaluate javascript in any active WKWebView. Special commands: list, switch #, load <url>, quit js> list 0 - Demo - js> load http://omz-software.com/pythonista/ js> list 0 - Demo - Pythonista for iOS js> document.title Pythonista for iOS js> quit >>> v2 = WKWebView(name='Other view') >>> WKWebView.console() Welcome to WKWebView console. Evaluate javascript in any active WKWebView. Special commands: list, switch #, load <url>, quit js> list 0 - Demo - Pythonista for iOS 1 - Other view - js> switch 1 js> load https://www.python.org js> document.title Welcome to Python.org js> window.doesNotExist.wrongFunction() ERROR: TypeError: undefined is not an object (evaluating 'window.doesNotExist.wrongFunction') (https://www.python.org/, line: 1, column: 20) None js> quit
-
@mikael I like that .... ;-)
-
@mithrendal, well, the credit for the whole console idea belongs to you. I looked at your code and experimented with keyboard auxiliary view for entering the javascript code and so on and so forth, but in the end I decided on this option because it adds very few lines of code to the package.
The one thing I would still like to be able to do, though, is add the lines entered in the ”js console” to the Pythonista console history, so that the history buttons would work naturally. @omz, is there anything exposed that could be used for this?
-
not sure if this still works. but the basic approach does:
cvc=UIApplication.sharedApplication().\ keyWindow().rootViewController().\ accessoryViewController().\ consoleViewController cvc.history()
gives you access to console history, which is just an NSArray of NSStrings(newline terminated)
.cvc.history=[ns('hello\n')] + list(cvc.history())
or maybe with a little less copying, though not sure if you are always guaranteed a mutable array like this:
cvc.history().insertObject_atIndex_(ns('hi\n'),0)
(seems to work, but safer might be to call mutableCopy, then do the insert, and assign back)
-
@JonB, sweet! Works perfectly & committed.
-
Moved the code to a dedicated repository and split the documentation into a README.
Couple of small functionality updates:
Cache and timeouts
For remote (non-file)
load_url
requests, there are two additional options:- Set
no_cache
toTrue
to skip the local cache, default isFalse
- Set
timeout
to a specific timeout value, default is 10 (seconds)
Setting a custom user agent
WKWebView has a
user_agent
property that can be used to retrieve or set a value reported to the server when requesting pages. - Set
-
@mikael thanks for this great module. Just wanted to mention that the only way I was able to get pydrive to work with google/oauth authentication flow was by using WKWebView and setting the user agent.
-
Couple of additions, thanks @Brun0oO.
Cache and timeouts
For remote (non-file)
load_url
requests, there are two additional options:- Set
no_cache
toTrue
to skip the local cache, default isFalse
- Set
timeout
to a specific timeout value, default is 10 (seconds)
You can also explicitly clear all data types from the default data store with the
clear_cache
instance method. The method takes an optional parameter, a plain function that will be called when the async cache clearing operation is finished:def cleared(): print('Cache cleared') WKWebView().clear_cache(cleared)
Media playback
Following media playback options are available as WKWebView constructor parameters:
inline_media
- whether HTML5 videos play inline or use the native full-screen controller. The default value for iPhone is False and the default value for iPad is True.airplay_media
- whether AirPlay is allowed. The default value is True.pip_media
- whether HTML5 videos can play picture-in-picture. The default value is True.
- Set
-
This post is deleted!