xmartlabs / Eureka

Elegant iOS form builder in Swift
https://eurekacommunity.github.io
MIT License
11.78k stars 1.33k forks source link

Internal inconsistency exception when scrolling #2189

Open rph8 opened 2 years ago

rph8 commented 2 years ago

Hi everyone,

I am using the latest Eureka and seem to run into internal inconsistency exceptions caused by a .cellUpdate. It seems to be related to an issue described on Apple's forums: https://developer.apple.com/forums/thread/92132 (there is no solution documented there). Whenever I scroll the view, the internal inconsistency exception (see stack trace below) is raised, causing a crash. Without scrolling I can also sometimes see the exception in the log, but it somehow does not lead to a crash.

Environment: Eureka 5.3.4, Xcode 13.1 and iOS 15.0.2

So my code looks roughly like this:

retrieveSection()  <<< TextAreaRow() { row in
        row.tag = "someTag"
        row.title = "someTitle"
        row.baseCell.isUserInteractionEnabled = false
        row.hidden = false
        row.value = "someString"

    }.cellUpdate({ (cell, row) in
        cell.textLabel?.textColor = UIColor.blue
        cell.textLabel?.font = someFont

        cell.textView.font = someOtherFont
        cell.textView.textColor = UIColor.green

        cell.tintColor = UIColor.black

        guard let htmlData = htmlString.data(using: String.Encoding.utf8, allowLossyConversion: true) else {
            return nil
        }

        guard let someHTMLAsAttributedString = try? NSAttributedString(data: data,
                                      options: [.documentType: NSAttributedString.DocumentType.html,
                                                .characterEncoding: String.Encoding.utf8.rawValue],
                                                                       documentAttributes: nil) else {
            return nil
        }

        cell.textView.attributedText = someHTMLAsAttributedString

        cell.height = { cell.textView.contentSize.height }

        cell.isUserInteractionEnabled = false
    })

And here is the exception + stack trace that I get:

Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'UITableView internal inconsistency: _visibleRows and _visibleCells must be of same length. _visibleRows: {0, 9}; _visibleCells.count: 20, _visibleCells: (
"<Eureka.TextAreaCell: 0x1071f4800; baseClass = UITableViewCell; frame = (0 35; 359 368); autoresize = W; userInteractionEnabled = NO; tintColor = UIExtendedSRGBColorSpace 0 0 0 1; layer = <CALayer: 0x2821a5a20>>",
"<Eureka.TextAreaCell: 0x10720d800; baseClass = UITableViewCell; frame = (0 403; 359 60); autoresize = W; userInteractionEnabled = NO; tintColor = UIExtendedSRGBColorSpace 0 0 0 1; layer = <CALayer: 0x2821b9e00>>",
"<TtGC6Eureka17AlertSelectorCellSS: 0x107174200; baseClass = UITableViewCell; frame = (0 463; 359 44); text = 'some text'; autoresize = W; tintColor = UIExtendedSRGBColorSpace 0 0 0 1; layer = <CALayer: 0x282152320>>",
"<TtGC6Eureka17AlertSelectorCellSS: 0x107214c00; baseClass = UITableViewCell; frame = (0 507; 359 44); text = 'some text'; autoresize = W; tintColor = UIExtendedSRGBColorSpace 0 0 0 1; layer = <CALayer: 0x282152680>>",
"<TtGC6Eureka11LabelCellOfSS: 0x10720fc00; baseClass = UITableViewCell; frame = (0 551; 359 44); text = 'some text'; autoresize = W; layer = <CALayer: 0x282153940>>",
"<TtGC6Eureka13SegmentedCellSS: 0x1071a7800; baseClass = UITableViewCell; frame = (0 595; 359 44); autoresize = W; tintColor = UIExtendedSRGBColorSpace 0.603922 0.239216 0.215686 1; layer = <CALayer: 0x28215c740>>",
"<TtGC6Eureka11LabelCellOfSS: 0x10c043000; baseClass = UITableViewCell; frame = (0 639; 359 44); text = 'some text'; autoresize = W; layer = <CALayer: 0x282154660>>",
"<Eureka.TextCell: 0x10c248000; baseClass = UITableViewCell; frame = (0 683; 359 44); autoresize = W; tintColor = UIExtendedSRGBColorSpace 0 0 0 1; layer = <CALayer: 0x2821557c0>>",
"<Eureka.TextAreaCell: 0x10c231a00; baseClass = UITableViewCell; frame = (0 727; 359 60); autoresize = W; userInteractionEnabled = NO; tintColor = UIExtendedSRGBColorSpace 0 0 0 1; layer = <CALayer: 0x282156240>>",
"<Eureka.TextAreaCell: 0x1071f4800; baseClass = UITableViewCell; frame = (0 35; 359 368); autoresize = W; userInteractionEnabled = NO; tintColor = UIExtendedSRGBColorSpace 0 0 0 1; layer = <CALayer: 0x2821a5a20>>",
"<Eureka.TextAreaCell: 0x10720d800; baseClass = UITableViewCell; frame = (0 403; 359 60); autoresize = W; userInteractionEnabled = NO; tintColor = UIExtendedSRGBColorSpace 0 0 0 1; layer = <CALayer: 0x2821b9e00>>",
"<TtGC6Eureka17AlertSelectorCellSS: 0x107174200; baseClass = UITableViewCell; frame = (0 463; 359 44); text = 'some text'; autoresize = W; tintColor = UIExtendedSRGBColorSpace 0 0 0 1; layer = <CALayer: 0x282152320>>",
"<TtGC6Eureka17AlertSelectorCellSS: 0x107214c00; baseClass = UITableViewCell; frame = (0 507; 359 44); text = 'some text'; autoresize = W; tintColor = UIExtendedSRGBColorSpace 0 0 0 1; layer = <CALayer: 0x282152680>>",
"<TtGC6Eureka11LabelCellOfSS: 0x10720fc00; baseClass = UITableViewCell; frame = (0 551; 359 44); text = 'some text'; autoresize = W; layer = <CALayer: 0x282153940>>",
"<TtGC6Eureka13SegmentedCellSS: 0x1071a7800; baseClass = UITableViewCell; frame = (0 595; 359 44); autoresize = W; tintColor = UIExtendedSRGBColorSpace 0.603922 0.239216 0.215686 1; layer = <CALayer: 0x28215c740>>",
"<TtGC6Eureka11LabelCellOfSS: 0x10c043000; baseClass = UITableViewCell; frame = (0 639; 359 44); text = 'some text'; autoresize = W; layer = <CALayer: 0x282154660>>",
"<Eureka.TextCell: 0x10c248000; baseClass = UITableViewCell; frame = (0 683; 359 44); autoresize = W; tintColor = UIExtendedSRGBColorSpace 0 0 0 1; layer = <CALayer: 0x2821557c0>>",
"<Eureka.TextAreaCell: 0x10c231a00; baseClass = UITableViewCell; frame = (0 727; 359 60); autoresize = W; userInteractionEnabled = NO; tintColor = UIExtendedSRGBColorSpace 0 0 0 1; layer = <CALayer: 0x282156240>>",
"<Eureka.TextCell: 0x107268a00; baseClass = UITableViewCell; frame = (0 787; 359 5); hidden = YES; autoresize = W; userInteractionEnabled = NO; layer = <CALayer: 0x282144a00>>",
"<Eureka.TextAreaCell: 0x10726aa00; baseClass = UITableViewCell; frame = (0 792; 359 60); hidden = YES; autoresize = W; userInteractionEnabled = NO; tintColor = UIExtendedSRGBColorSpace 0 0 0 1; layer = <CALayer: 0x282147cc0>>"
)'
terminating with uncaught exception of type NSException

