0

I have a swift app based on Master-Detail template. Every row in MasterView table is based on custom cell received from a nib. Every cell includes UIlabel and UIbutton. The logic of the app is following. If user taps on a row DetailView shows some details depending on selected row. The button on the row does not call tableView(_, didSelectRowAtIndexPath). If user taps on the button inside a row only an image belongs to DetailView should be changed (other elements on DetailView remain the same) but it isn't. If I select another row and than select previous row back, changed image is shown on the DetailView as it was foreseen. The question is how to redraw the image in the DetailView just by tapping on the button. I've tried to do following but with no success:

class MasterViewCell: UITableViewCell {
   weak var detailViewController: DetailViewController?

   @IBAction func buttonTap(sender: AnyObject) {
       //method to set new image
       detailViewController!.setNewImage()
       detailViewController!.view.setNeedsDisplay()    
   }
}

class MasterViewController: UITableViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        let nib = UINib(nibName: "itemCell", bundle: nil)
        tableView.registerNib(nib, forCellReuseIdentifier: "Cell")
        if let split = self.splitViewController {
            let controllers = split.viewControllers
            self.detailViewController = (controllers[controllers.count-1] as! UINavigationController).topViewController as? DetailViewController
        }
    }

   override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as? MasterViewCell
        cell?.detailView = self.detailViewController
        return cell!
}

2 Answers2

0

You need to use a handler

typealias ButtonHandler = (Cell) -> Void

class Cell: UITableViewCell {

    var changeImage: ButtonHandler?

    func configureButton(changeImage: ButtonHandler?) {
        self.changeImage = changeImage
    }


    @IBAction func buttonTap(sender: UIButton) {
        changeImage?(self)
    }
}

And in your MasterView

 override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
            let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! Cell

            cell.configureButton(setNewImage())

            return cell
        }

        private func setNewImage() -> ButtonHandler {
            return { [unowned self] cell in
                let row = self.tableView.indexPathForCell(cell)?.row //Get the row that was touched
                //set the new Image
            }
        }

SOURCE: iOS Swift, Update UITableView custom cell label outside of tableview CellForRow using tag

Community
  • 1
  • 1
Leandro P
  • 213
  • 4
  • 12
  • Thanks a lot. It works. But actually I've found another solution, that seems more flexible and could be adapted for other purposes. See my comment below – Grigory Konovalov Jul 11 '16 at 06:21
0

I've found the solution. I've used protocol-delegate mechanism. Now the code is:

//protocol declaration:
protocol MasterViewCellDelegate: class {
  func updateImage(sender: MasterViewCell, detVC: DetailViewController)
}
// cell class
class MasterViewCell: UITableViewCell {
   weak var masterViewCellDelegate: MasterViewCellDelegate?  // protocol property
   weak var masterViewController: MasterViewController? {
     didSet {
        // set delegate
        self.masterViewDelegate = masterViewController!.detailViewController
     }
   }

   @IBAction func buttonTap(sender: AnyObject) {
     var detVC: DetailViewController?
     if let split = masterViewController!.splitViewController {
        let controllers = split.viewControllers
        detVC = (controllers[controllers.count - 1] as! UINavigationController).topViewController as? DetailViewController
   }
   // call delegate 
   masterViewCellDelegate?.updateImage(self, detVC: detVC)
}

class MasterViewController: UITableViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        let nib = UINib(nibName: "itemCell", bundle: nil)
        tableView.registerNib(nib, forCellReuseIdentifier: "Cell")
        if let split = self.splitViewController {
            let controllers = split.viewControllers
            self.detailViewController = (controllers[controllers.count-1] as! UINavigationController).topViewController as? DetailViewController
        }
    }

   override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as? MasterViewCell
        cell?.masterViewController = self
        return cell!
}

// declare detailviewcontroller as delegate 
class DetailViewController: UIViewController, MasterViewCellDelegate {
  func updateImage(sender: MasterViewCell, detVC: DetailViewController){
    detVC.setNewImage()
  }
}

It may well be that this solution is excessively complex, but it works and easy could be adapted for various purposes.

  • While your method is correct, we often don't use delegations for master->detail communication. We use delegations when the detailView wants to **talk back** to the master. – Ray Tso Mar 13 '17 at 02:39
  • @RayTso, I agree. But for this specific problem it seems the best solution. – Grigory Konovalov Mar 13 '17 at 07:12
  • You ***do*** know I'm talking about segues? – Ray Tso Mar 13 '17 at 07:15
  • @RayTso, I thought we were talking about delegations. I start thinking that didn't get you point. – Grigory Konovalov Mar 13 '17 at 07:22
  • For ***master to detail*** communication: segues, for ***detail to master*** communications: delegations. But you don't *have to* do it this way, it's just normally done this way. – Ray Tso Mar 13 '17 at 07:33