How to list all live (non-deallocated) UIViewControllers? - ios

Some UIViewControllers do not seem to get deallocated.
What's the best way to list and identify all live (non-deallocated) UIViewControllers?

Run app in debugger and use "Debug memory graph" button and see the list of the view controllers in the panel on the left. If you happened to follow the convention of including ViewController in the name of your view controllers (e.g. MainViewController, DetailsViewController, etc.), you can filter the list of objects listed in the left panel by typing ViewController in the "filter" text box at the bottom of the left panel:
In this example, I also clicked on my third view controller, and I can see it was presented by the second one, which was presented by the first one.
The other approach is to use the "view debugger" , but that only shows the view controllers that are currently present in the active view controller hierarchy and may not show view controllers whose views are not currently visible because the view controller presented another view controller modally.

In addition to Rob's answer, if you want to see them initialized and deinitialized in real time you can print to console.
class Random32ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
print("32 did load")
}
deinit {
print("32 did deinit")
}
}
You can do this method on all class types beyond just view controllers.

Related

Do UIViewControllers get destroyed and recreated in iOS during lifecycle events? Or do they simply get "re-attached"?

Let us consider the following example subclass of a UIViewController
import Foundation
class CustomViewController: UIViewController
{
override func viewDidLoad()
{
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool)
{
super.viewWillAppear(animated)
}
init()
{
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder)
{
super.init(coder: coder)
}
}
According to the following stackoverflow post
viewDidLoad(...) will be called once whenever the view controller needs to load its view hierarchy. Obviously, that'll happen the first time that the controller accesses its view. If the view controller later unloads its view, then viewDidLoad(...) will be called again the next time the view is loaded. A view controller won't unload its view just because the view is hidden, but it might do so if memory starts to run low.
But what about the UIViewController itself as an object in memory? During low memory events, or when the app has been placed into the background, will the UIViewController object simply be discarded and recreated with init(...)? Or will it be guaranteed a lifeline and be preserved - so only viewDidLoad(...) will be called again?
The entire premise of your question is incorrect.
The question and answer you cite are way outdated and wrong. Views are not magically unloaded and reloaded. viewDidLoad can never be called more than once by the runtime on a given VC instance, and using it for onetime early lifetime tasks is correct.
As for your question itself: No VC will ever be forcibly destroyed by the runtime due to low memory. If your memory usage is excessive and you fail to reduce it, the runtime does not dismantle piecemeal the object structure of your app; it kills your entire app.
Adding to Matt's answer:
View controllers get created when it is time to display them, and persist until UIKit is done with them.
The lifecycle of a view controller depends on how you manage it. Often that's based on what kind of parent view controller hosts it.
Some examples (not an exhaustive list)
If a view controller is displayed modally, it is likely created, displayed, and then discarded once it is dismissed.
Navigation controllers manage a "stack" of view controllers. All the view controllers in the stack exist in memory for the whole time they are on the stack. You create a new view controller and push it onto the stack to display it. If you pop one or more view controllers off the stack, the navigation controller releases its strong reference to them, and they will be deallocated unless you keep another strong reference to them somewhere (which is not typical.)
You created all the view controllers that will be displayed in a tab bar controller at the time you create the tab bar controller, and they persist for the life of the tab bar controller.
Page view controllers keep strong references to the page(s) being displayed, and possibly to the next and previous view controller. Other page's view controllers get discarded, and new view controllers are created to display new pages.
As Matt pointed out in his answer, the quote you included about viewDidLoad is out of date and wrong. (I think that was from iOS 3, if memory serves.) A view controller's viewDidLoad method is called once and only once in its lifetime, when it's views are first loaded. The view controller's views will never be deallocated until the view controller itself is deallocated.

SwiftUI - Updating a Page View Controller with a new page

