简体   繁体   中英

UITableView Crashes When numberOfRowsInSection is Greater Than 1

I can't seem to get my custom UITableView class to work properly as an inputView to a UITextField that exists within another UITableView.

Please refer to screen shot below. I have MainTableViewController that contains two sections: Section-1 and Section-2. Each section contains just one cell. Each cell has a UITextField. Section-1 is the area of concern. It contains a UITextField with a custom UITableView as its inputView. I named this custom UITableView InputTableView and implemented the UITableViewDataSource protocol.

class InputTableView: UITableView, UITableViewDataSource

The goal is to have the InputTableView act like an option picker. The user would click a cell within InputTableView and that selection will populate specialTextField .

When I click the cell, the InputView appears as expected. Test Title is the UITableView section title. Section 0 Row 0 is the cell content.

UITableViewController 图片

This is the numberOfRowsInSection in the InputTableView class:

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

If I return anything greater than 1, the app will crash with an error like this:

TableViewControllerTest[721:126633] * Terminating app due to uncaught exception 'NSRangeException', reason: '* -[__NSSingleObjectArrayI objectAtIndex:]: index 1 beyond bounds [0 .. 0]' *** First throw call stack: (0x18db211b8 0x18c55855c 0x18db12420 0x19417c7bc 0x193ee7a40 0x193d1f9d4 0x193af5c20 0x193ab5478 0x193ab48c8 0x193ab4654 0x193ab4488 0x193d12e74 0x193d125a4 0x193d124a0 0x193a54550 0x193d35120 0x193d34aac 0x193a5399c 0x193a52fcc 0x1939d3090 0x1939f6c58 0x1939d22c4 0x18e57ad10 0x1939d2138 0x1939de018 0x1939dd904 0x1943b2298 0x1943b37f8 0x1943ac3c8 0x1943b37c8 0x1943a8488 0x1943b331c 0x1943abe38 0x193a9d240 0x1a1d15e98 0x1939fca78 0x193a5ab4c 0x193a5aebc 0x193add0b4 0x193b84128 0x193b83630 0x193f9ef80 0x193fa2688 0x193b6973c 0x193a080f0 0x193f92680 0x193f921e0 0x193f9149c 0x193a0630c 0x1939d6da0 0x1a1cb21e8 0x1941c075c 0x1941ba130 0x18daceb5c 0x18dace4a4 0x18dacc0a4 0x18d9fa2b8 0x18f4ae198 0x193a417fc 0x193a3c534 0x1000191f4 0x18c9dd5b8) libc++abi.dylib: terminating with uncaugh t exception of type NSException (lldb)

1) If I add another row to MainTableViewController (add 1 row to Section-1 for a total of 2 rows) then the numberOfRowsInSection can return 2 and run just fine. If I add another row for a total of 3 rows, then the function can return 3 and run just fine. Anything greater and it crashes again. I'm thinking that these are somehow related. However, how can that be if the two items aren't even in the same class?

2) If I comment out the line inputTableView.delegate = self then the code again runs without crashing. I can set numberOfRowsInSection to return any number (I tried 5 and it works). However, because the delegate is now not set, I cannot do anything with the user interaction.

My Full Code:

//
//  MainTableViewController.swift
//  TableViewControllerTest
//
//  Created by Zion Perez on 1/29/17.
//  Copyright © 2017 Zion Perez. All rights reserved.
//

import UIKit

class InputTableView: UITableView, UITableViewDataSource {

    func setupTableView(){
        self.dataSource = self
    }

    // MARK: - TableView DataSource
    // https://developer.apple.com/reference/uikit/uitableviewdatasource
    func numberOfSections(in tableView: UITableView) -> Int {
        print("numberOfSections")
        return 1
    }

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

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        print("tableView cellForRowAt: " + indexPath.description)

        let id = "BasicCell"
        var cell: UITableViewCell? = tableView.dequeueReusableCell(withIdentifier: id)
        if cell == nil {
            cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: id)
        }
        cell?.textLabel?.text = "Section \(indexPath.section) Row \(indexPath.row)"
        return cell!
    }

    func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        print("tableView titleForHeaderInSection")
        return "Test Title"
    }
}

class MainTableViewController: UITableViewController {

    @IBOutlet weak var specialTextField: UITextField!

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        let frame = CGRect(x: self.view.frame.minX, y: self.view.frame.minY, width: self.view.frame.width, height: 200.0)
        let inputTableView = InputTableView(frame: frame)
        inputTableView.setupTableView()
        inputTableView.delegate = self
        specialTextField.inputView = inputTableView
    }

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

    // MARK: - TableViewDelegate
    // https://developer.apple.com/reference/uikit/uitableviewdelegate
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        print("selected row at " + indexPath.description)
    }

    override func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
        print("deselected row at " + indexPath.description)
    }
}

My code is also here on Github: https://github.com/starkindustries/TableViewControllerTest

Other sources that I've researched: UITableView crashes when number of rows >1

Edit (Solved Notes):

