[iOS] UICollectionView初始化滚动到中间的bug

转载请保留地址wossoneri.com

问题

首先看一下我之前写的demo:link

demo是封装了一个控件,直接在MainViewControllerviewWillAppear里初始化,并且调用一个初始化滚动到中间的方法,方法主要是调用了

- (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,我记录它的原因,一方面是解决过程中学习到了一些不了解的方法,另一方面是记录一下解决问题的思路,希望可以对以后的学习有帮助。也希望将来回头看这一段的时候可以对问题有更好的解决思路。

posted @ 2016-01-21 23:34  Wossoneri  阅读(4252)  评论(0编辑  收藏  举报