Create close button for each view controller on the uinavigationcontroller stack - ios

I'd like to have a close button on each view controller that appears in the navigation stack. I've read here that I need to create an object that is a uinavigationdelegate, I think this object will have a method like didTapCloseButton?
Questions:
Should I create a protocol and make everything confirm to it, i.e.:
protocol CustomDelegate: UINavigationControllerDelegate {
func didTapCloseButton()
}
public class ViewController: CustomDelegate {
func didTapCloseButton() {
//not sure what goes in here?
}
}
How do I get the close button to show on the navigation bars of every view?
When the user clicks the close button, how do I get that to dismiss every view on that stack?
Thanks for your help!

Here a simple solution. Create UINavigationController subclass and override pushViewController method.
class NavigationController: UINavigationController {
override func pushViewController(_ viewController: UIViewController, animated: Bool) {
super.pushViewController(viewController, animated: animated)
let closeBarButtonItem = UIBarButtonItem(
title: "Close",
style: .done,
target: self,
action: #selector(self.popViewController(animated:)))
viewController.navigationItem.rightBarButtonItem = closeBarButtonItem
}
}

Not sure if this is what you intended but you can do:
protocol CustomDelegate: UINavigationControllerDelegate {
func didTapCloseButton()
}
extension CustomDelegate where Self : UIViewController{
func didTapCloseButton(){
// write your default implementation for all classes
}
}
now for every UIViewController class you have you can just do :
class someViewController: CustomDelegate{
#IBAction buttonClicked (sender: UIButton){
didTapCloseButton()
}
}

Related

Know when user navigates to another screen

I want to know that when user navigates to another screen. If there is any change in navigation stack, It should notify me. To do that, I do not want to write any code in viewwillappear of any viewcontroller. I wish to write it once and thus I can observe that which screen is navigated.
Consider writing a super UIViewController which all your UIViewControllers inherit from. Then you just write the code in your super ViewController
Sample code for #Nikolaj Nielsen answer:
class BaseViewController: UIViewController {
var identifier: String {
return ""
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print("Appeared VC: \(identifier)")
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
print("Disappeared VC: \(identifier)")
}
}
class CustomViewController: BaseViewController {
override var identifier: String {
return "CustomViewController"
}
}

Getting destination ViewController from UIBarButtonItem?

