Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BUG IN CLIENT OF UIKIT: UIApplication.openURL(_:) deprecated, migrate to UIApplication.open(_:options:completionHandler:) #953

Open
HeRo002 opened this issue Dec 11, 2024 · 4 comments

Comments

@HeRo002
Copy link

HeRo002 commented Dec 11, 2024

My Python-Kivy apps cannot open a webbrowser on iOS. It works on Ubuntu 20.04 and Android 8.1.0.

A few months ago installing (building) Kivy-ios (on my MacOS 15.0.1) with the "toolchain build python3 kivy" command failed, so I made a copy of the newest version of the kivy-ios repository with the following commands in Terminal:

(venv) henrikroseno@MBPtilhdeHenrik ~ % git clone https://github.com/kivy/kivy-ios
(venv) henrikroseno@MBPtilhdeHenrik ~ % cd kivy-ios
(venv) henrikroseno@MBPtilhdeHenrik kivy-ios % pip3 uninstall kivy-ios
(venv) henrikroseno@MBPtilhdeHenrik kivy-ios % pip3 install -e .
(venv) henrikroseno@MBPtilhdeHenrik kivy-ios % cd ..
(venv) henrikroseno@MBPtilhdeHenrik ~ % toolchain build python3 kivy

Then the build worked, although it took several hours.

Before creating the below example Xcode project, I updated kivy-ios with the following commands in Terminal on MacOS:

henrikroseno@MBPtilhdeHenrik ~ % . venv/bin/activate
(venv) henrikroseno@MBPtilhdeHenrik ~ % cd kivy-ios
(venv) henrikroseno@MBPtilhdeHenrik kivy-ios % git pull origin master
remote: Enumerating objects: 6, done.
remote: Counting objects: 100% (6/6), done.
remote: Compressing objects: 100% (5/5), done.
remote: Total 6 (delta 1), reused 5 (delta 1), pack-reused 0 (from 0)
Unpacking objects: 100% (6/6), 2.92 KiB | 272.00 KiB/s, done.
From https://github.com/kivy/kivy-ios

  • branch master -> FETCH_HEAD
    09ec398..1a8d21a master -> origin/master
    Updating 09ec398..1a8d21a
    Fast-forward
    .github/workflows/pypi-release.yml | 2 +-
    1 file changed, 1 insertion(+), 1 deletion(-)
    (venv) henrikroseno@MBPtilhdeHenrik kivy-ios % pip3 uninstall kivy-ios
    (venv) henrikroseno@MBPtilhdeHenrik kivy-ios % pip3 install -e .
    (venv) henrikroseno@MBPtilhdeHenrik kivy-ios % cd ..
    (venv) henrikroseno@MBPtilhdeHenrik ~ % toolchain build python3 kivy

Now the build finished in seconds, so I am not sure if anything was actually updated(?)

Versions

  • Python : 3.11.6
  • MacOS version : 15.0.1
  • XCode Version : 16.1
  • Cython version : 0.29.36
  • Kivy v. 2.3.0

Describe the bug
I get this error message when the user presses an URL in an RST-document, trying to open a webbrowser to show the webpage, in a Python-Kivy app on an iPhone running iOS 18.1.1:

BUG IN CLIENT OF UIKIT: The caller of UIApplication.openURL(:) needs to migrate to the non-deprecated UIApplication.open(:options:completionHandler:). Force returning false (NO).

To Reproduce
Here is a minimal runnable example 'main.py' that activates the bug when the user presses the URL in the RST-document:

from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.relativelayout import RelativeLayout
from kivy.utils import platform
from kivy.clock import Clock
import webbrowser

if platform == 'ios':
    import ios

from kivy.uix.scrollview import ScrollView
from kivy.properties import ColorProperty
from kivy.uix.rst import RstDocument

class MyScrollView(ScrollView):
    pass

class MyRstDoc(RstDocument):
    pass