Any ideas why this leads to a crash?

mats-claassen commented 2 years ago

Hi, a quick try could be to remove row.baseCell.isUserInteractionEnabled = false from the row initializer and moving it to either cellSetup or cellUpdate. The cell should not be accessed in the row initializer because it will lead to the cell being instantiated at the wrong time.

rph8 commented 2 years ago

Hey @mats-claassen, thanks for the idea. Unfortunately, that did not help. Simply moving that line into the update and not accessing row.baseCell from the row initializer did not solve issue. The exception-triggered crash persists as before. Any other ideas?

rph8 commented 2 years ago

@mats-claassen It seems that patching Row.swift regarding the updateCell either solves or at least masks the problem.

So if we change

override open func updateCell() {
        super.updateCell()
        cell.update()
        customUpdateCell()
        RowDefaults.cellUpdate["\(type(of: self))"]?(cell, self)
        callbackCellUpdate?()
}

into

override open func updateCell() {
        super.updateCell()
        DispatchQueue.main.async { [weak self] in
            guard let self = self else { return }
            self.cell.update()
            self.customUpdateCell()
            RowDefaults.cellUpdate["\(type(of: self))"]?(self.cell, self)
            self.callbackCellUpdate?()
        }
}

So mainly moving the cellUpdate from a that function directly into a subsequent main loop run seems to do the trick. Do you know why? Should this be patched in Eureka?

rph8 commented 2 years ago

Opened a pull request for the proposed fix: #2195

mats-claassen commented 2 years ago

I don't think this is a good way to fix an issue unless there is really no way to fix it from the user side. It seems weird that this is not something happening normally when using cellUpdate and I haven't seen any similar issue reported by someone else.

I also couldn't reproduce that crash. Some new comments regarding your code: Try setting textAreaMode to readOnly instead of setting userInteractionEnabled. You can also experiment with textAreaHeight instead of setting the height directly. If that doesn't change anything then please make sure there is no other part of your code modifying the form while these updates happen.

tonklon commented 4 months ago

We saw the same issue with creating attributes strings inside the cellUpdate block.

We fixed it by creating a new row class for html strings and create the attributed string inside the update method.

Hope this helps someone:

open class HTMLTextCell: _TextAreaCell<String>, CellType {
  public required init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
    super.init(style: style, reuseIdentifier: reuseIdentifier)
  }

  public required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
  }

  override open func update() {
    super.update()

    if let htmlString = row.value {
      let options: [NSAttributedString.DocumentReadingOptionKey: Any] = [
        .documentType: NSAttributedString.DocumentType.html,
        .characterEncoding: String.Encoding.utf8.rawValue,
      ]
      let data = htmlString.data(using: .utf8)!
      let attributedString = try? NSAttributedString(
        data: data,
        options: options,
        documentAttributes: nil
      )

      textView.text = nil
      textView.attributedText = attributedString
    } else {
      textView.text = nil
      textView.attributedText = nil
    }
  }
}

open class _HTMLTextRow: AreaRow<HTMLTextCell> {
  public required init(tag: String?) {
    super.init(tag: tag)
  }
}

public final class HTMLTextRow: _HTMLTextRow, RowType {
  public required init(tag: String?) {
    super.init(tag: tag)
  }
}