【開發問題記錄①】關於滑動CollectionView時ContentSize變化的問題

JoyZhang發表於2017-12-14

本文提供了需求所描述問題的解決方案,但是關於滑動CollectionView時KVO監聽ContentSize會不斷觸發回撥的問題依然困擾著我

先丟擲我的疑惑:

KVO監聽UICollectionView的ContentSize,可是為hen麼滑動CollectionView這個操作會使觀察者監聽到contentSize的變化呢?現實對我的認知造成了衝擊

表情1.jpg
在我的理解中,在DataSource沒有資料來源變化的情況下,CollectionView的ContentSize渲染結束後應該是確定了的(當然CollectionView在滑動過程中對item本身就存在著不斷地渲染,我提到的渲染。。。你懂的,就是整體上的,在我理解哈),所以!CollectionView在滑動時不應該觸發KVO的監聽呀。 很有道理對不對?但事實不是這樣的sad☹️ 現實是:滑動CollectionView會使KVO瘋狂呼叫監聽ContentSize的回撥

接下來闡述一下心路歷程吧

需求描述:

產品同學期望在拍攝新的照片後能夠橫向滑動到新增的照片,讓使用者感知到照片拍攝成功。如圖:

p1.png

p2.png

實現思路:

如需滾動到新增照片(即CollectionView的底部)需要照片在CollectionView上完成渲染操作,我自yan而yan的就想到了KVO,去監測CollectionView的ContentSize,當新增照片成功渲染到CollectionView上後ContentSize勢必會改變,這時我修改ContentOffset去完成自動滑動。 初版實現程式碼如下:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
      if([keyPath isEqualToString:@"contentSize"]) {
            if (context == photosCollectionViewContext) {      // 此處context為自定義識別符號,客官不必糾結其意義
                CGFloat offset = MAX(self.photosCollectionView.contentSize.width - self.photosCollectionView.frame.size.width, 0.0);
                [self.photosCollectionView setContentOffset:CGPointMake(offset, 0) animated:YES];
            }
      }
}
複製程式碼

遇到的問題:

當我對展示照片的CollectionView做橫向滑動時(照片數量多於4張),KVO竟然監測到CollectionView.ContentSize變化,不斷回撥其 - observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context 方法,導致CollectionView滑動卡頓且最終永遠停在照片尾部(即底部)。

解決方法:

我們在建立觀察者的時候通過增加 NSKeyValueObservingOptionNew NSKeyValueObservingOptionOld引數將變化後的新值舊值告訴我們:

[self.photosCollectionView addObserver:self 
                            forKeyPath:@"contentSize" 
                               options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld 
                               context:photosCollectionViewContext];
複製程式碼

通知會呼叫 - observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context 方法,我們利用 change 中的資料newold進行邏輯上的判斷,控制CollectionView產生我們所需要的滑動。 解決:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
    
    if (context == photosCollectionViewContext) {
            NSValue *newSizeValue = change[@"new"];
            NSValue *oldSizeValue = change[@"old"];
            CGSize newSize = [newSizeValue CGSizeValue];
            CGSize oldSize = [oldSizeValue CGSizeValue];
            // 判斷條件:若新值與舊值相等即ContentSize沒有發生變化則忽略
                       新值contentSize.width > 舊值content.size即新增圖片導致的contentSize增大才處理滑動
            if (![newSizeValue isEqualToValue:oldSizeValue] && newSize.width > oldSize.width) {
                CGFloat offset = MAX(self.photosCollectionView.contentSize.width - self.photosCollectionView.frame.size.width, 0.0);
                [self.photosCollectionView setContentOffset:CGPointMake(offset, 0) animated:YES];
            }
        }
    }
}
複製程式碼

這樣就可以滿足需求了。

But!!!!!!有一個問題卻不斷困擾著我就是文章開頭提到的滑動操作為什麼會觸發KVO?

出於好奇,我在解決方法中的這段程式碼上打了斷點,滑動CollectionView觀察每次newSizeoldSize的數值,發現在滑動中newSizeoldSize的值始終是完全一致的,此處是印證了我開頭所說的 在DataSource沒有資料來源變化的情況下,CollectionView的ContentSize渲染結束後應該是確定了的

這個說法。這就讓我對KVO的觸發機制產生了疑問,對KVO來說:

KVO的監聽從對應key-path的屬性是否呼叫了- ()set方法,即使UICollectionViewContentSize的值沒有變化,但是滑動中UICollectionView.contentSize- ()set方法被呼叫了,KVO的檢測到並回撥,後續空了重寫一下了解一下UICollectionView.contentSize相關內容再做補充。

相關文章