Can you detect when a UIViewController has been dismissed or popped? - ios

I have some cleanup that needs to be performed in a shared resource any time one of my view controllers is dismissed/popped/unloaded? This could either be when the user hits the back button on that individual screen or if a call to popToRootViewController is made (in which case, I would ideally be able to clear up every controller that was popped.)
The obvious choice would be to do this in viewDidUnload, but of course, that isn't how unload works. Is there a way to catch all cases to where the ViewController is removed from the stack?
edit:Forgot to mention that I am doing this using Xamarin so that may or may not impact the answers.

override func viewDidDisappear(animated: Bool) {
super.viewDidDisappear(animated)
if (isBeingDismissed() || isMovingFromParentViewController()) {
// clean up code here
}
}
EDIT for swift 4/5
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
if (isBeingDismissed || isMovingFromParent) {
// clean up code here
}
}

-dealloc is probably your best bet. The view controller will be deallocated when it is popped from the stack, unless you are retaining it elsewhere.
viewWillDisappear: and viewDidDisappear: aren't good choices because they are called any time the view controller is no longer shown, including when it pushes something else on the stack (so it becomes second-from-the-top).
viewDidUnload is no longer used. The system frameworks stopped calling this method as of iOS 6.

Building upon #Enricoza's comment, if you do have your UIViewController embedded in a UINavigationController, try this out:
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if ((navigationController?.isBeingDismissed) != nil) {
// Add clean up code here
}
}

Related

When navigating back in navigation controller view doesn't load

I have weird situation and have no clue how to debug it. I load three viewControllers in navigation controller. When Im navigating back from there second and first ViewController doesn't display anything just white screen I added print methods everywhere in lifecycle methods and it seems that it loads views but anyway they not visible. What could be the problem?
Yep It's weird, There maybe some code which do something with your view on such events like:
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
// remove some subviews or change constraints.
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// remove some subviews or change constraints.
}
Please send us a code of the view controller which has the problems and code how exactly you show the controller.

Get a notification with viewWillAppear - in another VC?

Say you have
var someVC: UIViewController
is it possible to essentially do the following, somehow?
get a notification when {
someVC has a viewWillAppear
self.#selector(wow)
}
#objc func wow() {
print("we spied on that view controller, and it just willAppeared"
}
Is that possible ?
(Or maybe on didLayoutSubviews ?)
(I realize, obviously, you can do this by adding a line of code to the UIViewController in question. That's obvious. I'm asking if we can "add on" to it from elsewhere.)
If I understand your question correctly, you want ViewController B to receive a notification when viewWillAppear is called in ViewController A? You could do this through the Notifications framework. Keep in mind that both VC's have to be loaded for one to receive a notification.
Alternatively, if the two VC's are on the screen at the same time, then I'd recommend a delegate pattern - have VC A tell an overarcing controller class that it's viewWillAppear has been called, and this overarcing controller will then inform ViewController B.
To do this using Notifications:
(This is from memory, so please excuse typos)
class TestClassA: UIViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// To improve this code, you'd pull out the Notification name and perhaps put it into an extension, instead of hardcoding it here and elsewhere.
NotificationCenter.default.post(Notification.init(name: Notification.Name.init(rawValue: "viewControllerAppeared")))
}
}
class TestClassB: UIViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
NotificationCenter.default.addObserver(self, selector: #selector(viewControllerAppeared(notification:)), name: Notification.Name.init(rawValue: "viewControllerAppeared"), object: nil)
}
#objc func viewControllerAppeared(notification: NSNotification) {
print("other viewcontroller appeared")
}
}
Documentation

iOS equivalent for onRestart()

What is the iOS equivalent for onRestart() used on Android?
onRestart() is called when current activity is being re-displayed to the user (the user has navigated back to it).
I believe you need viewWillAppear method:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
//your code here
}
It is called every time right before view is going to be shown. So it will be called when view is shown for the first time as well.
If you want to avoid running your code for the first time viewWillAppear is called, you will have to add a flag property and check if it has been set previously.
If you're trying to capture whenever the scene in question comes into view, there are two cases you might be concerned about:
If you transition to this scene (or dismissing/popping back to this scene) from within the app. In that case, use viewWillAppear:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
update() // your routine for updating what is displayed to the user
}
If your app is running and you press the "home" button (or go to another app), and then later return to the your app (before it is terminated), viewDidAppear is not called. To detect that scenario, you can observe .UIApplicationDidBecomeActive:
private var observer: NSObjectProtocol?
override func viewDidLoad() {
super.viewDidLoad()
observer = NotificationCenter.default.addObserver(forName: .UIApplicationDidBecomeActive, object: nil, queue: nil) { [weak self] notification in
self?.update()
}
}
deinit {
if let observer = observer {
NotificationCenter.default.removeObserver(observer)
}
}

