Forum Archive

Problems when using objc_util()

rozaimech

Can you help me solve the problem about object_c? I'm using objc_util() to create a UIDragInteraction, but it didn't work. The error message is like this.
This is my code and error message.What I ultimately want to accomplish is to implement UIDragInteraction and UIDropInteraction operations in pythonista ui view.

The error message is:

Traceback (most recent call last):
  File "_ctypes/callbacks.c", line 259, in 'converting callback result'
TypeError: cannot be converted to pointer
# coding: utf-8
from objc_util import *
import ui
def dragInteraction_itemsForBeginningSession_(_m,_c,interaction,session):
    provider=ObjCClass('NSItemProvider').alloc().initWithObject('i am string')
    item=ObjCClass('UIDragItem').alloc().initWithItemProvider(provider)
    nsarry=ObjCClass("NSArray").arrayWithObject(item)
    return nsarry
methods=[dragInteraction_itemsForBeginningSession_]
protocols=['UIDragInteractionDelegate']
draginter=create_objc_class('itemsForBeginningSession',methods=methods,protocols=protocols)

@on_main_thread
def create_drag(f_v):
    ob_fv=ObjCInstance(f_v)
    f=CGRect(CGPoint(100,100 ), CGSize(300, 300))
    ob_cv=ObjCClass("UIView").alloc().initWithFrame(f)
    color=ObjCClass('UIColor').colorWith(red=1.0, green=0.0, blue=0.0, alpha=1.0)
    ob_cv.setBackgroundColor(color)
    delegate=draginter.alloc().init()
    obj_draginter=ObjCClass('UIDragInteraction').alloc().initWithDelegate(delegate)
    obj_draginter.setEnabled(True)
    ob_cv.addInteraction(obj_draginter)
    ob_fv.addSubview(ob_cv)

cui=ui.View(bg_color='#ffffff',farme=(0,0,500,500))
create_drag(cui)
cui.present()

JonB

Is that a full on crash or a normal traceback? If a normal traceback, print the full traceback so we can get the offending line.

If it is a crash, install @dgelessus 's faulthandler.
https://github.com/dgelessus/pythonista_startup

cvp

@rozaimech try this, it works

    return nsarry.ptr 
cvp

@JonB there is no crash, only an error

Exception ignored in: <function dragInteraction_itemsForBeginningSession_ at 0x117dcf7b8>
Traceback (most recent call last):
  File "_ctypes/callbacks.c", line 259, in 'converting callback result'
TypeError: cannot be converted to pointer
mikael

@rozaimech, this looks interesting - unfortunately there is not way to dnd on an iPhone, I think.

rozaimech

@cvp thanks! it's work!

rozaimech

@mikael This code should work on iphones. This feature is turned off by default on iphones, but as obj_draginter.setEnabled (True) is turned on, it can be used.

rozaimech

@JonB Thank you for your help! I should continue to sleep now.

mikael

@rozaimech, ok, thanks, now I got it. Long press to start, also added a text field to be on the receiving end of the interaction (the string).

As a detail, there is no need to create the red view ”in ObjC” to make it drag-and-droppable. This works just as well, and has the benefit of being more readable:

@on_main_thread
def create_drag(f_v):
    drag_source = ui.View(
        background_color='red',
        frame=(100, 100, 300, 300),
    )
    delegate = draginter.alloc().init()
    obj_draginter = ObjCClass('UIDragInteraction').alloc().initWithDelegate(
        delegate)
    obj_draginter.setEnabled(True)
    drag_source.objc_instance.addInteraction(obj_draginter)
    f_v.add_subview(drag_source)
rozaimech

@mikael thanks ,but how to find these hidden interfaces....I want to learn

mikael

@rozaimech, you are quite right, I had not realized that the objc_instance attribute was not really documented anywhere.

But the main thing I tried to convey was that you can get the ObjC version of views and other UI class instances with

ObjCInstance(regular_ui_view)

... and that is documented in the objc_util docs. Using regular_ui_view.objc_instance is just a convenience feature, as you do not always need to import objc_util to e.g. set some ObjC-level attributes.

