Search code examples
iosswiftsegue

Access previous viewController element with its tag


In my scenario, I need to access a button with a specific tag from the previous viewController if that button exists. That button will be located in a reused table view cell.

I want to change that button's text from the current view. I thought about sending data with NotificationCenter but there might be several viewController that segued to current view so It was not a good way.

Tried with one

override func didMove(toParentViewController parent: UIViewController?) {
    super.didMove(toParentViewController: parent)

    if parent == self.navigationController?.parent {
        //check if previous viewController has the button and access it    
    }
}

Any help?


Solution

  • (this answer is not about app architecture, just posting a simple solution to author's problem)

    You say that your button represents a 'follow' state of your model (Profile). You probably want to have a model that will represent a profile:

    class Profile {
        var following : Bool = false
    }
    

    Your first ViewController will probably look like this:

    class ProfileListViewController : UIViewController, ProfileDetailsViewControllerDelegate {
        var profiles : [Profile] = [...]
    
        func userDidChangeProfileInfo(_ profile : Profile)() {
            (...)
        }
    }
    

    When you open a profile you will call something like this in your ProfileListViewController:

    func openProfileDetails(at indexPath: IndexPath) {
        let profile = profiles[indexPath.row]
    
        let detailsViewController = ProfileDetailsViewController.getInstance()
        detailsViewController.profile = profile
        detailsViewController.delegate = self
    
        self.navigationController?.pushViewController(detailsViewController, animated: true)
    }
    

    the delegate field is a protocol that can look like this and is implemented in the code above:

    protocol ProfileDetailsViewControllerDelegate : class {
        func userDidChangeProfileInfo(_ profile : Profile)
    }
    

    The ProfileDetailsViewController:

    class ProfileDetailsViewController : UIViewController {
    
       var profile: Profile? 
    
       weak var delegate : ProfileDetailsViewControllerDelegate?
    
       func didTapFollowButton() {
           profile.following = true
           delegate?.userDidChangeProfileInfo(profile)
       }
    }
    

    back in your ProfileListViewController, the delegate method will be called and you can reload your rows (or the whole tableview if you'd like):

    func userDidChangeProfileInfo(_ profile : Profile)() {
        if let row = profiles.firstIndex(where: { $0 == profile }) {
            tableView.reloadRows(at: [IndexPath(row: row, section: 0)], with: .automatic)
        }
    }
    

    Up next the cell will be recreated at this index so the cellForRowAt method will be called. You can set up your cell again based on changes in your model (change texts, style, return different cell etc, whatever floats your boat and fits your use cases):

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    
        let cell = tableView.dequeueReusableCell(...)
    
        let profile = profiles[indexPath.row]
    
        if profile.following {
            cell.type = .following
        } else {
            cell.type = .notFollowing
        }
    
        return cell
    
    }
    

    The cell itself can look like this:

    enum ProfileTableCellMode {
        case following
        case notFollowing
    }
    
    class ProfileTableCell : UITableViewCell {
    
        @IBOutlet weak var followButton : UIButton!
    
        var state: ProfileTableCellMode = .notFollowing { //default value
            didSet {
                onStateUpdated()
            }
        }
    
        func onStateUpdated() {
            switch state {
    
            case .following:
                followButton.setTitle("Unfollow", for: .normal)
    
    
            case .notFollowing:
                followButton.setTitle("Follow", for: .normal)
    
            }
        }
    
    }
    

    You could also skip all of the delegating stuff and just straight up do something like this in ProfileListViewController:

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        self.tableView.reloadData()
    }
    

    So the whole table reloads when the ProfileListViewController is back to being the top controller.

    The most important thing here is to keep UI (user interface) separate from the state (models etc). UI should render/update itself based on the state and should not handle any business logic other than passing along the 'i was clicked, please handle it' to the logic.