I have programmed UIBarButtonItem so that it does some action before switching to a previous view. I was wondering how do I get the viewcontroller of that transitioning scene from my UIBarButtonItem?
So, scene 1 -> scene 2 (current scene) -> scene 1 (after clicking the UIBarButtonItem button)
I've tried to pass the previous scene variables (that I need) to the current scene to perform action on (sense I don't think the transitioning scene is instantiating a new view, but that doesn't work
override func viewDidLoad() {
super.viewDidLoad()
loadTuple()
let addButton: UIBarButtonItem = UIBarButtonItem(title: "Save", style: .plain, target: self, action: #selector(saveExercise(_: )))
self.navigationItem.setRightBarButton(addButton, animated: true)
}
#objc func saveExercise(_ sender: UIBarButtonItem) {
self.addNewTupleToDB(element: self.getNewTuple())
self.saveData()
debugPrint("saveExercise")
self.exerciseVCTableView?.reloadData() // tried to pass the table view from the previous scene to call here
self.navigationController?.popViewController(animated: true)
// Want to save reload the table data of the scene this button transitions to
}```
You may use delegate pattern for solving this. Delegate pattern is something, to delegate some work to other and return to the work after delegation is done.
Suppose ViewController1 has UIBarButton , goes to ViewController2, some function done and return to ViewController1
let us take a protocol
protocol MyProtocol {
func myFunction()
}
then in ViewController2 add a delegate method. Assuming in ViewController2, you have to call a method doMyWork and some work will be done here, then you have to pop.
class ViewController2 {
var delegate : MyProtocol?
override func viewDidLoad() {
super.viewDidLoad()
doMyWork()
}
func doMyWork() {
// my works
delegate?.myFunction()
self.navigationController.popViewController()
}
}
now the viewController1 have to receive the delegate work has done.
in viewController1, in barButtonItem
class ViewController1 {
#objc func barButton(_sender : UIBarButton) {
let viewController = ViewController2()
viewController.delegate = self
self.naviagtionController.pushViewController(viewController, animated : true)
}
}
now you have to implement protocol method
extension ViewController1 : MyProtocol {
func myFunction() {
self.tableView.reloadData()
}
}

Go back to root when nav back button is press

I have the method to check when the back button in navigation bar is press and the method go back to root page but for some reason when self.navigationController?.popToRootViewController(animated: true) it only go back to the previous page. do anyone know how to go back to the root when navigation bar back button is pressed?
override func didMove(toParentViewController parent: UIViewController?) {
super.didMove(toParentViewController: parent)
if parent == nil{
self.navigationController?.popToRootViewController(animated: true)
}
}
In this question he is asking how to what method can he use to customise his back button. In my code its able to detect when user press on back button and self.navigationController?.popToRootViewController(animated: true)
is suppose to bring the page back to the root page, however there are somethings in the system preventing my app to go back to the root page.
i think the best way is to create your own custom back button at this page
override func viewDidLoad {
super.viewDidLoad()
navigationItem.hidesBackButton = true
let newBackButton = UIBarButtonItem(title: "Back", style: UIBarButtonItemStyle.plain, target: self, action: #selector(YourViewController.back(sender:)))
navigationItem.leftBarButtonItem = newBackButton
}
func back(sender: UIBarButtonItem) {
// Perform your custom actions
// ...
// Go back to the root ViewController
_ = navigationController?.popToRootViewController(animated: true)
}
credit to this answer by 'fr33g' : Execute action when back bar button of UINavigationController is pressed
Personally I would not recommend what you are trying to achieve, but anyways here is a different solution without customizing the back button.
Steps to implement
Create CustomNavigationController by subclassing
UINavigationController
Override popViewController(animated:)
When ViewController conforms to Navigationable and
shouldCustomNavigationControllerPopToRoot() returns true, call super.popToRootViewController
Otherwise proceed with normally popping the ViewController
Source Code
Custom Navigation Controller
import UIKit
class CustomNavigationController: UINavigationController {
// MARK: - Initializers
override init(rootViewController: UIViewController) {
super.init(rootViewController: rootViewController)
initialSetup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initialSetup()
}
// MARK: - Setups
private func initialSetup() {
// DISCLAIMER: This code does not support `interactivePopGestureRecognizer`, therefore we disable it
interactivePopGestureRecognizer?.delegate = nil
}
// MARK: - Overrides
override func popViewController(animated: Bool) -> UIViewController? {
if shouldNavigationPopToRoot {
return super.popToRootViewController(animated: animated)?.last
}
return super.popViewController(animated: animated)
}
// MARK: - Helpers
private var shouldNavigationPopToRoot: Bool {
return (topViewController as? Navigationable)?.shouldCustomNavigationControllerPopToRoot() == true
}
}
View Controller conforming to Navigationable
import UIKit
protocol Navigationable: class {
func shouldCustomNavigationControllerPopToRoot() -> Bool
}
class ViewController: UIViewController, Navigationable {
// MARK: - Protocol Conformance
// MARK: Navigationable
func shouldCustomNavigationControllerPopToRoot() -> Bool {
return true
}
}
Output

Static barButtonItems in a navigation controller?

I'm wondering what's the best way to achieve the following. My application when launches goes to the following tableview:
When you select a category, a segue to another tableview is made and looks like this:
What I want to do, is eventually have the 'Basket' barButtonItem in the first view to update with the total price of the basket amount. I also want the button to be visible from the entire navigation controller cycle.
Is there a way that I can have the Basket button show on every stage of the navigationcontroller process?
So for example I'd like to have the button show on the second table view.
Yes you can achieve it by use of take class of UINavigationController like below
class CustomNavigationController: UINavigationController, UINavigationControllerDelegate{
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
}
// MARK: Private Functions
private func addRightBarButtonTo(viewController: UIViewController){
barButtonItem = UIBarButtonItem(title: "Basket", style: .plain, target: self, action: #selector(CustomNavigationController.dismiss(_:)))
viewController.navigationItem.rightBarButtonItem = barButtonItem
}
// MARK: UINavigationController Delegate
func navigationController(navigationController: UINavigationController, willShowViewController viewController: UIViewController, animated: Bool) {
self.addRightBarButtonTo(viewController)
}
#objc func dismiss(sender: Any){
self.presentingViewController?.dismissViewControllerAnimated(true, completion: nil)
}
}
Use CustomNavigationController as rootView Controller of the Window.
Second Way
Take extension of UIViewController
extension UIViewController {
func addRightButtonItem() {
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Basket", style: .done, target: self, action: #selector(barButtonMethod(_:)))
}
#objc func barButtonMethod(_ sender: UIBarButtonItem) {
// Your code
}
}
and call below method in viewWillAppear of each and every viewController
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.addRightButtonItem()
}

Closing a view controller through its delegate in Swift

I have the following protocols:
protocol MyDelegate: class {
func closeViewController()
}
protocol MyProtocol: class {
weak var delegate: SomeClass? {get set}
}
And the following class:
class SomeClass: MyDelegate {
var myViewController: UIViewController
init(myViewController: UIViewController){
self.myViewController = myViewController
self.myViewController.delegate = self
}
func closeViewController() {
myViewController.dismiss(animated: true, completion: nil)
}
}
The idea here is that SomeClass takes a view controller and sets itself as the view controller's delegate.
The View controller is defined like so:
class SomeViewController: UIViewController, MyProtocol {
weak var delegate: SomeClass?
...
#IBAction func close(_ sender: Any) {
delegate?.closeViewController()
}
...
}
where the close function is mapped to a close button in storyboard.
I initialize both SomeClass and my view controller inside another view controller.
var someViewController = // initialized here
var someClass = SomeClass(myViewController: someViewController)
self.present(someViewController, animated: true, completion: nil)
However, when I press the close button, nothing happens at all. The view controller does not dismiss.
On the other hand, if I change the close() function in my ViewController to be:
#IBAction func close(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
}
Then it dismisses itself as expected, showing that the function is correctly mapped to the button.
How do I go about dismissing my view controller from another class?
You have declared delegate property as weak and there isn't any strong reference of SomeClass object. Object should be nil by the time close button callback and closeViewController() is never called.
If I may suggest that you've made this much more complicated than it needs to be. I would ditch the protocol altogether and just implement a simple delegate pattern.
class SomeViewController {
func close() {
print("did close")
}
}
class SomeObject {
weak var delegate: SomeViewController?
func close() {
delegate?.close()
}
}
let viewController = SomeViewController()
let object = SomeObject()
object.delegate = viewController
object.close() // prints "did close"

Resources