Trevor
Trevor

Reputation: 2457

Watch a cell's variable from view controller

I have a table view inside of a view controller. When you click on a view in the cell, it either hides or show another view and changes the height of the cell.

I need to call a method on the table view to reload this cell. Or I need to watch this cell from my view controller.

Here is where I'm at on my code. Any help or suggestions are greatly appreciated. If my question is unclear, please let me know and I'll try to go into more detail

Cell

class CheckoutAddressTableViewCell: UITableViewCell {

    let addressFieldsHeight:CGFloat = 330

    var initialized = false;
    var sameAsButtonIsChecked:Bool = false {
        didSet {
            if sameAsButtonIsChecked  {
                sameAsCheckboxImageView.image = #imageLiteral(resourceName: "iconCheckboxChecked")
                addressFieldsView.isHidden = true
                addressFieldsHeightConstraint.constant = 0
            }else{
                sameAsCheckboxImageView.image = #imageLiteral(resourceName: "iconCheckbox")
                addressFieldsView.isHidden = false
                addressFieldsHeightConstraint.constant = addressFieldsHeight
            }

        }
    }
}

View Controller

import UIKit

class CheckoutViewController: UIViewController {

    let cellSpacingHeight:CGFloat = 30.0

    @IBOutlet weak var tableView: UITableView!

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        tableView.delegate = self
        tableView.dataSource = self
        tableView.separatorStyle = .none
        tableView.allowsSelection = false
        tableView.keyboardDismissMode = .onDrag

        NotificationCenter.default.addObserver(self, selector: #selector(CheckoutViewController.keyboardWillShow(notification:)), name: NSNotification.Name.UIKeyboardDidShow, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(CheckoutViewController.keyboardWillHide(notification:)), name: NSNotification.Name.UIKeyboardDidHide, object: nil)

    }

    deinit {
        NotificationCenter.default.removeObserver(self)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    // MARK: Keyboard Notifications
    @objc func keyboardWillShow(notification: NSNotification) {
        if let keyboardHeight = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue.height {
            tableView.contentInset = UIEdgeInsetsMake(0, 0, keyboardHeight, 0)
        }
    }

    @objc func keyboardWillHide(notification: NSNotification) {
        UIView.animate(withDuration: 0.2, animations: {
            // For some reason adding inset in keyboardWillShow is animated by itself but removing is not, that's why we have to use animateWithDuration here
            self.tableView.contentInset = UIEdgeInsetsMake(0, 0, 0, 0)
        })
    }



}

// datasource
extension CheckoutViewController: UITableViewDataSource{

    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 1
    }

    // Set the spacing between sections
    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        return cellSpacingHeight
    }

    // Make the background color show through
    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        let headerView = UIView()
        headerView.backgroundColor = UIColor.clear
        return headerView
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {


            let cellID = "CheckoutAddressTableViewCellReuseID"
            let cell = tableView.dequeueReusableCell(withIdentifier: cellID) as! CheckoutAddressTableViewCell

            cell.setUp(setUpCase: .billing)
            cell.headerLabel.text = "BILLING INFORMATION"

//            watch cell.sameAsButtonIsChecked

            return cell


        return UITableViewCell()
    }


}

// delegate
extension CheckoutViewController:UITableViewDelegate {

}

Upvotes: 0

Views: 160

Answers (3)

inokey
inokey

Reputation: 6170

While using delegates is ok solution for your case I'd suggest using a closure on a cell. Now your cell related logic is at the same place where you actually create cell, which gives extra visibility compared to Delegation pattern.

You can add the following into your cell class

typealias CellAction: () -> Void
var onMyAction: CellAction?

This could be any function that should trigger the action

func yourFuncThatTriggersTheAction() {
     onMyAction?()
}

Now in your cellForRowAt

cell.onMyAction = {
     //do whatever necessary when action is invoked
}

Note that having this method in cellForRowAt also solves a problem of getting the indexPath of a cell that triggering some action.

Upvotes: 2

Ladislav
Ladislav

Reputation: 7283

You can either create a protocol and set your UIViewController to be the delegate for each cell, something like:

@objc protocol YourCellProtocol {
    cell(_ cell: CheckoutAddressTableViewCell, didChangeState state: Bool)
}

You add a delegate property to your UITableViewCell subclass and call the delegate when state changes

class CheckoutAddressTableViewCell: UITableViewCell {

    weak var delegate: YourCellProtocol?

    let addressFieldsHeight:CGFloat = 330
    var initialized = false;
    var sameAsButtonIsChecked:Bool = false {
        didSet {
            //call the delegate with state change
            delegate?.cell(self, didChangeState: sameAsButtonIsChecked)

            if sameAsButtonIsChecked  {
                sameAsCheckboxImageView.image = #imageLiteral(resourceName: "iconCheckboxChecked")
                addressFieldsView.isHidden = true
                addressFieldsHeightConstraint.constant = 0
            }else{
                sameAsCheckboxImageView.image = #imageLiteral(resourceName: "iconCheckbox")
                addressFieldsView.isHidden = false
                addressFieldsHeightConstraint.constant = addressFieldsHeight
            }
        }
    }
}

In your UIViewController you conform to YourCellProtocol and you implement it

func cell(_ cell: CheckoutAddressTableViewCell, didChangeState state: Bool) {
    //state changed for a cell
}

And in cellForRowAt: you set your UIViewController as the delegate:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    ...
    cell.delegate = self
    ...   
}

Or you can go NotificationCenter route where you post notificaiton when cell state changes with

let notificatioName = Notification.Name("CellStateChanged")
NotificationCenter.default.post(name: notificatioName, object: nil, userInfo: ["state": sameAsButtonIsChecked])

Then in your UIViewController you observe for this notification

let notificatioName = Notification.Name("CellStateChanged")
NotificationCenter.default.addObserver(self, selector: #selector(cellDidChangeState(_:)), name: notificatioName, object: nil)

@objc func cellDidChangeState(_ notification: Notification) {
    if let userData = notification.userInfo, let state = userData["state"] {
        //you have cells state here
    }
}

Upvotes: 1

dvp.petrov
dvp.petrov

Reputation: 1120

Delegation pattern is best suited in your case. You need to do several things:

  1. Create protocol:

    protocol CheckoutAddressCellActionDelegate {
        func didUpdateLayout(forCell cell: UITableViewCell)
    }
    
  2. Add delegate property to CheckoutAddressTableViewCell:

    weak var delegate: CheckoutAddressCellActionDelegate?
    
  3. Call the delegate method when your layout change trigger is executed (maybe in sameAsButtonIsChecked):

    delegate?.didUpdateLayout(forCell: self)
    
  4. Next in your CheckoutViewController you need first to set the cell's delegate in cellForRowAt method:

    cell.delegate = self
    
  5. And lastly add extension to the CheckoutViewController that implements the CheckoutAddressCellActionDelegate and reloads the indexPath for the cell:

    extension CheckoutViewController: CheckoutAddressCellActionDelegate {
        func didUpdateLayout(forCell cell: UITableViewCell) {
            if let indexPath = tableView.indexPath(for: cell) {
                tableView.reloadRows(at: [indexPath], with: .automatic)
            }
        }
    }
    

Upvotes: 1

Related Questions