How to dismiss a stack of UIViewControllers to a certain Root ViewController? - ios

Some RootViewController presents a ParentViewController which than presents a ChildViewController.
How can I dissmiss the ChildViewController animated directly to the RootViewController whithout showing the ParentViewController again?
In Detail
Assume some presents the ParentViewController which lets the user enter some credentials to login to some user account.
Once the connection is established the ParentViewController presents the ChildViewController which shows connection / account details to the user
When the user closes the ChildViewController it should be dismissed animated (slide down, etc.). But instead of returning to the ParentViewController the user should get back directly to the RootViewController
Of course it would be possible, that the ParentViewController does not present the ChildViewController itself but (somehow) tells the RootViewController to this. This way it would be no problem to directly return from the ChildViewController to the RootViewController. However, this is NOT what I am looking for, since the RootViewController should not know about the ChildViewController or even care if the ParentViewController presents other VCs.
I am looking for a solution where the ParentViewController controls whether itself is shown after the VC it presented is dismissed or its parent (= the root VC).
Code:
typealias CompletionBlock = () -> Void
class RootViewController: UIViewController {
#IBAction func showParentVC(_ sender: Any) {
let parentVC = ParentViewController()
parentVC.completion = {
self.dismiss(animated: true, completion: nil)
}
present(parentVC, animated: true)
}
}
class ParentViewController: UIViewController {
var completion: CompletionBlock?
#IBAction func showChild(_ sender: Any) {
let childVC = ChildViewController()
childVC.completion = {
self.completion?()
}
present(childVC, animated: true)
}
}
class ChildViewController: UIViewController {
var completion: CompletionBlock?
#IBAction func close(_ sender: Any) {
completion?()
}
}
Using this code does NOT solve the described problem. If close is called on the ChildViewController the RootViewController calls self.dismiss(animated: true, completion: nil). This way the ChildViewController animates away and the ParentViewController becomes visible. Then the ParentViewController animates away and the RootViewControllerbecomes visible.
How to skip the ParentViewController and directly show the RootViewController after animating away the ChildViewController?

My recommendation is embed your RootViewController into a NavigationController (if you don't have it yet) and present both parent a child with
navigationController?.present(viewController, animated: true, completion: nil)
//instead of viewController.present(...)
And then youcan use this method from your childViewController
navigationController?.popToRootViewController(animated: true)

One approach would be to set the view's alpha to Zero when you present another VC onto the "stack" of presented VCs.
So, present the first modal VC from the "root" VC as normal. For each "child" that presents another VC, use:
present(vc, animated: true, completion: {
self.view.alpha = 0.0
})
Now, when you call-back to the "root" VC to dismiss all the VCs, you won't see the partial / flash of the intermediate VC / VCs.
Here is a complete example to test. No #IBOutlets or #IBActions ... just start with a black view controller and assign its Custom Class to MultiPresentDismissViewController:
import UIKit
class MultiPresentDismissViewController: UIViewController {
let theLabel: UILabel = {
let v = UILabel()
v.textAlignment = .center
v.font = UIFont.boldSystemFont(ofSize: 40)
v.backgroundColor = .yellow
v.text = "\"Root\" VC"
return v
}()
let showAnotherButton: UIButton = {
let v = UIButton()
v.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
v.setTitle("Present a VC", for: .normal)
v.setTitleColor(.blue, for: .normal)
v.setTitleColor(.cyan, for: .highlighted)
return v
}()
let theStackView: UIStackView = {
let v = UIStackView()
v.axis = .vertical
v.alignment = .fill
v.distribution = .fill
v.spacing = 32
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
[theLabel, showAnotherButton].forEach {
theStackView.addArrangedSubview($0)
$0.layer.borderWidth = 1.0
}
theStackView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(theStackView)
NSLayoutConstraint.activate([
theStackView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
theStackView.topAnchor.constraint(equalTo: view.topAnchor, constant: 100.0),
theStackView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.8),
])
showAnotherButton.addTarget(self, action: #selector(presentAnotherVC), for: .touchUpInside)
}
#objc func presentAnotherVC() -> Void {
let vc = AnotherViewController()
vc.myID = 1
present(vc, animated: true, completion: nil)
}
}
class AnotherViewController: UIViewController {
let theLabel: UILabel = {
let v = UILabel()
v.textAlignment = .center
v.font = UIFont.boldSystemFont(ofSize: 100)
v.backgroundColor = .yellow
return v
}()
let showAnotherButton: UIButton = {
let v = UIButton()
v.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
v.setTitle("Present Another VC", for: .normal)
v.setTitleColor(.blue, for: .normal)
v.setTitleColor(.cyan, for: .highlighted)
return v
}()
let defaultDismissButton: UIButton = {
let v = UIButton()
v.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
v.setTitle("Default Dismiss All", for: .normal)
v.setTitleColor(.blue, for: .normal)
v.setTitleColor(.cyan, for: .highlighted)
return v
}()
let theStackView: UIStackView = {
let v = UIStackView()
v.axis = .vertical
v.alignment = .fill
v.distribution = .fill
v.spacing = 20
return v
}()
var myID: Int = 0
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .random()
[theLabel, showAnotherButton, defaultDismissButton].forEach {
theStackView.addArrangedSubview($0)
$0.layer.borderWidth = 1.0
}
theStackView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(theStackView)
NSLayoutConstraint.activate([
theStackView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
theStackView.topAnchor.constraint(equalTo: view.topAnchor, constant: 100.0),
theStackView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.8),
])
theLabel.text = "\(myID)"
showAnotherButton.addTarget(self, action: #selector(presentAnotherVC), for: .touchUpInside)
defaultDismissButton.addTarget(self, action: #selector(defaultDismissAll), for: .touchUpInside)
}
#objc func presentAnotherVC() -> Void {
let vc = AnotherViewController()
vc.myID = myID + 1
present(vc, animated: true, completion: {
self.view.alpha = 0.0
})
}
#objc func defaultDismissAll() -> Void {
// walk up the "presenting" hierarchy to find the "root" VC
var vc = self.presentingViewController
while vc?.presentingViewController != nil {
vc = vc?.presentingViewController
}
vc?.dismiss(animated: true, completion: nil)
}
}
extension CGFloat {
static func random() -> CGFloat {
return CGFloat(arc4random()) / CGFloat(UInt32.max)
}
}
extension UIColor {
static func random() -> UIColor {
return UIColor(red: .random(),
green: .random(),
blue: .random(),
alpha: 1.0)
}
}