Muescha's answer helped me solve my problem. For reference, the fixed version of the code can be found at this github link below (delegateRefactor branch). The original code with the issue is at the github link above (master branch).

https://github.com/starkindustries/TableViewControllerTest/tree/delegateRefactor

you need to put also an UITableViewDelegate to your InputTableView .

why? since you have it before in the ViewController (your should name it TableViewController or MainTableViewController so that someone else have a hint thats not a normal view controller).

but you have drawn in the Interface Builder with 2 Sections and with 1 Cell per Section. this cells are populated from a hidden UITableViewDataSource and a hidden UITableViewDelegate . if i click in the table view and see the outgoing connections in interface builder but no class file there.

i think when you have the UITableViewDelegate of InputTableView is set to MainTableViewController , then they crash on other requests. in the crash stacktrace there was also a heightForRowAt call one of the last calls (thats why: than more error and stacktrace you post to your question: it is better to finde the error - very good that you post your example project to github. otherwise the cell settings in the main storyboard would be hidden for the reviewers)

but you have only one cell on the Main Table in section one. thats why it crashed with 2 cells. it try to get the height for cell 2 but there are only 0...0 index that means = 1 cell.

the hard point was, that the UITableViewDataSource and the UITableViewDelegate of MainTableViewController are hidden somewhere and autowired some how. (thats why i personally don't like interface builder and storyboard and do all by code)

here the proof Screenshot:

在此处输入图片说明

Code changes:

  • self.delegate = self moved to InputTableView

  • protocol UITableViewDelegate added to InputTableView

here is the code:

import UIKit

class InputTableView: UITableView, UITableViewDataSource, UITableViewDelegate {

    func setupTableView(){
        self.dataSource = self
        self.delegate = self
    }

    // MARK: - TableView DataSource
    // https://developer.apple.com/reference/uikit/uitableviewdatasource
    func numberOfSections(in tableView: UITableView) -> Int {
        print("numberOfSections")
        return 1
    }

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

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        print("tableView cellForRowAt: " + indexPath.description)

        let id = "BasicCell"
        var cell: UITableViewCell? = tableView.dequeueReusableCell(withIdentifier: id)
        if cell == nil {
            cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: id)
        }
        cell?.textLabel?.text = "Section \(indexPath.section) Row \(indexPath.row)"
        return cell!
    }

    func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        print("tableView titleForHeaderInSection")
        return "Test Title"
    }
}

class ViewController: UITableViewController {

    @IBOutlet weak var specialTextField: UITextField!

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        let frame = CGRect(x: self.view.frame.minX, y: self.view.frame.minY, width: self.view.frame.width, height: 200.0)
        let inputTableView = InputTableView(frame: frame)
        inputTableView.setupTableView()
        specialTextField.inputView = inputTableView
    }

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

    // MARK: - TableViewDelegate
    // https://developer.apple.com/reference/uikit/uitableviewdelegate
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        print("selected row at " + indexPath.description)
    }

    override func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
        print("deselected row at " + indexPath.description)
    }
}

Maybe the problem is because of the UITableViewController subclass.. It requires only one table view. Just assign the delegate of the input view to it's self and then use a custom delegate to send the selected index or the needed information back to the table view controller.

I think you should deal with those dataSource methods for the InputTableView directly in your ViewController.

import UIKit

class InputTableView: UITableView, UITableViewDataSource {

    func setupTableView(){
        self.dataSource = self
    }

    // MARK: - TableView DataSource
    // https://developer.apple.com/reference/uikit/uitableviewdatasource  
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        print("tableView cellForRowAt: " + indexPath.description)

        let id = "BasicCell"
        var cell: UITableViewCell? = tableView.dequeueReusableCell(withIdentifier: id)
        if cell == nil {
            cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: id)
        }
        cell?.textLabel?.text = "Section \(indexPath.section) Row \(indexPath.row)"
        return cell!
    }

    func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        print("tableView titleForHeaderInSection")
        return "Test Title"
    }
}

class ViewController: UITableViewController {

    @IBOutlet weak var specialTextField: UITextField!
    var inputTableView: InputTableView!

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        let frame = CGRect(x: self.view.frame.minX, y: self.view.frame.minY, width: self.view.frame.width, height: 200.0)
        inputTableView = InputTableView(frame: frame)
        inputTableView.setupTableView()
        inputTableView.delegate = self
        specialTextField.inputView = inputTableView
    }

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

    // MARK: - TableViewDelegate
    // https://developer.apple.com/reference/uikit/uitableviewdelegate
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        print("selected row at " + indexPath.description)
    }

    override func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
        print("deselected row at " + indexPath.description)
    }

    func numberOfSections(in tableView: UITableView) -> Int {
        if tableView == inputTableView {
           print("numberOfSections")
           return 1
        } else {
           deal with your other tableView
        }
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        if tableView == inputTableView {
           print("numberOfRowsInSection ")
           return 1
        } else {
           deal with your other tableView
        }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM