Question summary: It crashes when I have a lot of cells in my UITableView
when animating the height of a UITableViewCell
from a UITextView
editing it's text. Using iOS 8 self-sizing-cells.
Long Question: I have successfully implemented so I can dynamically with iOS 8 self-sizing cells enter text into the cells UITextView and change the cells height without losing focus(firstReponder). However, if the tableView is too large (have too many rows) it crashes. Here is my stacktrace:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSArrayM insertObject:atIndex:]: object cannot be nil'
*** First throw call stack:
(
0 CoreFoundation 0x000000010b3b3d85 __exceptionPreprocess + 165
1 libobjc.A.dylib 0x000000010b9cbdeb objc_exception_throw + 48
2 CoreFoundation 0x000000010b274cc5 -[__NSArrayM insertObject:atIndex:] + 901
3 UIKit 0x0000000108b05439 __46-[UITableView _updateWithItems:updateSupport:]_block_invoke1029 + 180
4 UIKit 0x0000000108a7e838 +[UIView(UIViewAnimationWithBlocks) _setupAnimationWithDuration:delay:view:options:factory:animations:start:animationStateGenerator:completion:] + 582
5 UIKit 0x0000000108a7ec6d +[UIView(UIViewAnimationWithBlocks) animateWithDuration:delay:options:animations:completion:] + 105
6 UIKit 0x0000000108b05048 -[UITableView _updateWithItems:updateSupport:] + 4590
7 UIKit 0x0000000108afd5a0 -[UITableView _endCellAnimationsWithContext:] + 15360
8 Test RYM 0x0000000107e6a173 _TFE8Test_RYMCSo11UITableView31reloadDataAnimatedKeepingOffsetfT_T_ + 163
9 Test RYM 0x0000000107e6a242 _TToFE8Test_RYMCSo11UITableView31reloadDataAnimatedKeepingOffsetfT_T_ + 34
10 Test RYM 0x0000000107dcda90 _TFC8Test_RYM20AgendaViewController19cellHeightDidUpdatefTCSo11NSIndexPath6heightV12CoreGraphics7CGFloat_T_ + 144
11 Test RYM 0x0000000107dcdb04 _TToFC8Test_RYM20AgendaViewController19cellHeightDidUpdatefTCSo11NSIndexPath6heightV12CoreGraphics7CGFloat_T_ + 68
12 Test RYM 0x0000000107e725cb _TFC8Test_RYM27AgendaDecisionTableViewCell20updateTextViewHeightfT_T_ + 907
13 Test RYM 0x0000000107e731fa _TFC8Test_RYM27AgendaDecisionTableViewCell17textViewDidChangefCSo10UITextViewT_ + 42
And the code that causes it:
// In UITableView extension
func reloadDataAnimatedKeepingOffset() {
//let offset = contentOffset
//UIView.setAnimationsEnabled(false)
beginUpdates()
endUpdates()
//UIView.setAnimationsEnabled(true)
//layoutIfNeeded()
//contentOffset = offset
}
// In a self-sizing UITableViewCell subclass
func updateTextViewHeight() {
let size = decisionTextView.bounds.size
let newSize = decisionTextView.sizeThatFits(CGSize(width: size.width, height: CGFloat.max))
let newHeight = newSize.height
if size.height != newHeight {
textViewHeightConstraint.constant = newHeight
agendaViewController?.cellHeightDidUpdate(indexPath!, height: newSize.height)
}
}
// In the ViewController managing the tableView
public func cellHeightDidUpdate(indexPath: NSIndexPath, height: CGFloat) {
updateHelperAlphas()
tableView?.reloadDataAnimatedKeepingOffset()
}
It crashes in the call to endUpdates()
. I've tried to remove the tableView:estimatedHeightForRowAtIndexPath:
method mentioned in UITableView insertRowsAtIndexPaths throwing __NSArrayM insertObject:atIndex:'object cannot be nil' error without success.
It also seems to occur only when the list is long.
Edit: More methods I use:
public func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 48.0
}
public func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
if (section == 0) {
return 0
}
else {
let currentSectionIsEmpty = sectionIsEmpty(section)
if ((!isInEditProtocolMode && isProtocolMode) || isPreviousProtocolMode) && currentSectionIsEmpty {
return 0
}
let subSection = sectionHelper.subSectionForSection(section)
let isProtocolTopSection = isProtocolMode && subSection == 0
if (isProtocolTopSection) {
return UITableViewAutomaticDimension
}
else {
return agendaHeaderHeight
}
}
}
public func tableView(tableView: UITableView, estimatedHeightForHeaderInSection section: Int) -> CGFloat {
if (section == 0) {
return 0
}
else {
let subSection = sectionHelper.subSectionForSection(section)
let isProtocolTopSection = isProtocolMode && subSection == 0
if (isProtocolTopSection) {
return protocolAgendaHeaderHeight
}
else {
return agendaHeaderHeight
}
}
}
public override func viewDidLoad() {
super.viewDidLoad()
tableView.rowHeight = UITableViewAutomaticDimension
}
Edit2: This is almost the same problem. tableView crashes on end up with more than 16 items However I cannot remove the estimation of cell height as this breaks my dynamic heights for my self-sizing cellviews.
Edit 3:
Tried (from comments below) to use CATransaction.setDisableActions(_)
and setContentOffset(_:animated:)
without any help. It seems to be not related to this at all as removing all but beginUpdates()
and endUpdates()
does not help either. reloadDataAnimatedKeepingOffset()
seems to be only called once and no other reloadData
seems to be called at the same time. Setting estimated height to 1 instead of 0 does not help either. It weirdly shows the section zero header instead (not height 1).
Edit 4:
On request here are my numberOrRowsInSection
and cellForRowAtIndexPath
methods (the are a bit complex):
public func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
let mainSection = sectionHelper.mainSectionForSection(section)
let subSection = sectionHelper.subSectionForSection(section)
let isHeaderSection = subSection <= 0
if isHeaderSection {
return isProtocolMode ? 0 : cellIdSectionList[0].count
}
let rowType = sectionHelper.rowTypeForSection(section)
let agenda = agendaForSection(section)
switch rowType {
case .NoteRow:
return mainSectionShowingPlaceholderNewNoteCell == mainSection || !agenda.protocolString.isEmpty ? 1 : 0
case .ActionRow:
let count = max(0, agenda.actionListCount())
return count
case .DecisionRow:
var count = agenda.decisions.count ?? 0
count = max(0, count)
count = indexPathShowingPlaceholderNewDecisionCell?.section == section ? count+1 : count
return count
default:
return 0
}
}
public func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let row = indexPath.row
let section = indexPath.section
let cellId = cellIdForIndexPath(indexPath)
let cell = tableView.dequeueReusableCellWithIdentifier(cellId, forIndexPath: indexPath)
let allowEditing = (isInEditProtocolMode || !isProtocolMode) && !isPreviousProtocolMode
if let titleCell = cell as? StandardTitleTableViewCell {
titleCell.setup(agendaForSection(section), meeting:selectedMeeting!, indexPath:indexPath)
titleCell.delegate = self
}
if let descriptionCell = cell as? StandardDescriptionTableViewCell {
descriptionCell.setup(agendaForSection(section), meeting:selectedMeeting!, indexPath: indexPath, forceExpand: hasExpandedDescriptionCellView)
descriptionCell.delegate = self
}
if let noteCell = cell as? AgendaNotesTableViewCell {
noteCell.setup(agendaForSection(section), meeting:selectedMeeting!, indexPath: indexPath, allowEditing:allowEditing)
noteCell.agendaViewController = self
}
if let decisionCell = cell as? AgendaDecisionTableViewCell {
let decisionList = agendaForSection(section).decisions
let isNewDecisionCell = row >= decisionList.count
if !isNewDecisionCell {
let decision = decisionList[row]
decisionCell.setup(decision, meeting:selectedMeeting!, indexPath: indexPath, allowEditing: allowEditing)
}
else {
decisionCell.setup(newDecisionToAdd!, meeting:selectedMeeting!, indexPath: indexPath, allowEditing: allowEditing)
}
decisionCell.agendaViewController = self
}
if let actionCell = cell as? StandardTableViewCell,
let actionList = agendaForSection(section).actionList {
let action = actionList.actions[row]
actionCell.setupAsActionListCell(action:action, indexPath: indexPath, delegate: self)
}
if let textCell = cell as? MeetingTextTableViewCell {
var placeholderText = ""
if indexPathIsActionTextPlaceholderCell(indexPath) {
placeholderText = __("agenda.noActions.text")
}
else if indexPathIsDecisionTextPlaceholderCell(indexPath) {
placeholderText = __("agenda.noDecisions.text")
}
textCell.setup(placeholderText)
}
return cell
}
public func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView?
{
if (section == 0) {
return nil
}
let subSection = sectionHelper.subSectionForSection(section)
let cellId = isProtocolMode && subSection == 0 ? protocolHeaderCellId : headerCellId
let cell = tableView.dequeueReusableHeaderFooterViewWithIdentifier(cellId)
let agenda = agendaForSection(section)
if let standardHeaderCell = cell as? StandardTableViewHeaderCell {
let subSection = sectionHelper.subSectionForSection(section)
let currentSectionIsEmpty = sectionIsEmpty(section)
let protocolIsLocked = selectedMeeting!.protocolIsLocked
if (isPreviousProtocolMode && currentSectionIsEmpty) {
return nil
}
let allowEditing = (isInEditProtocolMode || !isProtocolMode) && !isPreviousProtocolMode && !protocolIsLocked
let showRightAddButton = ((subSection == 1 && currentSectionIsEmpty) || subSection == 2 || (subSection == 3 && indexPathShowingPlaceholderNewDecisionCell?.section != section)) && allowEditing
let headerTitle = headerTitleList[subSection]
standardHeaderCell.setupWithText(headerTitle, section:section, showAddButton: showRightAddButton, delegate: self)
}
else if let protocolHeaderCell = cell as? ProtocolTableViewHeaderCell {
let showSeparator = section > 1
let onlyShowAttachmentIfItHaveAttachments = isPreviousProtocolMode || (isProtocolMode && !isInEditProtocolMode)
let showAttachmentIcon = !onlyShowAttachmentIfItHaveAttachments || agenda.attachments.count > 0
protocolHeaderCell.setup(showSeparator: showSeparator, agenda: agenda, section: section, showProtocolIcon: false, showAttachmentIcon: showAttachmentIcon, delegate: self)
}
return cell!.wrappedInNewView()
}
// In UIView extension
func wrappedInNewView() -> UIView
{
let view = UIView(frame: frame)
autoresizingMask = [.FlexibleHeight, .FlexibleWidth]
view.addSubview(self)
return view
}
Here is radar about this iOS bug: http://openradar.appspot.com/15729686 All what I can suggest you to do is to replace this:
public func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 48.0
}
with this:
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 48.0
If still crashes, then also try to remove tableView:estimatedHeightForHeaderInSection:
method
We should cross our fingers and hope this radar will be closed with new iOS X
:)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With