xmartlabs / XLPagerTabStrip

Android PagerTabStrip for iOS.
MIT License
6.97k stars 1.33k forks source link

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

Open always19 opened 7 years ago

always19 commented 7 years ago

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.
gali8 commented 7 years ago

+1

SValchyshyn commented 6 years ago

+1

chayakornS commented 6 years ago

+1

mcinside13 commented 6 years ago

I have the same problem, and my version (8.0.0) have the 7ff9ed7 changes :(

danielstorch commented 6 years ago

+1

dmead28 commented 6 years ago

+1

kevinmun commented 6 years ago

+1

nickbit commented 6 years ago

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()
    }
}
Appswage commented 6 years ago

Thanks. @nickbit! This took many hours out of my day until I came upon this. Works great!

aalenliang commented 5 years ago

emm... when using @nickbit 's solution, i crashed in another place:

if !indexPathsToReload.isEmpty {
    buttonBarView.reloadItems(at: indexPathsToReload)
}

with the log

Project(74688,0x700007fb1000) malloc: Heap corruption detected, free list is damaged at 0x600001f48000
*** Incorrect guard value: 105553124933760
Project(74688,0x10e4235c0) malloc: Heap corruption detected, free list is damaged at 0x600001f48020
*** Incorrect guard value: 5134468915308744792
Project(74688,0x700007fb1000) malloc: *** set a breakpoint in malloc_error_break to debug
Project(74688,0x10e4235c0) malloc: *** set a breakpoint in malloc_error_break to debug
dasSoumen commented 5 years ago

@aalenliang I am also getting the same crash. Any update?

daoseng33 commented 5 years ago

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

ShoaibPathan commented 5 years ago

getting the same error Heap corruption detected, free list is damaged at reloadItems. if !indexPathsToReload.isEmpty { buttonBarView.reloadItems(at: indexPathsToReload) }

ShoaibPathan commented 5 years ago

@aalenliang have u fixed it the issue heap corruption

aalenliang commented 5 years ago

@dasSoumen @ShoaibPathan Sorry for the late reply, i used george-lin/XLPagerTabStrip instead.

kevintorch commented 5 years ago

I'm still getting this error... I updated this library today with cocoa pods...any help?

PrashantAhar commented 5 years ago

Still getting this crash , Has somebody fixed this. Need help in this

dhruvalpatel-tamrd commented 4 years ago

For me, it wasn't crashing if I call reloadPagerTabStripView() from the switch of the same page in a different container. but if I call the same method just after page open then It was crashing. Here is the workaround I found to solve this issue temporary:: Just add the delay of 3 seconds.

extension DispatchQueue {

static func background(delay: Double = 0.0, background: (()->Void)? = nil, completion: (() -> Void)? = nil) {
    DispatchQueue.global(qos: .background).async {
        background?()
        if let completion = completion {
            DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: {
                completion()
            })
        }
    }
}

}

USAGE---> SVProgressHUD.show() DispatchQueue.background(delay: 3.0, completion:{ // do something in main thread after 3 seconds reloadPagerTabStripView() SVProgressHUD.dismiss() })

rromanchuk commented 4 years ago

this project needs to fork

minhmera commented 4 years ago

updated on 24 Aug 2020 Still get error at : buttonBarView.reloadItems(at: indexPathsToReload)

any solution guys ?

hechukwu commented 4 years ago

Is there any solution for this? Will appreciate it

hechukwu commented 4 years ago

@nickbit I get 'cachedCellWidths' is inaccessible due to 'private' protection level

hechukwu commented 4 years ago

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