I'm building a weather app that is hosted on a public repo here.
The most important files for this issue are the PageView and the RepresentedPageViewController.
I've created a UIPageViewController that interfaces with SwiftUI via UIViewControllerRepresentable. It allows a user to swipe through different cities and see each city's weather data, much like Apple's Weather app. When the makeUIViewController method of my page view controller is called I set its view controllers (there are 3 in this case to begin with, where each represents a city):
pageViewController.setViewControllers([controllers[0]],
direction: .forward,
animated: false)
This works fine and I'm able to navigate (swipe) between the different pages.
In the app's search menu, a user can then tap on a new city that they want to get the weather for. This adds the new city to the app's datasource of cities that the page view controller uses to create a page for each city.
Because the data object that holds the cities is stateful, the UI is recomputed when that object is set (when a user adds a new city). This triggers the updateUIViewController method in the page view controller. In this method, I reset the page view controller's viewcontrollers (there are now 4 because the user has added a new city):
func updateUIViewController(_ uiViewController: UIPageViewController, context: UIViewControllerRepresentableContext<RepresentedPageViewController>) {
// Check that the view controllers of the page view controller need to be reset.
// This is true when a user has added a new city because we'll create a new
// view controller to be added to the page view controller. We perform this check because
// we don't want to reset the viewcontrollers in all cases where the updateUIViewController method is called.
if shouldResetControllers {
uiViewController.setViewControllers([controllers[0]],
direction: .forward,
animated: false)
shouldResetControllers = false
}
}
My issue is that once this is done, the user is only able to see the first city of the viewcontrollers. The page view controller is still there because the user is still able to swipe, but there is only one city (the first one). I've created a screen recording of the result which you can view here.
think the problem stays in the Coordinator:
when you update controllers in pageviewcontroller, the parent property of Coordinator remains the same (and because it is a struct, all his properties). So you can simply add this line of code in your updateUIViewController method:
context.coordinator.parent = self
also, remember that animation of pageViewController.setViewControllers will occur, so you have to set animated to false, or handle it properly.
There are many other ways to solve this (I wrote the most intuitive solution),
the important thing is to know where the error comes from.
It seems like this is a SwiftUI bug. I had a similar problem where a UI update would reload the PageViewController with a single ViewController.
Adding an id to the PageViewController, described in "Updating an #Environment to pass data to PageViewController in SwiftUI results in loss of swiping between ViewControllers", seems to work.

Assigning two views to a single Container View using swift in iOS 8

I am trying to assign two view controllers to a Container View using the Interface Builder. I tried to do so, but whenever I try to "embed" the second view controller to my Container View, instead of adding another VC to it, it just replaces the one that was embedded already.
Ultimately, my main goal is to have a screen that has the following elements (in order, from top to bottom):
-A navigation bar
-A view of height 50 that contains a segmented controller (which will switch between tableVCs)
-A main view, which will contain my Container view
-A tabbed bar
My current setup is almost as described above. Here is a picture:
The view controller I am interested in the most is in the one with the highlighted container(HomeViewController). The approach I am currently using is hacky, because I currently have 2 container views, one on top of another, and they embed the 2 table view controllers depicted to the right (one per container).
I do not like this approach very much because both containers get instantiated whenever the main VC (Home View Controller) is instantiated, therefore making 2 network calls by default to load their content, possibly slowing down the device and maybe using more memory than needed.
Ideally, I would load the content of one table view controller that is mapped to one of the segmented controls. Then, I would have a mechanism that somehow instantiates the second table view controller whenever I go to the second button in the segmented control (and possibly deallocating/getting rid of the other VCs), and so on with the third. Or somehow be able to display/alternate between 2 or more view controllers in an area (view) inside my HomeViewController.
Currently I have this simple code that switches (hides and shows) between container views in my HomeViewController:
#IBAction func segmentChanged(sender: AnyObject) {
switch segmentedControl.selectedSegmentIndex{
case 0:
println("index1 selected")
containerView1.hidden = false
containerView2.hidden = true
break
case 1:
println("index2 selected")
containerView1.hidden = true
containerView2.hidden = false
break
default:
containerView1.hidden = false
containerView2.hidden = true
break
}
}
As I said, this only switches between the views that are loaded already in my view controller, with the data in them already.
I just wanted to see if what I am trying to code is doable, or if I am actually tackling the problem the right way, although I doubt I am doing so.
Thank you for reading my post and for your advice in advance.
Cheers!
Add embed segue to NavigationController, add ViewController as rootViewController to NavigationController, from rootViewController add as many segue as you wish. To load controller you need just override segue class to push without navigation.
class NoAnimationSegue: UIStoryboardSegue {
override func perform() {
self.sourceViewController.navigationController?.pushViewController(self.destinationViewController, animated: false)
}
}

