UINavigationController has to call method when updates view with a new ViewController - ios

I have a UINavigationControllerSubclass. When view controller is popped to some new view controller (by navigationController.popViewController, navigationController.popToRootViewController or even by manually sliding from left to right)
I need to call inside my navigation controller:
viewController.newTopViewController.updateBackButtonTitle()
What is the best approach to accomplish that?

One way of doing it would be the following:
class CustomNavigationController: UINavigationController {
override func popToRootViewController(animated: Bool) -> [UIViewController]? {
shouldUpdateBackButtonTitle()
return super.popToRootViewController(animated: animated)
}
override func popViewController(animated: Bool) -> UIViewController? {
shouldUpdateBackButtonTitle()
return super.popViewController(animated: animated)
}
private func shouldUpdateBackButtonTitle() {
viewController.newTopViewController.updateBackButtonTitle()
}
}

When you return to viewController call this viewWillAppear method. Inside that function you can check your rootviewController then you can call your
updateBackbuttonTitle()<
function.

You can use viewWillAppear method, and easily update UI Controls
super.viewWillAppear(animated)

Related

How to detect which ViewController is being popped on popViewController func override?

I have sublassed UINavigationController to conform my needs and use case. There in order to detect back action I have overriden method that is properly called on desired action:
var popViewController: ((UIViewController) -> Void)?
override func popViewController(animated: Bool) -> UIViewController? {
return super.popViewController(animated: animated)
}
I would like to check which VC is being currently popped in order to compare it further to evaluate some properties.
First thing I tried is to add var that will be changed inside this override method.
var popViewController: ((UIViewController) -> Void)?
But I have no further clue what should be done.
Is that even possible to do here?
I don't understand why you have that variable with a closure type, you can check the view controller that is being popped right in the override method without needing any stored properties:
override func popViewController(animated: Bool) -> UIViewController? {
let popVC = super.popViewController(animated: animated) // this is the view controller that will be popped
// Do what ever check you want to do here
return popVC
}
According to the documentation, the result of calling func popViewController(animated: Bool) on a UINavigationController returns the popped controller if any was popped.
https://developer.apple.com/documentation/uikit/uinavigationcontroller/1621886-popviewcontroller

XLPagerTabStrip select what view controller will be shown first

I'm using XLPagerTabStrip to switch among a collection of view controllers. I have three view controllers and I would like that middle view controller is shown by default as first.
I could use
let parentViewController = self.parent! as! ParentViewController
parentViewController.moveToViewControllerAtIndex(1)
inside my first view controller, but that first view controller loads some data from the server and if I switch to another view controller while it is loading data, that first view controller will freeze and it won't load data.
Is there a way to show middle view controller as first by default?
jump to the defenition of 'currentIndex' and change it to public from private. then you can select your current controller by this code:
currentIndex = 1
In function:
override func viewControllers(for pagerTabStripController:
PagerTabStripViewController) -> [UIViewController] {
// This line will help you achieve the requirement
pagerTabStripController.currentIndex = /* required index */
}
It will work smoothly after you make currentIndex in PagerTabStripViewController as public.
To prevent loading the first tab, moveToViewControllerAtIndex() must be called before viewDidLoad() is called in your PagerTabStripViewController subclass.
override func viewControllers(for pagerTabStripController: PagerTabStripViewController) -> [UIViewController] {
pagerTabStripController.moveToViewController(at: 0) // required index
}
For Move Specific Tab XLPagerTabStrip in swift 5
override func viewDidAppear(_ animated: Bool) {
if nowFrom == "sendvc"
{
self.moveToViewController(at: 3,animated: false)
}
}
You have to use the following lines:
override func viewDidAppear(_ animated: Bool) {
self.moveToViewController(at: 2)
reloadPagerTabStripView()
}

How do I insert code in ViewDidAppear and ViewDidDisappear From other class

I want to change button image whenever UISideMenuNavigationController Appear Or Disappear.
This is the class that has a button.
class MenuViewController: UIViewController {
#IBOutlet var btnMenu: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
}
}
This is the other class that i want to insert code.
open class UISideMenuNavigationController: UINavigationController {
override open func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// insert some code here but from MenuViewController class
}
override open func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
// insert some code here but from MenuViewController class
}
}
I don't want to change UISideMenuNavigationController class because it is framework from pods.
I'm using framework side menu from https://github.com/jonkykong/SideMenu
I need to change button image whenever Side Menu Appear Or Disappear. I can't find the way from ReadMe Side Menu. That's why I think need to insert the code in ViewDidAppear and ViewDidDisappear Method from Side Menu Class but don't want to break the class.
You simply need to subclass UISideMenuNavigationController and override the viewDidAppear & viewDidDisappear methods to invoke a delegate.
protocol MyUISideMenuDelegate {
func menuDidAppear(_ menu:MyUISideMenuNavigationController) -> Void
func menuDidDisappear(_ menu:MyUISideMenuNavigationController) -> Void
}
open class MyUISideMenuNavigationController: UISideMenuNavigationController {
var menuDelegate: MyUISideMenuDelegate?
override open func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.menuDelegate?.menuDidAppear(self)
}
override open func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
self.menuDelegate?.menuDidDisappear(self)
}
}
Then have you view controller with the button implement the protocol and set itself as the delegate.
You could also have your menu subclass send NSNotification and have any other objects that are interested subscribe to those. This way you completely decouple the menu and the other classes.
Your MenuViewController class could have a function that changes the image on the button. Eg, func changeButtonMenuImage().
Your 'UISideMenuNavigationController' (or a subclass of it) could have some sort of connection to the MenuViewController either as a property, instance variable or an IBOutlet. Eg, #IBOutlet var menuController: MenuViewController
Then your viewDidAppear and viewDidDisappear can call it's function. Eg, menuController.changeButtonMenuImage()

