How to access the toolbar in a container view controller from its children? - objective-c

I have the following view controller structure in my app:
A container view controller with a UIToolBar (which is not a navigation bar) has a UIPageViewController embedded and this UIPageViewController presents additional child view controllers:
container view controller (toolBar) -> PageViewController -> ViewController 1,
ViewController 2
I would like to add and remove buttons (BarButtonItems) to and from the toolbar on the container view controller depending on the child view controllers 1 and 2 presented.
How would you recommend that the child view controllers 1 and 2
access the toolbar in the container view controller to add and
remove buttons?
It seems that the toolbarItems property in the container view
controller is always 0 even though there are buttons in it. Any
ideas on why this could be? Is the toolbarItems property <> 0
only for navigation bars?
Edit
Based on the suggestion from user DBD I have added the following methods to the view controller CompanyViewController which has the toolbar and is the base class for my view controllers in a split views detail window (all my detail view controller inherit from CompanyViewController). One of these detail view controllers is used as a content view controller with a page view controller and the child view controllers described above.
CompanyViewController:
- (void)addToolBarItems:(NSArray *)buttonArray
{
NSMutableArray *items = [[self.toolbar items] mutableCopy];
[buttonArray enumerateObjectsUsingBlock:^(UIBarButtonItem *barButtonItem, NSUInteger idx, BOOL *stop) {
if (! [items containsObject:barButtonItem] ) [items insertObject:barButtonItem atIndex:0];
}];
[self.toolbar setItems:items animated:NO];
}
- (void)removeToolBarItems:(NSArray *)buttonArray;
{
NSMutableArray *items = [[self.toolbar items] mutableCopy];
[buttonArray enumerateObjectsUsingBlock:^(UIBarButtonItem *barButtonItem, NSUInteger idx, BOOL *stop) {
if ( [items containsObject:barButtonItem] ) [items removeObjectAtIndex:idx];
}];
[self.toolbar setItems:items animated:NO];
}
This is how I try to access the CompanyViewController from my child controllers:
- (CompanyViewController*)parentViewControllerWithToolbar
{
UIViewController *parentPageViewController = self.parentViewController;
CompanyViewController *parentContentViewController = (CompanyViewController*)parentPageViewController.parentViewController;
return (CompanyViewController*)parentContentViewController;
}
However, when trying to call the addToolBarItems method from the child view controller I can't get the method in Xcodes's autocomplete: It seems that I have no access to this method even though I imported #import "CompanyViewController.h".
Any suggestions on what I might be missing here?

I would suggest not accessing the toolbar directly. Instead, I'd suggest the container class having public methods like
- (void)addToolBarItem:(UIBarButtonItem *)button atIndex:(int)index;
- (void)removeToolBarItemAtIndex:(int)index;
Then all your child has to do is get the parent, possibly do a type cast and call the method.
Encapsulating your UI changes like this remove the need for potentially duplicate code in your child classes to modify the toolbar, protect much of your code from possible API changes by Apple and allow you to easily decide to swap out the tool bar to something like a custom control in the future without causing ripple code change effects.
EDIT:
I thought you were using a container view controller, not a UISplitViewController. I'm going to re-state my understanding so you can tell me if I got it wrong.
You have a UISplitViewController displaying a "master" and "detail" views. You want the detail view to call back to the master and request a toolbar change.
In detail view controller, you want the master, but calling "parent" just gives you the UISplitViewController. So you have to access the split view controller and get the master view controller from there.
MasterViewController *foo = [self.splitViewController.viewControllers objectAtIndex:0];
[foo removeToolBarItems:bar];
Which says, go get the UISplitViewController which I belong to (same as calling parent), then access the array of view controllers it holds. Take the first item in the array and assign as your master view controller. From the docs on the UISplitViewController
viewControllers
The array of view controllers managed by the receiver.
#property(nonatomic, copy) NSArray *viewControllers
Discussion
The
array in this property must contain exactly two view controllers. The
view controllers are presented left-to-right in the split view
interface when it is in a landscape orientation. Thus, the view
controller at index 0 is displayed on the left side and the view
controller at index 1 is displayed on the right side of the interface

Related

UIPageViewController within NavigationController

I've read every tutorial I've found about UIPageViewController, but they show just basics, I'd like to create something like new twitter app has:
UIPageViewController is embedded into Navigation controller, title of navigation bar is based on current page and those page dots are there as well, user can tap on item on current page(item from table view/collection view) to see detail.
I was able to come up with something similar, each page had collection view, and showing detail of some item was reflected in navigation bar, there was correct title and "<" button, but I wasn't able to change title based on currently shown page
Please, could you describe me how to do this in few steps/basic structure of controllers?
Don't know if you are still working on this but here goes anyway. To set up a UIPageViewController you might use the tutorial and two questions below.
http://www.appcoda.com/uipageviewcontroller-storyboard-tutorial/
How to implement UIPageViewController that utilizes multiple ViewControllers
How to add UIBarButtonItem to NavigationBar while using UIPageViewController
The last link pertains specifically to setting the contents of the navigationBar depending on what you are viewing.
The key is to create a UINavigationItem Property in the .h file of your UIPageViewController content view controllers, meaning the ones/one that are displaying whatever it is you are displaying.
From my code in FirstViewController.h SecondViewController.h and ThirdViewController.h
#property (strong, nonatomic) UINavigationItem *navItem;
In the second and third links above you'll see a storyboard layout of a Master-Detail application (which uses a navigation controller). The UIPageViewControllerDataSource is the DetailViewController. The three pages associated with the pageViewController are my content view controllers.
In DetailViewController.m you have to instantiate the contentViewControllers somewhere. At that point you pass the DetailViewControllers navigationItem id to the content view controllers. Here is how I instantiate my content view controllers using the delegate methods of the UIPageViewController.
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController
{
NSString * ident = viewController.restorationIdentifier;
NSUInteger index = [_vc indexOfObject:ident];
if ((index == 0) || (index == NSNotFound)) {
return nil;
}
index--;
if (index == 0) {
return [self controllerAtIndex:index];
}else if (index == 1){
return [self secondControllerAtIndex:index];
}else if (index == 2){
return [self thirdControllerAtIndex:index];
}else{
return nil;
}
}
The delegate method calls the method below. It is almost directly from the tutorial link with just a few modifications.
-(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;
}
Notice that properties are passed into the view controller including self.navigationItem. Passing it in ensures you can make changes to the navigationBar items.
Then in the viewDidAppear method of your content view controller you can simply set the title on the navigation bar like this.
navItem.navigationItem.title = #"Whatever you want the title to be";
It is important to use viewDidAppear because viewDidLoad is not called every time the screen appears. I believe the UIPageViewController caches the page before and the page after whatever you are viewing which saves the system from having to load the page every time you navigate to it.
If you are using a single view controller for all you pages like the tutorial does you will have to use the index property to know what to set the title to.
Hope this helps!
I had a very similar setup and solved the problem.
My setup is that I have a UIPageViewController inside a UINavigationController because I wanted the navigation bar to be persistent while I swiped between each view controller. I wanted the title of the current UIViewController inside the UIPageViewController to become the title of the UINavigationController.
The way to do this is to implement the UIPageViewControllerDelegate method pageViewController didFinishAnimating which triggers after a change to a view controller is made from the UIPageViewController. You can probably see where this going: From here, set the navigationItem.title property of the UIViewPageController, which the UINavigationController uses to set it's own title, with that of the current view controller's title.
Example:
- (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed
{
if(finished && completed)
{
NSString *titleOfIncomingViewController = [[pageViewController.viewControllers firstObject] title];
pageViewController.navigationItem.title = titleOfIncomingViewController;
}
}
NB: This delegate method triggers only off gesture-initiated scrolls.
Solution given by Mr. Micnguyen is the exact solution but the mentioned delegate method didFinishAnimating() is called when swipe action is done due to which initially title is not shown.
Hence to resolve that problem, we need to set its initial value in viewDidLoad() method of UIPageViewController class as mentioned below:
- (void)viewDidLoad(){
self.navigationitem.title = arrayname[0];
}
I had this question too, but ended up doing something different, because I'm using Swift, so I thought I'd share here.
I ended up embedding a UIPageViewController in a Container View in my Navigation Controller. On a page swipe, I used pageViewController(_:didFinishAnimating:previousViewControllers:transitionCompleted:), with my PageViewController as the UIPageViewDelegate. From there I created a protocol that I used to send data about the VC displayed to the parent VC, and change the title using self.title = "My Title".
I didn't make the parent VC the UIPageViewDelegate because I found it to be easier to access the displayed VC from the PageViewController than from the parent VC as let childVC = pageViewController.viewControllers![0] as! DetailViewController.

How to implement more than one controller in objective C

First of all I don't know If controller is the right word. What I want to achieve is this.
#interface ClubViewController : CoreDataTableViewController :NRGridViewController
{
I know that this is not possible in objective-C. But is there a way to work around this?
Because I want to use CoreDateTableViewController and NRGridViewController.
Kind regards
Stef
EDIT
This is how my storyboard Hierarchy looks like.
-ViewController
-TableView
-View
-TableViewCell
So I have a tableview Controller but above this tableview controller you find a small view with three buttons. When I push on button 1 I want to take the tableview away and draw a gridView with the NRGridview Controller. But when I push on button 2 and 3 I fill up my tableview using the CoreDataTableViewController.
I hope this explains more my problem.
I think one way to do this is with a container view with a container view controller inside it. That container controller would have 2 child controllers which would be your CoreDateTableViewController and NRGridViewController. I've implemented something like this, and I can show you some code if you're interested.
After Edit: In a test app, I started with a single view template and a storyboard. I added two buttons to the top of the view and a container view to the bottom half of the view (this first controller is of class ViewController). I then dragged out a new view controller, and control dragged from the container view to the new controller and chose the "embed segue" (this will resize the view to be the same size as the container view). The class of this controller was changed to my subclass, ContainerController. I then created 2 more controllers for the 2 views that will be managed by the container controller (the views need to have their size set to "freeform" in IB so you can set the size to be the same as the container view). Here is the code in ContainerController:
- (void)viewDidLoad
{
[super viewDidLoad];
self.cont1 = [[FirstController alloc]initWithNibName:#"FirstView" bundle:nil];
self.cont2 = [[SecondController alloc]initWithNibName:#"SecondController" bundle:nil];
[self addChildViewController:self.cont1];
self.currentController = self.cont1;
[self.view addSubview:self.cont1.view];
}
-(void)switchToFirst {
if (self.currentController != self.cont1) {
[self addChildViewController:self.cont1];
[self moveToNewController:self.cont1];
}
}
-(void)switchToSecond {
if (self.currentController != self.cont2) {
[self addChildViewController:self.cont2];
[self moveToNewController:self.cont2];
}
}
-(void)moveToNewController:(id) newController {
[self.currentController willMoveToParentViewController:nil];
[self transitionFromViewController:self.currentController toViewController:newController duration:.6 options:UIViewAnimationOptionTransitionFlipFromLeft animations:^{}
completion:^(BOOL finished) {
[self.currentController removeFromParentViewController];
[newController didMoveToParentViewController:self];
self.currentController = newController;
}];
}
The only code I have in ViewController are the IBActions for the 2 buttons that switch the views. Those methods just call methods in the container controller:
-(IBAction)chooseFirstController:(id)sender {
[self.childViewControllers.lastObject switchToFirst];
}
-(IBAction)chooseSecondController:(id)sender {
[self.childViewControllers.lastObject switchToSecond];
}
What you are trying to do in your code is creating a class that is a subclass of multiple other classes, which is not possible. If you really want to do this, check out this question: Inherit from two classes
If you are trying to create multiple instances:
CoreDataTableViewController and NRGridViewController are just classes, which you will have to instantiate to get an actual object.
You can instantiate e.g. an NRGridViewController using
NRGridViewController *controller=[[NRGridViewController alloc] init];
I hope this answers your question, it is a bit difficult to understand your question.
Instead of taking tableViewController, take normal TableView (drag and drop from the storyboard to the particular position on the view).
when the button 1 is pressed Make the Table View hidden in the buttons action method. and initialize the grid view / or set grid view hidden to NO. (all views have the property of hidden in ios)
when you press on the 2nd and 3rd button set the grid view hidden and set tableview hidden equal to NO. and fetch the coredata and store it in array or dictionary or you can reload the tableview.
(Initially, before pressing the button 2 & 3 , the table view has no values. so you can set a bool property that when you press the button 2 or 3 set the bool and use the bool to reload your tabe view )
if you did not get my explanation ping me back.

Container View Controllers pre iOS 5

iOS 5 adds a nice feature allowing you to nest UIViewControllers. Using this pattern it was easy for me to create a custom alert view -- I created a semi-transparent view to darken the screen and a custom view with some widgets in it that I could interact with. I added the VC as a child of the VC in which I wanted it to display, then added its views as subviews and did a little animation to bring it on the screen.
Unfortunately, I need to support iOS 4.3. Can something like this be done, or do I have to manage my "alert" directly from the VC in which I want to display it?
MORE INFO
So if I create a custom view in a nib whose file owner is "TapView" and who has a child view that is a UIButton. I tie the UIButton action to a IBAction in TapView.
Now in my MainControllerView I simple add the TapView:
TapView *tapView = [[TapView alloc] init];
[[self view] addSubview:tapView];
I see my TapView, but I can't interact with the UIButton on it and can interact with a UIButton on the MainControllerView hidden behind it. For some reason I am not figuring out what I'm missing...
Not sure if this helps, but, in situations where I've needed more control over potential several controllers, I've implemented a pattern where I have a "master" controller object (doesn't need to be descendent from UIViewController), which implements a delegate protocol (declared separately in it's own file), and then have whatever other controllers I need to hook into declare an object of that type as a delegate, and the master can do whatever it needs to do in response to messages from the controllers with the delegate, at whatever point you need; in your case, that being displaying the alert and acting as it's delegate to handle the button selection. I find this approach to be very effective, simpler and usually cleaner. YMMV ;-)
Regd. your second query, where you are trying to create a custom view using nib. Don't change the FileOwner type, instead set "TapView" for the class property of the top level view object.
Once you have done this, you might experience difficulty when making connections. For that just manually choose the TapView file for making connections.
Also, to load the view you need to load its nib file. For which you can create a class level helper method in TapView class like below
+(TapView *) getInstance
{
NSArray *bundle = [[NSBundle mainBundle] loadNibNamed:#"TapView" owner:self options:nil];
TapView *view;
for (id object in bundle) {
if ([object isKindOfClass:[TapView class]]) {
view = (TapView *) object;
break;
}
}
return view;
}
Now, you get a refrence to you view object like this
TapView *tapView = [TapView getInstance];

Changing master view and detail view from uisplitview

On my storyboard, my project begins with a split view that automatically assigns my custom UITableViewController (embedded in a navigation controller) as the detail view controller (done by relationship segue). How do I access the split view controls from my custom UITableViewController so I can change the master view controller views as appropriate?
UIViewController has a property splitViewController that is a reference to the split view controller the viewController is embedded inside. Since your table view controller is embedded inside a navigation controller, which is itself embedded inside a split view controller, you first need to get a reference to the nav controller, and then from that get its reference to the split view.
So in your custom tableViewController's code you can do this:
UISplitViewController *splitVC = [[self navigationController] splitViewController];
The from that you can get a reference to your masterViewController. The splitViewController has a property viewControllers which is an NSArray of two elements. The element at index zero is the master viewController. The element at index 1 is your detail view controller.
UIViewController *masterVC = [[splitVC viewControllers] objectAtIndex:0];
Note that if your master is a custom viewController subclass (which it probably is) you should cast it as such when you pull it out of the array.
If you want to relace the master view controller with a new viewController entirely, you can do that by creating a new array with your new master VC and the existing detail view controller and assigning it to your split view controller's viewControllers property:
UIViewController *detailVC = [[splitVC viewControllers] objectAtIndex:1];
NSArray *newViewControllerArray = [NSArray arrayWithObjects:newMasterVC, detailVC, nil];
splitVC.viewControllers = newViewControllerArray;

presentModalViewController on Parent from UITableView inside UIViewController

This one is probably something simple, still learning the ins-and-outs on this but I've run out of searches for this one with no available answer.
I've got a UIViewController with several elements displayed on it, one such element is a UITableView. The UITableView has it's own class and is allocated in the UIViewControllers viewWillAppear
- (void)viewWillAppear:(BOOL)animated
{
UITableView *insideTableView = [[UITableView alloc] init];
tableView.delegate = insideTableView;
tableView.dataSource = insideTableView;
}
Everything is working fine in regards to the tableview. Today I am experimenting with a few additions, one of which is a new view popup on cell selection within that tableview.
Inside my TableView Class, I have the following:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"Cell Pressed, Present View");
PopupView *popupView = [[PopupView alloc] initWithNibName:#"PopupView" bundle:nil];
popupView.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[self presentModalViewController:popupView animated:YES];
}
Now it gets called fine, verified by the NSLog, however the view doesn't appear. I know the problem is related to the fact that I want PopUp to appear over the TableViews Parent rather than itself.
I'm just not sure how to properly call it in this instance.
The delegate is a UIViewController which doesn't have its view property set, which is why presentModalViewController:: doesn't work.
You need the view controller containing the table view to present the modal view controllers, but note that that view controller is not the parent of the table view delegate. This is because you have no view controller hierarchy in place.
The easiest way to fix this is to put those methods inside the view controller whose view contains the table view. Alternatively the table view delegate needs to hold a reference to the view controller so it can call presentModalViewController:: on it.
The latter approach can lead to retain cycle, so you have to use a non-retaining reference. The nicest implementation is the delegate pattern.
Also, you don't want to do the instantiation in viewWillAppear: because that can be called multiple times during the lifecycle of a view controller. Put the code in viewDidLoad and balance it in dealloc. Right now you are leaking memory every time your view appears, which when your modal view controller is working will be every time the modal view controller is presented and dismissed.