Search code examples
iosswiftcore-datauinavigationcontroller

UINavigationView pop to root view controller problem


I have two view controllers in my app. In first view I display core data objects. I have custom UIView with UILabel to do that. In second view I have action to add new core data objects. Both are embedded in UINavigationController, where first view is root view controller. When I add object I want to bring user back to root view, but it must contains new data.

I was trying to use .popToRootViewController() action but when I go back, my data wasn't updated. I have same data, as when I launched my app.

Code from SecondViewController

  private func saveCaffeineData(coffeeName: String, coffeeSelectedSize: String, coffeeCaffeineAmount: Double, coffeeshop: String, date: Date) {
        guard let appDelegate =
            UIApplication.shared.delegate as? AppDelegate else {
                return
        }

        let managedContext = appDelegate.persistentContainer.viewContext

        let entity = NSEntityDescription.entity(forEntityName: "CaffeineData", in: managedContext)!
        let caffeineData = NSManagedObject(entity: entity, insertInto: managedContext)

        caffeineData.setValuesForKeys(["coffeeName" : coffeeName, "coffeeSize" : coffeeSelectedSize, "caffeineAmount" : coffeeCaffeineAmount, "coffeeShop" : coffeeshop, "date" : date])

        do {
        try managedContext.save()
            allCaffeineData.append(caffeineData)

            navigationController?.popToRootViewController(animated: true)
        } catch let error as NSError {
            print("Could not save. \(error), \(error.userInfo)")
        }
    }

Code from FirstViewController