Builder.load_string('''
#:set my_font_size sp(15)
#:set large_font_size sp(30)
#:set my_padding dp(10)
#:set my_bar_color [.3, .3, .3, .9]
#:set white 1, 1, 1, 1
#:set black 0, 0, 0, 1

<MyScrollView>:
    do_scroll_x: False
    do_scroll_y: True
    bar_color: my_bar_color
    bar_inactive_color: my_bar_color
    bar_width: 0.6 * my_padding
    canvas.before:
        Color:
            rgba: 0.9, 0.9, 0.9, 1
        Rectangle:
            size: self.size
            pos: self.pos

<RstParagraph>:
    on_ref_press: app.action_ref(args)

<MyRstDoc>:
#    base_font_size: 0.6 * my_font_size
#    base_font_size: 2 * my_font_size
    base_font_size: 30  # THAT made it work!!
    bar_color: my_bar_color
    bar_inactive_color: my_bar_color
    bar_width: 0.6 * my_padding

''')

introscreen_rst = """
Test of calling webbrowser with URL
-------------------------------------

This is a test of opening a link (URL) in a webbrowser. The link is Transformation.dk: 
[color=3333ff][ref=https://www.transformation.dk][u]https://www.transformation.dk[/u][/ref][/color]
It works on my Ubuntu 20.04.
"""


class RootLayout(RelativeLayout):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        Clock.schedule_once(self.ios_safe_area)

    def ios_safe_area(self, _):
        if platform == 'ios':
            self.safe_area = ios.get_safe_area()
            # iPhone 12 Mini: ios.get_safe_area() =  {'top': 43.0, 'bottom': 29.0, 'right': 0.0, 'left': 0.0}
            print("ios.get_safe_area() = ", self.safe_area)
            self.scale = ios.get_scale()  # = kivy.metrics.dp(1)
            self.no_safe_area = True
            for key in self.safe_area:
                if self.safe_area[key] * self.scale > 15: self.no_safe_area = False
                # print(f"{self.safe_area[key] * self.scale=}")
            print("self.no_safe_area = ", self.no_safe_area)
            if self.no_safe_area:
                self.root_content = RootContent()
            else:
                new_x = self.scale * self.safe_area["left"]
                new_y = self.scale * self.safe_area["bottom"]
                new_width = self.width - self.scale * (self.safe_area["left"] + self.safe_area["right"])
                new_height = self.height - self.scale * (self.safe_area["bottom"] + self.safe_area["top"])
                self.root_content = RootContent(x=new_x, y=new_y, width=new_width, height=new_height,
                                                size_hint=(None, None))  # Only relevant on iOS: size_hint=(None, None)
                # Resize app Window when keyboard appears and disappears NOT working on iOS, because of ios.get_safe_area().
        else:
            self.root_content = RootContent()
        print("RootLayout(RelativeLayout): height=", self.height, "width=", self.width)
        self.add_widget(self.root_content)


class RootContent(BoxLayout):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        print("In _init_: RootContent(BoxLayout): x =", self.x, "y =", self.y, "height =", self.height, "width =", self.width)

        # ScrollView with RST-doc
        self.scroll_view = MyScrollView(size_hint=(1, 1))
        self.rst_doc = MyRstDoc(text=introscreen_rst)
        self.scroll_view.add_widget(self.rst_doc)
        self.add_widget(self.scroll_view)

        Clock.schedule_once(self.after_init)

    def after_init(self, _):
        print("After _init_: RootContent(BoxLayout): x =", self.x, "y =", self.y, "height =", self.height, "width =", self.width)


class GeoESPTraining(App):
    def build(self):
        return RootLayout()

    def action_ref(self, args):
        print("webbrowser.open - args = ", args)
        webbrowser.open(args[1])

if __name__ == '__main__':
    GeoESPTraining().run()

The Xcode project was created with the 'toolchain create' command.

Expected behavior
I expected a webbrowser to open and show the content at the URL. That's what happens on Ubuntu 20.04 and Android 8.1.0.

Logs
Xcode log output from running the RST-URL webbrowser Kivy app error 'main.py' above:

