You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
If an Obj C object has a property, and implements a Protocol with selectors whose first argument matches that property, it is not possible to invoke the selectors.
To demonstrate the problem: on an instance of DetailedList, use Python code to programmatically select a row on the DetailedList. This would be [controller.tableView.delegate tableView:controller.tableView didSelectRowAtIndexPath:...] in ObjC, where controller is the instance of TogaTableViewController, which maps to controller.tableView.delegate(controller.tableView, didSelectRowAtIndexPath:...) in Rubicon.
This will fail on v0.3.1, but pass on the beeware/toga#2025 branch, because of the way the widget is constructed.
The iOS backend uses a UITableViewController with a default UITableView to represent a DetailedList; the UITableViewController has a tableView property describing the view that it is controlling.
A UITableView can specify a UITableViewDelegate; the delegate responds to a number of selectors, including tableView:didSelectRowAtIndexPath:.
In v0.3.1, a subclass of UITableViewController was used (TogaTableViewController) that also acted as the delegate. When an attempt is made to invoke tableView:didSelectRowAtIndexPath:, you get the error:
Traceback (most recent call last):
...
self.native.delegate.tableView(self.native, didSelectRowAtIndexPath=path)
TypeError: 'ObjCInstance' object is not callable
self.native.delegate returns the delegate, which is a TogaTableViewController instance; since this is a subclass of UITableViewController, it has a tableView property, so the Python tableView attribute is an ObjCInstance, not an ObjCMethod. It doesn't matter if you also explicitly declare the delegate as implementing the UITableViewDelegate protocol. You also can't pass the tableView:didSelectRowAtIndexPath: message directly.
The beeware/toga#2025 branch fixes this by using a raw UITableViewController, and making the delegate a subclass of NSObject. This removes the ambiguity between [UITableViewController tableView] and tableView:didSelectRowAtIndexPath:
This limitation doesn't exist on ObjC code - other bugs notwithstanding, the v0.3.1 branch works, and the UITableView is able to invoke the delegate methods defined in Python, no matter how it's defined. It only emerged in testing when we were programmatically invoking these methods from Python.
This also doesn't affect methods like tableView:cellForRowAtIndexPath: which are defined as part of the UITableViewDataSource protocol - or, at least, there's a different workaround. If you explicitly invoke the method on the controller.dataSource, rather than on the controller object directly, the method resolves. This appears to be because the data source is implicitly set by constructing the UITableViewController; if you explicitly set the dataSource property of the controller, the same problem manifests.
Expected behavior
It should be possible to retrieve the tableView property and invoke the tableView:didSelectRowAtIndexPath: selector on a delegate object.
Screenshots
No response
Environment
Operating System: iOS 16.4
Python version: 3.10
Software versions:
Rubicon: 0.4.6
Toga: 0.3.1
Logs
Additional context
There's probably an argument to be made that this is a documentation issue. Mapping from ObjC to Python is always going to be leaky, and there's workarounds available.
Another approach would be to require explicit casting to the Protocol in this case.
The text was updated successfully, but these errors were encountered:
Hrm, this is tricky. If I'm understanding the issue correctly, there's no way to implement the behavior you're looking for. If obj.tableView gets the value of the property tableView, then there's no way to make obj.tableView(...) call a method that starts with tableView:. Both cases end up calling obj.__getattr__("tableView") with no way to tell if the value will be used on its own or in a method call.
I think this has nothing to do with where the methods and properties are declared - the alternative calls that you've found work because the object in question has no tableView property, not because it implements the protocol differently.
A simple workaround would be to use the "long form" of the call - obj.tableView_didSelectRowAtIndexPath_(tableView, indexPath) - which won't conflict with the tableView property.
A possible nicer solution: allow suppressing the property syntax, with a new method that's an inverse of declare_property. Then the property getting syntax would be obj.tableView() and not obj.tableView, but other obj.tableView(...) method calls would also work.
Yeah - I agree that this is somewhere between very difficult or impossible - hence my suggestion that we might need to "fix" this with documentation that it isn't possible.
I had a vague thought that maybe this is something we could fix by catching implementing __call__() on ObjCInstance. Since ObjC doesn't really have an analog of Python's __call__(), ObjCInstances won't be callable, so a request to make such a call should be an indication that you're looking to pass a message with the attribute name that produced the instance... but there will be some interesting interplay with properties (i.e., things like widget.isHidden vs widget.isHidden()), and the housekeeping required to store the instance that produced the ObjCInstance so that you can pass a message to that instance started to tie my brain in knots.
An inverse of declare_property is also an interesting idea, and I suspect has less brain-bending required to make it work.
I logged this mostly as a warning for others in a similar situation. As I indicated on the ticket, I have a workaround that is complete viable; and your suggestion about invoking the method directly is an even better workaround.
Describe the bug
If an Obj C object has a property, and implements a Protocol with selectors whose first argument matches that property, it is not possible to invoke the selectors.
Steps to reproduce
This was discovered when developing beeware/toga#2025; the iOS DetailedList code in v0.3.1 contains a setup that demonstrates the problem; the corresponding code in #2025 (to be included in v0.3.2) contains a workaround.
To demonstrate the problem: on an instance of DetailedList, use Python code to programmatically select a row on the DetailedList. This would be
[controller.tableView.delegate tableView:controller.tableView didSelectRowAtIndexPath:...]
in ObjC, wherecontroller
is the instance of TogaTableViewController, which maps tocontroller.tableView.delegate(controller.tableView, didSelectRowAtIndexPath:...)
in Rubicon.This will fail on v0.3.1, but pass on the beeware/toga#2025 branch, because of the way the widget is constructed.
The iOS backend uses a UITableViewController with a default UITableView to represent a DetailedList; the UITableViewController has a
tableView
property describing the view that it is controlling.A UITableView can specify a UITableViewDelegate; the delegate responds to a number of selectors, including
tableView:didSelectRowAtIndexPath:
.In v0.3.1, a subclass of UITableViewController was used (
TogaTableViewController
) that also acted as the delegate. When an attempt is made to invoketableView:didSelectRowAtIndexPath:
, you get the error:self.native.delegate
returns the delegate, which is aTogaTableViewController
instance; since this is a subclass of UITableViewController, it has atableView
property, so the PythontableView
attribute is an ObjCInstance, not an ObjCMethod. It doesn't matter if you also explicitly declare the delegate as implementing theUITableViewDelegate
protocol. You also can't pass thetableView:didSelectRowAtIndexPath:
message directly.The beeware/toga#2025 branch fixes this by using a raw UITableViewController, and making the delegate a subclass of NSObject. This removes the ambiguity between
[UITableViewController tableView]
andtableView:didSelectRowAtIndexPath:
This limitation doesn't exist on ObjC code - other bugs notwithstanding, the v0.3.1 branch works, and the UITableView is able to invoke the delegate methods defined in Python, no matter how it's defined. It only emerged in testing when we were programmatically invoking these methods from Python.
This also doesn't affect methods like
tableView:cellForRowAtIndexPath:
which are defined as part of the UITableViewDataSource protocol - or, at least, there's a different workaround. If you explicitly invoke the method on thecontroller.dataSource
, rather than on thecontroller
object directly, the method resolves. This appears to be because the data source is implicitly set by constructing the UITableViewController; if you explicitly set thedataSource
property of the controller, the same problem manifests.Expected behavior
It should be possible to retrieve the
tableView
property and invoke thetableView:didSelectRowAtIndexPath:
selector on a delegate object.Screenshots
No response
Environment
Logs
Additional context
There's probably an argument to be made that this is a documentation issue. Mapping from ObjC to Python is always going to be leaky, and there's workarounds available.
Another approach would be to require explicit casting to the Protocol in this case.
The text was updated successfully, but these errors were encountered: