Popping ViewControllers when using delegation - ios

I'm a bit weak in my Objective C I'll admit, my ultimate goal is pass data from ViewController3 back to ViewController1. Actually, that part is already done and successful. However when calling [self.navigationController popToRootViewControllerAnimated:YES] I get EXC_BAD_ACCESS.
ViewController1 <ViewController2Delegate>
- (void) didAddEventLocation:(Event *)event {
NSLog(#"Event name = %#", event.name); //Shows name successfully
}
ViewController2 <ViewController3Delegate>
- (void) didAddEvent:(Event *)event {
[self.delegate didAddEventLocation:event];
}
ViewController3
[self.delegate didAddEvent:event];
[self.navigationController popToRootViewControllerAnimated:YES];
Sorry for the poorly formatted code, just trying to simplify. Doing [self.navigationController popViewControllerAnimated:YES] has no problem, however it only takes me to ViewController2. I know I'm doing something very wrong here, but can't quite place my finger on how to resolve it. Let me know if I need to clarify.

Use Zombies to hunt down what is giving you the EXC_BAD_ACCESS. Some object has been released and is now being called on when you are popping back to the root view controller.
Try this link:
How do I set up NSZombieEnabled in Xcode 4?

Looks like, that one of your you controllers(first in my opinion) is deallocated. In VC3 method check that self.navigationController exists. Then you have to check all his VCs. I think that nothing holding first VC. Problem may be solved by using(for example) addChildViewController method of your navigation controller, or if smth will have a reference to your controllers.
Also, you can use NSNotificationCenter to send some information from one instance to another if you have problems with path between them.
HTH!

Related

How do I avoid keeping views in memory? Do I need to set all objects to nil?

I'm new to programming and have been developing an iOS app over the last couple months. To me the app looks like its functionally really close to done but I hit an issue today that I think might be a bigger underlying problem.
When I dismissViewController in a navigation controller and go back to the view later it seems to still have the same values. I thought when I do a dismiss that view is destroyed and a new one created later. I've been trying to read about it and I think maybe its a memory cycle thing, the view is kept in memory because there are objects in the view that still have pointers? Is there some general rules on how to handle this? Should I be setting object to nil any time I leave a view controller?How to I make sure I'm not keeping unnecessary things in memory?
If you create your view controller in the following manner, creating it and then pushing it(commented out line) or presenting it, then it is guaranteed that the ViewController will always have an initial state as defined by your initializtion code.
- (IBAction)showViewController: (UIButton *)sender {
MyViewController *vc = [[MyViewController alloc] init];
[self presentViewController: vc animated: YES completion: nil];
//[self.navigationController pushViewController:vc animated:YES]
}
check the viewController you dismiss,if the properties has strong reference(strong,retain) point to parentViewController.

Pushing a new VC into the navigationController seems to have no effect

This problem sounds quite basic but I don’t understand what I am overlooking.
I am trying to push a new view controller into a navigation controller, however the topViewController remains unaffected.
#import "TNPViewController.h"
#interface TNCViewController : UIViewController <UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>
#implementation TNCViewController
-(void)userDidSelectNewsNotification:(NSNotification*)note
{
TNPViewController *nextViewController = [[TNPViewController alloc] init];
[[self navigationController] pushViewController:nextViewController animated:YES];
UIViewController *test = [[self navigationController] topViewController];
}
The test shows an instance of TNCViewController instead of TNPViewController. How is this possible?
UPDATE
Thanks for everyone's participation. The method name indicating notifications is a red herring. I found the problem, as Stuart had mentioned previously but deleted later on. (As I have high reputation score, I still can see his deleted post).
My initial unit test was this:
-(void)testSelectingNewsPushesNewViewController
{
[viewController userDidSelectNewsNotification:nil];
UIViewController *currentTopVC = navController.topViewController;
XCTAssertFalse([currentTopVC isEqual:viewController], #"New viewcontroller should be pushed onto the stack.");
XCTAssertTrue([currentTopVC isKindOfClass:[TNPViewController class]], #"New vc should be a TNPViewController");
}
And it failed. Then I set a breakpoint and tried the test instance above and it still was showing the wrong topviewcontroller.
At least the unit test works if I change
[[self navigationController] pushViewController:nextViewController animated:YES];
to
[[self navigationController] pushViewController:nextViewController animated:NO];
A better solution is to use an ANIMATED constant for unit tests to disable the animations.
This doesn't really answer your question about why your navigationController is not pushing your VC. But it is a suggestion about another possible approach.
You could instead add a new VC on the Storyboard and simply activate the segue when the userDidSelectNewsNotification method is activated. Then change the information accordingly to the event in the VC, specially since you are initializing it every time anyway.
This is something of a stab in the dark, but the issue is hard to diagnose without more information.
I see you're trying to push the new view controller in response to a notification. Are you sure this notification is being handled on the main thread? UI methods such as pushing new view controllers will fail (or at least behave unpredictably) when not performed on the main thread. This may also go some way to explaining the odd behaviour of topViewController returning an unexpected view controller instance.*
Ideally, you should guarantee these notifications are posted on the main thread, so they will be received on that same thread. If you cannot guarantee this (for example if you're not responsible for posting the notifications elsewhere in your code), then you should dispatch any UI-related code to the main thread:
- (void)userDidSelectNewsNotification:(NSNotification *)note
{
dispatch_async(dispatch_get_main_queue(), ^{
TNPViewController *nextViewController = [[TNPViewController alloc] initWithNibName:#"TNPViewController" bundle:nil];
[self.navigationController pushViewController:nextViewController animated:YES];
});
}
Also, it appears you are not initialising TNPViewController using the designated initialiser (unless in your subclass you are overriding init and calling through to initWithNibName:bundle: from there?). I wouldn't expect this to cause the transition to fail entirely, but may result in your view controller not being properly initialised.
In general, you might be better creating your view controllers in a storyboard and using segues to perform your navigation transitions, as #Joze suggests in his answer. You can still initiate these storyboard segues in code (e.g. in response to your notification) with performSegueWithIdentifier:, but again, be sure to do so on the main thread. See Using View Controllers in Your App for more details on this approach.
*I originally wrote an answer trying to explain the unexpected topViewController value as being a result of deferred animated transitions. While it is true that animated transitions are deferred, this does not prevent topViewController from being set to the new view controller immediately.

How to handle prepareForSegue from another NavigationController?

I call performSegueWithIdentifier like that based on design reason.
- (void)showViewWithSegueIdentifier:(NSString *)segueIdentifier {
[self.navigationController.navigationController.topViewController performSegueWithIdentifier:segueIdentifier sender:self];
}
when I try to handle prepareForSegue method, it is still not called.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
NSLog(#"Call");
}
Is it possible to handle prepareForSegue from another controller?
I believe you can do what you want, although most of the time it is not a good idea. However, you may have a good reason.
To figure out why it isn't work I would look at what self.navigationController.navigationController.topViewController really is. If it is nil nothing will happen and it won't crash. Guessing your navigation controller doesn't have a navigation controller.
try
UIViewController *viewController = self.navigationController.navigationController.topViewController;
NSLog(#"This is my view controller: %#", viewController);
[viewController performSegueWithIdentifier:segueIdentifier sender:self];
and see if there is actually a viewController.
Edit:
If it is not nil there is also a chance that topViewController would return the UIViewController that called it. You could add this log right under the "This is my view controller" log and see if it is the same as above.
NSLog(#"This called prepareForSegue: %#", self);
The thing that doesn't add up though is you didn't mention the app crashing, you just said it didn't work. Meaning that whatever you are calling prepareForSegue on must have the same segue identifier. I believe view controllers get really mad if you call a segue that isn't there and crash the app at least that is what I remember. However, it has been a while sense I did that and could be wrong.
One last thing I would look at is the viewDidLoad: of the controller you expect to perform the segue and log it out and see if it matches the "This is my view controller" log.
I hope that helps and good luck. =)

Drawing in segue vs writing code to transition views - why does data sometimes not transfer?

Let's say I have a UILabel on ViewControllerA and and UITextField on ViewControllerB. I want to go to ViewControllerB and input text then press a button to go back to ViewControllerA. The UILabel should now read whatever was typed in the UITextField.
I was able to accomplish the above by using NSUserDefaults and also using delegation. I am using Storyboards to do this. My question is about the segues used in the storyboards.
It seems when using delegation I must go to and from the storyboard with code and not visually connect the view controllers with a segue in order for the data to transfer. Here is the code when I press a button on my ViewController A:
- (IBAction)pressFirstButton:(id)sender {
UIStoryboard* sb = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
RBViewController2 *vc2 = [sb instantiateViewControllerWithIdentifier:#"ViewController2"];
[vc2 setDelegate:self];
[self presentViewController:vc2 animated:YES completion:NULL];
}
when passing the data back from ViewControllerB to ViewControllerA I do this:
- (IBAction)buttonSegueBackTo1:(id)sender {
NSString *sendThis = self.textFieldVC2.text;
[self.delegate passTextFieldInput:sendThis];
[self dismissViewControllerAnimated:YES completion:NULL];
}
No segue has been drawn between the view controllers and everything works fine. If I don't write this same code, and draw in a segue, the data won't pass backwards. However when I try passing data like this using NSUserDefaults I don't have to write the code to go to and from the view controllers. Instead I can simply connect the view controllers with a drawn segue. The weird thing is, if I'm trying to pass the data in using NSUserDefaults when manually coding the view controllers (an not drawing the segue) the data doesn't transfer.
I'm thinking maybe instead of writing the code in the -(IBAction) pressFirstButton:(id) sender method, I should be putting the code in the prepareForSegue method.
My question is why do drawn segues sometime cause data to be lost? Must all delegation be done without drawn segues? If NSUserDefaults require segues to transfer properly and delegation require code to transfer properly then if I have a view that requires both, it seems that NSUserDefaults will trump the delegation b/c the manual segue being used "resets" the view and only the NSUserDefaults data remains.
Normal segues (any other than unwind segues) ALWAYS create new view controllers. So, if you're using anything other than an unwind segue to go back to A, you're really not "going back", you're creating a new ViewControllerA. Unwind segues aren't normally used in a case like you're presenting, just going back one controller, but you could.
This situation also isn't a good place to use user defaults. The Apple recommended way, is to use delegation, like you do in the code your question. The way you show, is probably the best way to do it, rather than using a segue to go back. You certainly could use a segue to go forward though, and in that case you would implement prepareForSegue: so you can set yourself as the delegate and/or pass any data forward.
Your question is a little dense to parse but I think you are asking if there is a way to get pass data through segues. The answer is yes, and much better than the workarounds you are trying.
In your button you would call:
[self performSegueWithIdentifier:#"scrollerSegue" sender:self];
This will trigger the method below and it is here that you can set the data on the destination viewController. You can set abstract properties on the destination viewController but you can't populate an UIKit elements (labels, imageViews, etc.) because they don't exist yet. Instead set properties and then in viewWillAppear in the destination viewController, do the set up as needed. (alternatively, instead of passing data, you could just set the delegate and then call methods on the delegate to get the data as needed).
For getting data back, using the delegate and calling methods on it seems to be the Apple recommended way of doing things.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"scrollerSegue"])
{
ScrollViewController * target = segue.destinationViewController;
target.assetsArray = self.assetsArray;
target.delegate = self;
}
}

viewController's dealloc don't be called when called navigationController popViewControllerAnimated

I have a MyViewController, it's based on UIViewController, and I used it like the following code:
MyViewController *nextViewController = [[MyViewController alloc] init];
[self.navigationController pushViewController:nextViewController animated:YES];
[nextViewController release];
And in the MyViewController, with a user event, have the following code:
[self.navigationController popViewControllerAnimated:YES];
Now, I find that, the MyViewController's dealloc don't be called, but, when I switch the App to background, for example, pass the home button, the dealloc method has been called! This a big problem! There will be got a lot of MyViewController wouldn't be release, when user go to a MyViewController, and go back, again and again, and just, the lots of memory could be release only when the App goto background.
So, can anyone help me about this, thanks!
The obvious reason is that something is retaining your viewController. You will have to look closely at your code. Do you do anything that in your class that uses delegates, since they sometimes retain the delegate. NSURLConnection will retain your class, and so does NSTimer. You can scatter code in you class and log your class's retain count, and try to find out where. In the code you showed so far the retain could should just be 1, since the class is only retained by the navigation controller.
Also, before you pop your view, get a reference to it, pop it with NO animation, and then send it some message that has it report the retain count (this would be some new method you write). That new method could also log other things, like whether it has any timers going, NSURLConnections, etc.

Resources