UINavigationController does not release memory after back button is tapped - ios

I have a mainViewController and a secondViewController. The SecondViewController is pushed using segue (iOS 7 app).
The problem is that when the back button is tapped and the app is back to mainViewController, the secondViewController memory is not released.
In prepareforSegue, I don't do any allocation. I merely set the delegate for the SecondViewController (aka cvc below). The CollectionViewController (aka SecondViewController) is quite big so as the user goes back-and-forth between the main VC and the SecondViewController memory usage keeps increasing and the app crashes eventually.
-(void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"Show Gallery"]) {
CollectionViewController *cvc = (CollectionViewController *)segue.destinationViewController;
cvc.delegate = self;
}
}
Also, the delegate is a weak reference:
#property (weak, nonatomic) id <collectionCellSelectedDelegate> delegate;

It's not enough information to localize the issue.
Are you sure CollectionViewController object is leaking? I mean it's possible that the issue is caused by non-released sub-object used in CollectionViewController code. You can try Profiler Xcode Instrument to clarify it.
Also the issue may be caused by custom UIStoryboardSegue subclass (of course if you are using it).
Anyway, it would be great the take a look at storyboard and CollectionViewController code

Related

Error passing value in segue between ViewController and UITabBarController

I have been having trouble with what seems to be a fairly simple problem but I can't for the life of me work it out.
My initial view is a single view not embedded in anything where the user logs in. After they press a button I want to transition to a view that is part of a 2 tabbed UITabBarController that is also embedded in a UINavigationController. I have tried every type of Segue but I either get a NSInvalidArgumentException with the UITabBarController or it takes me to the next view but the tab bar and the navigation bar do not appear on the screen.
Hopefully this is detailed enough to understand what I am trying to do, if it is not I can upload a picture of my storyboard.
Edit - Here is my storyboard and my prepareForSegue method
Storyboard : http://oi60.tinypic.com/2pyub0o.jpg
#property (nonatomic) int numberOfPlayers;
#property (strong, nonatomic) UITabBarController *tbc;
#property (strong, nonatomic) MPAMainViewController *mvc;
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if([[segue identifier] isEqualToString:#"startGameSegue"]){
self.numberOfPlayers = [[self.textField text] intValue];
self.tbc = (UITabBarController *) [segue destinationViewController];
self.mvc = [self.tbc.viewControllers objectAtIndex:0];
self.mvc.numberOfPlayers = self.numberOfPlayers;
}
The error I am currently encountering is "-[UINavigationController setNumberOfPlayers:]: unrecognized selector sent to instance 0x7fc103493380"
Thanks!

ViewController's private data set to nil after returning from segue

I have two views. The main view is ViewController and the next is AddItemViewController. ViewController has a tableview that lists items that you add when you go to AddItemViewController. There is a button on AddItemViewController that segues back to ViewController. The problem is, upon returning to ViewController expecting that an item be added, the private data of ViewController is suddenly set to nil. I have lost data and any chance to interact with my objects after returning from the segue.
Here is the data that's getting set to nil
#property (strong, nonatomic) costEstimator *myCost;
#property NSString *testString;
What am I doing wrong?
Here is my prepareforsegue code in the AddItemViewController
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
ViewController *vC = [segue destinationViewController];
[vC addSomething:_selectedItem withQuantity:[_quantBox.text doubleValue]];
}
Any help is greatly appreciated. Thanks :)
You want to pop the AddItemViewController in this case. When you segued from ViewController to AddItemViewController, I am guessing you did a push. What this does effectively is it adds AddItemViewController ontop of ViewController in the memory stack. By 'segue-ing' again from AddItemViewController to ViewController, you are adding ANOTHER ViewController instance ONTOP of AddItemViewController. This is why you think you are losing your data when in actuality, you aren't. You are only seeing the wrong view controller.

didSelectViewController does not get called on certain occasions