rozaimech

@mikael ok

cvp

@rozaimech said:

cui=ui.View(bg_color='#ffffff',farme=(0,0,500,500))

Small typing error: farme ipo frame 🙄

rozaimech

@cvp hello..do you know how to create a block in the dropInteraction_performDrop_?

# coding: utf-8
from objc_util import *
import ui

def dropInteraction_canHandleSession_(_m,_c,interaction,session):
    return True
def dropInteraction_sessionDidUpdate_(_m,_c,interaction,session):
    #UIDropOperationCancel  = 0,
    #UIDropOperationForbidden = 1,
    #UIDropOperationCopy   = 2,
    #UIDropOperationMove   = 3,
    return ObjCClass('UIDropProposal').alloc().initWithDropOperation(2).ptr
def dropInteraction_performDrop_(_m,_c,interaction,session):
    session=ObjCInstance(session)
'''object c:
      [session loadObjectsOfClass:[NSString class] completion:^(NSArray<__kindof id<NSItemProviderReading>> * _Nonnull objects) {
    self.dropLabel.text = objects.firstObject;}];
'''
    session.loadObjectsOfClass_completion_(NSString,)
methods=[dropInteraction_canHandleSession_,dropInteraction_sessionDidUpdate_,dropInteraction_performDrop_]
protocols=['UIDropInteractionDelegate']
dropDelegate=create_objc_class('MydropDelegate',methods=methods,protocols=protocols)


Delegate=dropDelegate.alloc().init()
dropInteraction=ObjCClass('UIDropInteraction').alloc().initWithDelegate(Delegate)

drop_view = ui.View(bg_color='blue',frame=(0,0,500,500))
drop_view.objc_instance.setUserInteractionEnabled(True)
drop_view.objc_instance.addInteraction(dropInteraction)
drop_view.present()


cvp

@rozaimech first of all, did you read objc_util doc in Pythonista?
Then, there are a lot of examples in the forum.
Last, I'll try to do it, I just read your request.

rozaimech

@cvp
I'm sorry to make you feel like I'm a lazy kid to sit idle and enjoy the fruits of other's work .I have read Pythonista documents and source code many times, but there are not many similar examples about objc_util. I can't master English very well, but thank you very much for your help! I'm also trying to find relevant solutions.

cvp

@rozaimech ok and Sorry, I'm still busy with your problem.
A block is ok but loadObjectsOfClass_completion_ is not a method for session...

cvp

@rozaimech I'be done a step more but not yet ok

def dropInteraction_performDrop_(_m,_c,interaction,session):
    session=ObjCInstance(session)
    print('dropInteraction_performDrop_ called')
    '''object c:
      [session loadObjectsOfClass:[NSString class] completion:^(NSArray<__kindof id<NSItemProviderReading>> * _Nonnull objects) {
    self.dropLabel.text = objects.firstObject;}];
     '''    
    def handler(_cmd):#,obj1_ptr,_error):
        print('block called')
    handler_block = ObjCBlock(handler, restype=None, argtypes=[c_void_p])#, c_void_p, c_void_p])
    for item in session.items():
      print(item)
      itemProvider = item.itemProvider()
      print(itemProvider)
      ret = itemProvider.loadObjectsOfClass_completion_(NSString, handler_block) 
cvp

@rozaimech we will win, I hope.
You did a small error in loadObjectOfClass_completionHandler_

def dropInteraction_performDrop_(_m,_c,interaction,session):
    session=ObjCInstance(session)
    print('dropInteraction_performDrop_ called')
    '''object c:
      [session loadObjectsOfClass:[NSString class] completion:^(NSArray<__kindof id<NSItemProviderReading>> * _Nonnull objects) {
    self.dropLabel.text = objects.firstObject;}];
     '''    
    def handler(_cmd,obj1_ptr,_error):
        print('block called')
        obj1 = ObjCInstance(obj1_ptr)
        print(obj1)
    handler_block = ObjCBlock(handler, restype=None, argtypes=[c_void_p, c_void_p, c_void_p])
    for item in session.items():
      print(item)
      itemProvider = item.itemProvider()
      print(itemProvider)
      #print(dir(itemProvider))
      itemProvider.loadObjectOfClass_completionHandler_(NSString, handler_block) 
cvp

@rozaimech I don't know what you want to drop.
I try with a photo (with the Photos app in split view with Pythonista) and, if you comment the print(obj1) line,
- we see that the dropped item is a public.jpeg
- the block is called
Up to here, it is ok but there is still work (for you?) th analyze the droped item. I've tried but not yet solved...

rozaimech

@cvp
I made a program to synchronize the clipboard of PC and iPad, but it can only run in pythonista. If I use drop and drag, I can realize the clipboard of iPad (pythonista runs as a window)

cvp

@rozaimech said:

I can realize the clipboard of iPad

Sorry, I don't understand

rozaimech

@cvp
I haven't used objc_util too much before in pythonista, and I have learned a lot recently, including the pythonista pointer you told me, the syntax of Object C, and the current problem is that I'm not familiar with the use of block as a parameter. Next, I will continue to study it

rozaimech

@cvp

@rozaimech said:

I can realize the clipboard of iPad

sorry,it's mean “can realize the global clipboard of iPad”

cvp

@rozaimech I'm sincerely sorry, perhaps due to my poor English, but I can't understand this "realize"

rozaimech

@cvp
I should have misused my words..
realize..
implements..
implementation ...
implementation function? xd

cvp

@rozaimech do you know the clipboard Pythonista module?

cvp

@rozaimech If you only want to drop a text in a Pythonista script, you don't need Objectivec, you can drop any text in a ui.TextField

rozaimech

@cvp
link img

This is my work of art. Please don't laugh

cvp

@rozaimech Nice, seriously

cvp

@zrzka wrote a drag script

mikael

@cvp, looking at the docs, would think you need 3 args for the completion block, and it seems you have tried that. What were your conclusions?

mikael

@rozaimech, good to note that the view you attach the drag on must be touch_enabled. Just spent a while wondering why my Label is not happening...

rozaimech

@cvp
drag already written
and drop already written, too
but Random crashes have no error log

cvp

@rozaimech did you install @dgelessus ´s log as adviced by @jonb in a previous post of this topic? And is it a _objc_exception.txt file in the root?

rozaimech

@cvp
The first call successfully gets the dragged data, but the second flash back. I'm trying to resolve this error.The error log is

Fatal Python error: Segmentation fault

Thread 0x000000016fc77000 (most recent call first):
cvp

@rozaimech I got it more than ten times during my tests for trying to help you. I didn't find the reason.
If you change the code of a delegate method, restart Pythonista, that helps often

rozaimech

@cvp
ok....I should have avoided this mistake long ago...
Just let this asynchronous function run on the main thread. Everything is normal, no flashbacks, and no errors........

cvp

@rozaimech Congratulations....
If not secret/confidential 😀, please share your code

rozaimech

@cvp
There will be an error message:TypeError..

# coding: utf-8
from objc_util import *
import ui

def dropInteraction_canHandleSession_(_m,_c,interaction,session):
    return True
def dropInteraction_sessionDidUpdate_(_m,_c,interaction,session):
    return ObjCClass('UIDropProposal').alloc().initWithDropOperation(2).ptr

def dropInteraction_performDrop_(_m,_c,interaction,session):
    session=ObjCInstance(session)
    def handler(_cmd,obj_ptr):
        obj=ObjCInstance(obj_ptr)
        print(obj)
    handler_block = ObjCBlock(handler, restype=None, argtypes=[c_void_p,c_void_p])

    for item in session.items():
        itemProvider =item.itemProvider()
        uiimage =ObjCClass('UIImage')
        nsstring =ObjCClass('NSString')
        if(itemProvider.canLoadObjectOfClass(uiimage)):
            on_main_thread(itemProvider.loadObjectOfClass_completionHandler_(ObjCClass('UIImage'),handler_block))
        elif(itemProvider.canLoadObjectOfClass(nsstring)):
            on_main_thread(itemProvider.loadObjectOfClass_completionHandler_(nsstring,handler_block))
        else:
            continue

