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
Related
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 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).
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 am presenting a modal view controller from another modal view controller, and this worked fine under all iOS versions prior to iOS6. But under iOS6 I am getting the following warning message in the emulator:
Warning: Attempt to present <UINavigationController: 0x14e93680> on <UINavigationController: 0x9fc6b70> while a presentation is in progress!
The modal view controller is not shown if this warning appears. Basically I am using code like this to show the modal view controller:
WebAuthViewController *authController = [[WebAuthViewController alloc] initWithNibName:nil bundle:nil];
authController.challenge = challenge;
authController.delegate = self;
UINavigationController *aNavController = [[UINavigationController alloc] initWithRootViewController:authController];
[self presentModalViewController:aNavController animated:YES];
[aNavController release];
[authController release];
The view that is already shown is a UIWebView also shown in a modal view, like this:
WebViewController *addController = [[WebViewController alloc] initWithNibName:nil bundle:nil];
addController.urlToLoad = [NSURL URLWithString:urlString];
addController.delegate = self;
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:addController];
navigationController.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentModalViewController:navigationController animated:YES];
[navigationController release];
[addController release];
The apple docs still suggest that one is supposed to be able to stack navigation controllers like this, so I am at a loss to explain why this happens. Any hints?
A view controller can only present a single view controller. This might have been allowed before, but is probably enforced in iOS6 due to an internal reorganization (presentModalViewController:animated: is deprecated in iOS6). It's time to change your organization of view controllers. Perhaps it is possible to introduce a navigation controller which is to be presented. If there is already a presented view controller, push your next one to the navigation stack.
OK, that was really dumb - I forgot the quotes around the URL for curl and the shell did execute the command in the background without arguments.
i came across of an issue with dismissmodalview. it is apparently depreciated in ios 6. can anyone suggest a fix. i tried this code but still brings up the warning.
if ([[self parentViewController] respondsToSelector:#selector(dismissModalViewControllerAnimated:)]){
[[self parentViewController] dismissModalViewControllerAnimated:YES];
} else {
[[self presentingViewController] dismissViewControllerAnimated:YES completion:nil];
}
can anyone suggest anything. basically i am trying to dismiss the modal view which i am using in my view controller.
adrian
Have you tried? I'm using this in one of my projects with success.
[self dismissViewControllerAnimated:YES completion:nil];
You receive a warning because the selector is deprecated in iOS 6 and you use it. If you are targeting iOS 5 and above, you should not be using dismissModalViewControllerAnimated:.
If you really need to use it, you can call performSelector: like so:
if ([[self parentViewController] respondsToSelector:#selector(dismissModalViewControllerAnimated:)]){
[[self parentViewController] performSelector:#selector(dismissModalViewControllerAnimated:) withObject:#YES];
}
A word of advice: it is not considered good practice for a view to close itself. You should create a delegate protocol for your modal view controller, and have the presenting view controller by the modal's delegate.