Related

How to get rounded corner & dynamic height of popped custom modalPresentationStyle VC

When present a VC, the default style is not cover full screen and with a rounded corner like below gif illustrated.
But I want to control the height of modalPresentation, let's say a 1/4 screen height by default and dynamic changed according to tableView's rows of popped VC. So I implement a custom modalPresentationStyle on the base VC wit below code.
However, I found these issues after:
The popped VC is not rounded corner but with rectangle corner.
I cannot drag to move the popped VC anymore, it is in fixed position.
It would be more better if I could to increment the popped VC's height per its tableView rows count. Not a must item.
#objc func collectButtonTapped(_ sender: Any?) {
let vc = PlayListViewController()
vc.modalPresentationStyle = .custom
vc.transitioningDelegate = self
present(vc, animated: true)
}
func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
return HalfSizePresentationController(presentedViewController: presented, presenting: presentingViewController)
}
class HalfSizePresentationController: UIPresentationController {
override var frameOfPresentedViewInContainerView: CGRect {
guard let bounds = containerView?.bounds else { return .zero }
return CGRect(x: 0, y: bounds.height * 0.75, width: bounds.width, height: bounds.height * 0.75)
}
}
Try to assign cornerRadius to your vc:
#objc func collectButtonTapped(_ sender: Any?) {
let vc = PlayListViewController()
vc.modalPresentationStyle = .custom
vc.transitioningDelegate = self
// assign corner radius
vc.view.layer.cornerRadius = 20
vc.view.clipsToBounds = true
vc.view.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMinXMinYCorner] // this is for corner radius only for top
present(vc, animated: true)
}
for total control of vc presented position, you can use child vc and auto layout, for present child vc (like a modal presentation style) you can use UIView.animate on child vc top constraint.
this is an example of child vc and auto layout:
import UIKit
class YourController: UIViewController {
private lazy var firstChildVc = AiutiFirst()
let myButton: UIButton = {
let b = UIButton(type: .system)
b.layer.cornerRadius = 10
b.clipsToBounds = true
b.backgroundColor = .black
b.setTitleColor(.white, for: .normal)
b.setTitle("Present", for: .normal)
b.addTarget(self, action: #selector(handlePresent), for: .touchUpInside)
b.translatesAutoresizingMaskIntoConstraints = false
return b
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .blue
addChildVC()
}
var up = false
#objc fileprivate func handlePresent() {
print("present")
if up == false {
UIView.animate(withDuration: 0.3, delay: 0, options: .curveEaseOut) {
self.menuDown?.isActive = false
self.menuUp?.isActive = true
self.myButton.setTitle("Dismiss", for: .normal)
self.view.layoutIfNeeded()
} completion: { _ in
print("Animation completed")
self.up = true
}
} else {
UIView.animate(withDuration: 0.3, delay: 0, options: .curveEaseOut) {
self.menuUp?.isActive = false
self.menuDown?.isActive = true
self.myButton.setTitle("Present", for: .normal)
self.view.layoutIfNeeded()
} completion: { _ in
print("Animation completed")
self.up = false
}
}
}
var menuUp: NSLayoutConstraint?
var menuDown: NSLayoutConstraint?
fileprivate func addChildVC() {
addChild(firstChildVc)
firstChildVc.view.translatesAutoresizingMaskIntoConstraints = false
firstChildVc.view.layer.cornerRadius = 20
firstChildVc.view.clipsToBounds = true
firstChildVc.view.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMinXMinYCorner] // this is for corner radius only for top
view.addSubview(firstChildVc.view)
menuUp = firstChildVc.view.topAnchor.constraint(equalTo: view.centerYAnchor)
menuDown = firstChildVc.view.topAnchor.constraint(equalTo: view.bottomAnchor)
menuDown?.isActive = true
firstChildVc.view.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
firstChildVc.view.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
firstChildVc.view.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
firstChildVc.didMove(toParent: self)
view.addSubview(myButton)
myButton.bottomAnchor.constraint(equalTo: view.centerYAnchor, constant: -40).isActive = true
myButton.widthAnchor.constraint(equalToConstant: 200).isActive = true
myButton.heightAnchor.constraint(equalToConstant: 50).isActive = true
myButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
}
}
And this is the result:
To animate child vc presentation you can use UIView.animate function for top child vc constraint, or Pan gesture do drag it, or whatever you deem necessary and valid to use...
to show it full screen simple set child vc top anchor to top of intere view:
menuUp = firstChildVc.view.topAnchor.constraint(equalTo: view.topAnchor)

