Is there a way to call a function once a View Controller leaves the view stack? - swift

ViewDidDisapear and ViewWillDisappear are still called if another View Controller is above the (say, for example, you push a view controller on top of it). Is there a function that is only called once the view controller is removed from the navigation stack? Adding a function to the back button works, but what if the user decides to to the edge pan gesture to dismiss the view? Is there an action that accounts for both events?

Yes, I can think of few ways to do this off the top of my head
One option would be to add some code to a dealloc method of the UIViewController.
If you don't expect the view controller controller to get deallocated when it leaves the stack you can also set a UINavigationControllerDelegate for the UINavigationController and define
func navigationController(_ navigationController: UINavigationController,
didShow viewController: UIViewController,
animated: Bool) {
guard let poppedViewController =
navigationController.transitionCoordinator?.viewController(forKey: .from)
<Do something with the popped VC>

Related

iOS Responder Chain and Events

I'm a little confused. There are two UIViewControllers in UINavagationController's stack. I try to call the method of first UIViewController within the second UIViewController. See code below
class VC1: UIViewController {
#objc func method1() {
//not called after tap button
}
}
class VC2: UIViewController {
let button = UIButton()
override func viewDidLoad() {
super.viewDidLoad()
button.addTarget(nil, action: #selector(VC1.method1), for: .touchUpInside)
}
}
But method1 isn't called when I tap on the button. What's the problem?
The responder chain is based on superviews and parent view controllers. So VC1 is not up the responder chain from VC2. The responder chain from VC2 leads to its parent (the UINavigationController); it does not go by way of any other children of the UINavigationController.
The responder chain, starting from button, goes through button's ancestor views and view controllers. Since VC1 and VC2 are separate items in the navigation controller's stack, VC1 is not an ancestor of VC2. They are more like siblings of each other. So VC1 is not in button's responder chain.
You could just set VC1 as the target of the button action:
let vc1: VC1 = ...
button.addTarget(vc1, action: #selector(VC1.method1), for: .touchUpInside)
But that means VC2 needs reference to the existing VC1 instance. How do you get that reference? It depends on how you created VC2 and pushed it on the navigation stack.
If VC1 creates VC2 in code, then VC1 can just give the new VC2 a reference to self (the VC1) when creating the VC2.
If you have a storyboard segue from VC1 that pushes VC2 onto the stack, then you need to implement prepare(for: UIStoryboardSegue, sender:) in VC1, and in that method you need to hand self (VC1) to the destination of the segue (VC2).
When you push, the relationship is between the pushedViewController and navigationController.
When you modally present, the relationship is between the presentedViewController and UIWindow. Understandably there is a relationship between the presenting and presented view controllers, but it's just a matter of them having references to one another. Their not hooked up through the responder chain pipeline...
Only when a viewController is made a childViewController of anotherViewController then their responder chain is hooked in the way you think meaning the childViewController’s nextResponder becomes its ‘parentViewController’
———
From nextResponder docs:
For example, UIView implements this method and returns the
UIViewController object that manages it (if it has one) or its
superview (if it doesn’t). UIViewController similarly implements the
method and returns its view’s superview. UIWindow returns the
application object. The shared UIApplication object normally returns
nil, but it returns its app delegate if that object is a subclass of
UIResponder and has not already been called to handle the event.
Reading this we understand that neither the presentingViewController nor the pushingViewController are a viewController’s superview

How to maintain the navigation with a segue "present modally"?

I have : navigation controller -> tableViewController -> tab bar Controller -> ViewController1 / ViewController2 / ViewController3
I click on a cell on the TableViewController and I open the TabBar. All is OK
But, I wanted to have more details from the datas in the TableViewController so I decided to make a popup with the content of the cell. I found this tutorial https://www.youtube.com/watch?v=S5i8n_bqblE => GREAT ! It's about the use of segue "present modally" with a viewcontroller containing the popup. I made a link from the popup to the tabBarController and I lose the Navigation Bar
I tried to play with navigationBar but nothing is working. I changed the type of segue but I don't obtain what I want.
I think the problem come from the type of segue. It's OK if I use it like a go/back in viewController. Do you have any solution about using this sort of popup or do I have to use another way ?
Thanks
Ok, let's take a look.
Navigation Bar is a view which is provided by Navigation Controller. Sometimes we are confused with navigation bars and navigation items. Navigation bar is the only one and it belongs to navigation controller, navigation item belongs to individual view controller from navigation stack. So, first step is simple: if you want navigation bar, wrap your modally presented controller into navigation stack.
Yes, you will face other problem, the blurred view of previous controller will become a black area. Why? there is special object called Presentation Controller (UIPresentationController) which is responsible for how controller will be presented. And it hides view of previous controller by default (in sake of performance, I think).
Ok, let's move. We can create custom presentation controller and tell it not to hide view of previous controller. Like this:
class CustomPresentationController: UIPresentationController {
override var shouldRemovePresentersView: Bool {
return false
}
}
Next step. In controller we want present modally we have to specify to things: we want to use custom presentation controller and we also want to adjust delegate object for transitioning (where we can specify custom presentation controller). The trick is that you have to do it inside initialiser, because viewDidLoad is too late: controller had been already initialised:
class PopupViewController: UIViewController {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
modalPresentationStyle = .custom
transitioningDelegate = self
}
}
Final step. When PopupViewController became delegate for its own transitioning, it means this controller is responsible for all of them. In our particular case popup controller provides custom version of presentation controller. Like this:
extension PopupViewController: UIViewControllerTransitioningDelegate {
func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
return CustomPresentationController(presentedViewController: presented, presenting: presenting)
}
}
That's all. Now you should see view of previous controller.

