Alarming memory increase with custom segue - ios

I'm creating an app with XCode 6 (Swift) and recently managed to make a custom segue that doesn't use an animation. What I soon realised was that, when switching between two view controllers (tied together with the custom segues), the application memory usage (viewable with XCode) seemed drastically increase with every view controller switch. Is this something that I need to worry about? Does Swift automatically take care this memory increase, so that the app doesn't end up using an insane amount, or am I doing something wrong?
NoAnimationSegue.swift
import Foundation
import UIKit
#objc(NoAnimationSegue)
class NoAnimationSegue: UIStoryboardSegue {
override func perform () {
let src: UIViewController = self.sourceViewController as UIViewController
let dst: UIViewController = self.destinationViewController as UIViewController
src.presentViewController(dst, animated: false, completion: nil)
}
}

When you go from the first view controller to the second, you can presentViewController, but when you go back to the first, you do not want to "present" again. When you "present" one view controller from another, it keeps the first one in memory, so it's there, ready for you when you "dismiss" the second view controller to return back to the first.
Thus, if you repeat that process of presenting from one view controller to another in a circular fashion, you'll end up with multiple instances of the view controllers in memory. If you "present" to go from one view controller to another, then you'll want to unwind or dismiss to get back to the first view controller.
Alternatively, if you don't want to use the present/dismiss pattern, you can alternatively use tab bar controller if you want to jump back and forth between view controllers. Or you can use page view controller. Or you can use your own custom container view controller and replace the child view controller as you jump back and forth.
But just remember, if you present or push from one view controller to another, then you'll have to dismiss or pop to return back.

Related

What is the difference between presenting a ViewController vs adding a ViewController as a child

I just read an interesting article that introduced me to a new concept that I have never heard before, which is adding a ViewController as a child of another ViewController. The article uses the UIActivityIndicatorView as an example to demonstrate a common use for when to use the addChild method and this got me wondering why would you use that method instead of just presenting the view controller.
The part I don't fully understand is the benefit of using that specific method since both presenting and adding as a child could be modular in a way that they can be reusable.
When would you use addChild instead of just presenting it? When does that make sense?
addChild:
let parent = UIViewController()
let child = MyViewController()
parent.view.addSubview(child.view)
parent.addChild(child)
child.didMove(toParent: parent)
Present:
let storyboard : UIStoryboard = UIStoryboard(name: "StoryboardName", bundle:nil)
let myVC = storyboard.instantiateViewController(withIdentifier: "myViewController") as! MyViewController
self.present(myVC , animated: true, completion: nil)
The difference is that when you present a controller, it becomes modal, and when you add it as a child controller, it doesn't. The difference is in semantics and use cases.
Modal
As an example of modal window, imagine a window with an OK button in macOS that prevents you from interacting with the parent window until you close it.
Hence why there can be only one view controller presented by a particular controller: the presented controller becomes “main” for user interaction, and the user must close it to return to the parent window.
A typical example of a modal window is an alert.
Child
But a child view controller is a different story. There can be any amount of them on a particular VC.
The parent controller becomes sort of container for child view controllers. For example, there can be a dashboard with multiple panels, and each panel can be a separate controller. This allows to separate logic between different pieces of an application instead of cramming everything into a single controller, to implement user customization in easy way, etc.
A typical example of a parent-child controller interaction is UINavigationController: the navigation controller is the parent, and all view controllers pushed into it are its children. We don't make an uber controller with the whole application's logic, we separate it between different screens, and the navigation controller is only responsible for presentation.
It's very simple. addChild is when you want to embed the view controller in you parent view controller (you will also need to follow up with a view.addSubview(child.view)). present is when you want to, well, present it... think of it as a change of screens.
addChild:
present:

How to access previous view controller from a dismiss command

Throughout my app I use a navigation controller to push and pop my view controllers. When I pop from one ViewController, I check to see if I can reload the data in the previous one with this:
_ = self.navigationController?.popViewController(animated: true)
if let previousViewController = self.navigationController?.viewControllers.last as? AnimalsVC {
previousViewController.tableView.reloadData()
}
This works every time, but now I need to do the same with another view, but it's not apart of the navigation controller because I modally present (instead of pushing it to the navigation controller). Now there's no way I can access the previous ViewController like before and I can not figure out how to access the previous ViewController.
How can I access the previous ViewController to reload the tableview like the example code without accessing the navigation controller?
I know this is possible with notifications, but I prefer not to use that method if possible!
First of all, It's not necessary to access the previous ViewController to reload tableview(or any other func)
I recommend you to use Delegate to achieve the same feature.
Holding a reference to the previous viewController in the way you mentioned will make your app very hard to maintain when your app gets more complicated.
You can call tableview.reloadData() in viewWillAppear method in the controller that you present modally

How to correctly dismiss previous view controller

