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.
Swipe TableViewCell to show multiple actions
-
Hello,
I would like to have another action (in addition to the delete action) be available when swiping a TableViewCell to the side. From this older forum post it seems like I need to override a method, but I didn't get enough details from that to be able to do it myself (do I override in the TableView or in the delegate?). I also found this website explaining how to do it in Swift https://useyourloaf.com/blog/table-swipe-actions/#swipe-actions-ios-11, which shows a different and newer method to override since the older one is now deprecated. However, I'm not sure how to translate this code into Python, and I'm also not sure how to override the method since my delegate is not actually a subclass of UITableViewDelegate (and I'm not sure how to make it one). Any help would be greatly appreciated!
-
-
@colint There is some error or something missing in my code because, after you execute your own handler code, the swipe menu does not disappear.
Normally, you should end by setting an actionPerformed flag (see here) to perform or not the standard process (like a delete) and then reset the swipe. I've tried some modification but I get a segmentation error.def handler(_action_performed): print('handler called') #action_performed = True return . . . handler_block = ObjCBlock(handler, argtypes=[c_void_p]) UIContextualAction.setHandler_(handler_block)
I think we need some help from our guru @JonB , thanks to him in advance
-
@cvp The handler gets called with 3 arguments, the last of which is a completion_handler. You must call the completion_handler with True or False, indicating whether the action was successful.
Somewhere we have an example of a handler like that -- let me search...
-
@JonB
https://forum.omz-software.com/topic/6402/dealing-with-a-completion-handler/6This would have to be customized for this specific completion handler... Rubicon is a lot better for this.
-
@cvp
So, the handler has to have following signaturetypedef void (^UIContextualActionHandler)(UIContextualAction *action, __kindof UIView *sourceView, void (^completionHandler)(BOOL actionPerformed));
Thus, we have 3 args: two ObjCInstance and a block which has one bool argument and no return type. These are all c_void_p, but the last one has to be treated as a block structure whose invoke field has the appropriate CFUNCTYPE, so we can call it.
Referring back to a time when I was smarter, I think the following should do it, in this case
# descriptor for completion handler that is sent to our handler class _block_descriptor (Structure): _fields_ = [('reserved', c_ulong), ('size', c_ulong), ('copy_helper', c_void_p), ('dispose_helper', c_void_p), ('signature', c_char_p)] InvokeFuncType = ctypes.CFUNCTYPE(None, *[c_void_p, ctypes.c_bool]) class _block_literal(Structure): _fields_ = [('isa', c_void_p), ('flags', c_int), ('reserved', c_int), ('invoke', InvokeFuncType), ('descriptor', _block_descriptor)] def handler(_action, _sourceView, _comp): print(ObjCInstance (_action)) blk=_block_literal.from_address(_comp) blk.invoke(comp,True) handler_block=ObjCBlock(handler,restype=None,argtypes=[c_void_p,c_void_p, c_void_p])
-
@JonB thanks for your usual help, tried this but still segmentation error
# descriptor for completion handler that is sent to our handler class _block_descriptor (Structure): _fields_ = [('reserved', c_ulong), ('size', c_ulong), ('copy_helper', c_void_p), ('dispose_helper', c_void_p), ('signature', c_char_p)] InvokeFuncType = ctypes.CFUNCTYPE(None, *[c_void_p, ctypes.c_bool]) class _block_literal(Structure): _fields_ = [('isa', c_void_p), ('flags', c_int), ('reserved', c_int), ('invoke', InvokeFuncType), ('descriptor', _block_descriptor)] def handler(_action, _sourceView, _comp): print(ObjCInstance (_action)) blk=_block_literal.from_address(_comp) blk.invoke(comp,True) @on_main_thread def Main(): global UISwipeActionsConfiguration root_vc = UIApplication.sharedApplication().keyWindow().rootViewController() tableviewcontroller = UITableView.alloc().initWithStyle_(UITableViewStyle.UITableViewStylePlain) #=============== TableView delegate: begin #set delegate tb_ds = TVDataSourceAndDelegate.alloc().init().autorelease() tableviewcontroller.tableView().setDataSource_(tb_ds) tb_dl = UITableViewDelegate.alloc().init().autorelease() tableviewcontroller.tableView().setDelegate_(tb_dl) # set actions if swipe UIContextualAction = ObjCClass('UIContextualAction').alloc() UIContextualAction.setTitle_("@Ryubai's' action 😅") handler_block=ObjCBlock(handler,restype=None,argtypes=[c_void_p,c_void_p, c_void_p]) UIContextualAction.setHandler_(handler_block) UIContextualAction.setBackgroundColor_(ObjCClass('UIColor').blueColor().colorWithAlphaComponent(0.5)) UIContextualAction2 = ObjCClass('UIContextualAction').alloc() #UIContextualAction2.setStyle_(1) UIContextualAction2.setTitle_("my delete") # block does not have parameter nor return, thus we can use a Python def UIContextualAction2.setHandler_(handler2) UIContextualAction2.setBackgroundColor_(ObjCClass('UIColor').redColor().colorWithAlphaComponent(1.0)) UISwipeActionsConfiguration = ObjCClass('UISwipeActionsConfiguration').configurationWithActions_([UIContextualAction, UIContextualAction2]) #=============== TableView delegate: end root_vc.presentViewController_animated_completion_(tableviewcontroller, True, None)
-
What is handler2? Also, maybe we need to retain_global, since you define the block inside a function.
-
@JonB hanler2 for delete tab but not yet modified nor used...I did not tap delete
-
@JonB ok, I move the block outside main(), no more segmentation error, but
Traceback (most recent call last): File "_ctypes/callbacks.c", line 234, in 'calling callback function' File "/private/var/mobile/Containers/Shared/AppGroup/1B829014-77B3-4446-9B65-034BDDC46F49/Pythonista3/Documents/MesTests/UITableViewController user swipe.py", line 132, in handler blk.invoke(comp,True) NameError: name 'comp' is not defined
-
@cvp ok, removing the blk.invoke doesn't crash... Maybe we have the block signature wrong.
-
Ahh, by printing arguments, I realized that I forgot that blocks have hidden arguments pointing to themselves.
class _block_descriptor (Structure): _fields_ = [('reserved', c_ulong), ('size', c_ulong), ('copy_helper', c_void_p), ('dispose_helper', c_void_p), ('signature', c_char_p)] InvokeFuncType = ctypes.CFUNCTYPE(None, *[c_void_p, ctypes.c_bool]) class _block_literal(Structure): _fields_ = [('isa', c_void_p), ('flags', c_int), ('reserved', c_int), ('invoke', InvokeFuncType), ('descriptor', _block_descriptor)] def handler(_blk, _action, _sourceView, _comp): print(ObjCInstance (_action)) print(ObjCInstance(_sourceView)) print(ObjCInstance(_comp)) blk=_block_literal.from_address(_comp) print(blk.descriptor.signature) blk.invoke(_comp, True) handler_block=ObjCBlock(handler,restype=None,argtypes=[c_void_p, c_void_p,c_void_p, c_void_p])
-
…. But now I get a crash whenever I press enter at the console again. I think something in the VC needs to be retained?
-
@JonB With new code, I get
Traceback (most recent call last): File "_ctypes/callbacks.c", line 234, in 'calling callback function' TypeError: handler() missing 1 required positional argument: '_comp'
-
@JonB my error, I forgot to add a c_void_p
No more segmentation error
Log is
<UIContextualAction: 0x2856e4e60: style=0, title=@Ryubai's' action 😅, backgroundColor=UIExtendedSRGBColorSpace 0 0 1 0.5> <UISwipeActionStandardButton: 0x115808080; frame = (81 0; 120 43.5); anchorPoint = (0, 0.5); opaque = NO; autoresize = W+H; tintColor = UIExtendedGrayColorSpace 1 1; layer = <CALayer: 0x28360d860>> <__NSStackBlock__: 0x16bb0c608> b'5\xf0\xb2\xdc\x01'
-
Strange... I don't get a crash right away, but if I close the VC and type anything in the console, I get a seg fault. I moved everything into the main code, deleted the auto releases, and tried dismissing the VC from within the handler, but same result. Maybe I broke something else elsewhere.
Well ... If it works, good! Note you can use the action argument's title property to figure out which button was tapped, if you don't want to have a different handler for each button.
-
@JonB If I start Pythonista and I run the script a first time, the 2nd button is absent. Then, I close the program and relaunch it, the 2nd button appears. That's also strange
And if I relaunch it a third time, I have a segmentation error
-
@JonB said
Referring back to a time when I was smarter
I can't believe that, smarter than now....Is that really possible 😉
-
This post is deleted! -
One answer might be to simple remove the blk.invoke line.
I will experiment a bit more this weekend