I have the following storyboard in an iOS AR game I'm building in Swift:
Both VCs B1 and B2 invoke the main VC M which has an AR Scene that builds a hierarchy of nodes that take up quite some memory.
VC M has 2 Container Views C1 and C2. C1 is a custom dialog with 4 buttons and another Container View C1C of its own which shows a confirmation dialog. (The last VC on the right is no longer used).
In a certain condition, when the user clicks on "OK" in C1C, the game should end and the user should be taken back to B1 or B2.
All of this works fine, except the memory taken by M has not been deallocated and starting a new game will only increase the memory usage each time until the app will crash.
I researched this issue and I found 2 main reasons for this:
A. using regular segue to go back instead of doing an unwind segue: indeed that was my first mistake. So I fixed that and I can now confirm that going back from B1 or B2 back to A does call their respective deinit, but going back from M still does not so I'm left with multiple copies of M and their Container Views as well. (I'm not using a Navigation Controller).
B. there may be some strong reference cycle or other code like a timer that doesn't let the VC get destroyed.
I've already started searching for such issues without any luck so far. But before I go on, I have a few questions:
since I'm dealing with an AR Scene, should I be doing something more to deinitialize that M VC?
in which VC should I be doing the segue back to B1/B2? (BTW, both have the same unwind action and the app knows by itself to which VC to go back to, that works like a charm).
So should I run the performSegue(withIdentifier:, sender:) from C1C which is where the user presses the OK button or should it run from the main M VC?
In order to accomplish the latter, I created a function unwindToParentVC() in M which runs the segue code. Now since I'm using Container Views, I can reference the parent VCs and since I'm 2 levels deep already, in C1C I just call parentVC?.parentVC?.unwindToParentVC().
I ran it both ways, from C1C and indirectly from M as mentioned above and both ways work fine, but maybe one is correct and the other is not?
is it even correct to run code from M through C1 or C1C like that as above, like I called parentVC?.parentVC?.unwindToParentVC()? I'm doing more of that. Is that creating some unwanted reference cycles? I didn't think it does, but maybe I'm wrong?
When I checked the Memory Graph, I did see the M VC and its children appear multiple times, but all the arrows seemed to be going in a single direction from left to right. I didn't see any loop. Does that mean that I have no strong reference cycle? (There were only loops from the VCs to themselves).
If there's anything else that may help me resolve this issue, you'll be very welcome obviously.
Thanks!
Related
I've two UIViewControllers say A and B. And I've a segue from A to B which is being used by multiple buttons in A.
I've a use case where I want to give option to user to jump from B to B itself but to the next level of a game. You can think of it like A being level selection screen (Level 1, 2, 3, 4...and so on), and B being the game screen for level 1/2/3/4/etc.
When level 1 is finished, I want to give user a button with an option to jump to next level directly (i.e. same UIViewController with different data for the level). Can somebody suggest what's the best way to implement it?
I suggest you should better subclass a baseGameViewController, which own your game scence needs common property, and every viewController has its own special feature.
Your post's meaning is impractical, because:
In storyboard, every ViewController interface only can have one ViewController Class.
If you use one ViewController to handle so many scence event and data, it is complex, and also it is not accord with design model.
Apologies, if this is a little unclear - I'm a noob when it comes to iOS programming. Here's the scenario:
I've got a LogInView, a CategoryView, a CheckerView, a WalkthroughView, and a LandingPageView.
The user starts at LogInView, and depending on the app's bluetooth state, and whether or not the user has been registered, either goes to:
CheckerView (Registered, Bluetooth Off)
LandingPageView (Registered, Bluetooth On)
CategoryView (Unregistered)
If the user hits CategoryView, depending on the state of his bluetooth connection, he goes to either (this part, so far, works okay):
WalkthroughView (Bluetooth on)
CheckerView (Bluetooth off)
The catch is that CategoryView will always go through WalkthroughView, regardless of whether or not bluetooth is on. So, here's what my storyboard looks like:
A right hot mess, I know. Since both LogInView and CategoryView can, at some point, go into CheckerView, I need a way to check which of the segues was used, such that:
CheckerView will always go into LandingView if the previous view was LogInView, and
It will always go into WalkthroughView if the previous view was CategoryView.
I'm vaguely aware of a prepareForSegue function, but I've no idea yet how to use it, nor where to put it (from the previous page, or on the receiving page?)
Any suggestions? Thanks.
It sounds like you're testing conditions to determine where you'll segue. If that's the case, perhaps you could test conditions (registered/unregistered, Bluetooth enabled/disabled). Based on the various conditions, you could use performSegueWithIdentifier to determine where to go next and set up the next ViewController in prepareForSegue using the a segue identifier, rather than "looking back" to see where you came from.
My standard suggestion is that once application state becomes complex it should be moved out of view controllers and into an actual Data Model object.
The Data Model can either be a custom class you create (preferred for scalability). Or, in this case where there's not a great volume of information being shared, you could look at putting it into NSUserDefaults and reading from there when needed.
Now, yes, there are hundreds of questions (and answers) of how to perform custom segues. However, and I'm no exaggerating, ALL of these answers are wrong (all 50+ I've seen)! Sorry, this might sound harsh, but the truth is, NONE of the suggested answers gives the same (correct) result as Apples built in transitions do (vertical cover etc.).
To be more specific, this is the result that's expected (confirmed with logs):
Segue begins (adds view to hierarchy, invokes viewWillAppear on destinationVC and viewWillDisappear on sourceVC and starts animation).
animation is performed for the whole duration
Segue ends (animation finished, sets the destinationVC as the current VC, either on stack or modally presented. Invokes viewDidAppear on destinationVC and viewDidDisappear on sourceVC).
In short: invoke viewWillAppear/Disappear -> animate transition -> invoke viewDidAppear/Disappear
Using apples built-in segues, this is the expected behavior but somehow not a single soul except me have had issues with this. A lot of versions even add the destination-view as subview first, animates it then removes it again and calls
[srcVC presentModalViewController:destVC animated:NO];
or
[srcVC.navigationController pushViewController:destVC animated:NO];
causing the view-events to be sent in all kinds of random order (same issue with CoreAnimations).
In my case, all I really want is the "Vertical Cover"-transition reverted (top to bottom), with all other events sent EXACTLY as expected (shown above).
So, am I just supposed to live with all kinds of ugly workarounds (doing my "tasks" in hard-coded methods called whenever I need them to etc.), or is there some hidden proper way of doing this in a reusable manner?
Funny thing: Even apple suggest that you do it the "wrong" way, making it seem like the right way but with inconsistent outcome compared to their own ways… So my best guess is that apple do this under the hood, and forgot to give enough flexibility for clients to perform the same operations (big flaw in other words), or that I'm just on some bad trip and see some issue that doesn't exist…
Okay, this might not be a true answer of how to solve it for custom segues (subclassing UIStoryboardSegue), but it does solve the general issue!
After some casual reading about new features, I stumbled upon a new way to do custom transitions between ViewControllers introduced in iOS7 called nothing more than "Custom Transitions" i guess!
Read about it here and here, or video from WWDC here.
I've just dipped my toes, but basically it is a new, closer to the system way of doing transitions and allows for better control = better looking transitions. After glancing at the example provided by the blog I referenced (git found here) I can confirm that FINALLY, we are able to do transitions which behave as ONE EXPECTS THEM TO with events fired at the expected occasions!
Since I'm just reading about it I can't give a thorough explanation yet, but check out the links :)
Note: This is maybe not supposed to completely replace custom segues, but can be used in a similar fashion (check examples) so if you need that little extra fancy transition, this is definitely the way to go by the looks of it! Basically you setup segues in the storyboard, and hook up the correct transition-delegates in the prepareForSegue:-method.
I have a simple app that consists of a sidebar menu (I'm using SWRevealViewController) which contains a table view, each cell of which has a segue pointing to a UIWebViewController. So the user can pull open the sidebar and switch between various configured mobile sites (among other things).
I've got it working fine, but I've noticed that, as I switch back and forth between the sidebar tabs, the number of controllers that get pinged during a memory warning keeps growing. It appears that a new UIWebVewController is created each time I switch tabs, which is fine, except that the framework code appears to be keeping a list of each controller that is created and is never letting go, causing the memory to keep climbing. I'm sure there's a way that I can clean up that list, but I haven't found it yet…
So, my questions are
What is it that is holding on to references to each UIViewController that is created, and where can I find/access that?
How do I clean that up?
What framework code/class is in charge of calling didReceiveMemoryWarning:, and where does that guy get the list of controllers that need to receive the warning?
In searching around, I came across this StackOverflow question, which hints that popViewControllerAnimated: might be how I can cleanup unneeded controllers, but I'm not sure which object I should be calling that on, since I don't know the answer to #1 or #3 above...
It turns out, in my case, the thing holding a reference to my controllers (question #1) was a scheduled NSTimer that the controller was creating with itself as the target. To clean it up (question #2), I needed to invalidate the timer prior to leaving the controller (in my case, in the viewWillDisappear: method) via [myTimer invalidate].
I still haven't found the answer to question #3, and I'm still curious to know how Apple keeps track of which controllers are still alive and, therefore, need the memory warning, but question #3 isn't as important to me, anymore, now that my memory leak is gone. :)
Check if SWRevealViewController is holding on to the View Controller it is pushing on the stack. Usually you would create a dictionary of UINavigationControllers each one for your ViewController and then use the dictionary to retrieve the UINavigationControllers each time you need it.
This may be impossible, but I'm trying to save the state of my application between scene transitions, but I can't figure out what to do. Currently I love the way that when you have an application running and hit the home button, you can go back to that application just where you left off, but if you transition between scenes (in a storyboard), once you get back to that scene the application state was not saved.
I only have two different scenes that need to be saved (you transition back and forth from one to the other). How can I go about saving a storyboard scenes state without taking up precise memory?
More Detailed: Here is my entire storyboard. You transition back and forth between scenes using the plus toolbar button. On the second scene the user can tap on the table view cells and a real image will fill the image view (See figure 1.2)
Figure 1.1
In figure 1.2 you see what happens when you tap inside one of the many table view cells (an image view pops up.)
Figure 1.2
THE PROBLEM: When you tap a table view cell, which fills an image view (shown in figure 1.2) it works fine if you stay on that scene or even hit the iPhone home button (if you hit the iPhone home button and then reopen the app the scene's state was saved and the image view filled with a simple image still shows just like we left it), but if I transition (using the plus button) back to the first scene, and then use the plus button on the first scene to get back to the second scene the image view that I created (shown in figure 1.2) disappears and the second scene loads without saving the state and image views we filled.
EDIT: I tried using the same view controller for both scenes, but it didn't solve the problem.
UPDATE: I just found the following code (that I think stores a views state). How could I use this and is this what I've been looking for?
MyViewController *myViewController=[MyViewController alloc] initWithNibName:#"myView" bundle:nil];
[[self navigationController] pushViewController:myViewController animated:YES];
[myViewController release];
I would suggest a combination of two things:
1. Take DBD's advice and make sure that you don't continuously create new views
2. Create a shared class that is the data controller (for the golfers, so that the data is independent of the scene)
The correct way to make the segues would be to have one leading from the view controller on the left to the one on the right. However, to dismiss the one on the right you can use
-(IBAction)buttonPushed:(id)sender
[self dismissModalViewControllerAnimated:YES];
}
This will take you back the the view controller on the left, with the view controller on the left in its original state. The problem now is how to save the data on the right.
To do this, you can create a singleton class. Singleton classes have only one instance, so no matter how many times you go to the view controller on the right, the data will always be the same.
Singleton Class Implementation (Of a class called DataManager) - Header
#interface DataManager : NSObject {
}
+(id)initializeData;
-(id)init;
#end
Singleton Class Implementation (Of a class called DataManager) - Main
static DataManager *sharedDataManager = nil;
#implementation DataManager
+(id)initializeData {
#synchronized(self) {
if (sharedDataManager == nil)
sharedDataManager = [[self alloc] init];
}
return sharedDataManager;
}
-(id)init {
if(self == [super init]) {
}
return self;
}
#end
Then, inside your view controller code you can grab this instance like this
DataManager *sharedDataManager = [DataManager initializeDataManager];
This way you will have the same data no matter how many times you switch views.
Also, you can better adhere to MVC programming by keeping you data and your view controllers separate. (http://en.wikipedia.org/wiki/Model–view–controller)
Figure 1.1 has a fundamental flaw which I believe the basis of your problem.
Segues (the arrows between controllers on the storyboard) create new versions of the UIViewControllers. You have circular segues. So when you go "back" to the original screen through the segue is really taking you forward by creating a new version.
This can create a major problem for memory usage, but it also means you can't maintain state because each newly created item is an empty slate.
Since your are using a UINavigationController and pushViewController:animated: you should "pop" your controller to get rid of it.
On your "second" scene, remove the segue from the + button and create an IBAction on a touchUpInside event. In the IBAction code add the "pop"
- (IBAction)plusButtonTapped {
[self.navigationController popViewControllerAnimated:YES];
}
I see what you mean. This should happen to every application, as when the last view controller in the navigation stack is transitioned away from, it is deallocated and freed. If you need to save values such as text or object positions, a plist may be the way to go. See this related question for how to use a plist.
Apple isn't going to do this for you. You should probably just save the state of each view using NSUserDefaults and each time your application launches re-load your saved data.
If you are storing everything in CoreData you would only need to save the active view and a few object ids, if not you would need to save any data you have.
Don't expect iOS to save anything that you have in memory between launches. Just store it in NSUserDefaults and load it each time.
Store the state of the scene in NSUserDefaults or inside a plist file then when loading up the scene just load it with the settings from there. If the images are loaded from the internet you might also want to save them locally on your iphones hard drive so it runs a bit smoother.
I don't think you should cycle the segues, just use one that connects viewcontroller 1 from viewcontroller 2 should be enough and that way you make sure that no additional viewcontrollers are being made (memory problems maybe?)
However for your particular problem, I believe that you should use core data to save the exact state of your table, view because ios doesn't save the exact state of view at all times. it will require work but you will achieve what you want. You will need to save the exact photo( using a code or enums that will be saved), the location in the table view, the score or well whatever data you need to save that state.
The best of all is that coredata is so efficient that reloading the data when the app is relaucnhed or into foreground it takes no time, and ive used core data to load more than 5k of records until now and works just fine and its not slow at all.
When i get back home ill provide a code you might use to get an idea of what i mean.
The key here is to:
Have some sort of storage for the data that your application needs. This is your application's data model.
Give each view controller access to the model, or at least to the part of the model that it needs to do its job. The view controller can then use the data from the model to configure itself when it's created, or when the view is about to appear.
Have each view controller update the model at appropriate times, such as when the view is about to disappear, or even every time the user makes a change.
There are a lot of ways that you can organize your data in memory, and there are a lot of ways that you can store it on disk (that is, in long term storage). Property lists, Core Data, plain old data files, and keyed archives are all possibilities for writing the data to a file. NSArray, NSDictionary, NSSet, and so on are all classes that you can use to help you organize your data in memory. None of that has anything to do with making your view controllers feel persistent, though. You'll use them, sure, but which one you choose really doesn't matter as far as updating your view controllers goes. The important thing, again, is that you have some sort of model, and that your view controllers have access to it.
Typically, the app delegate sets up the model and then passes it along to the view controllers as necessary.
Something else that may help is that you don't have to let your view controller(s) be deleted when they're popped off the navigation stack. You can set up both view controllers in your app delegate, if you want, so that they stick around. You can then use the ones you've got instead of creating new ones all the time, and in so doing you'll automatically get some degree of persistence.