Programming

UICollectionView reloadData가 iOS 7에서 제대로 작동하지 않음

procodes 2020. 8. 29. 21:41
반응형

UICollectionView reloadData가 iOS 7에서 제대로 작동하지 않음


대부분 원활하게 진행되는 iOS 7에서 실행되도록 앱을 업데이트했습니다. 나는 하나 이상의 앱 reloadData에서 a의 UICollectionViewController방법이 예전처럼 작동하지 않는다는 것을 발견했습니다 .

을로드 UICollectionViewController하고을 UICollectionView정상적으로 일부 데이터로 채 웁니다 . 이것은 처음에 훌륭하게 작동합니다. 그러나 새 데이터를 요청하고 (를 채운 다음 UICollectionViewDataSource)를 호출 reloadData하면 데이터 소스에서 numberOfItemsInSectionand를 쿼리 numberOfSectionsInCollectionView하지만 cellForItemAtIndexPath적절한 횟수 를 호출하지 않는 것 같습니다 .

한 섹션 만 다시로드하도록 코드를 변경하면 제대로 작동합니다. 이것은 내가 이것을 바꾸는 데 문제가되지 않지만 내가해야한다고 생각하지 않는다. reloadData문서에 따라 보이는 모든 셀을 다시로드해야합니다.

다른 사람이 본 적이 있습니까?


메인 스레드에서 강제로 :

dispatch_async(dispatch_get_main_queue(), ^ {
    [self.collectionView reloadData];
});

제 경우에는 데이터 소스의 셀 / 섹션 수가 변경되지 않았으며 화면에 보이는 콘텐츠를 다시로드하고 싶었습니다.

나는 이것을 전화로 해결할 수 있었다.

[self.collectionView reloadItemsAtIndexPaths:[self.collectionView indexPathsForVisibleItems]];

그때:

[self.collectionView reloadData];

나는 똑같은 문제가 있었지만 무엇이 잘못되고 있는지 알 수있었습니다. 제 경우에는 정확하지 않은 collectionView : cellForItemAtIndexPath : 에서 reloadData호출 했습니다 .

reloadData의 호출을 메인 큐로 보내면 문제가 영원히 해결되었습니다.

  dispatch_async(dispatch_get_main_queue(), ^{
    [self.collectionView reloadData];
  });

일부 항목을 다시로드 할 수 없었습니다. 제 경우에는 사용중인 collectionView에 섹션이 하나만 있기 때문에 특정 섹션을 다시로드하기 만하면됩니다. 이번에는 콘텐츠가 올바르게 다시로드됩니다. 이것이 iOS 7 (7.0.3)에서만 발생하는 것이 이상합니다.

[self.collectionView reloadSections:[NSIndexSet indexSetWithIndex:0]];

스위프트 5 – 4 – 3

// GCD    
DispatchQueue.main.async(execute: collectionView.reloadData)

// Operation
OperationQueue.main.addOperation(collectionView.reloadData)

스위프트 2

// Operation
NSOperationQueue.mainQueue().addOperationWithBlock(collectionView.reloadData)

iOS 7에서 reloadData와 동일한 문제가 발생했습니다. 긴 디버그 세션 후 문제를 발견했습니다.

iOS7에서 UICollectionView의 reloadData는 아직 완료되지 않은 이전 업데이트를 취소하지 않습니다 (performBatchUpdates : 블록 내부에서 호출 한 업데이트).

이 버그를 해결하는 가장 좋은 방법은 현재 처리되고있는 모든 업데이트를 중지하고 reloadData를 호출하는 것입니다. performBatchUpdates 블록을 취소하거나 중지하는 방법을 찾지 못했습니다. 따라서 버그를 해결하기 위해 현재 처리중인 performBatchUpdates 블록이 있는지 여부를 나타내는 플래그를 저장했습니다. 현재 처리중인 업데이트 블록이 없으면 즉시 reloadData를 호출 할 수 있으며 모든 것이 예상대로 작동합니다. 현재 처리중인 업데이트 블록이있는 경우 performBatchUpdates의 전체 블록에서 reloadData를 호출합니다.


나도이 문제가 있었다. 우연히 테스트를 위해 강제로 다시로드하기 위해 collectionview 위에 버튼을 추가했고 갑자기 메서드가 호출되기 시작했습니다.

또한 간단한 것을 추가하면

UIView *aView = [UIView new];
[collectionView addSubView:aView];

메서드가 호출되게합니다.

또한 프레임 크기를 가지고 놀았고 메서드가 호출되었습니다.

iOS7 UICollectionView에는 많은 버그가 있습니다.


이 방법을 사용할 수 있습니다

[collectionView reloadItemsAtIndexPaths:arayOfAllIndexPaths];

아래 방법을 사용하여 모든 섹션과 행에 대해 루프를 반복하여 배열 모든 indexPath객체를 추가 할 수 있습니다.UICollectionViewarrayOfAllIndexPaths

[aray addObject:[NSIndexPath indexPathForItem:j inSection:i]];

이해하고 문제를 해결할 수 있기를 바랍니다. 더 많은 설명이 필요하면 회신 해주십시오.


Shaunti Fondrisi가 제공 한 솔루션은 거의 완벽합니다. 그러나 UICollectionView's reloadData()to NSOperationQueue'의 실행을 대기열에 mainQueue넣는 것과 같은 코드 또는 코드는 실제로 실행 타이밍을 실행 루프의 다음 이벤트 루프의 시작에 놓아 UICollectionView가볍게 업데이트 할 수 있습니다.

이 문제를 해결하기 위해. 동일한 코드의 실행 타이밍을 현재 이벤트 루프의 끝에 배치해야하지만 다음 루프의 시작 부분에는 배치하지 않아야합니다. 그리고 우리는 CFRunLoopObserver.

CFRunLoopObserver 모든 입력 소스 대기 활동과 실행 루프의 시작 및 종료 활동을 관찰합니다.

public struct CFRunLoopActivity : OptionSetType {
    public init(rawValue: CFOptionFlags)

    public static var Entry: CFRunLoopActivity { get }
    public static var BeforeTimers: CFRunLoopActivity { get }
    public static var BeforeSources: CFRunLoopActivity { get }
    public static var BeforeWaiting: CFRunLoopActivity { get }
    public static var AfterWaiting: CFRunLoopActivity { get }
    public static var Exit: CFRunLoopActivity { get }
    public static var AllActivities: CFRunLoopActivity { get }
}

이러한 활동 중 .AfterWaiting현재 이벤트 루프가 종료 .BeforeWaiting되려고 할 때 관찰 할 수 있으며 다음 이벤트 루프가 방금 시작되었을 때 관찰 할 수 있습니다.

NSRunLoop하나의 인스턴스 NSThread있고를 NSRunLoop정확히 구동 NSThread하므로 동일한 NSRunLoop인스턴스 에서 액세스가 항상 스레드를 교차하지 않는다는 것을 고려할 수 있습니다 .

이전에 언급 한 사항을 기반으로 이제 NSRunLoop 기반 작업 디스패처 코드를 작성할 수 있습니다.

import Foundation
import ObjectiveC

public struct Weak<T: AnyObject>: Hashable {
    private weak var _value: T?
    public weak var value: T? { return _value }
    public init(_ aValue: T) { _value = aValue }

    public var hashValue: Int {
        guard let value = self.value else { return 0 }
        return ObjectIdentifier(value).hashValue
    }
}

public func ==<T: AnyObject where T: Equatable>(lhs: Weak<T>, rhs: Weak<T>)
    -> Bool
{
    return lhs.value == rhs.value
}

public func ==<T: AnyObject>(lhs: Weak<T>, rhs: Weak<T>) -> Bool {
    return lhs.value === rhs.value
}

public func ===<T: AnyObject>(lhs: Weak<T>, rhs: Weak<T>) -> Bool {
    return lhs.value === rhs.value
}

private var dispatchObserverKey =
"com.WeZZard.Nest.NSRunLoop.TaskDispatcher.DispatchObserver"

private var taskQueueKey =
"com.WeZZard.Nest.NSRunLoop.TaskDispatcher.TaskQueue"

private var taskAmendQueueKey =
"com.WeZZard.Nest.NSRunLoop.TaskDispatcher.TaskAmendQueue"

private typealias DeallocFunctionPointer =
    @convention(c) (Unmanaged<NSRunLoop>, Selector) -> Void

private var original_dealloc_imp: IMP?

private let swizzled_dealloc_imp: DeallocFunctionPointer = {
    (aSelf: Unmanaged<NSRunLoop>,
    aSelector: Selector)
    -> Void in

    let unretainedSelf = aSelf.takeUnretainedValue()

    if unretainedSelf.isDispatchObserverLoaded {
        let observer = unretainedSelf.dispatchObserver
        CFRunLoopObserverInvalidate(observer)
    }

    if let original_dealloc_imp = original_dealloc_imp {
        let originalDealloc = unsafeBitCast(original_dealloc_imp,
            DeallocFunctionPointer.self)
        originalDealloc(aSelf, aSelector)
    } else {
        fatalError("The original implementation of dealloc for NSRunLoop cannot be found!")
    }
}

public enum NSRunLoopTaskInvokeTiming: Int {
    case NextLoopBegan
    case CurrentLoopEnded
    case Idle
}

extension NSRunLoop {

    public func perform(closure: ()->Void) -> Task {
        objc_sync_enter(self)
        loadDispatchObserverIfNeeded()
        let task = Task(self, closure)
        taskQueue.append(task)
        objc_sync_exit(self)
        return task
    }

    public override class func initialize() {
        super.initialize()

        struct Static {
            static var token: dispatch_once_t = 0
        }
        // make sure this isn't a subclass
        if self !== NSRunLoop.self {
            return
        }

        dispatch_once(&Static.token) {
            let selectorDealloc: Selector = "dealloc"
            original_dealloc_imp =
                class_getMethodImplementation(self, selectorDealloc)

            let swizzled_dealloc = unsafeBitCast(swizzled_dealloc_imp, IMP.self)

            class_replaceMethod(self, selectorDealloc, swizzled_dealloc, "@:")
        }
    }

    public final class Task {
        private let weakRunLoop: Weak<NSRunLoop>

        private var _invokeTiming: NSRunLoopTaskInvokeTiming
        private var invokeTiming: NSRunLoopTaskInvokeTiming {
            var theInvokeTiming: NSRunLoopTaskInvokeTiming = .NextLoopBegan
            guard let amendQueue = weakRunLoop.value?.taskAmendQueue else {
                fatalError("Accessing a dealloced run loop")
            }
            dispatch_sync(amendQueue) { () -> Void in
                theInvokeTiming = self._invokeTiming
            }
            return theInvokeTiming
        }

        private var _modes: NSRunLoopMode
        private var modes: NSRunLoopMode {
            var theModes: NSRunLoopMode = []
            guard let amendQueue = weakRunLoop.value?.taskAmendQueue else {
                fatalError("Accessing a dealloced run loop")
            }
            dispatch_sync(amendQueue) { () -> Void in
                theModes = self._modes
            }
            return theModes
        }

        private let closure: () -> Void

        private init(_ runLoop: NSRunLoop, _ aClosure: () -> Void) {
            weakRunLoop = Weak<NSRunLoop>(runLoop)
            _invokeTiming = .NextLoopBegan
            _modes = .defaultMode
            closure = aClosure
        }

        public func forModes(modes: NSRunLoopMode) -> Task {
            if let amendQueue = weakRunLoop.value?.taskAmendQueue {
                dispatch_async(amendQueue) { [weak self] () -> Void in
                    self?._modes = modes
                }
            }
            return self
        }

        public func when(invokeTiming: NSRunLoopTaskInvokeTiming) -> Task {
            if let amendQueue = weakRunLoop.value?.taskAmendQueue {
                dispatch_async(amendQueue) { [weak self] () -> Void in
                    self?._invokeTiming = invokeTiming
                }
            }
            return self
        }
    }

    private var isDispatchObserverLoaded: Bool {
        return objc_getAssociatedObject(self, &dispatchObserverKey) !== nil
    }

    private func loadDispatchObserverIfNeeded() {
        if !isDispatchObserverLoaded {
            let invokeTimings: [NSRunLoopTaskInvokeTiming] =
            [.CurrentLoopEnded, .NextLoopBegan, .Idle]

            let activities =
            CFRunLoopActivity(invokeTimings.map{ CFRunLoopActivity($0) })

            let observer = CFRunLoopObserverCreateWithHandler(
                kCFAllocatorDefault,
                activities.rawValue,
                true, 0,
                handleRunLoopActivityWithObserver)

            CFRunLoopAddObserver(getCFRunLoop(),
                observer,
                kCFRunLoopCommonModes)

            let wrappedObserver = NSAssociated<CFRunLoopObserver>(observer)

            objc_setAssociatedObject(self,
                &dispatchObserverKey,
                wrappedObserver,
                .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }

    private var dispatchObserver: CFRunLoopObserver {
        loadDispatchObserverIfNeeded()
        return (objc_getAssociatedObject(self, &dispatchObserverKey)
            as! NSAssociated<CFRunLoopObserver>)
            .value
    }

    private var taskQueue: [Task] {
        get {
            if let taskQueue = objc_getAssociatedObject(self,
                &taskQueueKey)
                as? [Task]
            {
                return taskQueue
            } else {
                let initialValue = [Task]()

                objc_setAssociatedObject(self,
                    &taskQueueKey,
                    initialValue,
                    .OBJC_ASSOCIATION_RETAIN_NONATOMIC)

                return initialValue
            }
        }
        set {
            objc_setAssociatedObject(self,
                &taskQueueKey,
                newValue,
                .OBJC_ASSOCIATION_RETAIN_NONATOMIC)

        }
    }

    private var taskAmendQueue: dispatch_queue_t {
        if let taskQueue = objc_getAssociatedObject(self,
            &taskAmendQueueKey)
            as? dispatch_queue_t
        {
            return taskQueue
        } else {
            let initialValue =
            dispatch_queue_create(
                "com.WeZZard.Nest.NSRunLoop.TaskDispatcher.TaskAmendQueue",
                DISPATCH_QUEUE_SERIAL)

            objc_setAssociatedObject(self,
                &taskAmendQueueKey,
                initialValue,
                .OBJC_ASSOCIATION_RETAIN_NONATOMIC)

            return initialValue
        }
    }

    private func handleRunLoopActivityWithObserver(observer: CFRunLoopObserver!,
        activity: CFRunLoopActivity)
        -> Void
    {
        var removedIndices = [Int]()

        let runLoopMode: NSRunLoopMode = currentRunLoopMode

        for (index, eachTask) in taskQueue.enumerate() {
            let expectedRunLoopModes = eachTask.modes
            let expectedRunLoopActivitiy =
            CFRunLoopActivity(eachTask.invokeTiming)

            let runLoopModesMatches = expectedRunLoopModes.contains(runLoopMode)
                || expectedRunLoopModes.contains(.commonModes)

            let runLoopActivityMatches =
            activity.contains(expectedRunLoopActivitiy)

            if runLoopModesMatches && runLoopActivityMatches {
                eachTask.closure()
                removedIndices.append(index)
            }
        }

        taskQueue.removeIndicesInPlace(removedIndices)
    }
}

extension CFRunLoopActivity {
    private init(_ invokeTiming: NSRunLoopTaskInvokeTiming) {
        switch invokeTiming {
        case .NextLoopBegan:        self = .AfterWaiting
        case .CurrentLoopEnded:     self = .BeforeWaiting
        case .Idle:                 self = .Exit
        }
    }
}

With the code before, we can now dispatch the execution of UICollectionView's reloadData() to the end of current event loop by such a piece of code:

NSRunLoop.currentRunLoop().perform({ () -> Void in
     collectionView.reloadData()
    }).when(.CurrentLoopEnded)

In fact, such an NSRunLoop based task dispatcher has already been in one of my personal used framework: Nest. And here is its repository on GitHub: https://github.com/WeZZard/Nest


Thanks first of all for this thread, very helpful. I had a similar issue with Reload Data except the symptom was that specific cells could no longer be selected in a permanent way whereas others could. No call to indexPathsForSelectedItems method or equivalent. Debugging pointed out to Reload Data. I tried both options above ; and ended up adopting the ReloadItemsAtIndexPaths option as the other options didn't work in my case or were making the collection view flash for a milli-second or so. The code below works good:

NSMutableArray *indexPaths = [[NSMutableArray alloc] init]; 
NSIndexPath *indexPath;
for (int i = 0; i < [self.assets count]; i++) {
         indexPath = [NSIndexPath indexPathForItem:i inSection:0];
         [indexPaths addObject:indexPath];
}
[collectionView reloadItemsAtIndexPaths:indexPaths];`

It happened with me too in iOS 8.1 sdk, but I got it correct when I noticed that even after updating the datasource the method numberOfItemsInSection: was not returning the new count of items. I updated the count and got it working.


Do you set UICollectionView.contentInset? remove the left and right edgeInset, everything is ok after I remove them, the bug still exists in iOS8.3 .


Check that each one of the UICollectionView Delegate methods does what you expect it to do. For example, if

collectionView:layout:sizeForItemAtIndexPath:

doesn't return a valid size, the reload won't work...


try this code.

 NSArray * visibleIdx = [self.collectionView indexPathsForVisibleItems];

    if (visibleIdx.count) {
        [self.collectionView reloadItemsAtIndexPaths:visibleIdx];
    }

 dispatch_async(dispatch_get_main_queue(), ^{

            [collectionView reloadData];
            [collectionView layoutIfNeeded];
            [collectionView reloadData];


        });

it worked for me.


Here is how it worked for me in Swift 4

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {

let cell = campaignsCollection.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! Cell

cell.updateCell()

    // TO UPDATE CELLVIEWS ACCORDINGLY WHEN DATA CHANGES
    DispatchQueue.main.async {
        self.campaignsCollection.reloadData()
    }

    return cell
}

inservif (isInsertHead) {
   [self insertItemsAtIndexPaths:tmpPoolIndex];
   NSArray * visibleIdx = [self indexPathsForVisibleItems];
   if (visibleIdx.count) {
       [self reloadItemsAtIndexPaths:visibleIdx];
   }
}else if (isFirstSyncData) {
    [self reloadData];
}else{
   [self insertItemsAtIndexPaths:tmpPoolIndex];
}

참고URL : https://stackoverflow.com/questions/18796891/uicollectionview-reloaddata-not-functioning-properly-in-ios-7

반응형