using Instrument after my migration to ARC I realise that the transition from screens does not clean memory.
example of steps :
1)Home screen A -> game screen B = rising of memory usage
2)Game is finished and I go from screen B back to Home screen A
For step 2, memory usage does not go lower. I'd like to have the memory consumed by screen B being freed when removing screen B from screen ... What should I do to be sure this freeing to happen ?
Going from A to B :
GameVC_iPad *game = [[GameVC_iPad alloc]initWithNibName:#"ClassicGameVC_iPad" bundle:nil];
[self presentViewController:game animated:YES completion:nil];
Going back to A from B is done using this code :
HomeVC_iPad *home = [[HomeVC_iPad alloc]initWithNibName:#"HomeVC_iPad" bundle:nil];
home.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self presentViewController:home animated:YES completion:nil];
Any clue ?
When you go back to A you should do
[self dismissModalViewControllerAnimated:YES];
What you are currently doing is creating a new view controller wich is wrong and navigate to it another time, so this is what is happening
A presents B wich then you present a new A wich then presents a new B ans so on...
Also note that when you navigate to a new viewController iOS caches some view data, so you will never be able to achieve a perfect memory usage before and after you went back,
Don't create a new copy of your home controller. Use dismissViewControllerAnimated:completion: to return to the existing one.
If you create a delegate class for b, so lets say for example you called it BDelegate and made the ViewController for A conform to that protocol, then you can easily pass a message back to A that you want B to be removed. So for example you could create :
BDelegate :
#protocol BDelegate <NSObject>
- (void)dismissViewB;
#end
Then change view controller for A (header file) to :
#interface AViewController : UIViewController <BDelegate>
Obviously using the actual name of your view controller in there.
In the body of the view controller A, add the following method
- (void)dismissViewB {
[self dismissViewControllerAnimated:YES completion:NULL];
}
Almost there! Now in your B view controller, wherever you want to actually remove the view, so I assume where you currently have
HomeVC_iPad *home = [[HomeVC_iPad alloc]initWithNibName:#"HomeVC_iPad" bundle:nil];
home.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self presentViewController:home animated:YES completion:nil];
Replace that with
[delegate dismissViewB];
Now all you need inside view controller B is an instance variable pointing to the delegate of A and to assign it. So in the header of controller B add something like
NSObject<BDelegate> *delegate;
And add the appropriate #property for it and #synthesise it in the body. Then when you create view controller B as you are in your first post, simply add
game.delegate = self
Then if it's all gone well, when you tap the button or do whatever you need to do to remove the view, the view controller A will dismiss it for you :)
Hope this helps
Related
I have an issue the dismissing view controller to the tabbed view controller. I will explain my flow.
view controller to tabbed view controller(push)
I have 5 tabs in the tabbed view controller in the 3 tabbed i have an camera view from their i am presenting the view controller and passing some parameters by using this code
UIStoryboard *storybord=[UIStoryboard storyboardWithName:#"Main" bundle:nil];
shareViewController *shareview=[storybord instantiateViewControllerWithIdentifier:#"share"];
[self presentViewController:shareview animated:YES completion:nil];
//shareview.finalvideourl=videoURL;
shareview.videooutputstring=videoPath;
from the share view controller I want send the data back to the 1st tab for this I am using the below code
UIStoryboard *story=[UIStoryboard storyboardWithName:#"Main" bundle:nil];
TabedfirstViewController *Tabedfirst=[story instantiateViewControllerWithIdentifier:#"id"];
UINavigationController *nc = [[UINavigationController alloc] initWithRootViewController:Tabedfirst];
[self presentViewController: nc animated:YES completion:^{
[[NSNotificationCenter defaultCenter] postNotificationName:#"ShareArray" object:_selectedimgarray];
[[NSNotificationCenter defaultCenter] postNotificationName:#"SharetitleArray" object:_newtile];
[[NSNotificationCenter defaultCenter] postNotificationName:#"sharevideooutputstring" object:_videooutputstring];
}];
When I do this very thing working good I am sending the data from share view to the tabbed view and I am printing it.
The problem is when I send the second time data from share viewcontroller to the tabbed view controller first data is deleting and the second passed data is replacing the first i.e. I have the 15 objects in an array, from share view controller I am passing array to the tabbed view controller now the array count is 16 and I am printing it, now again I am passing one more object from the share view to tabbed view the array count must increase to 17 but it 16 only.
In the App delegate add/make properties of your arrays like
#property (nonatomic, retain) NSMutableArray *array1;
In AppDelegate.m initiate all your properties
After that in the controller where you are storing the values add instance of AppDelegate as
In
- (void)viewDidLoad
{
[super viewDidLoad];
objAppDelegate = (AppDelegate*)[[UIApplication sharedApplication]delegate];
objAppDelegate.array1= _imagearray=[#[#"bootimages",#"fall-photography-in-hocking-hills", #"gift",#"hillimages",#"Mercedes-classe-S-W116_large_dettaglio_articolo",#"prinimages",#"resplendent",#"tnb4",#"Tomato-plant",#"Vole",#"waterimages",#"fall-photography-in-hocking-hills", #"gift",#"hillimages",#"Mercedes-classe-S-W116_large_dettaglio_articolo"]mutableCopy];
then
[objAppDelegate.array1 insertObject:[userInfo firstObject] atIndex:0];
The values are maintained and can be used anywhere in the whole project. If your number of objects keeps on increasing then you NSMutableDictionary and add as much key/value pairs as you like.
I'm not sure why you are creating new storyboards using your main storyboard. All you have to do is presentViewController from one the viewcontroller you want to return from with the id of a segue from that viewcontroller back to the root of your tabbed view controller. In your storyboard, ctrl+drag from a button (or whatever is triggering the segue) and drag it to the root of the tab view controller. Select the segue and in the attributes editor give it an id. then in the viewcontroller call presentViewcontroller with the id and it should work fine.
In the shareViewcontroller.h write the add the following method
#protocol childDelegate <NSObject>
-(void) sendImage:(UIImage *)img
#end
and the property
#property (assign) id <childDelegate> cDelegate;
once you have done that on the controller where you have the method
-(void) receivedArray:(NSNotification*)notification
add the method
-(void) sendImage:(UIImage *)img
{
// Add image to app.array1 here.
}
In the share view controller where you are sending the image to the last controller call this method
[self.cDelegate sendImage:"your image comes here"];
Still if you face as issue, then refer to How do I set up a simple delegate to communicate between two view controllers?
Let me know if you still face an issue.
Let's say I have 3 controllers (A, B, C). A and C is ViewControllers, B is NavigationController. Normal application flow is A as root view, A present (modal) B, B push C.
What I want is to present C as top view controllers without going through all the animation from A-B-C but still have the hierarchy (means C can go back to A), is it possible?
We can set window rooViewController directly to C but it wont have the hierarchy
EDIT:
Maybe my question isnt clear enough, the main point here is, when I open my app, I want to show C directly but still have A->B->C view hierarchy so I can go back to A via normal pop and dismiss
EDIT2:
I manage to show C with B-C hierarchy, so I can pop back to B from C. Now my problem is how can I present B (NavigationController) from A (ViewController) so when I close B it will **dismiss* to A
EDIT3:
I saw some answer that use NavigationController, it works BUT not what I want because normally from A to B I use modal presentViewController and from B to A I use dismissViewController
EDIT4:
So far what I got is
self.window.rootViewController = vcA;
[self.window makeKeyAndVisible];
[vcA presentViewController:vcB animated:NO completion:nil];
[vcB pushViewController:vcC animated:NO];
this will give correct hierarchy that I want but it give fast animation (a blink) showing A and than C and also give warning Unbalanced calls to begin/end appearance transitions for <vcA: 0x7fcfa0cf9c50>.
EDIT5:
I endup ignoring the warning and stick with my prev answer (but still welcome for another solution). And for the blinking problem I use workaround below
uiview *overlay = [new uiview]; // using vcA.frame
overlay.backgroundColor = white; // I use dominant color of vcC
vcA addSubview:overlay;
self.window.rootViewController = vcA;
[self.window makeKeyAndVisible];
[vcA presentViewController:vcB animated:NO completion:^{
[overlay removeFromSuperview];
}];
[vcB pushViewController:vcC animated:NO];
This will disguise the blinking behavior so no one will notice (I hope :-p)
Use a UINavigationViewController and then call
setViewControllers(_:animated:)
Use this method to update or replace the current view controller stack without pushing or popping each controller explicitly. In
addition, this method lets you update the set of controllers without
animating the changes, which might be appropriate at launch time when
you want to return the navigation controller to a previous state.
If animations are enabled, this method decides which type of
transition to perform based on whether the last item in the items
array is already in the navigation stack. If the view controller is
currently in the stack, but is not the topmost item, this method uses
a pop transition; if it is the topmost item, no transition is
performed. If the view controller is not on the stack, this method
uses a push transition. Only one transition is performed, but when
that transition finishes, the entire contents of the stack are
replaced with the new view controllers. For example, if controllers A,
B, and C are on the stack and you set controllers D, A, and B, this
method uses a pop transition and the resulting stack contains the
controllers D, A, and B.
Let me know if it help you :)
push A and B and C like you normally would but do it by using presentViewController:? animated:NO and pushViewController:? animated:NO -- not animating is the clue
e.g. (mock code)
applicationDidFinishLaunching {
id a = [MyA new]; //root, presents b
id b = [MyA new]; //pushes c you said.. so it is or has a navigationController
id c = [MyA new];
[a presentViewController:b animated:NO];
b.navigationController pushViewController:c animated:NO];
}
To segue to any ViewController you want to: Have you drawn a custom segue in the storyboard by holding down the control key on your keyboard and clicking the ViewController you want the segue to be in? While still holding control, you can drag it to the viewcontroller you want to segue to. After that just let go and XCode will let you choose the type of segue you want: push, modal, or custom.
After that, click the visual segue reference that Xcode creates (looks like a big grey arrow in the storyboard that points to your viewcontrolelrs) and click the attributes inspector. Then look where it says identifier. From there you can name the segue anything you want and reference it programmatically. Just do the above and call the below message and you should be able to go to any viewcontroller when ever you want to.
[self performSegueWithIdentifier:#"ShowViewControllerA" sender:self];
Also, I agree with everyone else saying to set Viewcontroller C as root ViewController in the storyboard. PresentViewController is also a good idea. etc
Follow #Daij-Djan's answer:
applicationDidFinishLaunching {
id a = [MyA new]; //root, presents b
id b = [MyA new]; //pushes c you said.. so it is or has a navigationController
nav d = [[Nav alloc] initWithRoot:b];
id c = [MyA new];
[a presentViewController:d animated:NO];
b.navigationController pushViewController:c animated:NO];
}
Why don't you just present C on top of A with presentViewController?
EDIT:
A -> C:
In vcB I would add a boolean property indicating whether we are in the mentioned flow and present vbB in vcA this way:
// We are in vcA where you want to present vcB and vcC
vcB.transient = YES;
[vcA presentViewController:vcB animated:NO completion:^{
[vcB pushViewController:vcC animated:NO];
}];
C -> A
When you want to go back to vcA, popping vcC will call viewDidAppear in vcB.
// We are in vcB
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
if(self.transient == YES) {
[self dismissViewControllerAnimated:NO completion:nil];
return;
}
}
With this solution when you go back from vcC to vcA you will temporary see vcB, as we have to wait for viewDidAppear to be called.
EDIT 2:
Alternatively if you don't need to directly go back from vcC to vbA, just use the first piece of code (no transient property required).
Keep A as rootVC(in applicationDidFinishLaunching). So when you open your app it will load A first.
Once A is loaded(in viewDidLoad) call a method to present B(keep animation = NO while presenting).
When B is loaded, call a method to push to C(keep animation = NO while pushing).
Hope this helps!
I have an app with 3 view controllers. A is the root, B is next and C is a modal view that appears / transitions when a button on B is clicked. What I want to do is sometimes move from C to A directly when a button on C is clicked (other times, the C->B transition should happen, which is ok). I've tried various options and incorporated info from different SO posts but in the end, stuck at the scenario where the direct transition from C to A happens but does show B momentarily.
Is there a way I can remove this momentary B appearance altogether. Also, I do need to have animations when the user goes from B to C or from C to B. It is the C to A transition that need not have an animation if there is a limitation there.
Hopefully you can chip in... have copied relevant code and further details below.
ViewControllerC.h:
#protocol ViewControllerCDelegate <NSObject>
- (void)didNavigateBackFromViewC:(ViewControllerC *)viewControllerC;
#end
#interface ViewControllerC : UIViewController
#property (nonatomic, weak) id < ViewControllerCDelegate > viewCDelegate;
#end
ViewControllerC.m:
- (IBAction)navigateToViewA:(id)sender
{
[self.viewCDelegate didNavigateBackFromViewC:self];
}
For the B->C and C->B transitions, I've used a separate transitioning controller that is a delegate of ViewControllerB. Also, ViewControllerB is a delegate of UIViewControllerTransitioningDelegate... I've not included those details for the sake of simplicity here but hopefully am able to convey the problem here about the C->A transition.
ViewControllerB.h:
#interface ViewControllerB () <ViewControllerCDelegate>
#end
ViewControllerB.m:
- (void)didNavigateBackFromViewC:(ViewControllerC *)viewControllerC
{
__block ViewControllerB *me = self;
[viewControllerC dismissViewControllerAnimated:YES completion:^{
[me.navigationController popToRootViewControllerAnimated:YES];
}];
}
really interesting question #vikram17000. First of all, you might want to try unwind segues, it seems that they can do what you want but I haven't tested that. It appered you were going the programmatic route, and here's what i found. I split (what I called) the 'back' and 'home' actions into two separate actions. Here's my ViewControllerC code:
-(IBAction)goBack:(id)sender
{
[self dismissViewControllerAnimated:YES completion:nil];
}
-(IBAction)goHome:(id)sender
{
[self.delegate ViewControllerCWillGoHome:self];
[self dismissViewControllerAnimated:YES completion:nil];
}
ViewControllerB is the delegate that gets the -...WillGoHome, and it popsToRootVC before VC C dismisses. This gets the behavior we want, C animates out and A is left - yay! Unfortunately there's a catch -
Unbalanced calls to begin/end appearance transitions for ViewControllerB: 0x7fc3c857ca80.
So is it possible? Yes. Does the os like it? No it does not. Hopefully this helps you get going though.
I've created sample project for your question.
There is protocol GoBackProtocol which contains method
-(void)controller:(UIViewController *)controller didPressBackButton:(UIButton *)button;
A and B controllers adopt GoBackProtocol. B controller implements -controller:didPressBackButton: method and decides based on button parameter what to do: hide presented controller with animation or hide it without animation and forward call to his delegate, which is controller A.
- (void)controller:(UIViewController *)controller didPressBackButton:(UIButton *)button
{
ViewControllerC *modalController = (ViewControllerC *)controller;
if (button == modalController.buttonB)
{
[controller.presentingViewController dismissViewControllerAnimated:YES completion:nil];
}
else if (button == modalController.buttonA)
{
[controller.presentingViewController dismissViewControllerAnimated:NO completion:nil];
[self.delegate controller:self didPressBackButton:nil];
}
}
Controller A implements -controller:didPressBackButton: as well and pops viewController B.
- (void)controller:(UIViewController *)controller didPressBackButton:(UIButton *)button
{
[self.navigationController popViewControllerAnimated:YES];
}
All Together it makes desired effect.
Please forget all custom protocol solutions here. Apple says:
The presenting view controller is responsible for dismissing the view controller it presented.
IMO you have at least two options here:
1) You could add the close button on C to public interface like
#property (nonatomic, strong, readonly) UIButton *closeButton;
Set the target differently and use a combination of dismissViewControllerAnimated:completion: and popToRootViewControllerAnimated: as in your method didNavigateBackFromViewC
2) You could use self.navigationController.presentingViewController in C to get access to the non-modal viewcontrollers.
As non-official option you could set the viewcontrollers array on a UINavigationController instance directly. Then you will get a Push Animation from C to A.
I haven't fully testing this out yet but results so far seem encouraging. Given that I wanted to be able to transition to B or C from A, and that B and C could navigate between each other, I've started playing around with making ViewControllerA as the container view controller for ViewControllerB and ViewControllerC. That allows me easy access to B / C from A, as well as the ability to transition between B and C seamlessly using custom transitions.
I have an app where you can customize products to varying degrees. In some cases the options are split to two views, while in some other cases the first step isn't necessary.
What I would like is to treat all products the same and push the first customization step view controller to the navigation controller stack, let that view controller decide whether or not this step is necessary. If it is not necessary I want it to apply some default options to the product and immediately skip (before the transition animation) to step 2 while not allowing the user to back up to the first step.
The normal UINavigationController.viewControllers stack may look like this when at step 2:
[ListView (root)] -> [CustomizeStep1] -> [CustomizeStep2]
But I want it to apply the default values to the product and amend the view controller stack so that:
[ListView (root)] -> [CustomizeStep1]
----- becomes -----
[ListView (root)] -> [CustomizeStep2]
What I've tried is to use code like this in the CustomizeStep1 view controller:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if (shouldSkipToStep2) {
UINavigationController *navController = self.navigationController;
// Move directly to step 2
UIStoryboard *storyboardLoader = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
UIViewController *customizeStep2VC = [storyboardLoader instantiateViewControllerWithIdentifier:#"customizeStep2"];
// Replace current view contoller
NSMutableArray *viewHierarchy = [NSMutableArray arrayWithArray:navController.viewControllers];
[viewHierarchy removeObject:self];
[viewHierarchy addObject:customizeVC];
// Apply new viewController stack
[navController setViewControllers:viewHierarchy animated:NO];
}
}
If I take a look at the navigation controller's viewControllers array after this has been set, everything looks as expected.
What happens in iOS 7
When doing this, the entire functionality of the UINavigationController breaks. The CustomizeStep1 view controller still animates in but is nonfunctional. Tapping the back button still shows CustomizeStep1. Trying to interact with the view controller crashes the app. (It works as expected if the view controller is displayed without the sliding transition, though.)
What happens in iOS 8
The CustomizeStep1 view controller still animates in, but immediately after the transition ends it snaps over to show CustomizeStep2. Other than that it works as intended.
So, my question is if there is a better place to add the code to amend the view controller stack on the navigation controller?
I obviously need to wait until the view controller has been added to the navigation controller, otherwise I can't replace the view controller in the stack. However, I need to be able to cancel the transition animation so that I can animate in CustomizeStep2 instead.
I appreciate if this is impossible, just wanted to check if anyone knows a good way around this.
Edit:
How I would like it to ideally appear to the user
Instead of viewWillAppear:, use viewDidAppear: which is called after the animation finishes.
You could have a boolean on your view controller denoting whether it is filled in or not:
#interface ViewControllerOne : UIViewController
#property (nonatomic, assign, getter = isInitiallyFilledIn) BOOL initiallyFilledIn;
#end
Then, when it is initially filled in, just denote this boolean value.
ViewControllerOne *viewController = [[ViewControllerOne alloc] init];
[viewController setInitiallyFilledIn:YES];
Now, in viewDidAppear:, check this boolean value and check whether that method has been launched before. If it hasn't been launched before (to allow editing) and it is initially filled in, push the next controller!
#interface ViewControllerOne
#property (nonatomic, assign) BOOL hasCheckedFillInStatusBefore;
#end
#implementation ViewControllerOne
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
if ([self isInitiallyFilledIn] && ![self hasCheckedFillInStatusBefore]) {
// push the next view controller
}
[self setHasCheckedFillInStatusBefore:YES];
}
#end
Alternatively, if you want to display the two view controllers at the same time, you could alter the navigation stack:
// create instances of ViewControllerOne and ViewControllerTwo
NSMutableArray *viewControllers = [[[self navigationController] viewControllers] mutableCopy];
[viewControllers addObjectsFromArray:#[viewControllerOne, viewControllerTwo]];
[[self navigationController] setViewControllers:viewControllers animated:YES];
Note, the ViewControllerOne will not have viewDidLoad called so if you do any setup in that method (such as a back button title or the view controller title), you will either have to manually invoke that method before setting the view controllers or move that setup to the initializer.
I am using Storyboard in my app and I want to pass data from one view to another view.
Instead of using segues I am using instantiateViewControllerWithIdentifier. In this case I am instantiate from my first TableViewController to a NavigationController which has a second TableViewController attached because I need the navigation in the second TableViewController. Now I want to pass data from my first TableviewController, depending which row was clicked, to my second TableviewController. In this case newTopViewController would be my NavigationController but my problem is now how to pass data from firstTableViewController to the secondTableviewController.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *identifier = [NSString stringWithFormat:#"%#Top", [menuArray objectAtIndex:indexPath.row]];
UIViewController *newTopViewController = [self.storyboard instantiateViewControllerWithIdentifier:identifier];
}
If you instantiate a navigationController, you can use the viewControllers property to get the inner viewController of the navigation controller.
Something like this:
UINavigationController *navigationController = [self.storyboard instantiateViewControllerWithIdentifier:identifier];
MBFancyViewController *viewController = navigationController.viewControllers[0];
// setup "inner" view controller
viewController.foo = bar;
[self presentViewController:navigationController animated:YES completion:nil];
newTopViewController.anyVariableToShow= anyVariableToSend;
I do this pretty often on a few of my apps...
//Create new VC
CookViewController *detailViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"CookVC"];
//Set recipe
[detailViewController setRecipe:recipe];
//Pop over VC (can be pushed with a nav controller)
[self presentPopupViewController:detailViewController animationType:MJPopupViewAnimationFade];
If you aren't using a navigation controller or segues, then I think you need to reconsider your app design.
Actually it's not just a data pass problem as this is a program control and data transfer question together.
Even you would have to rethink about your app's concept, as you'd like to use storyboard without the meaning of storyboard, it's up to you and I hope you have good reason to do what you do.
So when you decided not to use segue you lost the new and comfortable way of instantiating a new controller and transferring data with it and you have to do the transfer of control and the data in two distinct steps. When you instantiate another scene in storyboard (like you do with instantiateViewControllerWithIdentifier:) you just instantiated a new controller and transferred the control but not the data. Just think about it as you instantiated a new controller from a xib in an old way (so you have to use initWithCoder: or awakeFromNib in the second view controller as the storyboard will not call initWithName:bundle:), but did not do anything more.
So you will have a new controller (it named in the identity part of the second storyboard) which is hanging in the universe without any relationship or connection with anything else (as the storyboard picture illustrates it nicely) and you could do with it what you'd like.
So you'd like to do something and you need data from the previous storyboard (ViewController). What you need is making available those data to the second storyboard(ViewController), and as you know there are lot of solution for this which were available long time before even storyboard is existed.
So regarding your code, the "data transfer" is depending on your design, whether the two controllers are subclasses of each other or whatsoever...
If you don't like to deal with subclassing and like to decoupling them as much as possible, the best way just make a property of your data in the first controller and refer to them from the second (after importing the first's .h file) and just refer to it in it's viewDidLoad or in initWithCoder: or anywhere where you need them, as
secondViewControllerdata = firstViewControllerdata.thatDataProperty
Of course you can do the same in reverse and make a property of the second controller and refer to it in your first view controller.
You can define some parameter in UIViewController to receive data:
#property (assign) int param1;
#property (retain) NSMutableArray *param2;
and use below to pass the data:
[newTopViewController setParam1:XX];
[newTopViewController setParam2:XX];