Available orientation: KIVY_ORIENTATION=Portrait
Initializing python
<string>:1: DeprecationWarning: the imp module is deprecated in favour of importlib and slated for removal in Python 3.12; see the module's documentation for alternative uses
Running main.py: /private/var/containers/Bundle/Application/36AB57BF-3AC6-47E0-8F12-27FF8D586338/rst-url.app/YourApp/main.pyc
[INFO   ] [Logger      ] Record log in /private/var/mobile/Containers/Data/Application/E9CB2F25-6BF2-4366-9966-EE799821EFBA/Documents/.kivy/logs/kivy_24-12-11_1.txt
[INFO   ] [Kivy        ] v2.3.0
[INFO   ] [Kivy        ] Installed at "/private/var/containers/Bundle/Application/36AB57BF-3AC6-47E0-8F12-27FF8D586338/rst-url.app/lib/python3.11/site-packages/kivy/__init__.py"
[INFO   ] [Python      ] v3.11.6 (main, Oct 27 2024, 18:07:56) [Clang 16.0.0 (clang-1600.0.26.3)]
[INFO   ] [Python      ] Interpreter at "/private/var/containers/Bundle/Application/36AB57BF-3AC6-47E0-8F12-27FF8D586338/rst-url.app/rst-url"
[INFO   ] [Logger      ] Purge log fired. Processing...
[INFO   ] [Logger      ] Purge finished!
[INFO   ] [Factory     ] 195 symbols loaded
[INFO   ] [Image       ] Providers: img_imageio, img_tex, img_sdl2 (img_dds, img_ffpyplayer, img_pil ignored)
Got dlopen error on Foundation: dlopen(/System/Library/Frameworks/Foundation.framework/Versions/Current/Foundation, 0x0001): tried: '/System/Library/Frameworks/Foundation.framework/Versions/Current/Foundation' (no such file), '/private/preboot/Cryptexes/OS/System/Library/Frameworks/Foundation.framework/Versions/Current/Foundation' (no such file), '/System/Library/Frameworks/Foundation.framework/Versions/Current/Foundation' (no such file, not in dyld cache)
Got fallback dlopen error on Foundation: dlopen(/Groups/System/Library/Frameworks/Foundation.framework/Versions/Current/Foundation, 0x0001): tried: '/Groups/System/Library/Frameworks/Foundation.framework/Versions/Current/Foundation' (no such file), '/private/preboot/Cryptexes/OS/Groups/System/Library/Frameworks/Foundation.framework/Versions/Current/Foundation' (no such file), '/Groups/System/Library/Frameworks/Foundation.framework/Versions/Current/Foundation' (no such file)
[INFO   ] [Text        ] Provider: sdl2
[INFO   ] [Video       ] Provider: null(['video_ffmpeg', 'video_ffpyplayer'] ignored)
[INFO   ] [Window      ] Provider: sdl2
You need UIApplicationSupportsIndirectInputEvents in your Info.plist for mouse support
[INFO   ] [GL          ] Using the "OpenGL ES 2" graphics system
[INFO   ] [GL          ] Backend used <sdl2>
[INFO   ] [GL          ] OpenGL version <b'OpenGL ES 2.0 Metal - 101'>
[INFO   ] [GL          ] OpenGL vendor <b'Apple Inc.'>
[INFO   ] [GL          ] OpenGL renderer <b'Apple A14 GPU'>
[INFO   ] [GL          ] OpenGL parsed version: 2, 0
[INFO   ] [GL          ] Shading version <b'OpenGL ES GLSL ES 1.00'>
[INFO   ] [GL          ] Texture max size <4096>
[INFO   ] [GL          ] Texture max units <8>
[INFO   ] [Window      ] auto add sdl2 input provider
[INFO   ] [Window      ] virtual keyboard not allowed, single mode, not docked
[INFO   ] [Base        ] Start application main loop
ios.get_safe_area() =  {'top': 43.0, 'bottom': 29.0, 'right': 0.0, 'left': 0.0}
self.no_safe_area =  False
In _init_: RootContent(BoxLayout): x = 0 y = 97.875 height = 2095.0 width = 1080.0
RootLayout(RelativeLayout): height= 2338 width= 1080
[INFO   ] [GL          ] NPOT texture support is available
After _init_: RootContent(BoxLayout): x = 0 y = 97.875 height = 2095.0 width = 1080.0
webbrowser.open - args =  (<kivy.uix.rst.RstParagraph object at 0x10ae21240>, 'https://www.transformation.dk')
BUG IN CLIENT OF UIKIT: The caller of UIApplication.openURL(_:) needs to migrate to the non-deprecated UIApplication.open(_:options:completionHandler:). Force returning false (NO).