currentContext presentation style changes the size of the child view controller's view

I have a parent view controller and a child view controller that takes up a portion of the parent's main view:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let childVC = ChildVC()
addChild(childVC)
childVC.view.frame = CGRect(origin: CGPoint(x: 100, y: 100), size: CGSize(width: 200, height: 200))
view.addSubview(childVC.view)
childVC.didMove(toParent: self)
}
}
class ChildVC: UIViewController {
override func loadView() {
let v = UIView()
v.backgroundColor = .cyan
view = v
}
override func viewDidLoad() {
super.viewDidLoad()
let button = UIButton(type: .system)
button.setTitle("Present", for: .normal)
button.addTarget(self, action: #selector(pressed), for: .touchUpInside)
button.sizeToFit()
view.addSubview(button)
}
#objc func pressed() {
self.definesPresentationContext = true
self.providesPresentationContextTransitionStyle = true
self.modalTransitionStyle = .crossDissolve
let pvc = PresentedVC()
pvc.modalPresentationStyle = .currentContext
self.present(pvc, animated: true, completion: nil)
}
}
class PresentedVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .yellow
let button = UIButton(type: .system)
button.setTitle("Dismiss", for: .normal)
button.addTarget(self, action: #selector(pressed), for: .touchUpInside)
button.sizeToFit()
view.addSubview(button)
}
#objc func pressed() {
self.dismiss(animated: true, completion: nil)
}
}
When I present a view controller using the currentContext style, it presents the new view controller as it should, covering only the child view controller's view:
However, when I dismiss it, the size of the ChildVC's main view takes up the entire screen:
When I log the superview of the ChildVC's main view, it's still a subview of the parent view controller's main view so I'm not sure why this is happening.
Just change your presentationStyle to .overCurrentContext like this:
#objc func pressed() {
self.definesPresentationContext = true
self.providesPresentationContextTransitionStyle = true
self.modalTransitionStyle = .crossDissolve
let pvc = PresentedVC()
pvc.modalPresentationStyle = .overCurrentContext // <-- here
pvc.modalTransitionStyle = .crossDissolve // <-- and here
self.present(pvc, animated: true, completion: nil)
}
It will prevent the unwanted upscaling.

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)
}
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)
}
}

