Custom objc popover presentation code, why does it crash?
I've run out of ideas as to why this code crashes on the second display of the popover. I was trying to write custom code for displaying a popover, because I want to control more details of what happens when it is shown/dismissed, but I can't get the actual popover view display portion to be stable...this code crashes pythonista on me every time, almost always on the second attempt to show the popover:
import objc_util import ui def make_cgrect(x,y,w,h): import objc_util return objc_util.CGRect(objc_util.CGPoint(x,y),objc_util.CGSize(w,h)) def getUIViewController(view): import objc_util UIViewController = objc_util.ObjCClass('UIViewController') UIView = objc_util.ObjCClass('UIView') if isinstance(view,ui.View): viewobj = view.objc_instance elif isinstance(view,objc_util.ObjCInstance) and \ view.isKindOfClass_(UIView): viewobj = view elif isinstance(view,objc_util.ObjCInstance) and \ view.isKindOfClass_(UIViewController): viewobj = view viewResponder = viewobj.nextResponder() try: while not viewResponder.isKindOfClass_(UIViewController): viewResponder = viewResponder.nextResponder() except AttributeError: return None return viewResponder def showPopup(sender, popup): import objc_util parentvc = getUIViewController(sender) UIViewController = objc_util.ObjCClass("UIViewController") vc = UIViewController.new() vc.view = popup.objc_instance vc.modalPresentationStyle = 7 # this is the popover style value vc.preferredContentSize = objc_util.CGSize(popup.width, popup.height) popovervc = vc.popoverPresentationController() popovervc.sourceView = sender.objc_instance popovervc.sourceRect = make_cgrect(0,0,sender.width,sender.height) parentvc.presentViewController_animated_completion_(vc,True,None) #### create main view v = ui.View() v.frame = (0,0,400,400) #### create view to show as a popover v2 = ui.View() v2.frame = (0,0,200,50) v2.background_color = (1.0,0.0,0.0,1.0) #### put in a button to trigger the popover b = ui.Button() b.title = "Show Popup" b.frame = (0,0,100,30) b.border_width = 2 b.corner_radius = 3 b.center = (v.width*0.5,v.height*0.5) b.action = lambda s: showPopup(s,v2) #### present the main view v.add_subview(b) v.present(style="sheet")
Everything looks legit to me, and it does work the first time around, though apparently puts things in an unstable state. I must be doing something bad with a reference or something...but I've tried a bunch of different ways of storing things and it always crashes. Any more advanced objc_util gurus want to take a look?
Are you just getting segfault, or is there an objc exception that shows in the exception handler?
When you say attempt to show another popover, do you mean when you run the script again? Or when you dismiss the popover and show another?
For the latter, try sprinkling retain_global everywhere you can. Basically on any objc objects you create.
It's usually a segfault. Sometimes, and you're right...this is probably key, the objective-c exception is: "UIViewControllerHierarchyInconsistency" but I can't figure out where that inconsistency is coming from.
This is, as I said, attempting to present the popover a second time: show popover, dismiss it, show it again, crash. Between script runs, things reset and it will show the first time again without crashing.
It was my understanding that, once the popover view is dismissed, it is removed from the view hierarchy. If it is talking about the "v2" popover content view, even if I explicitly destroy and remake that view, it still crashes. Maybe this is two separate issues? I'll see if I can trace that inconsistency.
maybe some on_main_thread? technicallyi think apple says anything that screws around with view/vc heirarchy should be on the main thread.
another thought, does this happen only when showing the same view as a popover? what if you show popoverview1, then popover a different popoverview2?
Good point...I'll try throwing it on_main_thread explicitly. I'm pretty sure it doesn't crash if I show a different popover each time. It's the attempt to show the same popover more than once which causes it.
ok, seems the problem line is
vc.view = popup.objc_instance
the corresponding exception complains about the suiview being associated with another vc, and suggest removing that association.
so, this seems to work, before setting vc.view:
popup_vc=getUIViewController(popup) if popup_vc: popup_vc.view=None
I'll be honest, I must've gotten lost somewhere in my tests, because...that works!
...but where this code was actually being used, the popup view is created anew each time the popover is shown, so the view used in
vc.view = popupwas being given an unowned and un-presented view...as far as I know. Oh well...I'll see if there was some other crash-causing issue, since this now works reliably.
As usual, @JonB, thank you! Fresh eyes are always appreciated.
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.