XLPagerTabStrip: Crash by Index out of range because cachedCellWidths is not synched to viewControllers

Phenomenon

Crash by Index out of range because cachedCellWidths is not synched to viewControllers

image

fatal error: Index out of range
(lldb) po cachedCellWidths
▿ Optional<Array<CGFloat>>
  ▿ some : 6 elements
    - 0 : 93.0
    - 1 : 70.5
    - 2 : 94.5
    - 3 : 102.5
    - 4 : 165.5
    - 5 : 151.5
(lldb) po indexPath
▿ 2 elements
  - 0 : 0
  - 1 : 6
(lldb) po viewControllers.count
8

Analysis

  1.  self?.reloadPagerTabStripView() // I called reloadPagerTabStripView from my ButtonBarPagerTabStripViewController subclass
    
  2.  open override func reloadPagerTabStripView() {
         super.reloadPagerTabStripView()  // super.reloadPagerTabStripView() is called before cachedCellWidths is set. So, cachedCellWidths.count is different from viewControllers.count
         ...
         cachedCellWidths = calculateWidths() 
    
  3.  // Eventually, following is called first.
     open func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: IndexPath) -> CGSize {
         guard let cellWidthValue = cachedCellWidths?[indexPath.row] else { // So, it crashes here.
    

call stack

#3	0x00000001106ddf4b in ButtonBarPagerTabStripViewController.collectionView(UICollectionView, layout : UICollectionViewLayout, sizeForItemAtIndexPath : IndexPath) -> CGSize
#4	0x00000001106de24c in @objc ButtonBarPagerTabStripViewController.collectionView(UICollectionView, layout : UICollectionViewLayout, sizeForItemAtIndexPath : IndexPath) -> CGSize ()
#5	0x00000001130bfa82 in -[UICollectionViewFlowLayout _getSizingInfosWithExistingSizingDictionary:] ()
#6	0x00000001130c1114 in -[UICollectionViewFlowLayout _fetchItemsInfoForRect:] ()
#7	0x00000001130ba07e in -[UICollectionViewFlowLayout prepareLayout] ()
#8	0x00000001130da215 in -[UICollectionViewData _prepareToLoadData] ()
#9	0x00000001130919c0 in -[UICollectionView _endItemAnimationsWithInvalidationContext:tentativelyForReordering:animator:] ()
#10	0x000000011308e75a in -[UICollectionView _updateRowsAtIndexPaths:updateAction:] ()
#11	0x00000001106dd7b7 in ButtonBarPagerTabStripViewController.cellForItems(at : [IndexPath], reloadIfNotVisible : Bool)
#12	0x00000001106dcfc4 in ButtonBarPagerTabStripViewController.updateIndicator(for : PagerTabStripViewController, fromIndex : Int, toIndex : Int, withProgressPercentage : CGFloat, indexWasChanged : Bool)
#13	0x000000010ea12122 in XXXPagerTabStripController.updateIndicator(for : PagerTabStripViewController, fromIndex : Int, toIndex : Int, withProgressPercentage : CGFloat, indexWasChanged : Bool)
#14	0x00000001106e22a1 in protocol witness for PagerTabStripIsProgressiveDelegate.updateIndicator(for : PagerTabStripViewController, fromIndex : Int, toIndex : Int, withProgressPercentage : CGFloat, indexWasChanged : Bool) -> () in conformance ButtonBarPagerTabStripViewController ()
#15	0x00000001106f40b4 in PagerTabStripViewController.updateContent()
#16	0x00000001106f4c74 in PagerTabStripViewController.reloadPagerTabStripView()
#17	0x00000001106dbdf9 in ButtonBarPagerTabStripViewController.reloadPagerTabStripView() 

Environment

  1. I’m using 7ff9ed755462fe5e4f891868bc7eafd03f8c4355 close to HEAD in master. I believe this problem is not fixed yet. I checked later commit logs.
  2. It doesn’t happen every time. I don’t know how I can reproduce this problem yet.

About this issue

  • Original URL
  • State: open
  • Created 7 years ago
  • Comments: 23 (1 by maintainers)

Commits related to this issue

Most upvoted comments

The crash happens when the new viewControllers.count is greater that the existing viewControllers.count. The problem is that PagerTabStripViewController which is the superclass of ButtonBarPagerTabStripViewController, calls updateContent() before the new cachedCellWidths are calculated - the calculation is done in ButtonBarPagerTabStripViewController.reloadPagerTabStripView() AFTER super.reloadPagerTabStripView() is called

Here is a fix for the crash, which postpones the call to updateContent() after everything is in place:

File: ButtonBarPagerTabStripViewController.swift

add a variable:

private var shouldUpdateContent = true

replace reloadPagerTabStripView() with:

open override func reloadPagerTabStripView() {
    shouldUpdateContent = false
    super.reloadPagerTabStripView()
    shouldUpdateContent = true
        
    guard isViewLoaded else { return }
    buttonBarView.reloadData()
    cachedCellWidths = calculateWidths()
    updateContent()
    buttonBarView.moveTo(index: currentIndex, animated: false, swipeDirection: .none, pagerScroll: .yes)
}

and add this function:

open override func updateContent() {
    if shouldUpdateContent {
        super.updateContent()
    }
}

+1

@dasSoumen Have you tried call self.buttonBarView.layoutIfNeeded() before self.reloadPagerTabStripView()?

@minhmera try adding self.buttonBarView.layoutIfNeeded() before updateContent() with @nickbit answer. It seems to resolve the issue