Swift: Change value of another ViewController with delegation programmatically

I am trying to change the value of one ViewController by clicking a button inside a second ViewController with delegation.
But so far it only prints a message, but doesn't change the value.
I have this class where I defined a view:
class CounterView: UIView {
public var creditPointValue = Int()
let label = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
self.translatesAutoresizingMaskIntoConstraints = false
backgroundColor = .cyan
self.addSubview(label)
label.text = "Credit Points: \(creditPointValue)"
label.translatesAutoresizingMaskIntoConstraints = false;
label.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
label.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
}
func changeCreditPointValue(value:Int){
creditPointValue = creditPointValue + value
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I am using that view inside this ViewController and I want to manipulate the variable "creditPointValue":
protocol AddCreditsDelegate {
func addCreditsToCounter()
}
class ViewController: UIViewController {
var delegate: AddCreditsDelegate?
var counterView = CounterView()
let label = UILabel()
let button = UIButton(frame: CGRect(x: 100, y: 100, width: 100, height: 50))
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
view.backgroundColor = .white
view.addSubview(button)
button.backgroundColor = .red
button.setTitle("View2", for: .normal)
button.translatesAutoresizingMaskIntoConstraints = false
button.addTarget(self, action: #selector(changeView), for: .touchUpInside)
button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
button.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
override func viewDidAppear(_ animated: Bool) {
view.addSubview(counterView)
counterView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
counterView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
counterView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
//counterView.frame.size.height = 30
counterView.bottomAnchor.constraint(equalTo: counterView.topAnchor, constant: 50).isActive = true
}
#objc func changeView(){
delegate?.addCreditsToCounter()
navigationController?.pushViewController(ViewController2(), animated: true)
}
}
And inside this second ViewController I am trying to change the value by clicking the button I added to the view:
class ViewController2: UIViewController {
let button = UIButton(frame: CGRect(x: 100, y: 100, width: 100, height: 50))
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(button)
button.backgroundColor = .red
button.setTitle("add Credits", for: .normal)
button.translatesAutoresizingMaskIntoConstraints = false
button.addTarget(self, action: #selector(addCreditsButton), for: .touchUpInside)
button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
button.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
#objc func addCreditsButton(){
addCreditsToCounter()
}
}
extension ViewController2 : AddCreditsDelegate{
func addCreditsToCounter() {
let vc = ViewController()
vc.delegate = self
vc.counterView.creditPointValue += 5
print("pressed")
}
}
So far only the message "pressed" gets printed each time I click the button and I don't even know if I am going into the right direction with my approach trying to use delegation.
You should not create a new ViewController instance each time you call ViewController2.addCreditsToCounter, you already have a ViewController that creates a ViewController2 instance in ViewController.changeView. Just store a weak reference to ViewController in ViewController2 using delegate. In such case ViewController (not ViewController2) should conform to AddCreditsDelegate.
First of all, replace
protocol AddCreditsDelegate {
func addCreditsToCounter()
}
with
protocol AddCreditsDelegate: AnyObject {
func addCreditsToCounter()
}
Then add weak var delegate: AddCreditsDelegate? to ViewController2 and remove ViewController.delegate. Remove ViewController2.addCreditsToCounter, ViewController2 should not conform to AddCreditsDelegate. It means that in ViewController2.addCreditsButton you should call delegate?.addCreditsToCounter(), not addCreditsToCounter().
ViewController should conform to AddCreditsDelegate:
extension ViewController: AddCreditsDelegate {
func addCreditsToCounter() {
counterView.creditPointValue += 5
print("pressed")
}
}
And don't forget to initialize ViewController2.delegate. Replace your ViewController.changeView implementation with
let controller = ViewController2()
controller.delegate = self
navigationController?.pushViewController(controller, animated: true)
You create a new instance here
let vc = ViewController()
instead you need
#objc func changeView() {
let sec = ViewController2(),
sec.delegate = self
navigationController?.pushViewController( animated: true)
}
Then declare this inside the SecondVC
weak var delegate:ViewController?
Finally
func addCreditsToCounter() {
delegate?.counterView.creditPointValue += 5
print("pressed")
}

Dismissing to rootViewController without calling each viewDidAppear