Next Page on click by page based application

In my page based application i want to perform the operation next page on click of button?Is it possible to to go next page using button in page based application?Pls help me to solve this issue
Edit: My application like story book im showing set of images in pages while turning next page, i want to show the nxt page by click
In your app the class conforms to the UIPageViewControllerDataSource protocol, make one object of this class in root view controller suppose ModelController then the object is _modelController.
Then in root view controller just put two UIButton connect them with appropriate IBAction methods suppose for previous button previousClick: and for next button nextClick:.
Now the code for each method is as follow:
-(IBAction)previousClick:(id)sender { [_modelController pageViewController:self.pageViewController viewControllerBeforeViewController:_dataViewController]; }
-(IBAction)nextClick:(id)sender { [_modelController pageViewController:self.pageViewController viewControllerAfterViewController:_dataViewController]; }
Here I suppose that the pageViewController is the one object of type UIPageViewController and is one of the declared and synthesized variable of root view controller, and also same for the _dataViewController which is one UIViewController contains one UIImageView to present the images on the root view.
EDIT
This also can be done with the using the DataViewController for that all the logic have to move to that view controller and all so the references must be needed for the UIPageViewController and ModelController.

Why is my ViewController being removed from NavigationController.ViewControllers collection

I am navigating between screens in my iOS application.
BaseView.NavigationController.ViewControllers
As I switch screens, I keep a reference to the previous screen in a static variable.
At some point, one of my items gets removed from BaseView.NavigationController.ViewControllers even though it's still a valid viewcontroller and IsLoaded is still set to True/YES.
When I use (pardon my C#/MonoTouch)
BaseView.NavigationController.PopToViewController(CurrentViewController,false);
to show it again, I get NSInternalInconsistencyException Reason: Tried to pop to a view controller that doesn't exist. This is understandable because it's no longer in the ViewController collection.
The way I am switching screens is I am keeping a reference to he various screens and calling a common method to show the screen. In that method I use this logic to determine if I should push or pop.
if (CurrentViewController.IsViewLoaded)
{
BaseView.NavigationController.PopToViewController(CurrentViewController,false);
}
else
{
BaseView.NavigationController.PushViewController(CurrentViewController,true);
}
My question is where did it go and why would it have been removed from ViewControllers collection and when it's StillLoaded=true/YES?
If I understand correctly, you're using NavigationController.PopToViewController(controller); to navigate back to a certain view controller but keep a reference of the View Controllers that are popped from the navigation stack.
What I think is happening is because you're keeping a reference to these View Controllers, they're still in memory and thus the IsViewLoaded property is still true despite the View Controller not actually existing on the navigation stack.
Rather than using the IsViewLoaded property, you should check whether the View Controller exists in the NavigationController.ViewControllers array, if it does then Pop to it, if it doesn't then push it.
E.g.
if (BaseView.NavigationController.ViewControllers.Contains(CurrentViewController))
{
BaseView.NavigationController.PopToViewController(CurrentViewController,false);
}
else
{
BaseView.NavigationController.PushViewController(CurrentViewController,true);
}
Edit
So you mention you'd like a view to persist on the navigation stack. Well, using PopToViewController will remove ALL View Controllers between the TopViewController and the specified Controller.
In order to achieve what you're wanting, you could directly manipulate the NavigationControllers.ViewControllers array. Only problem with this is you'll lose the nice animations that the Push/Pop methods provide.
// Changes order of View Controllers currently in the stack. You can also add/remove
// controllers using this method.
NavigationController.ViewControllers = new UIViewController[]{
NavigationController.ViewControllers[1],
NavigationController.ViewControllers[0],
NavigationController.ViewControllers[3],
NavigationController.ViewControllers[2]};

Resources