iosswiftuiviewuistackviewpopviewcontroller

Show UIView When Popping Back on a Navigation Controller


So I'm trying to show an UIView in a stack view when I click continue on a controller and pop back to the previous controller. However, I'm having trouble showing the view when I pop. It will show if I present the controller, but I need it to show when I pop. How should I go about this? Thank you.

// ServiceDetailController

// MARK: - Properties

lazy var dateContainer: ShadowCardView = {
    let view = ShadowCardView()
    view.backgroundColor = .white
    view.addShadow()
    view.setHeight(height: 40)
        
    let stack = UIStackView(arrangedSubviews: [calendarIcon, dateLabel, timeOfDayLabel])
    stack.axis = .horizontal
    stack.distribution = .fillProportionally
    stack.spacing = 8
        
    view.addSubview(stack)
    stack.centerY(inView: view)
    stack.anchor(left: view.leftAnchor, right: view.rightAnchor, paddingLeft: 12, paddingRight: 70)
        
    view.addSubview(closeButton)
    closeButton.centerY(inView: view)
    closeButton.anchor(right: view.rightAnchor, paddingRight: 12)
        
    return view
}()

lazy var dateStack = UIStackView(arrangedSubviews: [dateLabelStack, dateContainer, dateContainerView])

// MARK: - Lifecycle

override func viewDidLoad() {
    super.viewDidLoad()
        
    configureUI()
}

// MARK: - Selectors

@objc func handleDateCreationTapped() {
    let controller = DateCreationController()
    controller.jobService = self.jobService
    navigationController?.pushViewController(controller, animated: true)
}

// MARK: - Helper Functions

fileprivate func configureUI() {
        
     setupNavigationBar()
        
     view.backgroundColor = .groupTableViewBackground
        
     setupServiceInfoView()
        
     setupFormView()
        
}

fileprivate func setupFormView() {
        
    showDataContainer(shouldShow: false)
    setupTapGestureRecognizers()
        
}