My View Contollers are like MainView -> NavigationController A -> ViewController A -> ViewController B -> ViewController C, I want to dismiss ViewControllers (A,B and C) and present MainView again... I used this line
self.view.window?.rootViewController?.dismiss(animated: false, completion: nil)
and it works just fine.. the problem is while dismissing viewControllers (A,B and C) it calls every viewDidAppear and this runs code that I don't want to be run.. what's the best practice to dismiss and go back to MainView without calling each viewController
You must have some other code that is causing the issue.
Here is a complete example...
PresDisViewController is the starting view controller
its button creates a nav controller with ViewControllerA as its root
ViewControllerA has a button that pushes ViewControllerB
ViewControllerB has a button that pushes ViewControllerC
ViewControllerC has a button that executes your self.view.window?.rootViewController?.dismiss(animated: false, completion: nil)
Each VC implements viewDidAppear() and prints itself to the debug console.
Output when I run it:
viewDidAppear scratchy.PresDisViewController
viewDidAppear scratchy.ViewControllerA
viewDidAppear scratchy.ViewControllerB
viewDidAppear scratchy.ViewControllerC
viewDidAppear scratchy.PresDisViewController
As you see, viewDidAppear is NOT called in the VCs when the nav controller is dismissed.
import UIKit
class ViewControllerA: UIViewController {
let btn: UIButton = {
let v = UIButton()
v.translatesAutoresizingMaskIntoConstraints = false
v.setTitle("Do Push", for: .normal)
v.backgroundColor = .red
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(btn)
NSLayoutConstraint.activate([
btn.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: -40.0),
btn.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0.0),
])
btn.addTarget(self, action: #selector(didTap(_:)), for: .touchUpInside)
}
#objc func didTap(_ sender: Any?) -> Void {
let vc = ViewControllerB()
self.navigationController?.pushViewController(vc, animated: true)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print("viewDidAppear", NSStringFromClass(self.classForCoder))
}
}
class ViewControllerB: UIViewController {
let btn: UIButton = {
let v = UIButton()
v.translatesAutoresizingMaskIntoConstraints = false
v.setTitle("Do Push", for: .normal)
v.backgroundColor = .systemYellow
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(btn)
NSLayoutConstraint.activate([
btn.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 0.0),
btn.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0.0),
])
btn.addTarget(self, action: #selector(didTap(_:)), for: .touchUpInside)
}
#objc func didTap(_ sender: Any?) -> Void {
let vc = ViewControllerC()
self.navigationController?.pushViewController(vc, animated: true)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print("viewDidAppear", NSStringFromClass(self.classForCoder))
}
}
class ViewControllerC: UIViewController {
let btn: UIButton = {
let v = UIButton()
v.translatesAutoresizingMaskIntoConstraints = false
v.setTitle("Do Dismiss", for: .normal)
v.backgroundColor = .blue
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(btn)
NSLayoutConstraint.activate([
btn.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 40.0),
btn.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0.0),
])
btn.addTarget(self, action: #selector(didTap(_:)), for: .touchUpInside)
}
#objc func didTap(_ sender: Any?) -> Void {
self.view.window?.rootViewController?.dismiss(animated: false, completion: nil)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print("viewDidAppear", NSStringFromClass(self.classForCoder))
}
}
class PresDisViewController: UIViewController {
let btn: UIButton = {
let v = UIButton()
v.translatesAutoresizingMaskIntoConstraints = false
v.setTitle("Do Present", for: .normal)
v.backgroundColor = .orange
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(btn)
NSLayoutConstraint.activate([
btn.topAnchor.constraint(equalTo: view.topAnchor, constant: 60.0),
btn.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0.0),
])
btn.addTarget(self, action: #selector(didTap(_:)), for: .touchUpInside)
}
#objc func didTap(_ sender: Any?) -> Void {
let vcA = ViewControllerA()
let navVC = UINavigationController(rootViewController: vcA)
navVC.modalPresentationStyle = .fullScreen
present(navVC, animated: true, completion: nil)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print("viewDidAppear", NSStringFromClass(self.classForCoder))
}
}
Here is how it is done via Storyboard. I don't know how to do it via code. I hope it helps
In your MainViewController add this:
#IBAction func unwindToMainView(segue:UIStoryboardSegue) { }
In all otherViewController (viewController A,ViewController B,ViewController C),
Click and drag like this
Then chose the segue you added in the mainVC
Now select that segue and give it an identifier
From here you can perform segue with that identifier wherever you want. I use button to perform that segue

Resources