0

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?

Utku Dalmaz
  • 9,780
  • 28
  • 90
  • 130
  • Have you considered implementing prepareForSegue and passing this button in this method? – efimovdk Oct 01 '18 at 13:02
  • I already use prepareForSegue. Can you show me how to use the passed button in the current view? – Utku Dalmaz Oct 01 '18 at 13:03
  • 1
    @UtkuDalmaz that is exactly what the posted answer does, excepted that it doesn't use storyboards logic. I do know why it has been downvoted heavily when the only guy who has responded is wrong in his first line and has provided a solution along the lines of changing your architecture. Did you understand the answer posted? – Rakesha Shastri Oct 01 '18 at 13:05
  • @RakeshaShastri yes I got it. but I don't know why It had that many downvotes – Utku Dalmaz Oct 01 '18 at 13:06
  • @UtkuDalmaz did you try it then? – Rakesha Shastri Oct 01 '18 at 13:06
  • I was going to try but downvotes made me stop actually. is there any other way to achieve this btw? – Utku Dalmaz Oct 01 '18 at 13:07
  • 1
    @UtkuDalmaz even i'd like to know if there is a better way. So i'll just leave my answer there for someone who will explain to me exactly why it is wrong. – Rakesha Shastri Oct 01 '18 at 13:10
  • What does your button represent? Some state? – FruitAddict Oct 01 '18 at 13:40
  • @FruitAddict it is follow button. user can follow other user with that button. But instead they can also go to users profile and follow there but I need to change that button when user followed on profile view. thats my issue. – Utku Dalmaz Oct 01 '18 at 13:44
  • let me post my answer – FruitAddict Oct 01 '18 at 13:44
  • @UtkuDalmaz i think the problem was i directly stored and accessed a property of the view controller instead of using protocols. The most recent answer would be the better version of my answer, which in the end does the same thing, but cleaner. – Rakesha Shastri Oct 01 '18 at 14:56

1 Answers1

0

(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.

FruitAddict
  • 2,042
  • 15
  • 16