Search code examples
iosswiftuitableviewuipickerview

NSRangeException while inserting rows with UIPickerView in between two static UITableViewCells


I have a static UItableView with four cells. when I try to insert a row after the third cell using insertRowsAtIndexPath tableView method and add UIPickerView to the added row I got this error: uncaught exception 'NSRangeException', reason: '-[__NSArrayI objectAtIndex:]: index 4 beyond bounds [0 .. 3]'

What I'm trying to achieve is the in-line datePicker in the iOS Calendar app but using a UIPickerView. I want to show a UIPicker / hide a pickerView immediately below the third row it is selected / deselected. I also want to do the same for last row.

NOTE: Adding the row with the pickerView works when I add below the fourth cell but doesn't work when inserting a cell between other cells.

If there is another way to achieve this without first creating the UIPickerView in the Storyboard, I would be glad.

var pickerIsVisible = false

func hidePicker() {
    pickerIsVisible = false
    let indexPathPicker = NSIndexPath(forRow: 3, inSection: 0)
    tableView.beginUpdates()
    tableView.deleteRowsAtIndexPaths([indexPathPicker], withRowAnimation: .Fade)
    tableView.endUpdates()
}

func showPicker() {
    pickerIsVisible = true
    let indexPathPicker = NSIndexPath(forRow: 3, inSection: 0)
    tableView.beginUpdates()
    tableView.insertRowsAtIndexPaths([indexPathPicker], withRowAnimation: .Fade)
    tableView.endUpdates()
}

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    if indexPath.section == 0 && (indexPath.row == 3 && pickerIsVisible) {
        var cell = tableView.dequeueReusableCellWithIdentifier("PickerCell") as UITableViewCell!
        if cell == nil {
            cell = UITableViewCell(style: .Default, reuseIdentifier: "PickerCell")
            cell.selectionStyle = .None
            let picker = UIPickerView(frame: cell.frame)
            picker.tag = 100
            picker.delegate = self
            picker.dataSource = self
            cell.contentView.addSubview(picker)
        }
        return cell
    } else {
        return super.tableView(tableView, cellForRowAtIndexPath: indexPath)
    }
}

override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    if section == 0 && pickerIsVisible {
        return 5
    } else {
        return super.tableView(tableView, numberOfRowsInSection: section)
    }
}

override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    if indexPath.section == 0 && indexPath.row == 2 {
        tableView.deselectRowAtIndexPath(indexPath, animated: false)
        noteTextField.resignFirstResponder()
        pickerIsVisible ? hidePicker() : showPicker()
    }
}

override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    if indexPath.section == 0 && pickerIsVisible && indexPath.row == 3 {
        return 117
    } else {
        return super.tableView(tableView, heightForRowAtIndexPath: indexPath)
    }
}

override func tableView(tableView: UITableView, var indentationLevelForRowAtIndexPath indexPath: NSIndexPath) -> Int {
    if indexPath.section == 0 && indexPath.row == 3 && pickerIsVisible {
        indexPath = NSIndexPath(forRow: 0, inSection: indexPath.section)
    }
    return super.tableView(tableView, indentationLevelForRowAtIndexPath: indexPath)
}

Solution

  • Solved by modifying these methods:

    override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
        if indexPath.section == 0 && pickerIsVisible && indexPath.row == 3 {
            return 117
        }
        if indePath.row == 4 && pickerIsVisisble {
            return 44
        }
        return super.tableView(tableView, heightForRowAtIndexPath: indexPath)
    }
    
    override func tableView(tableView: UITableView, var indentationLevelForRowAtIndexPath indexPath: NSIndexPath) -> Int {
        if indexPath.row == 3 || indexPath.row == 4 && pickerIsVisible {
            indexPath = NSIndexPath(forRow: 0, inSection: indexPath.section)
        }
        return super.tableView(tableView, indentationLevelForRowAtIndexPath: indexPath)
    }
    
    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        if indexPath.section == 0 && (indexPath.row == 3 && pickerIsVisible) {
            let cell = UITableViewCell(style: .Default, reuseIdentifier: nil)
            cell.selectionStyle = .None
            let frame = CGRect(x: 0, y: 0, width: cell.frame.size.width, height: 200)
            let picker = UIPickerView(frame: frame)
            picker.tag = 100
            picker.delegate = self
            picker.dataSource = self
            cell.contentView.addSubview(picker)
            return cell
        }
        if pickerIsVisible && indexPath.row == 4 {
                let cell = tableView.dequeueReusableCellWithIdentifier("Time Cell") as UITableViewCell!
                return cell
        }
        return super.tableView(tableView, cellForRowAtIndexPath: indexPath)
    }