I'm adding a UINavigationItem with a save button to the right side, and a back button on the left side to my UINavigationBar. I am setting my view controller to be the delegate, and the navigation bar is calling some delegate functions, but not -(BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item.
In ISStudioReviewViewController.h:
#interface ISStudioReviewViewController : UIViewController <UINavigationBarDelegate, UITextViewDelegate>
#property (weak, nonatomic) IBOutlet UINavigationBar *header;
#end
In ISStudioReviewViewController.m:
-(void)viewDidLoad {
[super viewDidLoad];
...
...
UINavigationItem *navHeader = [[UINavigationItem alloc] initWithTitle:#"Compose Review"];
[navHeader setHidesBackButton:NO];
UIBarButtonItem *actionButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemSave
target:self
action:#selector(saveReview)];
[navHeader setRightBarButtonItem:actionButton];
[header setDelegate:self];
[header pushNavigationItem:navHeader animated:NO];
}
-(BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item
{
NSLog(#"Popping item");
....
return YES;
}
-(BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPushItem:(UINavigationItem *)item
{
NSLog(#"Should push item");
return YES;
}
-(void)navigationBar:(UINavigationBar *)navigationBar didPushItem:(UINavigationItem *)item
{
NSLog(#"Pushing item!");
}
In my logs I can see that the shouldPush and didPush delegate methods are being called correctly, and my back button is being displayed. However clicking on the back button is not calling the delegate method, and Popping item is never logged.
I should note that the save button actionButton does properly call it's target without issue. I have tried not adding this button, and seeing if that is somehow causing the back button to fail, but no luck.
I am using ARC and targeting iOS 6.0.
The Apple Documentation states that you are not allowed to do this delegate.
In addition, a navigation controller object automatically assigns itself as the delegate of its UINavigationBar object and prevents other objects from changing that relationship.
One/The? way to do what you want is to put in your own back-button.
In that Method you do your tests and call
[self.navigationController popViewControllerAnimated:true];
if the user is allowed to go back.
Related
I need to hide an UIImageView, from an action triggered by a UISwitch which is inside a popover.
I'm using this piece of code however it does nothing when tapping the UISwitch, probably because the UISwitch its inside a popover view.
This code works perfectly on iPhone, however on iPad does not work and the UIImageView does not hide. Why?
- (IBAction)toggleImage:(id)sender {
if ([sender isOn]){
self.myImage.hidden = NO;
} else {
self.myImage.hidden = YES;
}
}
UIImageView is connected to an outlet and UISwitch is connected to an outlet and action.
Please help, thank you.
Since the switch being interacted with is on the popover and the image view that we want to change is on the underlying (presenting) VC, the proper approach is to make the underlying VC a delegate of the popover.
// MyPopoverVC.h
#protocol PopoverDelegate <NSObject>
- (void)popover:(MyPopoverVC *)vc changedSwitchTo:(BOOL)on;
#end
#interface MyPopoverVC : UIViewController
#property (nonatomic, weak) id<PopoverDelegate>delegate;
// ...
#end
In the Popover VC's implementation (IMPORTANT: the switch's IBAction should be wired to the popover vc)...
- (IBAction)toggleImage:(UISwitch *)sender {
[self.delegate popover:self changedSwitchTo:sender.on];
}
In the presenting vc, declare it as conforming to that <PopoverDelegate> protocol. Before presenting the popover, initialize the delegate...
MyPopoverVC *myPopoverVC = [[MyPopoverVC alloc] init...
myPopoverVC.delegate = self;
Also in the presenting vc, implement the delegate protocol...
- (void)popover:(MyPopoverVC *) changedSwitchTo:(BOOL)on {
self.myImage.hidden = !on;
}
I have trouble w/ hiding toolbar when leave screen by segue.
Details: App has a few dozen screens, all of them are belonged the same navigation controller. A few screens have toolbar. For these a few screens in -(void)viewDidLoad I use
[self.navigationController setToolbarHidden:NO animated:NO];
and in -(void)viewWillDisappear:(BOOL)animated:
[self.navigationController setToolbarHidden:YES animated:YES];
so toolbar is visible only on necessary screens and the screen which needs toolbar controls the visibility. All work well when I navigate by "back" button.
The trouble starting when I try to navigate by segue like this (goto home & goto another branch).
[owner.navigationController popToRootViewControllerAnimated:NO];
[self performSegueWithIdentifier:SEGUE_NAME sender:self];
toolbar is stay visible in spite of calling -(void)viewWillDisappear which should hide toolbar.
are there any ideas how to perform these "ToolBarHidden" by right way.
thanks
PS: of course I could hide toolbar on every screen, but I want to avoid this unnecessary operations and want to know how to do it right.
**STEP1:** in your controller.h
#interface ViewController : UIViewController {
UIToolbar *bar;
}
#property (nonatomic, strong) IBOutlet UIToolbar *bar;
#end
**STEP2:** in your controller.m
#synthesize bar;
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
self.bar.hidden = NO;
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
self.bar.hidden = YES;
}
**STEP3:** connect in Intrface
hope this help you!
Due to a weird request which I tried to turn down but it didn't work, I had to override the navigationBar's back Button.
I have made a custom UINavigationController subclass and hacked the
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item method.
Here is my code:
#interface CustomUINavigationController ()
#end
#implementation CustomUINavigationController
#pragma mark - UINavigationBar delegate methods
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item {
if ([[self.viewControllers lastObject] isKindOfClass:[ViewController1 class]]) {
ViewController1 *vc1 = (ViewController1 *)[self.viewControllers lastObject];
[vc1 handleBackAction];
if (vc1.canPopVC == YES) {
[self popViewControllerAnimated:YES];
return YES;
} else {
return NO;
}
}
[self popViewControllerAnimated:YES];
return YES;
}
#end
All works fine, except when I pop a viewController programmatically. The app crashed every time when I wanted to perform a push after said pop. Turning NSZombie on, revealed that when popping a viewController programmatically, its parent viewController is deallocated.
At this point, making a custom backButton is not a option since it will lose the native iOS 7 swipe to popViewController feature.
Crash log:
*** -[ContactsDetailViewController performSelector:withObject:withObject:]: message sent to deallocated instance 0x1806b790
(My previous post was completely wrong. This is a complete rewrite with an appropriate solution.)
I had this behavior pop up when I chose to delete some code generating a warning when I was converting to ARC -- code that I thought was not being called.
Here's the situation:
If you shadow navigationBar:shouldPopItem: in a subclass of UINavigationController, then the current view controller will NOT be popped when the user touches the NavBar's BACK button. However, if you call popViewControllerAnimated: directly, your navigationBar:shouldPopItem: will still be called, and the view controller will pop.
Here's why the view controller fails to pop when the user touches the BACK button:
UINavigationController has a hidden method called navigationBar:shouldPopItem:. This method IS called when the user clicks the BACK button, and it is the method that normally calls popViewControllerAnimated: when the user touches the BACK button.
When you shadow navigationBar:shouldPopItem:, the super class' implementation is not called, and hence the ViewController is not popped.
Why you should NOT call popViewControllerAnimated: within your subclass' navigationBar:shouldPopItem::
If you call popViewControllerAnimated: within navigationBar:shouldPopItem:, you will see the behavior that you desire when you click the BACK button on the NavBar: You can determine whether or not you want to pop, and your view controller pops if you want it to.
But, if you call popViewControllerAnimated: directly, you will end up popping two view controllers: One from your direct call to popViewControllerAnimated:, and one from the call you added to within navigationBar:shouldPopItem:.
What I believe to be the safe solution:
Your custom nav controller should be declared like this:
#interface CustomNavigationController : UINavigationController <UINavigationBarDelegate>
{
// .. any ivars you want
}
#end
Your implementation should contain code that looks something like this:
// Required to prevent a warning for the call [super navigationBar:navigationBar shouldPopItem:item]
#interface UINavigationController () <UINavigationBarDelegate>
#end
#implementation CustomNavigationController
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item
{
BOOL rv = TRUE;
if ( /* some condition to determine should NOT pop */ )
{
// we won't pop
rv = FALSE;
// extra code you might want to execute ...
} else
{
// It's not documented that the super implements this method, so we're being safe
if ([[CustomNavigationController superclass]
instancesRespondToSelector:#selector(navigationBar:shouldPopItem:)])
{
// Allow the super class to do its thing, which includes popping the view controller
rv = [super navigationBar:navigationBar shouldPopItem:item];
}
}
return rv;
}
I'm not 100% certain but I don't think you should actually be popping the view controller in that delegate method.
"should" delegate methods don't normally do something. They just assert whether something should or shouldn't be done.
Change your method to this...
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item {
if ([[self.viewControllers lastObject] isKindOfClass:[ViewController1 class]]) {
ViewController1 *vc1 = (ViewController1 *)[self.viewControllers lastObject];
[vc1 handleBackAction];
if (vc1.canPopVC == YES) {
return YES;
} else {
return NO;
}
}
return YES;
}
And see if it works.
All I have done is removed the popViewController calls.
EDIT - How to add a custom back button
In a category on UIBarButtonItem...
+ (UIBarButtonItem *)customBackButtonWithTarget:(id)target action:(#SEL)action
{
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
[button setBackgroundImage:[UIImage imageNamed:#"Some image"] forState:UIControlStateNormal];
[button setTitle:#"Some Title" forState:UIControlStateNormal];
[button addTarget:target action:action forControlEvents:UIControlEventTouchUpInside];
UIBarButtonItem *barButton = [[UIBarButtonItem alloc] initWithCustomView:button];
return barButtonItem;
}
Now whenever you want to set a custom back button just use...
UIBarButtonItem *backButton = [UIBarButtonItem customBackButtonWithTarget:self action:#selector(backButtonPressed)];
I would suggest a completely different approach.
Create a base class for the view controllers that you are pushing on the navigation stack. In the viewDidLoad method set your custom button as the leftBarButtonItem of the navigationItem and add a -backAction: which invokes the popViewControllerAnimated: method of the navigation controller.
That way you won't care about things like losing functionality of UINavigationController like the swipe to pop and you won't have to override the navigationBar:shouldPopItem: method at all.
You probably need to do [super shouldPop... instead of actual [self popViewControllerAnimated:YES];.
The reason being that the way UINavigationController implements stack is private, so you should mess with the method calls as little as possible.
Anyway, this looks like a hack. Moreover, the user will have no visual clue that you are blocking the navigation action. What's wrong with disabling the button via:
self.navigationController.navigationItem.backBarButtonItem.enabled = NO;
It's my fix to #henryaz answer for Xcode 11:
#interface UINavigationControllerAndNavigationBarDelegate : UINavigationController<UINavigationBarDelegate>
#end
#interface CustomNavigationController : UINavigationControllerAndNavigationBarDelegate
#end
// changed this method just a bit
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item {
BOOL shouldPop = // detect if need to pop
if (shouldPop) {
shouldPop = [super navigationBar:navigationBar shouldPopItem:item]; // before my fix this code failed with compile error
}
return shouldPop;
}
I am trying to implement a push-up UINavigationBar, where the position of the navigation bar is attached to the contentOffset of the UIScrollView (similar to how safari works in ios7).
In order to get the dynamic movement working I am using a UINavigationBar created programatically and added as a subview of the UIViewController's view (it is accessible as self.navbar).
The UIViewController is within a UINavigationController hierarchy, so I am hiding the built-in self.navigationController.navigationBar at the top of -viewWillAppear:.
The problem I am trying to solve is to add a back button to this new standalone navbar. I would preferably like to simply copy the buttons or even the navigationItems from the navigationController and its hidden built-in navbar, but this doesnt seem to work
Is my only solution to set leftBarButtonItem on my standalone navbar to be a fake back button (when there is a backItem in the navController's navbar)? This seems a bit hacky, and I'd rather use the built backButton functionality.
Another way to do that, once you have your own UINavigationBar set, is to push two UINavigationItems on your navigationBar, causing back button to appear. You can then customize what happens when the back button is pressed.
Here's how I did that
1 - Some UINavigationItem subclass, to define extra-behavior / customization parameters
#interface MyNavigationItem : UINavigationItem
//example : some custom back action when 'back' is pressed
#property (nonatomic, copy) void (^onBackClickedAction)(void);
#end
2 - Then wire that into your UINavigationBarDelegate :
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item
{
if ([item isKindOfClass:[MyNavigationItem class]]) {
MyNavigationItem *navItem = (MyNavigationItem *)item;
//custom action
if (navItem.backAction) {
navItem.backAction();
}
return YES;// return NO if you don't want your bar to animate to previous item
} else {
return YES;
}
}
You could adapt that scheme, calling your UINavigationController pop method on back action.
This is still hacky
Vinzzz' answer was a good solution. Here is my implementation, as the context was slightly different.
In the UIViewController's viewDidLoad method I setup my navbar's navigation items like this:
NSMutableArray* navItems = [#[] mutableCopy];
if (self.navigationController.viewControllers.count > 1)
{
NSInteger penultimateIndex = (NSInteger)self.navigationController.viewControllers.count - 2;
UIViewController* prevVC = (penultimateIndex >= 0) ? self.navigationController.viewControllers[penultimateIndex] : nil;
UINavigationItem* prevNavItem = [[UINavigationItem alloc] init];
prevNavItem.title = prevVC.title;
[navItems addObject:prevNavItem];
}
UINavigationItem* currNavItem = [[UINavigationItem alloc] init];
... <Add any other left/right buttons to the currNavItem> ...
[navItems addObject:currNavItem];
[self.navbar setItems:navItems];
...where self.navbar is my floating stand-alone UINavigationBar.
I also assign the current view controller to be self.navbar's delegate, and then listen for the -navigationBar:shouldPopItem: event that is triggered when the back button is pressed:
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item
{
if (navigationBar == self.navbar)
{
[self.navigationController popViewControllerAnimated:YES];
return NO;
}
return YES;
}
(If you return YES, it will crash when a swipe gesture is used in ios7).
Why UISearchBar does not get Focus and keyboard does not appear when UIViewController is pushed to navigation controller for the 2nd time? It seems as no element on the whole view has a focus.
MY USE CASE: I have 2 View Controllers: A and B. Transition is A->B. A, B are pushed to navigation controller.
When I show B for the first time focus is set to the searchbar and keyboard appears.OK.
Then I go back to the A. And again from A->B. Now for the second time there is no focus and keyboard does not appear. It is WRONG.
MY CODE: On the B UIViewController IBOutlet connection to the searchbar is created:
#property (weak, nonatomic) IBOutlet UISearchBar *mySearchBar;
Delegate of the mySearchBar is also set.
I set focus on the searchbar in B UIViewController in viewDidAppear method:
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
[self performSelector:#selector(setCorrectFocus) withObject:NULL afterDelay:0.2];
}
-(void) setCorrectFocus {
[self.mySearchBar becomeFirstResponder];
}
Transitions between controllers are done manually in code like this:
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"myStoryboard" bundle:nil];
AController *a = [storyboard instantiateViewControllerWithIdentifier:#"A"];
[self.navigationController pushViewController:a animated:NO];
Try this
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self setCorrectFocus];
}
-(void)setCorrectFocus
{
[self.mySearchBar becomeFirstResponder];
}
After long searching I found Error. Somewhere in storyboard old connection for UISearchBar with the name mySearchbar was left. Another thing that I had to correct was that the method
- (BOOL)searchBarShouldEndEditing:(UISearchBar *)searchBar;
returned NO, every time I went back to previous Controller. But it should return YES to be able to set focus on search bar next time.
Was strugling with the same - had a UISearchBar inside a UITextView's keyboard accessory and even after [self.searchBar becomeFirstResponder] was called, keyboard input was still received by the text view.
This is what finally helped:
[self.searchBar.window makeKeyAndVisible];
Apparently the keyboard accessory is part of a different UIWindow than the UITextField.
Note: To continue typing in the text view, you should then call something like [self.textView.window makeKeyAndVisible]; ...
Have you tried adding self.mySearchBar.delegate = self;
You should use strong in your property
#property (strong, nonatomic) IBOutlet UISearchBar *mySearchBar;
Always set strong in your property for IBOutlet. When you pop B from navigationController, mySearchBar has released, so you push back to B, [self.mySearchBar becomeFirstResponder] is not work.