I Have UINavigationController in my app, I want to have a right UIBarButtonItem to be shown in all navigation bar that appear in my application. this button will load menu, so I don't want to add this button in every navigation bar manually, also as the function is loading menu, I don't want to copy/past action for this button.
is there any way to handle this in ViewController.h and .m ?
so the button act as a universal bar button item?
What you can do is subclass the navigation controller. Here is an example
#interface NavigationController : UINavigationController
#end
#interface NavigationController () <UINavigationBarDelegate>
#end
#implementation NavigationController
- (void)viewDidLoad
{
[super viewDidLoad];
for (UIViewController* viewController in self.viewControllers){
// You need to do this because the push is not called if you created this controller as part of the storyboard
[self addButton:viewController.navigationItem];
}
}
-(void) pushViewController:(UIViewController *)viewController animated:(BOOL)animated{
[self addButton:viewController.navigationItem];
[super pushViewController:viewController animated:animated];
}
-(void) addButton:(UINavigationItem *)item{
if (item.rightBarButtonItem == nil){
item.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAction target:self action:#selector(action:)];
}
}
-(void) action:(UIBarButtonItem*) button{
}
#end
This cannot be done unless you use a custom view to look like NavigationBar.
By default, NavigationController clears all bar button items when a ViewController is pushed or popped. So for every ViewController, you need to create UIBarButtonItem every time in function
- (void)viewWillAppear:(BOOL)animated
or use can subclass UINavigationController and do as #rp90 answer.
You can add a button that appears in your UINavigationController by adding it in the UINavigationController's delegate - in this example, a singleton helper class.
Swift 3:
class NavHelper: UINavigationControllerDelegate {
static let shared = NavHelper()
func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
let barButtonItem = UIBarButtonItem(image: <yourButtonImage>, style: .plain, target: NavHelper.shared, action: #selector(NavDelegate.handleButton(_:)))
viewController.navigationItem.rightBarButtonItem = barButtonItem
}
func handleButton(_ sender: UIBarButtomItem) {
<yourCode>
}
}
Another option is to make your own navigation bar view as a part of the UIViewController. You can turn off Apple's and build your own. We did this to provide our own controls easier. The only thing you lose is the easy translucency under iOS 7. A lot of apps do this for the same reason we did.
You can create a new class that inherits from UIViewController and in the viewDidLoad method, create a UIBarButtonItem and add it to the navigationItem as a left/right bar item. Let's say this class is called CustomBarViewController:
UIBarButtonItem *barItem = [[UIBarButtonItem alloc] initWithTitle:#"hi" style:UIBarButtonItemStylePlain target:self action:#selector(doSomething:)];
[self.navigationItem setRightBarButtonItem:barItem];
In your existing view controllers, instead of having them inherit from UIViewController in the .h file, you can have them use CustomBarViewController instead:
#interface MyExistingViewController : CustomBarViewController
You can then put actions into the doSomething: method, or have it pass notifications to your existing view controllers.
You can add the button into a ContainerView. Thus it will be on at all times.
Food for thought....
Related
I have a ViewController (with a container view) embedded in a Navigation controller. The container contains a pageViewController with one of the 'pages' being a TableViewController (with UITableView outlet: 'aTableView'). I want to trigger the edit mode in the tableViewController when tapping a custom editButton in the navigation bar. When I create a custom editutton in the tableViewController the edit mode works as expected, but when I use the custom editButton in the navigation bar the setEditing bool value remains zero even when I setEditing to YES in the editButton selector. Here's the code:
ViewController.m
-(void)viewDidLoad {
self.editBtn = [UIButton buttonWithType:UIButtonTypeCustom];
[self.editBtn setFrame:CGRectMake(0, 0, 40, 22)];
[self.editBtn addTarget:self action:#selector(goToToggleEdit:) forControlEvents:UIControlEventTouchUpInside];
UIBarButtonItem *editButton=[[UIBarButtonItem alloc]initWithCustomView:self.editBtn];
self.navigationItem.rightBarButtonItem = editButton;
}
-(void)goToToggleEdit:(id)sender
{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
TableViewController *tvc = [storyboard instantiateViewControllerWithIdentifier:#"aTableViewController"];
if(something==foo){
[tvc toggleEdit];
}
}
aTableViewController.h
#interface aTableViewController : UITableViewController <UITextFieldDelegate> {
IBOutlet UITableView *aTableView;
}
-(void) toggleEdit ;
#end
aTableViewController.m
-(void)toggleEdit {
[aTableView setEditing:YES animated:YES];
NSLog(aTableView.editing ? #"Yes" : #"No"); // --> logss 'No'.
if (aTableView.editing){
//do something
}
else {
//do something else
}
}
How can I efficiently trigger the edit mode in the tableViewController this way?
Edit
#Bagrat Kirakosian pointed out to me that my view hierarchy (Navigation Controller > View Controller (with containter) > Page View Controller (in container) > Table View Controller) might be the problem. I just want to create a Navigation Bar (with an edit button) that is fixed, therefore I can't embed the Table View Controller directly in a Navigation Controller.
Thanks.
UPDATE: Solution
I have accepted #sebastien's solution although both #sebastien's and #Bagrat's solution work great. #Bagrat's answer includes direct access to the Table View Controllers, while #sebastien's solution calls edit mode in the pageViewController. I think, considering the tricky hierarchy, the latter is a bit more secure.
Here is the code for my View Controller that totally work fine. be sure you configure your bar button in the right method of View Controller lifecycle. Also be sure that your #selector is properly implemented in your code.
In the same View Controller put these two blocks of code
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
UIBarButtonItem *rightBarButton = [[UIBarButtonItem alloc] initWithImage:[[UIImage imageNamed:#"edit_icon.png" ] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate] style:UIBarButtonItemStylePlain target:self action:#selector(edit:)];
[rightBarButton setTintColor:[UIColor whiteColor]];
self.navigationItem.rightBarButtonItem = rightBarButton;
}
Afterwards you need also to put your edit selector
-(void)edit:(UIButton *)sender {
// Toggle edit by inverting current edit state
// Also in this block change your right bar button text or image
[self.tableView setEditing:!self.tableView.editing animated:YES];
}
UPDATE 1
After your comment we got to whole another question. You problem is not in the part where you try to call toggle edit. Your problem is the wrong hierarchy of controllers (Navigation Controller > View Controller > Page View Controller > Table View Controller). This might cause a problem. Try to change your controllers like this;
UINavigationController > UIPageViewController > UIViewController(s)
Also it's a good practice to have a UITableView in UIViewController rather than using really dead UITableViewController. Don't forget to connect your tableView IBOutlet (by the way you didn't need it in UITableViewController), also connect datasource and delegate to Files owner. In your MyTableViewVC.h file add this line
#interface MyTableViewVC : UIViewController <UITableViewDataSource, UITableViewDelegate>
After that all your calls will work fine.
UPDATE 2
After analyzing your entire structure I found a mistake that you do every time on the button click.
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
tvc = [[TodolistTableViewController alloc] init];
When you call storyboard every time it's ok but when you do [[TodolistTableViewController alloc] init] you are RE-MAKING the same table view controller every time but not even adding it to your main view. As I told you, your tricky hierarchy might cause difficulties but it has a solution.
In your PageViewController.m make tv1 and tv2 properties in .h file, like so.
#property (strong, nonatomic) UITableViewController *tv1;
#property (strong, nonatomic) UITableViewController *tv2;
Then in the view controller file do this
-(void)toggleEdit:(id)sender
{
PageViewController *current = (PageViewController *)[[self childViewControllers] firstObject];
if ([current isKindOfClass:[PageViewController class]])
{
[((TodolistTableViewController *)[current tv1]) toggleEdit];
}
}
Answer includes all security checks and direct access to your table view controllers, because you might need to change other properties/call functions later.
Now in -(void)toggleEdit:(id)sender you don't re-create your views every time but you catch the ones you already have in your current View Controller.
Good Luck!
Ok, your issue here is that you are trying to access an embedded controller in a wrong way.
You are actually managing 2 differents PageViewController:
The one you generated through your storyboard
The other one you are initiating in your code further
That's why you can't reach the expected result.
First of all, add a new method to your PageViewController:
PageViewController.h:
- (void)editTableAtIndex:(int)index;
PageViewController.m:
- (void)editTableAtIndex:(int)index {
[[self viewControllerAtIndex:index] setEditing:YES];
}
Now, in your main ViewController, access the PageViewController by using childViewControllers:
-(void)toggleEdit:(id)sender
{
PageViewController *pvc = self.childViewControllers[0];
[pvc editTableAtIndex:0];
}
It should be editing your TodoListTableView:
(Please notice that I used [pvc editTableAtIndex:0];, instead you should be calling something like [pvc editTableAtIndex:_PageViewController_current_index_];)
Please forgive me, there are already a ton of questions on how to add a UIBarButtonItem to a NavigationBar programmatically but I just can't seem to get any of the solutions to work in my situation.
Here is the layout of a simple test app I have been working with to learn the UIPageViewController.
I have the page view controller working nicely with three unique viewControllers. I would now like to set unique rightBarButtonItems for each of the view controllers. I can easily set a common barButtonItem in the DetailViewController by simply doing this.
UIBarButtonItem *newButton = [[UIBarButtonItem alloc] initWithTitle:#"Whatever"
style:UIBarButtonItemStylePlain
target:self
action:#selector(doSomething)];
self.navigationItem.rightBarButtonItem = newButton;
Given that I need a unique button for each of the three pages in the pageViewController I am not able to set the button in the detailViewController. I have tried the code above in viewDidAppear of the three controllers but the buttons will not appear. I have also tried creating the button in the same way and then setting it like this with no luck.
DetailViewController *vc = [[DetailViewController alloc] init];
vc.navigationItem.rightBarButtonItem = newButton;
I know I'm close here, I'm just not sure how to specify I need to add a button to the NavigationBar of the NavigationController that is running the detailViewController that my contentViewControllers are embedded in. Any help would be great ESPECIALLY SNIPPETS.
As a side note I have tried a few other methods that have come up problematic.
Setting the button in viewDidLoad rather than viewDidAppear
This will not work because the viewDidLoad method is not called everytime you swipe from one page to the next. The viewDidAppear method is however so I am trying to set the button there. It is possible viewWillAppear is called everytime but I haven't checked.
Setting the button in the UIPageViewController delegate methods
This is problematic if the user flips through the pages to quickly the button will fail to change or fail to appear.
SOLUTION
It turns out I was close… Rather than creating the button in the UIPageViewController methods I simply needed to create a navigationItem property in each of my three view controllers. When the controllers are instantiated in the delegate methods I simply set the navigationItem property equal to that of the detailViewController. That way in my viewDidApper methods I could create the button. Then at viewWillDisappear I set the button to nil. You can also just create the button at viewdidLoad of the DetailViewController and change the text and action in viewDidAppear of the individual viewControllers. Here is a sample.
In the delegate methods
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController
I call a helper method based on the index of the viewController. The helper method instantiates the view controllers when it is called. Once instantiated I set some properties including the navigationItem property.
-(FirstController *)controllerAtIndex:(NSUInteger)index
{
FirstController *fvc = [self.storyboard instantiateViewControllerWithIdentifier:#"FirstPageController"];
fvc.imageFile = self.pageImages[index];
fvc.titleText = self.pageTitles[index];
fvc.pageIndex = index;
fvc.navItem = self.navigationItem;
return fvc;
}
Then in the viewDidAppear of the viewController I just do this
-(void)viewDidAppear:(BOOL)animated
{
UIBarButtonItem *newButton = [[UIBarButtonItem alloc]
initWithTitle:#"Whatever" style:UIBarButtonItemStylePlain target:self action:#selector(doSomething)];
navItem.rightBarButtonItem = newButton;
}
If you need the button to change each time you change the page you look at, it must be done in one of the delegate methods that you have tried. I suggest however, that instead of creating a new button and setting it to the navigation item, you simply change the title.
You can create the button in interface builder and link it to a property in your DetailViewController and then call setTitle:forState with UIControlStateNormal on the button every time you go to a new page.
If you need the button to do something different for each page, I would recommend checking the current page in the buttons action method rather than declaring a new one each time.
You can find the correct navigation item to set buttons/titles on by finding the page controller in one of its paged view controllers, then getting its nav item. e.g.
UIViewController *pageController = [[self.navigationController childViewControllers] lastObject];
pageController.navigationItem.title = #"My page's title";
It's really annoying that self.navigationItem doesn't work!
One of my colleagues experienced the same problem with page view controller.
As far as I understand, you wont' be able to add UIBarButtonItem to navigation bar of member view controllers in UIPageViewController. Reason being 'navigationController' is set to nil for UIPageViewController. To confirm, print value of [UIPageViewController navigationController].
However you can do one thing to overcome this issue. Please download example PhotoScroller.
And do following changes in AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// kick things off by making the first page
PhotoViewController *pageZero = [PhotoViewController photoViewControllerForPageIndex:0];
UINavigationController * nav = [[UINavigationController alloc] initWithRootViewController:pageZero];
if (pageZero != nil)
{
// assign the first page to the pageViewController (our rootViewController)
UIPageViewController *pageViewController = (UIPageViewController *)self.window.rootViewController;
pageViewController.dataSource = self;
[pageViewController setViewControllers:#[nav]
direction:UIPageViewControllerNavigationDirectionForward
animated:NO
completion:NULL];
}
return YES;
}
And following changes in the PhotoViewController.m
- (void)loadView
{
.....
.....
self.title =#"Frogs";
UIBarButtonItem *newButton = [[UIBarButtonItem alloc] initWithTitle:#"Something" style:UIBarButtonItemStylePlain target:self action:#selector(doSomething)];
[newButton setTintColor:[UIColor redColor]];
self.navigationItem.rightBarButtonItem = newButton;
}
To avoid crash, you have to handle UIPageViewControllerDataSource delegate methods correctly in AppDelegate.m
Though, I wouldn't advice you to do above as it breaks the whole concept of UIPageViewController.
I hope this would be helpful.
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).
In a non-IUSplitViewController app, I am able to suppress the default back bar animation by adding this to my UIApplicationDelegate class header:
#interface MyNavigationBar : UINavigationBar { } #end
#interface MyNavigationController : UINavigationController { } #end
along with this in the corresponding .m:
#implementation MyNavigationController
- (UIViewController *)popViewControllerAnimated:(BOOL)animated
{
return( [super popViewControllerAnimated:NO] );
}
#end
#implementation MyNavigationBar
- (UINavigationItem *)popNavigationItemAnimated:(BOOL)animated
{
return( [super popNavigationItemAnimated:NO] );
}
#end
Of course I also assigned the Navigation Controller and Navigation Bar objects in MainWindow.xib to MyNavigationController and MyNavigationBar respectively in Interface Builder.
This works like a charm in a standard application.
My problem is achieving the same thing in a UISplitViewController app.
Specifically, I cannot figure out how to override the default behavior of UINavigationBar in that case in order to suppress animation of the navigation bar when a view controller is popped via the back bar button.
I can override the behavior of UINavigationController by doing this whenever I instantiate a UIViewController as the root of the UISplitViewController right pane:
[split is a pointer to my UISplitViewController]
MyNavigationController *nc = (MyNavigationController *) [split.viewControllers objectAtIndex:1];
nc = [[[MyNavigationController alloc] initWithRootViewController:someController] autorelease];
split.viewControllers = [NSArray arrayWithObjects: [split.viewControllers objectAtIndex:0], nc, nil];
split.delegate = someController;
To recap, when I hit the back bar button in my UISplitViewController app, the content area of the active view controller does not animate when popped via the back bar button, but the navigation bar does animate, which looks dopey.
I found the solution for the standard application case in this forum, but saw no mention of a UISplitViewController solution.
I tried overriding initWithCoder in MyNavigationController to assign an instance of MyNavigationBar to the navigationBar attribute, but it wouldn't let me since it is read-only.
Stumped.
I have a split-view app with a button in the detail view that, when clicked, will take the user to a full screen view of the selected image.
I understand that I need a new nib file and view controllers, but I'm not sure how to connect these new files with my existing RootViewController and DetailViewController files.
I know this is really vague, but any help at all would be most appreciated.
As long as you are moving to a single view(as in not another split view) you should only need one more view controller. If I understand what you are doing, then the progression should be something along the lines of:
Create a new view controller with associated .xib file.
Declare the specific instance of your new view controller, in this case called newViewController, in DetailViewController.h and synthesize it in DetailViewController.m
#interface DetailedViewController
{
NewViewController *newViewController;
}
#property (nonatomic, retain) NewViewController *newViewController;
#end
Add your IBAction to the header file of DetailViewController, this will be the function responsible for actually triggering your view switching
Implement the view switch action in your DetailViewController.m file, should look something like this:
(IBAction)switchToNewView:(id)sender
{
if (newViewController == nil)
{
NewViewController *newViewController =
[[NewViewController alloc]
initWithNibName:#"NewViewController"
bundle:[NSBundle mainBundle]];
self.newViewController = newViewController;
}
// How you reference your navigation controller will
// probably be a little different
[self.navigationController
pushViewController:self.newViewController
animated:YES];
}
Then in your DetailViewController.m file inside of the viewDidLoad function add the following:
UIBarButtonItem *addButton =
[[UIBarButtonItem alloc]
initWithBarButtonSystemItem: UIBarButtonSystemItemAdd
target:self action:#selector(switchToNewView:)];
self.navigationItem.rightBarButtonItem = addButton;
[addButton release];
The other option that you have if you choose to implement this through a UIButton, is to go create the button in Interface Builder on your NewViewController.xib, then select it, and in the Connections inspector, create a link between the "touchUpInside" event and the file owner, and then select your switchToNewView IBAction. This should accomplish the same thing.
Thats the general idea. I hope that helps!
EDIT: As asked in the comments, if adding a button as a UIBarButton as part of a navigation controller you would simply need to do something like below:
UIBarButtonItem *addButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:#selector(switchToNewView:)];
self.navigationItem.rightBarButtonItem = addButton;
[addButton release];