Sticking a custom back button item into a NavigationView?
Whoo-boy...I am spamming the airwaves this past couple days. Here's another one...in my attempts to get the NavigationView to be a little less half-baked for my purposes, I need a way to know when the back button is pressed. So I thought I'd be clever and insert my own UIBarButtonItem in the navigation bar the navigation controller is using so I could provide a target/action for when it is activated...so I tested it with this:
def backButtonBarItemAction(_self, _cmd): print "target called!" BackButtonBarItemTarget = objc_util.create_objc_class("BackButtonBarItemTarget", methods=[backButtonBarItemAction]) target = BackButtonBarItemTarget.new().autorelease() v1 = ui.View() v1.name = "view1" v2 = ui.View() v2.name = "view2" n = ui.NavigationView(v1) n.push_view(v2) nc = n.objc_instance.navigationController() vc = nc.bottomViewController() ni = vc.navigationItem() print "nav item:",ni print "back item:",ni.backBarButtonItem() UIBarButtonItem = objc_util.ObjCClass("UIBarButtonItem") print "target:",target backitem = UIBarButtonItem.alloc().initWithTitle_style_target_action_( "TEST",0,target,"backButtonBarItemAction") backitem.autorelease() ni.backBarButtonItem = backitem print "custom back item:",backitem print "back item now:",ni.backBarButtonItem() n.present() print "nav item after present:",ni
The strange thing here is that nothing seems to be wrong...the code all works with no tracebacks or errors in objective-c-land. However, while the back item text does change to "TEST", indicating my custom item is there, pressing the back button doesn't trigger my target selector...anyone have any ideas? I'm guessing something in the NavigationView implementation is overriding my target/action.
I believe action must be a selector. I'm not sure if objc_util handles the cast for you.
Try sel('backButton....'). Also, try it with a trailing colon, I forget if that is needed or not.
You could also use my view browser to see if your action is getting properly set
It shouldn't need anything special there...I'm using that same target/action setup for other, similar things and it works fine. Though I did try calling objc_util.sel("backButtonBarItemAction") with and without a trailing colon, but nothing changes.
Let me install your view browser and see what that shows me.
Well...I give up for now.
I can't find any way to insert myself into the back button action...I tried adding my own UIBarButtonItem, and even a UIBarButtonItem with a custom UIButton in it. Can't get anything to call my own target action.
I think the only real way to do this would be to insert my own UIViewController into the hierarchy to see when views are pushed or popped, or to just build my own NavigationView from scratch.
Have you tried using the hidden ui.Button to get an action target, similar to, say, the mapkit example?
Note you were setting the back button of the BOTTOM navigation item. You want the top.
That said, for me only the left bar button worked for me. I used a normal py left button items.
As here, you could check which view is on_screen
draw called when back tapped (or I am too naïve 😢)
import ui class MyView(ui.View): def __init__(self,name): self.name = name def draw(self): if self.name != n.name: if n.name != None: print(n.name,'closed') n.name = self.name v1 = MyView('v1') v2 = MyView('v2') v3 = MyView('v3') n = ui.NavigationView(v1) n.push_view(v2) n.push_view(v3) n.present()
Whoa! Love this community :)
Ok, in reverse order:
@cvp - I thought about it, and did some tests, but on_screen would have to be checked manually, and the problem here is I need to know when a view is popped off the stack, not check at some point in the future if it has been popped - and I'm not willing to mess with an update_interval hack to do that. Also, on_screen would have issues when the navigationview itself hasn't been shown, or is itself hidden, which would require holding a bunch of state.
@JonB - it's actually confusing, but you don't modify the back button on the currently visible view, you modify it on the view below that one, since that's what UIKit uses to populate the back button in the navigation bar. The whole thing is confusing, and Apple's docs are a bit hard to parse, but the applicable info is here: https://developer.apple.com/documentation/uikit/uinavigationcontroller?language=objc
The Left Item For all but the root view controller on the navigation stack, the item on the left side of the navigation bar provides navigation back to the previous view controller. The contents of this left-most button are determined as follows: If the new top-level view controller has a custom left bar button item, that item is displayed. To specify a custom left bar button item, set the leftBarButtonItem property of the view controller’s navigation item. If the top-level view controller does not have a custom left bar button item, but the navigation item of the previous view controller has an object in its backBarButtonItem property, the navigation bar displays that item. If a custom bar button item is not specified by either of the view controllers, a default back button is used and its title is set to the value of the title property of the previous view controller—that is, the view controller one level down on the stack. (If there is only one view controller on the navigation stack, no back button is displayed.)
anyway, it doesn't seem to matter if, in this two-view test stack, I set the back button item of the bottom view, or the left button item of the top view, I don't get my target action called. And the only time I even see the title of the back button change is when I set a custom back button item on the bottom view, as in the example.
@cvp I've tried all the things I can think of to get my custom button or custom action to show up and be called...setting all the applicable flags, sticking custom buttons in...nothing seems to work.
@mikael - what the?! I swear I searched for notifications which would let me know when the navigation view controller changes views...and didn't find any. And nobody seems to mention the existence of those in the discussions of the topic on stackoverflow, etc. but I think you've given me the winner...even though I feel like I'm doing a bit too much via the notification mechanism, it sure comes in handy!
Ahh, yes left button items set on the top view works, but back button would be on the one under the top.. got it.
Funny I thought you had tried and dismissed notifications already, or I would have mentioned it. I guess what we need is a UI.View subclass with a set of delegate decorators that register for notifications, but allow you to write the callback in python...
One other thing I tried but might be worth another look: the back button has a custom order that you can set. In that custom view you could install a ui.Button.
My initial efforts failed here, but I might have already been trying the top view, instead of bottom.
@JonB I only didn't mention/dismissed notifications because none showed up when I searched for it. As it turns out, the "UINavigationControllerDidShowViewControllerNotification" (cripes Apple, I appreciate verbosity, but for goodness sake!) notification is actually technically an undocumented private API, which is generally a no-no. I'll use it for now, since it seems to be my only route without rolling my own navigation controller setup.
@mikael notification for the WIN! Works like a charm, and can be isolated to the specific navigation controller I want to monitor.
@shinyformica ok, never mind.. I guess back button title and appearance only can be changed
If I correctly understand this, already pointed above, it should be possible to not display the back button but to display a left button, at the right of the back button, thus not in the top bar, thus exactly what you want.
But I've tried without success.
@cvp yeah...same here, no success with any of the things that seem like they should let me modify the back or left button items to something I am in control of.
The good news is that @mikael 's pointer to the hidden notification works perfectly, so I'm good to go.