Screenshots
Not relevant.

Additional context
RST-documents need the docutils module, which I installed with:
(venv) henrikroseno@MBPtilhdeHenrik ~ % toolchain pip install docutils

ChatGPT suggests this change to the Kivy-ios project:

(ChatGPT refers to 'ios.pyx', because I had mentioned https://github.com/kivy/kivy-ios/blob/master/kivy_ios/recipes/ios/src/ios.pyx .)

Implement a Custom iOS URL Opener

If the issue persists and the library hasn’t been updated, you can create a custom function in the ios.pyx file or in your app's code to use the modern API. Here’s an example of how you might update the open_url function:

Example Update to ios.pyx

def open_url(url: str):
    from rubicon.objc import ObjCClass, objc_str

    UIApplication = ObjCClass("UIApplication")
    NSURL = ObjCClass("NSURL")
    app = UIApplication.sharedApplication()
    ns_url = NSURL.URLWithString_(objc_str(url))
    if ns_url and app:
        options = {}
        app.openURL_options_completionHandler_(ns_url, options, None)

After modifying this file, rebuild the kivy-ios project.

I look forward to hear from you guys.

@HeRo002
Copy link
Author

HeRo002 commented Dec 12, 2024

I tried changing

        webbrowser.open(args[1])

to

        ios.open_url(args[1])

But I still get the same error.

@HeRo002
Copy link
Author

HeRo002 commented Dec 12, 2024

ChatGPT made a workaround based on the rubicon module. But as far as I can tell, that module does not work with Kivy-ios. 'toolchain pip install rubicon' could not install it, and according to 'toolchain recipes' it has no recipe.

Then I asked ChatGPT to rewrite it for pyobjus. This result works:

from pyobjus import autoclass
from pyobjus.dylib_manager import load_framework, INCLUDE

# Load UIKit framework
load_framework(INCLUDE.GLKit)

def open_url_ios_pyobjus(url):
    # Get UIApplication class and shared instance
    UIApplication = autoclass("UIApplication")
    app = UIApplication.sharedApplication()
    
    # Get NSURL class and create an NSURL object
    NSURL = autoclass("NSURL")
    ns_url = NSURL.URLWithString_(url)
    
    # Check if NSURL and UIApplication instances are valid
    if ns_url and app:
        # Use the modern openURL:options:completionHandler: method
        options = {}  # Create an empty NSDictionary equivalent
        app.openURL_options_completionHandler_(ns_url, options, None)


class GeoESPTraining(App):
    def build(self):
        return RootLayout()

    def action_ref(self, args):
        if platform == 'ios':
            print("open_url_ios_pyobjus(args[1]): args[1] = ", args[1])
            open_url_ios_pyobjus(args[1])
#             print("ios.open_url(args[1]): args[1] = ", args[1])
#             ios.open_url(args[1]) # webbrowser.open(args[1])
        else:
            print("webbrowser.open(args[1]): args[1] = ", args[1])
            webbrowser.open(args[1])

if __name__ == '__main__':
    GeoESPTraining().run()

The only thing I had to correct was:

load_framework(INCLUDE.GLKit)

ChatGPT suggested:

load_framework(INCLUDE.UIKit)

@HeRo002
Copy link
Author

HeRo002 commented Dec 12, 2024

NOTE: The code above is an addition to the 'main.py' code from my first message.

@HeRo002
Copy link
Author

HeRo002 commented Dec 12, 2024

Note also that you have to insert the following keys in the Info.plist file of your Xcode project:

<key>LSApplicationQueriesSchemes</key>
<array>
    <string>http</string>
    <string>https</string>
</array>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant