I am new to Storyboard-ing, been using IB mostly. I have an initial temporary View Controller (let's call it BootstrapViewController) which job is solely to load data from a webservice, then hand over control to a UITabViewController. The data is required to determine what tab items to show.
In non-Storyboard world, I just do:
// Do whatever you need to UITabViewController, then call this
[UIApplication sharedApplication].keyWindow.rootViewController = myTabVC;
Is this possible using Storyboard? I tried creating a named segue from BootstrapViewController to the tab view controller, then calling performSegueWithIdentifier, no luck.
You can do it exactly the same way you have always done it, by setting the window's rootViewController property. You can even load the tab bar out of a storyboard. Use UIStoryboard's instantiateViewControllerWithIdentifier: to load a particular view controller from a storyboard.
Related
I'm calling a tab bar controller modally from a view controller to implement a range of additional controls and inputs that the user can configure. In storyboard this is easy to do but how can I best pass a Core Data managed object context to the view controllers hosted by the tab controller? What is the best design approach here:
to forget storyboard and do this part of the app in code? That is straightforward. I simply pass the managed object context to each view before I add them to the tab controller.
to add a managed object context property to the view controller that launches the tab view controller? This is certainly possible using the presentingViewController property in each of the destination view controllers but does not seem to be what was originally intended.
communicate directly via some property of the root view controller? I have seen references to this on the web but am not sure about this.
Appart from the managed data context, nothing else is required appart from the dismissModalViewController message back to return to the original view. Everything else is managed via Core Data.
A couple of options:
Pass the managed object context during prepareForSegue (you have to access the tab view controller's viewControllers array to get hold of your individual view controllers)
Structure your app such that the core data stack is available globally, either from the application delegate class or a separate singleton. The view controllers can then ask for the managed object context when they need it.
Possibly do some abuse of delegates where you set some object as the tab bar controller's delegate that also happens to hold the managed object context - this will then be available from all the view controllers in the tab bar controller. This has only just occurred to me and is probably a bad idea.
By the time your main view controller gets a -prepareForSegue: message, the tab bar controller and the view controllers that it manages will have already been created. You can get the tab bar controller from the segue itself, and then get the array of view controllers from the tab bar controller like so:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
UITabBarController *tbc = [segue destinationViewController];
NSArray *controllers = [tbc viewControllers];
NSLog(#"View Controllers: %#", controllers);
}
Now, you'll want to do a little error checking to make sure that the destination controller really is the tab bar controller, but you can replace the NSLog() with code to configure the controllers however you like. For your purpose, that just means handing them the managed object context that they should operate on.
I have a uitabbar with 4 buttons. The start up screen loads as it should on the applications start. 2 of the buttons views don't load on start (good). 1 is a web view the other is a navigation controller with a table view. The last view does load on application start. It is another navigation controller with a table view.
I know which ones are loading and which are not because i added nslogs to the didload function on all top level controllers.
So the overall issue is that when i start my app, then rotate it, then go into the tabbar item that has already been loaded the header in the tableview which is a webview, never got the memo that it should have rotated, and therefore resized. Once your in the view if you rotate back and forth then it works as it should. Its just the initial time.
Update 1:
The question is how do I stop the 2nd controller that is on another button of the tab bar from loading on app start.
Update 2:
I do all my init stuff in didload but I tried adding the following code, but it never shows up in the log. I am not using IB, this is all done programmatically:
- (void)loadView
{
NSLog(#"Loading feedback");
}
Update 3:
I figured out whats causing this. I do have a xib that houses my tabbarcontroller. In that tabbarcontroller i have a navigation controller, then i have a view controller (I set the class to my feedback class), then i had the navigation item and a table view. When i add the tableview it triggers the controller to load. Simply removing that will stop it form loading early. Then to fix it, i created a xib for the feedback. So I dont like having nibs that server a single purpose, in this case its to get a table view in there and have it be a grouped style. I may try to just manually add the tableview instead of having a tableview controller.
Its much easier to do in code. The tab bar controller accepts the view controllers in the array and the default tab bar controller is the one which is at the first index of the tab bar controller.
UITabBarController *tabBarController = [UITabBarController alloc] init];
tabBarController.viewControllers=[NSArray arrayWithObjects:firstViewConoller,secondViewController,nil];
[self.view addSubview : tabBarController.view];
Place all the view related task in viewDidLoad method sometimes loadView gies problem this may be because of your viewController trying to load a new view which calls itself again and again so better to add them to viewDidLoad.
The view controller for the tab that loads prematurely probably accesses the self.view property before it is needed needed.
If finding he access point is hard add a breakpoint to loadView, make a dummy overload only calling [super loadView] if needed (The view is loaded from a NIB). The breakpoints stack trace will show you where you force the load to occur.
I'm getting confused on view controllers and would love a straight example. Here's the preamble:
I have a UIViewController with a matching .xib.
By default IB gives me a single View in the Document window.
I can make it appear by telling my UIWindow to addSubview:controller.view and bringSubviewToFront:controller.view
Here's the questions:
Should I add another View to the ViewController in IB? Or is there a better, programmatical way?
How do I tell the ViewController to switch between the Views?
From the ViewController downward, what does the code look like to achieve this?
I'm trying things but just making a mess so I thought I'd stop and ask...
Note that every button, label, image, etc. in your main view controller is actually a view in itself, however I've interpreted your question to mean that you want to manage multiple full-screen views or "screens". Each screen should have its own view controller to manage it. So to get the terminology right, a view-controller is an object that manages a single full-screen view (or almost full screen if it's nested inside a navigation controller or tab bar controller for example) and a view is the big area managed by the view controller as well as all the sub-views (images, buttons, labels, etc.) within it (they are all UIView sub-classes). The view controller manages all of them on that screen, if you want another screen/page then you should create a new view controller to manage it.
The root view controller (the one you add to the window) can be a plain old normal view controller that you've designed in IB, however it's probably more useful if you use a navigation controller or a tab bar controller and add your designed view controller to that - then you can push additional view controllers as needed.
Another way (if you don't want navigation or tab-bar style) would be to transition to other view controllers directly in the main window using whatever transitions you like (or just replace the old one). We'll leave that for now though.
Any sub-views of your main view controller (the one you've designed in IB) will be automatically loaded from the nib file, but you can also add your own views programatically if you want (typically you would use one or the other, i.e. nibs or programatically, but you can mix and match if you want). To do it programatically, override loadView in the view controller and then call [super loadView]; then do [self.view addSubView:myOtherView]; (create the myOtherView first of course). Note that the first time .view is accessed on your view controller, it actually calls loadView to create the view, so inside loadView it's important to call [super loadView]; before trying to access self.view :D
To switch between views, using the navigation or tab bar controllers makes it very easy. So put your main view controller inside (for example) a navigation controller and put the navigation controller in the window, so you've got window->navigationController->myController. Then from an action method in your view controller (you can hook up the action methods in IB), for example when an "about" button is pressed do this:
- (void)doAbout
{
// Create the about view controller
AboutViewController* aboutVC = [AboutViewController new];
// Push the view controller onto the navigation stack
[self.navigationController pushViewController:aboutVC animated:YES];
[aboutVC release];
}
Note that the about view controller is created programatically here - if your about view is designed in IB then instead use initWithNibName:bundle: to create it.
And that's how you manage multiple screens.
Is there a good UITabBarController example where it is NOT created in the appDelegate?
I would like to use a UITabBarController inside of a UIViewController, however dont know how to set the view outlet.
This is all very well documented here. It shouldn't matter where the UITabBarController instance is created, UIApplicationDelegate or not. In a nutshell, Create all of your respective UIViewController's and add them to an array. Then assign that array to your UITabBarController's viewControllers property. Then you can simply do something like [window addSubview:myTabBarController.view].
I'm just copy/pasting from the documentation here:
You should never access the tab bar
view of a tab bar controller directly.
To configure the tabs of a tab bar
controller, you assign the view
controllers that provide the root view
for each tab to the viewControllers
property. The order in which you
specify the view controllers
determines the order in which they
appear in the tab bar. When setting
this property, you should also assign
a value to the selectedViewController
property to indicate which view
controller is selected initially. (You
can also select view controllers by
array index using the selectedIndex
property.) When you embed the tab bar
controller’s view (obtained using the
inherited view property) in your
application window, the tab bar
controller automatically selects that
view controller and displays its
contents, resizing them as needed to
fit the tab bar interface.
Here's one implementation of UITabBarController initialized in a UIViewController.
The author also posted a github link to the xcode project.
I've been reading the Head First iPhone Development book and I understand how to get to a new view from a table but how exactly would I be able to get to a new view or view controller, by just simply pressing a button? Is that even possible?
I mean there are some apps where you click a button, not a table cell and it loads a new view. How exactly is that done? If someone could help out a newbie it would be greatly appreciated!
I think what you're looking for is a modal vew controller. THis presents a modal view like you described on top of everything else. If rootViewController is the view controller that is displaying your current view, and myNewViewController the view controller you want to display modally:
[rootViewController presentModalViewController:myNewViewController animated:YES];
There's plenty of examples of this kind of thing on the net, just search for presentModalViewController
Like bpapa said in the comments, it's hard to be specific without code. However, generally what you want to do is:
Build a navigation controller that contains one original view.
Create a button in your original view using the Interface Builder.
Build a callback method (usually defined with IBAction) that is run when the button is pushed.
In that callback method, create a new view and push it onto the navigation controller the same way you would using a table view cell.
Alternately, if you only want one level of hierarchy, you could use a modal view controller; instead of pushing onto the navigation controller in the last step, just present the modal view controller.
The general answer is that you have an object that manages which view controller loads when.
The most commonly used is the UINavigationController. It is a UIViewController that instead of controlling views, controls other view controllers. It works like a simple stack. You push views you want to display onto the nav's controller stack and when you want them to disappear you pop them off.
A common (though sloppy) way of using a nav is to make it a property of your app delegate. Then anywhere in your app you can references it by:
UINavigationController *nav=[[[UIApplication sharedApplication] delegate] navigationController];
The view controller for the first the user sees is held in the nav's topViewController property. If you want to load a view based on a user action in the topViewController.view, you would have something like this:
- (IBAction) loadNextView:(id) sender{ // Action called by a a UI event such as a button press.
UINavigationController *nav=[[[UIApplication sharedApplication] delegate] navigationController];
UIViewController *nextViewController=...// load from nib, connect with IBOutlet, create programmatically
[nav pushViewController:nextView animated:YES];
}
The first view disappears to be replaced by the next one. To return to the first view, you have a method in the next view controller like so:
- (IBAction) unloadSelf:(id) sender{ // Action called by a a UI event such as a button press.
UINavigationController *nav=[[[UIApplication sharedApplication] delegate] navigationController];
[nav popViewControllerAnimated:YES];
}
... and the nav returns you automatically to the previous view regardless of what that view was.
When you first start out, especially if you use Interface Builder, the structure of the app is largely hidden. Behind the scenes all view controllers and their views exist in a hierarchy of some kind that leads back up to the app delegate. You should train yourself to think in hierarchal terms even if it is not immediately obvious how that hierarchy is constructed.