Search code examples
iosswift3uipopovercontrollercustom-cell

UITableView popover with custom cell fails with cell attributes always nil


I'm trying to present a UITableView popover with custom cells but the UILabel outlets in my cell are always nil. I used the storyboard to create the popover tableview and the custom cell. Everything seems connected correctly and my custom cell has the "Cell" identifier. The popover shows correctly, it's just that the labels in the custom cells won't set because they are nil.

I have tried presenting the popover using the Apple method (see the buttonPressed func below) and the segue method suggested in UITableViewCell's attributes are nil when instantiating UITableView as a popover but neither fixed my problem.

My custom UITableViewCell class has two connected labels and one unconnected int. All storyboard connections seem valid:

class PopoverTableCell : UITableViewCell {
   @IBOutlet var label1: UILabel!   // <== connected in storyboard
   @IBOutlet var label2: UILabel!   // <== connected in storyboard

   var int3 : Int? = nil
}

Here's how I present it (Apple's method) from the view controller (via button push):

class ViewController: UIViewController {

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

   @IBAction func buttonPressed(_ sender: UIButton) {
      let popoverController = PopoverController()
      popoverController.modalPresentationStyle = UIModalPresentationStyle.popover
      popoverController.preferredContentSize = CGSize(width: 200, height: 300)

      present(popoverController, animated: true, completion: nil)

      let popoverPresentationController = popoverController.popoverPresentationController
      popoverPresentationController?.sourceView = sender
      popoverPresentationController?.sourceRect = CGRect(x: 0, y: 0, width: sender.frame.size.width, height: sender.frame.size.height)

   }

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

}

That successfully brings up the popover but when I try to setup the labels in the cell, it fails because the labels are always nil.

class PopoverController : UITableViewController {

   override func viewDidLoad() {
      super.viewDidLoad()

      // it makes no difference if I set these or not - storyboard does it for me
      self.tableView.delegate = self
      self.tableView.dataSource = self

      // if I don't register, the cell fails to deque
      self.tableView.register(PopoverTableCell.self, forCellReuseIdentifier: "Cell")
   }

   override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
      let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! PopoverTableCell
      cell.backgroundColor = UIColor.yellow
      cell.label1?.text = "Label 1111"
      cell.label2?.text = "Label 2222"
      cell.int3 = 5555
      print("label1: \(cell.label1)")     // <== always nil
      print("label2: \(cell.label2)")     // <== always nil
      print("label3: \(cell.int3)")       // <== always Optional(5555) which is correct
      return cell
   }

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

}

I put a breakpoint on the return statement of the cellForRowAt fund, I get this in the debugger:

Debugger Image

No matter what I do, the label1 and label2 outlets are always nil but the int3 (not an outlet) seems to work just fine.


Solution

  • I think the issue described in this comment:

      // if I don't register, the cell fails to deque
    

    and the nil outlets are a symptom of the same issue.

    If you create your outlets in the storyboard, they won't initialize properly through the default init() method, which is what you're using when you create the view controller using PopoverController().

    Try giving your view controller an identifier in the storyboard:

    view controller with storyboard id

    And then instantiate it like this:

      let popoverController = storyboard?.instantiateViewController(withIdentifier: "MyViewController") as! PopoverController
    

    In this case, the cells should pull from the storyboard (to be precise, it runs through the init?(coder aDecoder: NSCoder) method rather than init()) so their outlets will not be nil. You also should not need to register the cell if you instantiate the view controller this way.