methods=[dropInteraction_canHandleSession_,dropInteraction_sessionDidUpdate_,dropInteraction_performDrop_]
protocols=['UIDropInteractionDelegate']
dropDelegate=create_objc_class('MydropDelegate',methods=methods,protocols=protocols)


Delegate=dropDelegate.alloc().init()
dropInteraction=ObjCClass('UIDropInteraction').alloc().initWithDelegate(Delegate)

drop_view = ui.View(bg_color='blue',frame=(0,0,500,500))
drop_view.objc_instance.setUserInteractionEnabled(True)
drop_view.objc_instance.addInteraction(dropInteraction)
drop_view.present()
cvp

@rozaimech I don't understand why

on_main_thread(itemProvider.loadObjectOfClass_completionHandler_ 

raises this error

Traceback (most recent call last):
  File "_ctypes/callbacks.c", line 234, in 'calling callback function'
  File "/private/var/mobile/Containers/Shared/AppGroup/668A7D98-7216-47ED-917D-AA0B6173167E/Pythonista3/Documents/drop_provider.py", line 26, in dropInteraction_performDrop_
    on_main_thread(itemProvider.loadObjectOfClass_completionHandler_( nsstring, handler_block))
  File "/var/containers/Bundle/Application/34BAEE1A-BC33-4D6F-A0C1-B733E4991F31/Pythonista3.app/Frameworks/Py3Kit.framework/pylib/site-packages/objc_util.py", line 1085, in on_main_thread
    raise TypeError('expected a callable')
TypeError: expected a callable

Sure @JonB could help you

rozaimech

@cvp
jonb said :
the Pythonista hava two threads
1.The main thread, or ui thread
2.The interpreter thread

I don't know if the error is related to IOS thread.
but I remember that the apple development document said that the NSItemProvider interface should be used in DispatchQueue.main.async { }

I'm learning something about it..

cvp

@rozaimech what is strange is that itemProvider.loadObjectOfClass_completionHandler_ has a

 __call__  

method, thus should be callable

cvp

The code works, the problem is that on_main_thread function (see objc_util source code) raises an error if the called function is not Python-callable, but on_main_thread function continues and the function is called anyway...

JonB
on_main_thread(itemProvider.loadObjectOfClass_completionHandler_(ObjCClass('UIImage'),handler_block))

Try instead:

on_main_thread(itemProvider.loadObjectOfClass_completionHandler_)(ObjCClass('UIImage'),handler_block)

In other words, on_main_thread returns a method, which you call using arguments. The way you had it, you were calling a function, and passing it's output, which was not a function, to on_main_thread.

on_main_thread(original_func)(args)

I would also question whether you need to be calling on_main_thread at all here.

cvp

@JonB said:

on_main_thread(itemProvider.loadObjectOfClass_completionHandler_)(ObjCClass('UIImage'),handler_block)

Works 😀

No other words than magic

JonB

Ahh, I see you are trying to make sure the callback gets run on the main thread... The above code doesn't do that--it just sets the callback on the main thread.

If things within the callback must be done on the main thread, you need such code inside the callback handler itself. For instance, you could use a decorator on the handler.

Another common problem is calling ObjCInstance on some non object. @shinyformica had a thread a while back about a wierd seg fault, that we were able to fix I think by prechecking some condition in a loop, and only proceeding once things were correct. The details evade me.

cvp

@JonB Without on_main_thread, segmentation fault

cvp

@JonB this also gives segmentation fault

    @on_main_thread
    def handler(_cmd,obj_ptr): 
cvp

@cvp said:

on_main_thread(itemProvider.loadObjectOfClass_completionHandler_)(ObjCClass('UIImage'),handler_block)

works once and crashes with segmentation fault the second time...

cvp

@JonB this topic?

rozaimech

@cvp
ok...we won

# coding: utf-8
from objc_util import *
import ui,time

def dropInteraction_canHandleSession_(_m,_c,interaction,session):
    return True
def dropInteraction_sessionDidUpdate_(_m,_c,interaction,session):
    return ObjCClass('UIDropProposal').alloc().initWithDropOperation(2).ptr

def dropInteraction_performDrop_(_m,_c,interaction,session):
    session=ObjCInstance(session)
    def handler(_cmd,obj_ptr):
        obj=ObjCInstance(obj_ptr)
        print(obj)

    handler_block = ObjCBlock(handler, restype=None, argtypes=[c_void_p,c_void_p])
    def ptimer(progress):
        while True:
            if(progress.totalUnitCount()==progress.completedUnitCount()):
                break
            time.sleep(0.1)
    for item in session.items():
        itemProvider =item.itemProvider()
        uiimage =ObjCClass('UIImage')

        if(itemProvider.canLoadObjectOfClass(uiimage)):
            progress=itemProvider.loadObjectOfClass_completionHandler_(ObjCClass('UIImage'),handler_block)
            ptimer(progress)
        elif(itemProvider.canLoadObjectOfClass(NSString)):
            progress=itemProvider.loadObjectOfClass_completionHandler_(NSString,handler_block)
            ptimer(progress)

        else:
            print(itemProvider)

methods=[dropInteraction_canHandleSession_,dropInteraction_sessionDidUpdate_,dropInteraction_performDrop_]
protocols=['UIDropInteractionDelegate']
dropDelegate=create_objc_class('MydropDelegate',methods=methods,protocols=protocols)


Delegate=dropDelegate.alloc().init()
dropInteraction=ObjCClass('UIDropInteraction').alloc().initWithDelegate(Delegate)

drop_view = ui.View(bg_color='blue')
drop_view.objc_instance.setUserInteractionEnabled(True)
drop_view.objc_instance.addInteraction(dropInteraction)
drop_view.present()

cvp

@rozaimech said:

ok...we won

Whaaa, you won, congratulations

mikael

@rozaimech, congratulations!

So I take it this no longer crashes? Can you say what was the key difference – one less handler argument, the progress tracker, or something else I am not seeing?

cvp

@mikael said:

one less handler argument,

I think that third argument of handler, being an error parameter, could/should be there in case of error and you want to know the reason, but anyway, it runs ok so

cvp

@mikael I think that the reason is that without progress tracker, we analyzed too early the object parameter in the handler, so sometimes the object was ready, other times it was not thus crash

mikael

@cvp, thanks. That sounds likely, due to the ObjCInstance wrapper not always being up to speed with the actual ObjC instance.

cvp

@mikael Sincerely, I don't know but why to define a "completion handler" (which we could think it is called after completion) and also a "progress tracker"?

rozaimech

@cvp
This is Apple's UI design. The completion handler is asynchronous and runs immediately.
"ItemProvider. Loadobjectofclass_completionhandler_()" returns a progress, which can cancel operations or obtain progress data. During this period, developers can design a set of suitable UI animation to reduce the waiting experience of users, or cancel loading.

cvp

@rozaimech Yes, I know, I had read it also but, I think (maybe, or better, sure, I'm wrong) that a "completion handler" is called after completion...

rozaimech

@cvp
Usually the second run has an error,maybe it's after first completion......

mikael

@rozaimech, recommend trying objc_util.retain_global on the delegate. Classic fix for ”second run” issues.

mikael

@rozaimech, I forgot that we need to retain the reference to the block. This seems reliable, and no progress needed:

def handler(_cmd, _object, _error):
    obj = ObjCInstance(_object)
    print(obj)
handler_block = ObjCBlock(
    handler, restype=None,
    argtypes=[c_void_p, c_void_p, c_void_p])
retain_global(handler_block)
rozaimech

@mikael
@cvp
Thank you. I'm working on finishing my clipboard plan now. Thank you for your help. I have learned a lot!

cvp

@mikael said:

I forgot that we need to retain the reference to the block.

I also knew it from a previous similar problem but I already forgot it, as usual 😢

cvp

@rozaimech We have also to thank @JonB for his help about threads.
I didn't know this calling way

on_main_thread(original_func)(args) 
mikael

@cvp, yes. It applies to all descriptors, by definition.

Due to a bug, same way of calling using on_main_thread is currently needed for set_idle_timer_disabled not to crash.