Apologies as I know there are some similar questions, but I've been looking for two weeks through every one I can find, and cannot figure it out (I'm a bit of a novice).
I have a few different View Controllers, not using a Navigation Controller. I can segue between them no problem. The issue is, I need each view to be dismissed when I segue to a new one. Here is some of what I've tried so far.
Option 1 (in new View Controller)
override func viewDidAppear(_ animated: Bool) {
presentingViewController?.dismiss(animated: false, completion: nil)
}
Option 2 (in old View Controller)
override func viewDidDisappear(_ animated: Bool) {
self.dismiss(animated: false, completion: nil)
}
In both of these cases, the new view gets dismissed and I'm taken back to the old view. I've tried about 20 versions of similar code.
Should I be using the first VC in my program as my "main" view controller, and presenting/dismissing all others on top of it? I didn't think this approach seemed memory efficient, when the "main" VC is not often used after initially loading the app.
It seems like I'm missing or not understanding something. Any help would be greatly appreciated.
Think of it this way: A view controller can't exist on an island. It has to be presented on top of something.
That means when you present one VC on top of another, the presenting view controller is the "foundation" for the new one you just presented.
If you don't want to present VCs on top of each other, you have a couple of options:
1) Use a navigation controller. This is probably the best approach. You can present or push any view controller. If you decide to push, you can remove the old one from the navigation stack, or you can keep it there so the user can go back. There are lots of ways to use a navigation controller, and it's easily the most flexible way to navigate between controllers.
2) Use a tab bar controller. This works best if you have just a few different view controllers in your app, but it's good for certain use cases.
3) Do exactly what you said in your post (use the root view controller to present/dismiss all other VCs). As I said, you can't present a view controller out of thin air-- there always has to be something behind it. Unless there's a ton of stuff going on in your root VC, this shouldn't cause any memory issues. This approach should be fine unless you're very particular about the animations between your view controllers.
In general, I wouldn't worry too much about memory usage until it becomes a problem. It should be fine to present view controllers on top of each other for 99% of normal use cases.
if you want to present VC B from VC A and want to dismiss VC A while Presenting you can use this Code
let parentVC = presentingViewController
dismiss(animated: true) {
let vc = self.storyboard!.instantiateViewController(withIdentifier...)
parentVC.present(vc, animated: true)`enter code here`
} `enter code here`

How to use Storyboard view controllers without reloading each time they show

I have several UIViewControllers in a Storyboard.
I'm not using a Navigation controller, just simple Segues to show them and to unwind.
However each time show/unwind a View, it completely reloads.
I would like it to "load" only the first time it's shown, and then
hide/show as I move to other View Controllers.
How can this be done using Storyboards and Swift 2?
Assuming that you'd always have a base (menu or something) UIViewController, I'd use UINavigationController and just keep optional references to created UIViewControllers in this menu controller, like :
var firstViewController : VC1?
var secondViewController : VC2?
You should create a function to get those view controllers like:
func getVC1Instance() {
return firstViewController ?? firstViewController = self.storyboard.instantiateViewControllerWithIdentifier("VC1Identifier")
}
Then, when you click a button that should take you to VC1 for example, you should do
self.navigationController?.pushViewController(getVC1Instance())
I don't know what your use case is, but I really don't recommend retaining UIViewControllers that aren't shown on the screen. If you need to persist their state, just use NSUserDefaults / Singleton pattern to store the data like text typed into some textfields and just use them on viewDidLoad.

Multiple persistent view controllers

I'm trying to write an app that has a menu view controller with three buttons, each corresponding to a separate view. Let's call these views v1, v2 and v3 respectively.
Normal operation of the app calls for random cycling through these three views, i.e. a user viewing v1 can transition to either v2 or v3. I am currently presenting these three view controllers modally by calling self.performSegueWithIdentifier("mySegue", sender: self).
I am currently navigating around these three view controllers by modally presenting them if they are not already in the stack, or else calling dismissViewControllerAnimated on either the current view controller or it's parent depending on how far up the hierarchy the desired view is.
The problem with this is if the user presents all three views, resulting in a stack like:
menu -> v1 -> v2 -> v3 and then navigates first back to v1 and then back to v3. This causes both v3 and v2 to be dismissed and then v3 to be recreated, losing any prior interaction with these view controllers.
Is there any way to create a hierarchy that would function without dismissing these view controllers yet still allow navigation between any two views?
EDIT: I'm avoiding a UITabBarController due to limits of customization, I have certain transition animations that interact with elements of my equivalent "tab bar" that I can't do with the preexisting class.
Only do this if you are certain that UITabBarController is not what you want. It sounds like it probably is though.
Yes, there are several ways of doing this. One that come immediately to mind is to have a manager class to deal with the navigation and creation of the view controllers etc...
So, where you are currently creating your first view controller and putting it on the screen instead you now create this manager class. Then you tell it top display the first view controller.
So, now you're in vc1. (N.B. vc1 can be stored as a property of the manager class so it can keep it persistent).
In vc1 when the user taps the "go to vc2" bit then all vc1 does is talk back to the manager (probably via a delegate method). To a method something like... [self.delegate moveToViewController2];
The manager class then checks to see if vc2 has been instantiated (and does so if it hasn't) and then displays it.
In terms of transitioning between them you can do something like this in the manager class...
- (void)moveToViewController2
{
UIViewController *viewController2 = //get or create vc2
[self transitionToViewController:viewController2 withTransition:UIViewAnimationOptionTransitionFlipFromRight]; // or whichever transition you want
}
- (void)transitionToViewController:(UIViewController *)controller withTransition:(UIViewAnimationOptions)transition
{
[UIView transitionFromView:self.window.rootViewController.view
toView:controller.view
duration:0.65
options:transition
completion:^(BOOL finished) {
self.window.rootViewController = controller;
}];
}
I would suggest to use a UITabBarController, it does exactly what you are requesting without any extra work :)

Resources