Can not make view firstResponder in viewDidAppear method

I have UIViewController and I created my custom class for its view. By the way, I redefined method "canBecomeFirstResponder" in that class so it always returns true. I want my view to become first responder when the viewDidAppear method is called. But after I go to another controller and come back my program crashes. Can not understand why does this happens. Here is some code:
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
self.firstResponder = self.view.isFirstResponder()
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
if(self.firstResponder){
self.view!.becomeFirstResponder()
}
}
This link helped me a lot. https://stackoverflow.com/a/21906038/4092466 I should not have named the property "firstResponder". When I renamed it everything worked fine.

Blank screen after share content using UIActivityViewController

I share some contents using the following code
var textToShare = "Test"
let activityVC = UIActivityViewController(activityItems: [textToShare], applicationActivities: nil)
activityVC.excludedActivityTypes = [UIActivityTypePrint, UIActivityTypeCopyToPasteboard, UIActivityTypeAssignToContact]
presentViewController(activityVC, animated: true, completion: nil)
But when I press the cancel button or when the content is shared successfully, the App shows a blank screen.
How to fix this issue ?
UPDATE:
The blank screen just appears when I select mail or sms apps for the sharing target, For Telegram, Twitter and Facebook it is working perfect.
I commented all the code inside lifecycle methods, Still same issue.
override func viewDidAppear(animated: Bool)
{
//setControlsAreHidden(true)
}
override func viewWillAppear(animated: Bool)
{
//if dataAddedToView
//{
// activityIndicator?.removeFromSuperview()
//}
}
override func viewWillDisappear(animated: Bool)
{
//setControlsAreHidden(false)
}
I was having this same problem, and was able to solve it by adjust the sizing constraints of my primary UIView.
Code before:
override func loadView() {
self.view = UIView()
self.view.translatesAutoresizingMaskIntoConstraints = false
}
Code after:
override func loadView() {
self.view = UIView()
}
So just remove self.view.translatesAutoresizingMaskIntoConstraints = false
This might not apply to everyone, but based on other peoples' answers, I gather there are various issues with how views are displayed which can cause the same problem. If my fix doesn't work for you, I suggest commenting out all unnecessary code until the problem goes away, and then methodically bring small bits back online, testing along the way to identify your issue.
As you are presenting MFMailComposeViewController and MFMessageComposeViewController above your current UIViewController so when you dismiss the MFMailComposeViewController or MFMessageComposeViewController after sending the message then your UIViewController viewWillAppear method will be called as the view is appearing again, so in my opinion check in viewWillAppear or viewWillDisappear method whether you are making your view nil.
I don't see any calls to the super implementation in your viewDidAppear, viewWillAppear or viewWillDisappear methods.
What happens if you do?:
override func viewDidAppear(animated: Bool)
{
super.viewDidAppear(animated)
//setControlsAreHidden(true)
}
override func viewWillAppear(animated: Bool)
{
super.viewWillAppear(animated)
//if dataAddedToView
//{
// activityIndicator?.removeFromSuperview()
//}
}
override func viewWillDisappear(animated: Bool)
{
super.viewWillDisappear(animated)
//setControlsAreHidden(false)
}
Maybe you're not presenting the activity view controller in the proper view, try something like this:
if(SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(#"8.0")){
activityViewController.popoverPresentationController.sourceView = self.view;
}
It's a objective C code, but just as example of how to set the view.
If you need how to check the iOS version:
#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending)
You need to first check that your view controller is not being destroyed while you're presenting content. Some debug statements in the presenting view controller's lifecycle management routines (e.g., ViewWillDisappear) could help.
If the problem stems from the ViewController disappearing, you'll need to recreate it appropriately.
Also look at any place where you provide content: is it being triggered to draw at the right times?
Additionally, ensure you're using APIs as required (e.g., calling the superclass in all methods where it's required, such as the view controller lifecycle routines). See:
SO ViewController methods question
SO ViewController lifecycle question
Apple UIViewController reference (latest)
These are the areas I would focus my attention on and/or provide the relevant code for.

Resources