fileprivate func setupTapGestureRecognizers() {
        
     let dateTap = UITapGestureRecognizer(target: self, action: #selector(handleDateCreationTapped))
     dateTap.numberOfTapsRequired = 1
     dateTextField.addGestureRecognizer(dateTap)
        
}

func showDateContainer(shouldShow: Bool) {
            
       if shouldShow {
          dateContainer.isHidden = false
          dateStack.spacing = 5
       } else {
          dateContainer.isHidden = true
          dateStack.spacing = -18
       }
            
 }

// DateCreationController

// MARK: - Properties

private let continueButton: UIButton = {
     let button = UIButton(type: .system)
     button.setTitle("Continue", for: .normal)
     button.setTitleColor(.white, for: .normal)
     button.titleLabel?.font = UIFont(name: "AvenirNext-Medium", size: 14)
     button.backgroundColor = .darkGray
     button.setHeight(height: 50)
     button.layer.cornerRadius = 8
     button.addTarget(self, action: #selector(handleContinue), for: .touchUpInside)
     return button
}()

// MARK: - Selectors

@objc func handleContinue() {
        
    let controller = ServiceDetailController()
    controller.showDateContainer(shouldShow: true)
        
    navigationController?.popViewController(animated: true)

}

Solution

  • The main problem is this func in your DateCreationController:

    @objc func handleContinue() {
            
        // here, you are creating a NEW instance of ServiceDetailController
        let controller = ServiceDetailController()
        controller.showDateContainer(shouldShow: true)
            
        // here, you are popping back to where you came from... the EXISTING instance of ServiceDetailController
        navigationController?.popViewController(animated: true)
    
    }
    

    You need to use either delegate/protocol pattern or a closure.

    Here's an example using a closure...

    Add this var to your DateCreationController class:

    var continueCallback: ((Bool)->())?
    

    In your ServiceDetailController class, when you instantiate and push your DateCreationController, you'll also setup that closure:

    @objc func handleDateCreationTapped() {
        let controller = DateCreationController()
        controller.jobService = self.jobService
        
        // add the closure
        controller.continueCallback = { [weak self] shouldShow in
            guard let self = self else { return }
            self.showDateContainer(shouldShow: shouldShow)
            self.navigationController?.popViewController(animated: true)
        }
    
        navigationController?.pushViewController(controller, animated: true)
    }
    

    Then, in your button action in DateCreationController, you "call back" using that closure:

    @objc func handleContinue() {
        
        continueCallback?(true)
        
    }
    

    Here's a runnable example. It creates a simple yellow view as the dateContainer view, but it is hidden on load. Tapping anywhere will push to DateCreationController, which has a single "Continue" button. Tapping that button will execute the closure, where the dateContainer view will be set to visible and we'll pop back:

    class ServiceDetailController: UIViewController {
        
        var jobService: String = "abc"
    
        // plain yellow view
        let dateContainer: UIView = {
            let view = UIView()
            view.backgroundColor = .yellow
            return view
        }()
        
        // MARK: - Lifecycle
        
        override func viewDidLoad() {
            super.viewDidLoad()
    
            let infoLabel = UILabel()
            infoLabel.text = "Tap anywhere"
            view.addSubview(infoLabel)
            view.addSubview(dateContainer)
            
            infoLabel.translatesAutoresizingMaskIntoConstraints = false
            dateContainer.translatesAutoresizingMaskIntoConstraints = false
            
            let g = view.safeAreaLayoutGuide
            NSLayoutConstraint.activate([
                infoLabel.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
                infoLabel.centerXAnchor.constraint(equalTo: g.centerXAnchor),
                
                dateContainer.topAnchor.constraint(equalTo: infoLabel.bottomAnchor, constant: 20.0),
                dateContainer.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
                dateContainer.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
                dateContainer.heightAnchor.constraint(equalToConstant: 100.0),
            ])
    
            setupTapGestureRecognizers()
            
            // hide dateContainer on load
            showDateContainer(shouldShow: false)
        }
        
        // MARK: - Selectors
        
        @objc func handleDateCreationTapped() {
            let controller = DateCreationController()
            controller.jobService = self.jobService
            
            // add the closure
            controller.continueCallback = { [weak self] shouldShow in
                guard let self = self else { return }
                self.showDateContainer(shouldShow: shouldShow)
                self.navigationController?.popViewController(animated: true)
            }
    
            navigationController?.pushViewController(controller, animated: true)
        }
        
        // MARK: - Helper Functions
        
        fileprivate func setupTapGestureRecognizers() {
            
            let dateTap = UITapGestureRecognizer(target: self, action: #selector(handleDateCreationTapped))
            dateTap.numberOfTapsRequired = 1
            view.addGestureRecognizer(dateTap)
            
        }
        
        func showDateContainer(shouldShow: Bool) {
            
            if shouldShow {
                dateContainer.isHidden = false
            } else {
                dateContainer.isHidden = true
            }
            
        }
    }
    class DateCreationController: UIViewController {
        var jobService: String = "abc"
        
        var continueCallback: ((Bool)->())?
        
        lazy var continueButton: UIButton = {
            let button = UIButton(type: .system)
            button.setTitle("Continue", for: .normal)
            button.setTitleColor(.white, for: .normal)
            button.titleLabel?.font = UIFont(name: "AvenirNext-Medium", size: 14)
            button.backgroundColor = .darkGray
            button.layer.cornerRadius = 8
            button.addTarget(self, action: #selector(handleContinue), for: .touchUpInside)
            return button
        }()
    
        override func viewDidLoad() {
            super.viewDidLoad()
            view.backgroundColor = .green
            view.addSubview(continueButton)
            continueButton.translatesAutoresizingMaskIntoConstraints = false
            NSLayoutConstraint.activate([
                continueButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
                continueButton.centerYAnchor.constraint(equalTo: view.centerYAnchor),
                continueButton.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.75),
                continueButton.heightAnchor.constraint(equalToConstant: 50.0),
            ])
        }
        
        // MARK: - Selectors
        
        @objc func handleContinue() {
            
            continueCallback?(true)
            
        }
    }