I have the problem that many already have reported, didSelectViewController doesn't get called, but in my case it sometimes gets called. I have three tabs and three view controllers. Every time user presses second or third tab I need to execute some code. In my SecondViewController and ThirdViewController I have:
UITabBarController *tabBarController = (UITabBarController *)[UIApplication sharedApplication].keyWindow.rootViewController;
[tabBarController setDelegate:self];
Now everything works fine with the SecondViewController, the didSelectViewController gets called every time the second tab is pressed. Also in ThirdViewController didSelectViewControllergets called every time the third tab is pressed but only when second bar is meanwhile not pressed. So when I switch back and forth between FirstViewController and ThirdViewController everything is OK. But when I go in a pattern like first->second->third, then didSelectViewController doesn't get called in ThirdViewController. Also when I go like first->third->second->third didSelectViewController gets called in ThirdViewController the first time but not the second time. Any ideas?
It's hard to follow what exactly you are doing, but from what I understand you are responding to tab switches by changing the UITabBarController's delegate back and forth between SecondViewController and ThirdViewController.
If that is true, I would advise against doing this. Instead I would suggest you try the following:
Assign a delegate that never changes. For a start you could use your app delegate, but it would probably be better if you had a dedicated small class for this. I am sure that now you have a non-changing delegate, it will get 100% of all the calls to tabBarController: didSelectViewController:.
The object that is the delegate must have a reference to both the SecondViewController and ThirdViewController instances. If you are designing your UI with Interface Builder, you might do this by adding two IBOutlets to the delegate class and connecting the appropriate instances to the outlets.
Now when the delegate receives tabBarController: didSelectViewController: it can simply forward the notification to either SecondViewController or ThirdViewController, depending on which of the tabs was selected.
A basic code example:
// TabBarControllerDelegate.h file
#interface TabBarControllerDelegate : NSObject <UITabBarControllerDelegate>
{
}
#property(nonatomic, retain) IBOutlet SecondViewController* secondViewController;
#property(nonatomic, retain) IBOutlet ThirdViewController* thirdViewController;
// TabBarControllerDelegate.m file
- (void) tabBarController:(UITabBarController*)tabBarController didSelectViewController:(UIViewController*)viewController
{
if (viewController == self.secondViewController)
[self.secondViewController doSomething];
else if (viewController == self.thirdViewController)
[self.thirdViewController doSomethingElse];
}
EDIT
Some hints on how to integrate the example code from above into your project:
Add an instance of TabBarControllerDelegate to the .xib file that also contains the TabBarController
Connect the delegate outlet of TabBarController' to the TabBarControllerDelegate instance
Connect the secondViewController outlet of TabBarControllerDelegate to the SecondViewController instance
Connect the thirdViewController outlet of TabBarControllerDelegate to the ThirdViewController instance
Add a method - (void) doSomething to SecondViewController
Add a method - (void) doSomethingElse to ThirdViewController
Make sure that you don't have any code left in SecondViewController and ThirdViewController changes the TabBarController delegate!
Once you are all set and everything is working fine, you will probably want to cleanup a bit:
Change the names of the notification methods doSomething and doSomethingElse to something more sensible
If you followed the discussion in the comments, maybe you also want to get rid of the secondViewController and thirdViewController outlets
I too had this problem and got fed up with it. I decided to subclass UITabBarController and override the following methods. The reason I did both was for some reason on application launch setSelectedViewController: wasn't being called.
- (void)setSelectedIndex:(NSUInteger)selectedIndex
{
[super setSelectedIndex:selectedIndex];
// my code
}
- (void)setSelectedViewController:(UIViewController *)selectedViewController
{
[super setSelectedViewController:selectedViewController];
// my code
}
I just dug through this tutorial on storyboards, and I thought of an alternative to using UITabBarControllerDelegate. If you want to stick to UITabBarControllerDelegate then feel free to ignore this answer.
First, create a subclass of UITabBarController, let's call it MyTabBarController. In the storyboard editor you need to change the "Class" property of the tab bar controller so that the storyboard picks up your new class.
Add this code to MyTabBarController.m
- (void) prepareForSegue:(UIStoryboardSegue*)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"SecondVC"])
{
SecondViewController* secondViewController = (SecondViewController*)segue.destinationViewController;
[secondViewController doSomething];
}
else if ([segue.identifier isEqualToString:#"ThirdVC"])
{
ThirdViewController* thirdViewController = (ThirdViewController*)segue.destinationViewController;
[thirdViewController doSomethingElse];
}
}
In the storyboard editor, you can now select the two segues that connect to SecondViewController and ThirdViewController and change the segue identifier to "SecondVC" and "ThirdVC", respectively.
If I am not mistaken, that's all you need to do.

UIView not Initialized from Storyboard Segue

I am attempting to perform a Storyboard segue from a UIbutton that is a day on a calendar to a DayViewController with info on that day. The currentTitle of the sender UIbutton should be passed to a UILabel in the dayView of the DayViewController. It successfully segues to the new VC and assigns the button title to a property therein, however the dayView (and the base view) of my DayViewController is not getting initialized (their addresses in the debugger are both 0x0) and I get a blank page. How can I get these views to initialize in this segue? (I thought views linked to a VC were automatically initialized when segued to?)
Here's the prepareForSegue getting called:
-(void) prepareForSegue:(UIStoryboardSegue *)segue sender:(UIButton *)sender
{
if ([segue.identifier isEqualToString:#"ShowDay"]) {
DayViewController *vc = segue.destinationViewController;
[vc setCurrentDayNumber:sender.currentTitle];
}
Here is setCurrentDayNumber in the new VC
-(void)setCurrentDayNumber:(NSString *)currentDayNumber
{
_currentDayNumber = currentDayNumber;
[self.dayView setNeedsDisplay];
}
Here is my view that is linked to DayViewController in the storyboard.
#property (nonatomic, weak) IBOutlet DayView *dayView;
Thanks in advance!
When you declare a property as weak, it will not "hold on" to the value unless someone else has a reference to it as well. When the new view controller is pushed, the old button goes away, leaving currentDayNumber as the only reference, so it automatically sets itself to nil. (This is what weak is supposed to do.)
If you want to keep a reference to it no matter what, use strong instead of weak and make sure to set _currentDayNumber to nil somewhere (like viewDidUnload) so that you don't retain it forever.

Custom Segue Pushing/Popping UIViewControllers

I'm trying to implement an iBooks-like flip transition as a storyboard. The segue should push resp. pop the destinationViewController onto/from the UINavigationControllers stack.
I can push viewcontrollers in my segues perform method but I am not able to pop. When I pop the controller right after creating my flip animation the animation does not run and its callback - that should perform [[UIApplication sharedApplication] endIgnoringInteractionEvents] gets never called and my App results dead.
So I tried to push/pop in the animationDidStop:anim:flag delegate method but it never gets called with the flag set to true.
I assume that the segue is deallocated before the delegate method gets called. What else could I do?
Forgive me if I am completely misunderstanding this question, but it seems like you just want to do a basic horizontal flip back and forth between two view controllers. And even if you've already figured this out, maybe it will help anyone else who has the same question.
(1) In your storyboard (that has ViewController A & B) create a Modal Segue from A to B. Give it an identifier (showViewControllerB) and choose Transition:Flip Horizontal.
We set up the protocol and delegates:
(2a) In ViewControllerB.h add above #interface:
#class ViewControllerB;
#protocol ViewControllerBDelegate
- (void)viewControllerBDidFinish:(ViewControllerB *)controller;
#end
(2b) Add the delegate as a property:
#property (weak, nonatomic) id <ViewControllerBDelegate> delegate;
(3a) In ViewControllerB.m synthesize:
#synthesize delegate;
(3b) And delegate in the method to flip back:
- (IBAction)flipBack:(id)sender
{
[self.delegate viewControllerBDidFinish:self];
}
(4) In ViewControllerA.h add at the very top #import "ViewControllerB.h" and on the end of #interface <ViewControllerBDelegate>
(5a) In ViewControllerA.m add the method to conform to the protocol:
- (void)viewControllerBDidFinish:(ViewControllerB *)controller
{
[self dismissModalViewControllerAnimated:YES];
}
(5b) Then set it as the delegate in prepareForSegue:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"showViewControllerB"]) {
[[segue destinationViewController] setDelegate:self];
}
}
I hope this answers your question. If I misunderstood, just let me know.
Your question is a bit confusing as so mingle pop, push, flip and backflip. I´m not sure if ai can answer your question, but i can tell what i did.
If i push a viewController into the navigation controller stack and set the Storyboard Segue Style to Push, it will be pushed into the view from right to left. A back button appears and shows the title of the presentingViewController in it.
If i set the Storyboard Segue Style to Modal i can set the Transition to Flip Horizontal (what seems to be what you want). But then no Back Button will appear. In the presentedViewController i dismiss the view with:
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
It will flip the second view back with a right flip.
But this is the dirty solution and it is not recommended by apple.
http://developer.apple.com/library/ios/featuredarticles/ViewControllerPGforiPhoneOS/ManagingDataFlowBetweenViewControllers/ManagingDataFlowBetweenViewControllers.html#//apple_ref/doc/uid/TP40007457-CH8-SW9
Luke Dubert gave you an example how to implement the delegate.

Resources