lazy var todaySummaryView: TodaySummaryView = {
        let todaySummaryView = TodaySummaryView(todayCoffeeAmount: countTodayCaffeineAmount(), caffeineState: getCaffeineUserState()!)
        todaySummaryView.translatesAutoresizingMaskIntoConstraints = false
        todaySummaryView.backgroundColor = .white
        todaySummaryView.layer.cornerRadius = 10
        return todaySummaryView
    }()

    private func setupLayout() {
        title = "Dashboard"

        if caffeineData.count == 0 {
            view.addSubview(nothingView)
            view.addSubview(addDataButton)

            addDataButton.addTarget(self, action: #selector(addCaffeine), for: .touchUpInside)

            NSLayoutConstraint.activate([
                nothingView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
                nothingView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
                nothingView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
                nothingView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
                addDataButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
                addDataButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20),
                addDataButton.heightAnchor.constraint(equalToConstant: 50),
                addDataButton.widthAnchor.constraint(equalToConstant: 300)
            ])
        } else {
            view.addSubview(dashboardScrollView)
            view.addSubview(addDataButton)

            addDataButton.addTarget(self, action: #selector(addCaffeine), for: .touchUpInside)

            dashboardScrollView.addSubview(dashboardScrollViewContentView)
            dashboardScrollViewContentView.addSubview(todaySummaryView)

            NSLayoutConstraint.activate([
                dashboardScrollView.topAnchor.constraint(equalTo: view.topAnchor),
                dashboardScrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
                dashboardScrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
                dashboardScrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),

                dashboardScrollViewContentView.topAnchor.constraint(equalTo: dashboardScrollView.topAnchor),
                dashboardScrollViewContentView.leadingAnchor.constraint(equalTo: dashboardScrollView.leadingAnchor),
                dashboardScrollViewContentView.trailingAnchor.constraint(equalTo: dashboardScrollView.trailingAnchor),
                dashboardScrollViewContentView.bottomAnchor.constraint(equalTo: dashboardScrollView.bottomAnchor),
                dashboardScrollViewContentView.centerXAnchor.constraint(equalTo: dashboardScrollView.centerXAnchor),

                todaySummaryView.topAnchor.constraint(equalTo: dashboardScrollViewContentView.topAnchor, constant: 20),
                todaySummaryView.centerXAnchor.constraint(equalTo: dashboardScrollViewContentView.centerXAnchor),
                todaySummaryView.leadingAnchor.constraint(equalTo: dashboardScrollViewContentView.leadingAnchor, constant: 15),
                todaySummaryView.trailingAnchor.constraint(equalTo: dashboardScrollViewContentView.trailingAnchor, constant: -15),

                addDataButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
                addDataButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20),
                addDataButton.heightAnchor.constraint(equalToConstant: 50),
                addDataButton.widthAnchor.constraint(equalToConstant: 300)
            ])
        }
    }

    @objc private func addCaffeine() {
        let destinationViewController = SelectCoffeeTableViewController()

        navigationController?.pushViewController(destinationViewController, animated: true)
    }

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

    override func viewDidAppear(_ animated: Bool) {
        guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
            return
        }

        let managedContext = appDelegate.persistentContainer.viewContext
        let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "CaffeineData")

        do {
            caffeineData = try managedContext.fetch(fetchRequest)
            setupLayout()
        } catch let error as NSError {
            print("Something goes wrong! \(error.description)")
        }
    }

    private func countTodayCaffeineAmount() -> Float {
        var caffeineAmount = Float()

        let calendar = Calendar.current

        let todayDate = Date()
        let todayDay = calendar.component(.day, from: todayDate)
        let todayMonth = calendar.component(.month, from: todayDate)
        let todayYear = calendar.component(.year, from: todayDate)

        for caffeineRow in caffeineData {
            let caffeineDay = calendar.component(.day, from: caffeineRow.value(forKey: "date") as! Date)
            let caffeineMonth = calendar.component(.month, from: caffeineRow.value(forKey: "date") as! Date)
            let caffeineYear = calendar.component(.year, from: caffeineRow.value(forKey: "date") as! Date)

            if (todayDay == caffeineDay && todayMonth == caffeineMonth && todayYear == caffeineYear) {
                caffeineAmount += caffeineRow.value(forKey: "caffeineAmount") as! Float
            }
        }
        return caffeineAmount
    }

    private func getCaffeineUserState() -> CaffeineState? {
        var caffeineState: CaffeineState

        let caffeineAmount = countTodayCaffeineAmount()

        switch caffeineAmount {
        case 0..<200:
            caffeineState = .normal
        case 200..<300:
            caffeineState = .warning
        case 300...:
            caffeineState = .dangerous
        default:
            return nil
        }

        return caffeineState

Code from CustomView

//
//  TodaySummaryView.swift
//  Caffeinee
//
//  Created by Piotr Sirek on 11/06/2019.
//  Copyright © 2019 Piotr Sirek. All rights reserved.
//

import UIKit

class TodaySummaryView: UIView {
    var todayCoffeeAmount: Float = 0.0
    var caffeineState: CaffeineState = CaffeineState.normal

    lazy var todayLabel: UILabel = {
        let todayLabel = UILabel()
        todayLabel.font = UIFont.systemFont(ofSize: 16, weight: .bold)
        todayLabel.text = "Today"
        todayLabel.textColor = .white
        todayLabel.textAlignment = .center
        todayLabel.translatesAutoresizingMaskIntoConstraints = false
        return todayLabel
    }()

    lazy var todayLabelView: UIView = {
        let todayLabelView = UIView(frame: CGRect(x: 0, y: 0, width: 74, height: 28))
        todayLabelView.layer.cornerRadius = 5
        todayLabelView.translatesAutoresizingMaskIntoConstraints = false
        return todayLabelView
    }()

    lazy var todayStateLabel: UILabel = {
        let todayStateLabel = UILabel()
        todayStateLabel.font = UIFont.systemFont(ofSize: 16, weight: .semibold)
        todayStateLabel.textColor = .black
        todayStateLabel.textAlignment = .left
        todayStateLabel.translatesAutoresizingMaskIntoConstraints = false
        todayStateLabel.numberOfLines = 0
        return todayStateLabel
    }()

    lazy var caffeineAmountLabel: UILabel = {
        let caffeineAmountLabel = UILabel()
        caffeineAmountLabel.font = UIFont.systemFont(ofSize: 45, weight: .heavy)
        caffeineAmountLabel.textAlignment = .left
        caffeineAmountLabel.translatesAutoresizingMaskIntoConstraints = false
        caffeineAmountLabel.text = "142"
        return caffeineAmountLabel
    }()

    lazy var caffeineUnitLabel: UILabel = {
        let caffeineUnitLabel = UILabel()
        caffeineUnitLabel.font = UIFont.systemFont(ofSize: 18, weight: .heavy)
        caffeineUnitLabel.textAlignment = .left
        caffeineUnitLabel.translatesAutoresizingMaskIntoConstraints = false
        caffeineUnitLabel.text = "mg"
        return caffeineUnitLabel
    }()

    private func setupView() {
        self.addSubview(todayLabelView)
        self.addSubview(todayStateLabel)
        self.addSubview(caffeineAmountLabel)
        self.addSubview(caffeineUnitLabel)

        todayLabelView.addSubview(todayLabel)

        NSLayoutConstraint.activate([
            todayLabelView.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor, constant: 15),
            todayLabelView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor, constant: 15),
            todayLabelView.heightAnchor.constraint(equalToConstant: 28),
            todayLabelView.widthAnchor.constraint(equalToConstant: 74),

            todayLabel.centerXAnchor.constraint(equalTo: todayLabelView.centerXAnchor),
            todayLabel.centerYAnchor.constraint(equalTo: todayLabelView.centerYAnchor),
            todayLabel.heightAnchor.constraint(equalToConstant: 26),
            todayLabel.widthAnchor.constraint(equalToConstant: 70),

            todayStateLabel.topAnchor.constraint(equalTo: todayLabelView.bottomAnchor, constant: 20),
            todayStateLabel.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor, constant: 15),
            todayStateLabel.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor, constant: -15),
            todayStateLabel.centerXAnchor.constraint(equalTo: layoutMarginsGuide.centerXAnchor),

            caffeineAmountLabel.topAnchor.constraint(equalTo: todayStateLabel.bottomAnchor, constant: 15),
            caffeineAmountLabel.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor, constant: 15),
            caffeineAmountLabel.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor, constant: -15),

            caffeineUnitLabel.leadingAnchor.constraint(equalTo: caffeineAmountLabel.trailingAnchor, constant: 0),
            caffeineUnitLabel.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor, constant: -20)
        ])
    }

    private func presentState(todayCoffeeAmount: Float, caffeineState: CaffeineState) {
        caffeineAmountLabel.text = "\(todayCoffeeAmount)"

        switch caffeineState {
        case .normal:
            todayLabelView.backgroundColor = UIColor(red:0.24, green:0.63, blue:0.98, alpha:1.0)
            todayStateLabel.text = "Your caffeine level is quiet good today. Keep it up!"
        case .warning:
            todayLabelView.backgroundColor = UIColor(red:0.96, green:0.68, blue:0.42, alpha:1.0)
            todayStateLabel.text = "Your caffeine now is near your daily limit. Be carefull!"
        case .dangerous:
            todayLabelView.backgroundColor = UIColor(red:1.00, green:0.31, blue:0.31, alpha:1.0)
            todayStateLabel.text = "Your caffeine level is over your daily limit. Don’t drink any coffee for rest of the day."
        }
    }

    override class var requiresConstraintBasedLayout: Bool {
        return true
    }

    override var intrinsicContentSize: CGSize {
        return CGSize(width: 340, height: 200)
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        setupView()
    }

    init(todayCoffeeAmount: Float, caffeineState: CaffeineState) {
        super.init(frame: CGRect())
        setupView()
        presentState(todayCoffeeAmount: todayCoffeeAmount, caffeineState: caffeineState)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}


