I need a good explanation how can I handle the UINavigationControllers and the UITabBarControllers on iOS6.1 with StoryBoards.
When I load my app (1st ViewController) I need if (FB login = success) it jumps with segues to the 2nd ViewController automatically. Here I think I can't use a UINavigationController like root, apple's HIG don't like it.
I need a UITabBarController that connects to 3 UICollectionViewControllers (one tab for each one). I have to put the UITabBarController like root? If yes, how can I handle the others Viewontrollers between them? Like this:
I need a custom BarButtonItem (like the "Delete All" that you can see on the image 2) on each CollectionViewController, I need to use a UINavigationController for each one?
Let's assume you are happy to use unwind segues (if not there are many ways to do without).
1 When I load my app (1st ViewController) I need if (FB login = success) it jumps with segues to the 2nd ViewController automatically. Here I think I can't use a UINavigationController like root, apple's HIG don't like it.
You 1st VC (lets call it the loginVC)..
- should NOT be contained in the Navigation Controller.
- should be set as the application's initialViewController
Your 2nd VC (lets call it your startVC)
- SHOULD be contained in the Navigation Controller
- in that Navigation Controller's Identity Inspector, assign a storyboardID: #"InitialNavController"
In your App Delegate, let's have a loggedIn BOOL property:
#property (nonatomic, assign) BOOL loggedIn;
Now, in your LogInViewController...
In viewDidAppear check to see if we are already logged in, if so, navigate immediately to your startVC:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
if ([(AppDelegate*)[[UIApplication sharedApplication] delegate] loggedIn]) {
UINavigationController* navController =
[[self storyboard] instantiateViewControllerWithIdentifier:#"InitialNavController"];
[self presentViewController:navController
animated:NO
completion:nil];
}
}
It's important that this is placed in viewDidAppear, not (for example) in viewDidLoad - the segue won't work unless the initial view is properly initialised and onscreen.
Make an unwind Segue (and declare it in loginVC's #interface) … loginVC will be the destination VC if users log out.
- (IBAction)unwindOnLogout:(UIStoryboardSegue *)segue
{
[(AppDelegate*)[[UIApplication sharedApplication] delegate] setLoggedIn:NO];
}
(corrected - removed this line:
[[self presentedViewController] dismissViewControllerAnimated:YES
completion:nil];
we don't need to dismiss as the segue has already done that behind the scenes. This is redundant and logs an error message)
In other viewControllers, whereever appropriate you can make a 'logout' button. CTRL-drag from that button to the 'exit' symbol at the bottom of the ViewController in the storyboard, and you will be able to select this segue as the unwind segue.
2 I need a UITabBarController that connects to 3 UICollectionViewControllers (one tab for each one). I have to put the UITabBarController like root? If yes, how can I handle the others Viewontrollers between them? Like this:
I think you are trying to work out how the tabBarController relates to the NavigationController in the previous viewController (startVC). The answer is, it shouldn't - you really don't want to embed the Tab Bar VC in the previous Nav Controller as it will create weird situations for the Tab Bar's child viewControllers.
The navigation from startVC to the tabBarVC should be via a modal segue, NOT a push segue.
You can make another unwind Segue in startVC to facilitate return from your tabBarController's viewControllers:
- (IBAction)unwindToInitialNavFromModal:(UIStoryboardSegue *)segue {
}
(corrected - removed this line:
[[self presentedViewController] dismissViewControllerAnimated:YES completion:nil];
this method doesn't need any content to perform the dismissing)
3 I need a custom BarButtonItem (like the "Delete All" that you can see on the image 2) on each CollectionViewController, I need to use a UINavigationController for each one?
You won't get a Navigation bar in your tabBarVC by default.
You can provide one in two ways
- embed each child viewController in it's own Navigation Controller;
- manually drag a navigation bar to EACH child viewController's scene.
Either is fine, it really just depends whether you will want navigation to other ViewControllers.
You can then add a barButtonItem on the left or right to connect up to the initialVC's unwind segue (CTRL-drag to the 'exit' symbol).
Related
I am trying to optimize the code in my app. I have quite a few ViewControllers which all use a common "keypad".
I wanted to extract the keypad into a separate ViewController and then include it into the existing ViewControllers.
This way I could obliviate duplicate code (which was needed to deal with the reactions from the keypad) in the separate ViewControllers.
So in the KeyPadVC I have methods set up that look something like this.
-(IBAction)keyPadKeyPressed:(id)sender
{
[self.delegate interpretKeyPressed:[[sender titleLabel] text]];
}
In my "parent" ViewControllers I include the keypad by adding a subview to a plain UIView that I placed in Interface Builder (so that I have a visual placeholder) and hooked up to the variable keypadView.
-(void) viewDidLoad
{
[super viewDidLoad];
KeyPadViewController *kpVC = [[KeyPadViewController alloc] init];
[kpVC setDelegate: self];
[[self keypadView] addSubview:[kpVC view]];
}
This displays fine, but when I press a button on the KeyPadViewController I get a zombie object because the object was already released. I then tried to declare KeyPadViewController *kpVC in the #interface and tried a self instantiating method like:
-(KeyPadViewController *)kpVC
{
if (!kpVC) {
kpVC = [[KeyPadViewController alloc] init];
}
return kpVC;
}
I obviously modified the viewDidLoad method, but the result was always the same. The object gets released too soon. If I add NSLogs I can see that -(IBAction)keyPadKeyPressed from the KeyPadVC never gets called, because it KeyPadVC was already released.
What am I doing wrong? I am using ARC and iOS6 SDK.
Thanks
PS: this is pseudo-code to make things shorter - hope there are no typos - if so then that is not the issue. :)
KeyPadViewController *kpVC = [[KeyPadViewController alloc] init];
[kpVC setDelegate: self];
[[self keypadView] addSubview:[kpVC view]];
self.kpVC = kpVC;
That retains the view controller.
However, what you are doing is totally illegal because you are not using parent-child architecture. See my answer here: https://stackoverflow.com/a/15962125/341994
You can add a subview to your view, but you must not add a view controller's view as a subview to your view without going through the elaborate parent-child architecture. And you are not doing that.
I explain the parent-child architecture here:
http://www.apeth.com/iOSBook/ch19.html#_container_view_controllers
I'm trying to allow some views to rotate in my app (just two), I've done this before successfully, subclassing UINavigationcontroller and overriding the corresponding methods. The problem this time is that I'm using a third party project that creates the navigation controller from a .xib file (not programmatically). I changed the class in the .xib file in order to use my custom navigation controller and it does, but for some reason is ignoring override methods like shouldAutorotate and supportedInterfaceOrientations
Any ideas?
Thanks
I'm still looking for a more elegant way to handle this, but I'll share a little hack that has worked for me. If you find something better, please let me know.
An approach that has worked ok for me in apps with complex navigation trees, is to install the custom subclass for the very first navigation controller or very first view controller if you don't have a nav controller that is in your app. that is the one that is going to receive the shouldAutoRotate calls.
It sounds like you have done this already.
Now you need to insert your own logic in the shouldAutoRotate section. What I've done is to use a BOOL in the appDelegate as a place to store if a view controller should autorotate. Basically it works like this:
appDelete:
#property (nonatomic,assign) BOOL allowAutoRotationForThisViewController;
custom navigation controller at head of stack:
- (BOOL)shouldAutorotate {
AppDelegate *a = [[UIApplication sharedApplication] delegate];
return a.allowAutoRotationForThisViewController;
}
rotatable view controller:
-(void) viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
AppDelegate *a = [[UIApplication sharedApplication] delegate];
a.allowAutoRotationForThisViewController = YES:
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
AppDelegate *a = [[UIApplication sharedApplication] delegate];
a.allowAutoRotationForThisViewController = NO:
}
I'm not sure why they changed this in ios6, it seems much more difficult, especially when you have complex navigation structure. I have an app with "slide" type controller at the top of the stack, a tabbar controller and stacks of navigation controllers. Trying to pick through all of those to get a web view on the bottom of the stack to rotate, or get them to respond to the top level controller is very complicated. So I've used this approach.
Now, one thing you have to consider - is that if the BOOL is turned to NO, then no rotation will occur - so one gotcha is if your rotatable view controller pops back to its parent while its rotated. Then the parent will be rotated and the rotatable view controller will have set the rotation value back to no. I solved this in my approach by preventing it to dismiss unless it was in portrait mode - basically I disabled the "back" button in landscape mode.
this works like this:
- view comes on screen - sets rotation to YES
- rotation occurs
- at this point, the view that is being rotated will get the following selector call:
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
so, inside of that call, you would configure your local view and do anything you need to do to setup the view for the orientation change - like self.navigationController.hidesBackButton = YES;
hope that helps, and like I said, its a bit of a hack and I'm looking for something more elegant.
best of luck
I am in Xcode 4.5, with an iOS6 target.
Preamble: I have a libraryView (presenting view controller), with a popover containing a search. After the presentation of the search results, tapping a row dismisses the library and segues to entryView. That all works just fine.
My issue: upon closing entryView and returning to the libraryView, the search popover is still visible.
I have tried a number of different methods to remedy this:
I have added a Notification observer in the segue to the search popover, posted a Notification from the search controller, posted from the entryView to the following method located in the libraryView. And, yes, libraryView does have addObserver for this method:
- (void)searchComplete:(NSNotification *)notification
{
NSLog(#"SearchPopover dismiss notification?");
[_searchPopover dismissPopoverAnimated:YES];
}
I have added in testing...
if (_searchPopover.popoverVisible)
{
[_searchPopover dismissPopoverAnimated:YES];
}
To viewDidLoad, viewWillAppear, viewWillDisappear, awakeFromNib... all in the library. I have the searchPopover as a property and have tried it as an ivar.
Nothing I've tried dismisses the popover before the segue or after the return.
Anyone have any ideas? Help would be much appreciated!!!
I have found a solution to this issue... found it at this answer: iOS Dismissing a popover that is in a UINavigationController
But, there was a small additional step to make... correct a typo in the answer and change the "dismissPopover" method into an NSNotification Method. I added a segue for the popover, which normally isn't necessary. The key though, is the setting of the parent's definition of the popover to the UIStoryboardPopoverSegue.
Then, use notification to let the parent know it should dismiss.
From the parent view:
- (void)viewDidLoad
{
... other loading code...
[NSNotificationCenter.defaultCenter addObserver:self selector:#selector(dismissSearch:) name:#"dismissSearch" object:nil];
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"SearchSegue"])
{
[segue.destinationViewController setDelegate:self];
_searchPopover = [(UIStoryboardPopoverSegue *)segue popoverController];
}
}
- (void)dismissSearch:(NSNotification *)notification
{
NSLog(#"SearchPopover dismiss notification?");
[_searchPopover dismissPopoverAnimated:YES];
}
In my child view (SearchView). Ideally, it would be in the didSelectRowAtIndexPath. I found it also worked in the segue to the view where it will display the searched item, which is where I would normally place an addObserver. In this case, it is a postNotification...
[NSNotificationCenter.defaultCenter postNotificationName:#"dismissSearch" object:nil];
One last note: I was using an IBAction that checked for popover visibility when tapping the button... display or dismiss. Found that having this as well as the other method was causing the popover to dismiss immediately upon tapping the button! Commenting out the if/else check for visibility solved that!
Thanks for rdelmar for leading me on this path!
I'm having troubles using the storyboard.
I properly connected a button (inside a custom uiviewcell) to a scene (ProfileVC) through push segue (whose identifier is pic2profile).
Besides in the table view controller (HomeVC) that manages the cells I implemented the method prepareForSegue:
- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if([segue.identifier isEqualToString:#"pic2profile"]){
ProfileVC *profile = (ProfileVC *)segue.destinationViewController;
NSLog(#"%#", [segue identifier]);
Tweet *tweet = [tweets objectAtIndex:0];
profile.user = tweet.user;
}
}
As explained in the documentation, once called segue.destinationViewController at the end of prepareForSegue should be displayed the view controller at the end of the segue (ProfileVC in this case).
What happen is that I get (no errors!) a black screen and the method viewDidLoad in ProfileVC is not called though the object "profile" is not null.
Inside the storyboard the ProfileVC scene is linked to its view controller (ProfileVC) and is also set up a Storyboard ID.
I already tried this but it didn't work.
Are you performing a segue to a view controller using the code you have, but have a navigation controller in between? If so you have to use a different way, specifying the navigation controller in between.
If you don't need the navigation controller you can delete it and connect the cell to the view controller and keep the code you have. Otherwise you have to change it.
I'm using storyboarding and have a UITableView containing events, which when clicked load another view with more details. I also have an 'add' button on that list which goes to the same page but doesn't prepopulate the information and changes the banner button.
I do it by setting the detail item with the following method, and then in the configureView method I just check if the detail item exists.
- (void)setDetailItem:(id)newDetailItem {
if (self.detailItem != newDetailItem) {
_detailItem = newDetailItem;
[self configureView];
} }
This works ok, but I thought there might be a better way to distinguish between methods, eg by getting the segue identifier in this new view controller and using that. Is there an easy way to do this or do I need to pass this information through as part of the prepareForSegue method?
Using prepareForSegue: seems right. In general, it's a bad idea for methods to care about the conditions under which they're being called if it's not explicit in their parameters.