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 }
1 Answers
Answers 1
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
:)
0 comments:
Post a Comment