轉載請保留地址wossoneri.com
問題
首先看一下我之前寫的demo:link
demo是封裝了一個控件,直接在MainViewController的viewWillAppear里初始化,並且調用一個初始化滾動到中間的方法,方法主要是調用了
- (void)scrollToItemAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UICollectionViewScrollPosition)scrollPosition animated:(BOOL)animated;
方法,在初始化后將其滾動到中間的區域。不過,當我在項目里使用的時候,遇到了調用該方法卻無法滾動到中間的情況。
我的使用步驟是:在我界面的UIView中,創建該控件對象並且調用滾動到中間的方法。
scrollToItemAtIndexPath使用
發現效果無法實現,我第一時間檢查了函數有沒有調用,然后發現是調用的了,但沒有出現該出現的效果。所以簡單看一下該方法的官方注釋。xcode提示的描述是:
Scrolls the collection view contents until the specified item is visible.只是方法的作用,並沒有說使用條件。為了快速解決問題,google了一下scrolltoitematindexpath not working,在這里:link找到了答案:
不知道這是一個bug還是一個特性,每當在
UICollectionView顯示它的subview之前調用scrollToItemAtIndexPath:atScrollPosition:Animated方法,UIKit就會報錯。
所以要解決它,就應該在viewController中,在你能確認CollectionView完全計算出其subview布局的地方去調用這個方法。比如在viewDidLayoutSubviews里調用就沒有問題。
方法的意思已經明確,就是找到一個能計算出collectionview的所有布局地方調用滾動方法。但我的界面使用的是自動布局,只在模塊外有一個viewController,其余的都是在UIView中創建添加,所以在我使用這個控件對象的地方,我無法復寫viewController的方法。所以想了幾個辦法。
-layoutsubviews
既然情況發生在UIView中,那首先想到的是重寫該方法。重寫之前,看一下文檔說明:
Lays out subviews.
Discussion
The default implementation of this method does nothing on iOS 5.1 and earlier. Otherwise, the default implementation uses any constraints you have set to determine the size and position of any subviews.
Subclasses can override this method as needed to perform more precise layout of their subviews. You should override this method only if the autoresizing and constraint-based behaviors of the subviews do not offer the behavior you want. You can use your implementation to set the frame rectangles of your subviews directly.
You should not call this method directly. If you want to force a layout update, call the setNeedsLayout method instead to do so prior to the next drawing update. If you want to update the layout of your views immediately, call the layoutIfNeeded method.
該方法為子view布局。
子類可以復寫該方法為子view實現更精確的布局。但你只應該在用自動布局時無法實現效果時用它。而且你不應該直接調用該方法,如果需要強制刷新布局,調用setNeedLayout,會在下一次繪制刷新前更新布局。調用layoutIfNeed可以立即刷新布局。
看起來這個方法有用,我便試了一下。
[self setNeedLayout];
[self layoutIfNeed];
[myPicker scrollToCenter];
然而還是沒有效果。
view.window
前面的方法沒效果,我又想了一下scrolltoitematindexpath的實現條件,是在UICollectionView顯示之后調用才有效,所以我需要在UIView中獲得它顯示的狀態再去滾動。幸好,我的控件也是繼承UIView的,其中有這么一個屬性:
Property: window
This property is nil if the view has not yet been added to a window.
也就是說這個屬性值代表着這個view有沒有放在窗口中顯示,那么我就需要在當前UIView的生命周期中檢查myPicker對象的這個屬性就好了。所以最后的解決方法是,起一個子線程,對myPicker的狀態進行檢查,當狀態為顯示的時候調用滾動方法:
NSThread *checkShownThread;
checkShownThread = [[NSThread alloc] initWithTarget:self selector:@selector(checkIfViewIsShowing) object:nil];
[checkShownThread start];
- (void)checkIfViewIsShowing {
while (1) {
if (hPicker.window != nil) {
break;
}
}
dispatch_async(dispatch_get_main_queue(), ^{
[hPicker scrollToCenter];
});
[checkShownThread cancel];
checkShownThread = nil;
}
小結
這個bug只是一個很小的bug,我記錄它的原因,一方面是解決過程中學習到了一些不了解的方法,另一方面是記錄一下解決問題的思路,希望可以對以后的學習有幫助。也希望將來回頭看這一段的時候可以對問題有更好的解決思路。
