• @mikael I'm not very creative with these things - do you have some example code based on objc_util that I could translate to Rubicon?

    In general the syntax for classes looks like this:

    from rubicon.objc import NSInteger, NSObject, ObjCProtocol, objc_method WhateverDelegate = ObjCProtocol("WhateverDelegate") class MyCustomDelegate(NSObject, protocols=[WhateverDelegate]): # the protocols=[...] part is optional of course, if you don't need to implement any protocol @objc_method def someThing_didAThingWithCount_(self, something, count: NSInteger) -> None: print(something, count) something.delegate = MyCustomDelegate.new()

    The Python 3 type annotation syntax is used to set each method's return and argument types. You use regular ctypes type objects for the types, and Rubicon predefines some common typedefs like NSInteger. Anything you don't annotate is assumed to be an Objective-C object (ObjCInstance).

    Blocks are similar:

    from rubicon.objc import Block, NSInteger @Block def thing_callback(number: NSInteger) -> None: print(number) whatever.doThing("abc", withCallback=callback)
  • It worked perfectly!!
    I really appreciate it! Thanks :)

  • In answer to my own question at the end there: that "locations" parameter is not a "const float *", it is actually "const CGFloat *" which can be 32 or 64 bits depending on platform architecture, and therefore a C float is not necessarily compatible...so I needed to explicitly use the CGFloat datatype:

    CGFloat_p = ctypes.POINTER(objc_util.CGFloat) CGGradientCreateWithColors = objc_util.c.CGGradientCreateWithColors CGGradientCreateWithColors.restype = c_void_p CGGradientCreateWithColors.argtypes = [c_void_p, c_void_p, CGFloat_p] locations = (objc_util.CGFloat * 2)(0.0,1.0) gradient = CGGradientCreateWithColors(colorSpace, objc_util.ns(colors), ctypes.cast(locations, CGFloat_p))

    and it works fine. As always, thanks all!

  • Boring!

    Hello Hello Hello Hello John B. Nielsen Hello John B. Nielsen Hello John B. Nielsen Hello John B. Nielsen
  • There was an additional crashing issue: another version of the same problem when an object has gone out of scope in the python context when it is accessed in the objc context.

    I need to re-inforce the understanding that just because a python object like a ui.View is technically placed in a view hierarchy, or otherwise connected to the objc world, the underlying objc instance of a UIView is not in any way holding a strong reference to that python object, and I think vice-versa.

    So creating a local ui.View instance inside a method, and then adding that view to a purely objc view hierarchy, will not preserve the python object after it goes out of scope. So a later attempt to access the python object will segfault.


    import ui import objc_util class X(object): def buttonAction(self, sender): print sender def demoCrash(self, sender): b2 = ui.Button() b2.frame = (0,0,200,50) b2.title = "Crash?" b2.action = self.buttonAction v2 = ui.View() v2.frame = (0,0,200,50) v2.background_color = (1.0,1.0,1.0,1.0) v2.objc_instance.addSubview(b2) v2.present(style="sheet") v = ui.View() v.frame = (0,0,400,400) v.background_color = (1.0,1.0,1.0,1.0) x = X() b = ui.Button() b.title = "Show" b.frame = (0,0,100,30) b.center = (v.width*0.5,v.height*0.5) b.action = x.demoCrash v.add_subview(b) v.present(style="sheet")

    should have been obvious, but my code was more complex and hid what was happening.

  • Thanks, I'll try the key/value observer method.
    Gotta say it's awfully weird that Apple provided no reasonable API for this...at least for observing the value, if not setting it.

  • @JonB got it...thanks for the reply! I figured something like that was the reason...that these were not functions in any sense which could be "called". I can use one of a few ways I've found to do what I need in the cases I've seen.

  • @JonB yeah, changing c_void_p to c_byte still results in a crash with the same fault log, and setting the handler to None also results in a crash with the same fault log.

  • You may be interested in my objc viewbrowser:

    You can present your view, then traverse the heirarchy to see all of the UIViews. Tapping an item highlights it onscreen.
    Also, any action/targets are shown as little chain icons, which you can tap and it prints to the console the details on the target, selector, event codes, etc. Tapping the info button shows all of the objc properties, which is also navigatable.

  • The use case is a little complicated, and very task-specific:

    app is constantly listening for incoming messages manipulating controls in the view sends outgoing messages some incoming messages will try to set the value of a control being manipulated this leads to "fighting" between the user and the system knowing which control or controls are being interacted with, we can filter the incoming messages so they don't attempt to set controls actively being manipulated, effectively "debouncing" the signal

    I like the idea of a transparent layer...but I wanted to be more specific about what I considered "manipulating" the control...with this, I can say exactly what I consider to be user interaction for these purposes.

    Anyway, I am using the sender argument coming from the addTarget_action_forControlEvents() call, which is how I'm keying which controls are marked active. For my purposes it is working.Unfortunately, it seems like the various Pythonista-wrapped UIControls have different ways, or sometimes no way, of getting access to the actual UIControl instance they wrap, which makes it a little complicated to call the addTarget_ objectiveC method to install the monitor.

  • Hey thank you everyone.

    I must have had a typo when I initially posted this - but it seems to work now.

    Notes: I used ui.parse_color('blue').CGColor()

    This works with RGBA, Hex and Named Colors.

    Ill post the code when done, I am using this to create custom button styles, along with some custom styles for "checkbox" functionality (circles, squares, stars, etc)

    Thanks again for your help!

  • @mikael faulthandler seems really useful, Thanks!

  • Or, to look at it in another way, you are resizing the contained Stepper, but not the containing View.

  • @cbanta, what is the exception? Install the fault handler to find out.

  • @JonB It's when I read such posts that I recognize that I really don't know anything about Objective-C 😢

  • Thank you!!!!

  • @dgelessus : you're right ! I've extracted objc_util.py from my current Pythonista and have copied it to the XCode Template project (PythonistaAppTemplate/PythonistaKit.framework/pylib/site-packages directory ). Now, I can test my app :o)

    @JonB : I don't need to add the framework I want to load in my xcode project. I've removed the framework in the xcode project and my app can be launched without any problem...Perhaps as it's an Apple framework, I don't need to add it....My xcode knowledge is a little low ;o)

    Thank you for your help...

  • Hi,
    Finally, I managed to initialize an ar session
    but I remain open to any improvement (
    especially the famous ARKit constants and the call to the sleep function ;o)

    Here the code :

    # coding: utf-8 import objc_util from objc_util import * import ui import os import sys #from myDebugToolKit import * import time from enum import IntFlag load_framework('SceneKit') load_framework('ARKit') # Some 'constants' used by ARkit # But i can't transfer them to the ARKit framework, why ????? class ARWorldAlignment(IntFlag): ARWorldAlignmentGravity = 0 ARWorldAlignmentGravityAndHeading = 1 ARWorldAlignmentCamera = 2 class ARPlaneDetection(IntFlag): ARPlaneDetectionNone = 0 ARPlaneDetectionHorizontal = 1 << 0 ARPlaneDetectionVertical = 1 << 1 # Work In Progress here, I'm deciphering the ARKit constants... #class ARSCNDebugOption(IntFlag): # ARSCNDebugOptionNone = 0 # ARSCNDebugOptionShowWorldOrigin = int("ffffffff80000000", 16) # ARSCNDebugOptionShowFeaturePoints = int("ffffffff40000000", 16) class ARSessionRunOptions(IntFlag): ARSessionRunOptionsNone = 0 ARSessionRunOptionResetTracking = 1 << 0 ARSessionRunOptionRemoveExistingAnchors = 1 << 1 NSError = ObjCClass('NSError') SCNScene = ObjCClass('SCNScene') ARSCNView = ObjCClass('ARSCNView') ARWorldTrackingConfiguration = ObjCClass('ARWorldTrackingConfiguration') ARSession = ObjCClass('ARSession') UIViewController = ObjCClass('UIViewController') ARPlaneAnchor = ObjCClass('ARPlaneAnchor') # I should refactor te following line in a class but I need to learn more the create_objcc_class function sceneview = None # Here some set up functions used by the main class def createSampleScene(): # an empty scene scene = SCNScene.scene() return scene def setDebugOptions(arscn): # Work In Progress Here, I'm trying to decipher the arkit constants... #val = ARSCNDebugOption.ARSCNDebugOptionShowWorldOrigin | ARSCNDebugOption.ARSCNDebugOptionShowFeaturePoints val = int("fffffffffc000000", 16) # this value is a combination of ShowWorldOrigin and ShowFeaturePoints flags, but I can't isolate each flags.... print('Before calling setDebugOptions_(%s) : debugOptions=%s' %(hex(val), hex(arscn.debugOptions()))) arscn.setDebugOptions_(val) print('After calling setDebugOptions_(%s) : debugOptions=%s' % (hex(val),hex(arscn.debugOptions()))) def createARSceneView(x, y, w, h, debug=True): v = ARSCNView.alloc().initWithFrame_((CGRect(CGPoint(x, y), CGSize(w, h)))) v.setShowsStatistics_(debug) # I love statistics... return v # Some callback definitions used by create_objc_class def CustomViewController_touchesBegan_withEvent_(_self, _cmd, _touches, event): touches = ObjCInstance(_touches) for t in touches: loc = t.locationInView_(sceneview) sz = ui.get_screen_size() print(loc) @on_main_thread def runARSession(arsession): arconfiguration = ARWorldTrackingConfiguration.alloc().init() arconfiguration.setPlaneDetection_ (ARPlaneDetection.ARPlaneDetectionHorizontal) arconfiguration.setWorldAlignment_(ARWorldAlignment.ARWorldAlignmentGravity) # I do not use ARWorldAlignmentGravityAndHeading anymore because on my device, sometimes it fails to initialize the ar session because of an unitialized sensor (error 102). I think my magnetic phone casing plays tricks on me... arsession.runWithConfiguration_options_(arconfiguration, ARSessionRunOptions.ARSessionRunOptionResetTracking | ARSessionRunOptions.ARSessionRunOptionRemoveExistingAnchors ) time.sleep(0.5) # Let the system breathe ;o) Ok, that's the workarround I found to retrieve the ar session configuration (otherwise I got None).... print('configuration',arsession.configuration()) # Very usefull for the debuging (at least for me !) def CustomViewController_viewWillAppear_(_self, _cmd, animated): return def CustomViewController_viewWillDisappear_(_self, _cmd, animated): session = sceneview.session() session.pause() def MyARSCNViewDelegate_renderer_didAdd_for_(_self, _cmd, scenerenderer, node, anchor): if not isinstance(anchor, (ARPlaneAnchor)): return # to be implemented... def MyARSCNViewDelegate_session_didFailWithError_(_self,_cmd,_session,_error): print('error',_error,_cmd,_session) err_obj=ObjCInstance(_error) print(err_obj) # Again, very usefull for the debuging... # The main class... class MyARView(ui.View): def __init__(self): super().__init__(self) @on_main_thread def initialize(self): global sceneview self.flex = 'WH' screen = ui.get_screen_size() # set up the scene scene = createSampleScene() # set up the ar scene view delegate methods = [MyARSCNViewDelegate_renderer_didAdd_for_,MyARSCNViewDelegate_session_didFailWithError_] protocols = ['ARSCNViewDelegate'] MyARSCNViewDelegate = create_objc_class('MyARSCNViewDelegate', NSObject, methods=methods, protocols=protocols) delegate = MyARSCNViewDelegate.alloc().init() # set up the ar scene view sceneview = createARSceneView(0, 0, screen.width, screen.height) sceneview.scene = scene sceneview.setDelegate_(delegate) # set up the custom view controller methods = [CustomViewController_touchesBegan_withEvent_, CustomViewController_viewWillAppear_, CustomViewController_viewWillDisappear_] protocols = [] CustomViewController = create_objc_class('CustomViewController', UIViewController, methods=methods, protocols=protocols) cvc = CustomViewController.alloc().init() cvc.view = sceneview # internal scheming... self_objc = ObjCInstance(self) self_objc.nextResponder().addChildViewController_(cvc) self_objc.addSubview_(sceneview) cvc.didMoveToParentViewController_(self_objc) # here, we try... runARSession(sceneview.session()) # I call here this function because I'm trying to find the best place to run the ar session... setDebugOptions(sceneview) # I call here this function because I'm trying to find the best place to set the debuging options.... def will_close(self): session = sceneview.session() session.pause() if __name__ == '__main__': v = MyARView() v.present('full_screen', hide_title_bar=True, orientations=['portrait']) v.initialize()

    Note: if someone can correct the autorotate, I'm also interested !o)
    Edit: I found a solution for my autorotate problem -> I turn it off ;o)

    Live session

Internal error.

Oops! Looks like something went wrong!