Solution

  • You need to implement a delegate protocol, or use notifications to update the first controller. Here's an example how to use Delegates:

    protocol SecondViewControllerDelegate: class {
        func caffeineDataSaved(controller: SecondViewController)
    }
    
    class SecondViewController: UIViewController {
       weak var delegate: SecondViewControllerDelegate?
    
       private func saveCaffeineData(coffeeName: String, coffeeSelectedSize: String, coffeeCaffeineAmount: Double, coffeeshop: String, date: Date) {
            guard let appDelegate =
                UIApplication.shared.delegate as? AppDelegate else {
                    return
            }
    
            let managedContext = appDelegate.persistentContainer.viewContext
    
            let entity = NSEntityDescription.entity(forEntityName: "CaffeineData", in: managedContext)!
            let caffeineData = NSManagedObject(entity: entity, insertInto: managedContext)
    
            caffeineData.setValuesForKeys(["coffeeName" : coffeeName, "coffeeSize" : coffeeSelectedSize, "caffeineAmount" : coffeeCaffeineAmount, "coffeeShop" : coffeeshop, "date" : date])
    
            do {
            try managedContext.save()
                allCaffeineData.append(caffeineData)
                self.delegate?.caffeineDataSaved(controller: self)
    
                navigationController?.popToRootViewController(animated: true)
            } catch let error as NSError {
                print("Could not save. \(error), \(error.userInfo)")
            }
        }
    }
    

    First, in FirstViewController class, declare FirstViewController as a delegate of SecondViewController. You can do this on ViewDidLoad or where ever you instantiated SecondViewController:

    self.secondController.delegate = self
    

    Now, In the FirstViewController, you need to implement the delegate method:

     func caffeineDataSaved(controller: SecondViewController) {
        //Update your view.
    }