Reputation: 2457
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
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
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
Reputation: 1120
Delegation pattern is best suited in your case. You need to do several things:
Create protocol:
protocol CheckoutAddressCellActionDelegate {
func didUpdateLayout(forCell cell: UITableViewCell)
}
Add delegate property to CheckoutAddressTableViewCell
:
weak var delegate: CheckoutAddressCellActionDelegate?
Call the delegate method when your layout change trigger is executed (maybe in sameAsButtonIsChecked):
delegate?.didUpdateLayout(forCell: self)
Next in your CheckoutViewController
you need first to set the cell's delegate in cellForRowAt
method:
cell.delegate = self
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