Perform segue to another Navigation Controller without showing Tab Bar

I have a root Tab Host Controller with two Navigation Controller tab siblings: (1) Nearby Stops and (2) Saved Stops. Each of these has a View Controller respectively.
I would like to perform a segue from one of the sibling View Controllers to another Navigation Controller with Stop Schedule View Controller embedded in it, with the following requirements:
The root Tab Bar should not show at the bottom of this View Controller
I need to pass a Stop object to this View Controller before performing the segue
Storyboard:
Currently, I am performing a segue this way, though the Tab Bar remains on the Stop Schedule View Controller when it shouldn't.
func showStopSchedule(stop: Stop) {
let stopScheduleController = self.storyboard?.instantiateViewControllerWithIdentifier("StopScheduleViewController") as! StopScheduleViewController
stopScheduleController.stop = stop // pass data object
self.navigationController?.pushViewController(stopScheduleController, animated: true)
}
You can simply set the hidden property of your tab bar when the stop schedule view controller is displayed and unhide the tab bar before that view controller disappears
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.tabBarController?.tabBar.hidden=true
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
self.tabBarController?.tabBar.hidden=false
}
Update: To animate the transition you can use this:
class StopViewController: UIViewController {
var barFrame:CGRect?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
// self.tabBarController?.tabBar.hidden=true
if let tabBar=self.tabBarController?.tabBar {
self.barFrame=tabBar.frame
UIView.animateWithDuration(0.3, animations: { () -> Void in
let newBarFrame=CGRectMake(self.barFrame!.origin.x, self.view.frame.size.height, self.barFrame!.size.width, self.barFrame!.size.height)
tabBar.frame=newBarFrame
}, completion: { (Bool) -> Void in
tabBar.hidden=true
})
}
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
self.tabBarController?.tabBar.hidden=false;
if self.barFrame != nil {
UIView.animateWithDuration(0.3, animations: { () -> Void in
let newBarFrame=CGRectMake(self.barFrame!.origin.x, self.view.frame.size.height-self.barFrame!.size.height, self.view.frame.size.width, self.barFrame!.size.height)
self.tabBarController?.tabBar.frame=newBarFrame
})
}
}
}
You are not using the segue you just defined in your Storyboard. Instead, you are currently reloading your StopScheduleViewController manually, whereas you should only perform the segue you already have defined.
Add an Identifier to each of the Storyboard Segue you want to invoke programmatically,
then load them in this manner:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
performSegueWithIdentifier("showStopSchedule", sender: self)
}
If you want to only hide the navigationController the below code works.
self.navigationController?.navigationBar.hidden = true

How can you reload a ViewController after dismissing a modally presented view controller in Swift?

I have a first tableViewController which opens up a second tableViewcontroller upon clicking a cell. The second view controller is presented modally (Show Detail segue) and is dismissed with:
self.dismissViewControllerAnimated(true, completion: {})
At this point, the second view controller slides away and reveals the first view controller underneath it. I would then like to reload the first view controller. I understand that this may require use of delegate functions, but not sure exactly how to implement it
Swift 5:
You can access the presenting ViewController (presentingViewController) property and use it to reload the table view when the view will disappear.
class: FirstViewController {
var tableView: UITableView
present(SecondViewController(), animated: true, completion: nil)
}
In your second view controller, you can in the viewWillDisappear method, add the following code:
class SecondViewController {
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if let firstVC = presentingViewController as? FirstViewController {
DispatchQueue.main.async {
firstVC.tableView.reloadData()
}
}
}
}
When you dismiss the SecondViewController, the tableview of the FirstViewController will reload.
I solved it a bit differently since I don't want that dependancy.
And this approach is intended when you present a controller modally, since the presenting controller wont reload when you dismiss the presented.
Anyway solution!
Instead you make a Singleton (mediator)
protocol ModalTransitionListener {
func popoverDismissed()
}
class ModalTransitionMediator {
/* Singleton */
class var instance: ModalTransitionMediator {
struct Static {
static let instance: ModalTransitionMediator = ModalTransitionMediator()
}
return Static.instance
}
private var listener: ModalTransitionListener?
private init() {
}
func setListener(listener: ModalTransitionListener) {
self.listener = listener
}
func sendPopoverDismissed(modelChanged: Bool) {
listener?.popoverDismissed()
}
}
Have you Presenting controller implement the protocol like this:
class PresentingController: ModalTransitionListener {
//other code
func viewDidLoad() {
ModalTransitionMediator.instance.setListener(self)
}
//required delegate func
func popoverDismissed() {
self.navigationController?.dismissViewControllerAnimated(true, completion: nil)
yourTableViev.reloadData() (if you use tableview)
}
}
and finally in your PresentedViewController in your viewDid/WillDisappear func or custom func add:
ModalTransitionMediator.instance.sendPopoverDismissed(true)
You can simply reaload your data in viewDidAppear:, but that might cause the table to be refreshed unnecessarily in some cases.
A more flexible solution is to use protocols as you have correctly guessed.
Let's say the class name of your first tableViewController is Table1VC and the second one is Table2VC. You should define a protocol called Table2Delegate that will contain a single method such as table2WillDismissed.
protocol Table2Delegate {
func table2WillDismissed()
}
Then you should make your Table1VC instance conform to this protocol and reload your table within your implementation of the delegate method.
Of course in order for this to work, you should add a property to Table2VC that will hold the delegate:
weak var del: Table2Delegate?
and set its value to your Table1VC instance.
After you have set your delegate, just add a call to the delegate method right before calling the dismissViewControllerAnimated in your Table2VC instance.
del?.table2WillDismissed()
self.dismissViewControllerAnimated(true, completion: {})
This will give you precise control over when the table will get reloaded.

Resources