Performing segue with UINavigationController (without IBAction)

It's easier to show you a drawing and then explain.
Dashboard Storyboard
I have 2 separate UIViewControllers (i've included just one in the drawing, the other is irrelevant) embedded in container view called ContainerViewController.
Post Storyboard
NewPostViewController shows a UIButton that presents TextPostViewController. As you can see, all of them are embedded in UINavigationControllers. Now, once the completion block of the new post is being called, I have to present the ContainerViewController and it needs to handle it's own logic. The problem is that it's embedded in UINavigationController and once I present it, the UITaBbar is hidden.
I tried to do this:
self.performSegue(withIdentifier: "TextPostToNavContainerVC", sender: nil)
The transition is successful but I'm losing the UITabBar, even though in the DashboardViewController and the ContainerViewController I called:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
tabBarController?.tabBar.isHidden = false
}
What am I doing wrong or is there are better way to do that?
You should instantiate the tab bar controller. not the view controller.
Imagine you're putting a initial view controller ahead of your tab bar controller. Making your tab bar not being pushed
If I undestand it correctly.
You are doing this
Segue connect to a view controller
But you should actually do this Segue connected to a tab bar controller
You can try to add it as a child to control it's frame like this
let textPost = self.storyboard?.instantiateViewController(withIdentifier: "containerID") as! TextPostToNavContainerVC
textPost.view.frame = CGRect(x:20,y:0,width:self.view.frame.width,height:self.view.frame.height-50)
self.view.addSubview(nvc.view)
self.addChildViewController(textPost)
textPost.didMove(toParentViewController: self)

Segue stops working after first segue

I am trying to use a custom segue, that makes the view 'scroll' to the right or left, when a button is clicked. I added a custom class that looks like this
class horizontalSegue : UIStoryboardSegue {
override func perform() {
var oldView = self.sourceViewController.view as UIView
var newView = self.destinationViewController.view as UIView
oldView.window?.insertSubview(newView, aboveSubview: oldView)
newView.center.x = oldView.center.x + oldView.frame.width
newView.center.y = oldView.center.y
UIView.animateWithDuration(0.6, animations: { newView.center = oldView.center }, completion: { finished in Void })
}
}
but the problem is that after I segue once, I cannot segue back to the view I segued from. I think its because of the way I have the oldView and newView declared, the app isn't updating which view is which, so its segueing back to the current view when I try to segue to the first view.
If I am correct, how would I make sure the app updates which view is which?
Apple documentation says: "Regardless of how you perform the animation, at the end of it, you are responsible for installing the destination view controller (and its views) in the right place so that it can handle events. For example, if you were to implement a custom modal transition, you might perform your animations using snapshot images and then at the end call the presentModalViewController:animated: method (with animations disabled) to set up the appropriate modal relationship between the source and destination view controllers."
May be you should add something like
[self.navigationController pushViewController:vc animated:NO]
into your animation's completion handler?

How to call viewWillDisappear method in Tabbar Application having navigations also

I have created 5 Tabs in my application. In Tab1 i have UITableView. On didSelectRowAtIndexPath i am navigating to another UIView in which I am showing my all 5 Tabs. And I also play song in that navigated view.
Now when I click Back button in navigation and i again go to my original view, i am able to call viewWillDisappear (as expected and normal situation).
But when I click directly another tab then viewWillDisappear is not called in the navigated View. Why this Happens??
I have just thought in a way that when I directly clicks the another Tab then the view in Tab1 will call viewWillDisappear. But the navigated view will not call that method.
So what could be possible solutions?? kindly give some hints...
I got the viewWillDisappear to work by calling
self.definesPresentationContext = true
in viewDidLoad()
Otherwise, viewWillDisappear wasn't getting called. And this is what I have in it:
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
self.searchController.active = false
tableView.reloadData()
}
Hope this helps.
I think that you need to catch the event when you switch between tabs. When you switch from Tab1 to Tab2, as you expect, viewWillDisappear of Tab1 will not be called. Instead, the viewWillAppear of Tab2 will be called.
Else if you want to catch the event when you switch tabs, check this link.
That is because the you have created tabBarController and you are pushing it as a viewController from the mainView.
So whole TabBarController is treated as one viewController.
Hope this helps you.
if you want to call this method create the nsnotification center object viewWillDisappear
and when you want to call this method post this notification.
That's how I got it to work.
// UITabBarControllerDelegate
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
print("Selected view controller")
// do stuff...
self.navigationController?.setNavigationBarHidden(false, animated: false)
return true
}