滚动到集合视图中的项目会导致应用程序崩溃

时间:2013-12-03 15:54:28

标签: ios objective-c uicollectionview

我想滚动到UICollectionView

viewWillAppear的某个项目
- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];

    [collectionView_ scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:selectedIndex_ inSection:0]
                            atScrollPosition:UICollectionViewScrollPositionLeft
                                    animated:NO];
}

在iOS 6上,此代码崩溃了返回的应用

*** Assertion failure in -[UICollectionViewData layoutAttributesForItemAtIndexPath:], /SourceCache/UIKit_Sim/UIKit-2372/UICollectionViewData.m:485
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'must return a UICollectionViewLayoutAttributes instance from -layoutAttributesForItemAtIndexPath: for path <NSIndexPath 0x13894e70> 2 indexes [0, 2]'

在iOS7上,它不会崩溃,但根本无效。

滚动到正确的项目只能在viewDidAppear中使用,但我希望在正确的项目中显示带有集合的屏幕。我试图在viewDidLayoutSubviews中滚动它,但它也崩溃了。将呼叫包含在try-catch内可以避免崩溃,但仍然无效。

这有什么意义?是否无法显示正确的项目?

非常感谢你。

编辑1

我在viewWillAppearviewDidLayoutSubviews上打印了这个(selectedIndex_是2,该集合有10个项目):

UICollectionViewLayoutAttributes *test = [collectionView_ layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForRow:selectedIndex_ inSection:0]];

两个地方的结果都是这样。

<UICollectionViewLayoutAttributes: 0x11b9ff20> index path: (<NSIndexPath: 0x11b9c450> {length = 2, path = 0 - 2}); frame = (0 0; 0 0);

编辑2

这是我打印的集合contentSize的跟踪

2013-12-09 08:56:59.300 - didLoad {0, 0}
2013-12-09 08:56:59.315 - willAppear {0, 0}
2013-12-09 08:56:59.350 - viewDidLayoutSubviews {0, 0}
2013-12-09 08:56:59.781 - viewDidLayoutSubviews {3200, 223}
2013-12-09 08:56:59.879 - didAppear {3200, 223}
2013-12-09 08:56:59.882 - viewDidLayoutSubviews {3200, 223}

viewDidLoad

中以编程方式创建集合视图
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
[layout setScrollDirection:UICollectionViewScrollDirectionHorizontal];
collectionView_ = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
[collectionView_ setTranslatesAutoresizingMaskIntoConstraints:NO];
[collectionView_ setDelegate:self];
[collectionView_ setDataSource:self];
[collectionView_ setShowsHorizontalScrollIndicator:NO];
[collectionView_ setPagingEnabled:YES];
[collectionView_ setBackgroundColor:[UIColor whiteColor]];
[collectionView_ registerClass:[MyCollectionViewCell class] forCellWithReuseIdentifier:[MyCollectionViewCell collectionCellIdentifier]];
[scrollView_ addSubview:collectionView_];

scrollView_是通过XIB创建的(XIB中唯一的控件。我需要另一个滚动来将一些其他控件放在水平集合下面)。此方法的约束在updateViewConstraints

中设置
- (void)updateViewConstraints {
    [super updateViewConstraints];

    NSDictionary *views = [self viewsDictionary];
    NSDictionary *metrics = @{ @"bigMargin" : @12, @"collectionViewHeight" : @(collectionViewHeight_) };

    NSMutableString *verticalConstraints = [NSMutableString stringWithString:@"V:|[collectionView_(==collectionViewHeight)]"];

    [scrollView_ addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[collectionView_(==scrollView_)]|"
                                                                        options:0
                                                                        metrics:nil
                                                                          views:views]];

    if (extendedInformationView_) {

        [scrollView_ addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[extendedInformationView_(==scrollView_)]|"
                                                                            options:0
                                                                            metrics:nil
                                                                              views:views]];

        [verticalConstraints appendFormat:@"-bigMargin-[extendedInformationView_]"];
    }

    if (actionListView_) {

        [scrollView_ addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[actionListView_(==scrollView_)]|"
                                                                            options:0
                                                                            metrics:nil
                                                                              views:views]];

        [verticalConstraints appendFormat:@"-bigMargin-[actionListView_]"];
    }

    [verticalConstraints appendString:@"-bigMargin-|"];

    [scrollView_ addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:verticalConstraints
                                                                        options:0
                                                                        metrics:metrics
                                                                          views:views]];

}

MyCollectionViewCell在其initWithFrame方法中创建其所有控件,这是返回单元格的方法。

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {

    MyCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:[MyCollectionViewCell collectionCellIdentifier]
                                                                           forIndexPath:indexPath];

    // Data filling

    return cell;   
}

7 个答案:

答案 0 :(得分:7)

我有同样的问题,可以解决它。首先,当您创建 UICollectionView 时,您必须指定一个宽度为的框架,无论高度如何,但宽度对于滚动到正确的项目非常重要。

UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
[layout setScrollDirection:UICollectionViewScrollDirectionHorizontal];
collectionView_ = [[UICollectionView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, CGRectGetWidth([scrollView_ frame]), 0.0f)
                                     collectionViewLayout:layout];
[collectionView_ setDelegate:self];
[collectionView_ setDataSource:self];
[collectionView_ setBackgroundColor:[UIColor clearColor]];
[collectionView_ setTranslatesAutoresizingMaskIntoConstraints:NO];
[collectionView_ setShowsHorizontalScrollIndicator:NO];
[collectionView_ setPagingEnabled:YES];
[scrollView_ addSubview:collectionView_];

创建 UICollectionView 后,必须告诉视图需要更新其约束,因为在iOS6中你必须强制它,所以调用updateViewConstraints:

[self updateViewConstraints]

重写方法updateViewConstraints,并在此处设置所有视图约束。请记住在调用super之前删除视图的所有约束(在代码中没有删除它们),并在度量字典上设置UICollectionView的 width ,并且不要使用 [ collectionView _(== scrollView _)] 因为有时会失败,主要是在iOS6中。

- (void)updateViewConstraints {

    [scrollView_ removeConstraints:[scrollView_ constraints]];
    [super updateViewConstraints];

    NSDictionary *views = [self viewsDictionary];
    NSDictionary *metrics = @{ @"bigMargin" : @12, @"collectionViewHeight" : @(collectionViewHeight_), @"viewWidth" : @(CGRectGetWidth([scrollView_ frame]) };

    NSMutableString *verticalConstraints = [NSMutableString stringWithString:@"V:|[collectionView_(==collectionViewHeight)]"];

    [scrollView_ addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[collectionView_(==viewWidth)]|"
                                                                        options:0
                                                                        metrics:nil
                                                                          views:views]];

    if (extendedInformationView_) {

        [scrollView_ addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[extendedInformationView_(==scrollView_)]|"
                                                                            options:0
                                                                            metrics:nil
                                                                              views:views]];

        [verticalConstraints appendFormat:@"-bigMargin-[extendedInformationView_]"];
    }

    if (actionListView_) {

        [scrollView_ addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[actionListView_(==scrollView_)]|"
                                                                            options:0
                                                                            metrics:nil
                                                                              views:views]];

        [verticalConstraints appendFormat:@"-bigMargin-[actionListView_]"];
    }

    [verticalConstraints appendString:@"-bigMargin-|"];

    [scrollView_ addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:verticalConstraints
                                                                        options:0
                                                                        metrics:metrics
                                                                          views:views]];

}

最后,要将UICollectionView滚动到正确的项目,请在 viewWillLayoutSubviews 上执行此操作,并且不要忘记检查UICollectionView的大小是否不为零以避免应用程序崩溃:

- (void)viewWillLayoutSubviews {
    [super viewWillLayoutSubviews];

    if (!CGSizeEqualToSize([collectionView_ frame].size, CGSizeZero)) {

        [collectionView_ scrollToItemAtIndexPath:_selectedRowItem_ inSection:0]
                                atScrollPosition:UICollectionViewScrollPositionLeft
                                        animated:NO];
    }
}

这就是全部。希望它有所帮助!

答案 1 :(得分:3)

您的集合视图的框架最初是CGRectZero,要使流程布局正常工作,它需要具有带框架的集合视图。当您更新视图生命周期中的布局约束时,似乎会发生这种情况。

collectionView_ = [[UICollectionView alloc] initWithFrame:self.view.bounds collectionViewLayout:layout];

希望这有帮助。

答案 2 :(得分:2)

我已经能够重现你的问题了。 问题是UICollectionView在您尝试滚动到指定的NSIndexPath时不知道其内容大小。

这是重现问题的代码:

@interface TLCollectionViewController : UIViewController

@end

@interface CollectionCell : UICollectionViewCell

@property (nonatomic, strong) UILabel *titleLbl;

@end

@implementation CollectionCell

- (id)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        _titleLbl = [[UILabel alloc] init];

        [_titleLbl setTranslatesAutoresizingMaskIntoConstraints:NO];
        [self.contentView addSubview:_titleLbl];

        NSArray *titleLblHConstrArr = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[titleLbl]|" options:kNilOptions metrics:nil views:@{ @"titleLbl" : _titleLbl }];
        NSArray *titleLblVConstrArr = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[titleLbl]|" options:kNilOptions metrics:nil views:@{ @"titleLbl" : _titleLbl }];

        [[self contentView] addConstraints:titleLblHConstrArr];
        [[self contentView] addConstraints:titleLblVConstrArr];

        [self setBackgroundColor:[UIColor whiteColor]];
    }
    return self;
}

- (void)prepareForReuse {
    [super prepareForReuse];
    self.titleLbl.text = @"";
}

@end

@interface TLCollectionViewController () <UICollectionViewDataSource>

@property (nonatomic, strong) NSArray *items;
@property (nonatomic, strong) UICollectionView *collView;

@end

@implementation TLCollectionViewController

- (void)loadView {
    self.view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height)];
    [self.view setBackgroundColor:[UIColor whiteColor]];
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    self.items = @[ @"one", @"two", @"three", @"one", @"two", @"three", @"one", @"two", @"three"
                    , @"one", @"two", @"three", @"one", @"two", @"three", @"one", @"two", @"three", @"one", @"two", @"three"
                    , @"one", @"two", @"three", @"one", @"two", @"three", @"one", @"two", @"three", @"one", @"two", @"three"
                    , @"one", @"two", @"three", @"one", @"two", @"three", @"one", @"two", @"three", @"one", @"two", @"three"
                    , @"one", @"two", @"three", @"one", @"two", @"three", @"one", @"two", @"three", @"one", @"two", @"three"
                    , @"one", @"two", @"three", @"one", @"two", @"three", @"one", @"two", @"three", @"one", @"two", @"three"
                    , @"one", @"two", @"three", @"one", @"two", @"three", @"one", @"two", @"three", @"one", @"two", @"three"
                    , @"one", @"two", @"three", @"one", @"two", @"three", @"one", @"two", @"three", @"one", @"two", @"three" ];

    UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
    [layout setScrollDirection:UICollectionViewScrollDirectionHorizontal];
    self.collView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
    [self.collView setDataSource:self];
    [self.collView setTranslatesAutoresizingMaskIntoConstraints:NO];
    [self.collView registerClass:[CollectionCell class] forCellWithReuseIdentifier:@"collCell"];

    [self.view addSubview:self.collView];

    NSArray *collViewHConstrArr = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[collView(==300)]" options:kNilOptions metrics:nil views:@{ @"collView" : self.collView }];
    NSArray *collViewVConstrArr = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[collView(==300)]" options:kNilOptions metrics:nil views:@{ @"collView" : self.collView }];

    [self.view addConstraints:collViewHConstrArr];
    [self.view addConstraints:collViewVConstrArr];
}

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    // BUG: here on iOS 6 exception is raised, because UICollectionView doesn't know about it's content size and about it's frame
    // but on iOS 7 it does know about it's frame, thus makes it possible to know about attributes
    id attr = [self.collView layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForRow:70 inSection:0]];
    [self.collView scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:70 inSection:0]
                          atScrollPosition:UICollectionViewScrollPositionLeft
                                  animated:NO];
}

#pragma mark - UICollectionViewDataSource

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
    return self.items.count;
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    CollectionCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"collCell" forIndexPath:indexPath];
    cell.titleLbl.text = self.items[indexPath.row];
    return cell;
}

@end

此行为在iOS 6和iOS 7上有所不同。 在iOS 6中,如果您尝试获取UICollectionView没有内容大小框架的属性,则会收到NSInternalInconsistencyException个异常。至于iOS 7,这种情况有所改变,现在您不必了解UICollectionView内容大小,也不了解它的框架,以获取特定NSIndexPath的属性。

关于-[UICollectionViewData layoutAttributesForItemAtIndexPath:]的调用 - 在尝试执行UICollectionView的任何类型滚动时会自动调用此方法。

enter image description here

回答你的问题:

  

是否无法显示正确的项目?

是的,那里不可能做到。您必须知道布局才能正确滚动。执行滚动的正确方法是在-[UIViewController viewDidLayoutSubviews]

中实现它
- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];
    [self.collView scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:70 inSection:0]
                      atScrollPosition:UICollectionViewScrollPositionLeft
                              animated:NO];
}

答案 3 :(得分:1)

-scrollToItemAtIndexPath:atScrollPosition:Animated UICollectionView 尚未布局时调用时,UIKit似乎会崩溃,正如您在{{3}上看到的那样}

因此,您只能将其放在iOS7上的 viewDidAppear和以及 viewDidLayoutSubviews 中,并且只能将其放在iOS6上的 viewDidAppear 中。为什么在iOS6上排除 viewDidLayoutSubviews 会在您的日志中显示:

2013-12-09 08:56:59.300 - didLoad {0, 0}
2013-12-09 08:56:59.315 - willAppear {0, 0}
2013-12-09 08:56:59.350 - viewDidLayoutSubviews {0, 0}
2013-12-09 08:56:59.781 - viewDidLayoutSubviews {3200, 223}
2013-12-09 08:56:59.879 - didAppear {3200, 223}
2013-12-09 08:56:59.882 - viewDidLayoutSubviews {3200, 223}

第一次调用 viewDidLayoutSubviews 时, UICollectionView 尚未布局。 iOS7刚刚第二次调用 viewDidLayoutSubviews ,但iOS 6将在第一时间崩溃。

答案 4 :(得分:1)

这是一个随机的额外保留在以前的工作。我已经有几次内存被释放并重新分配,因此应用程序正在查找已被回收的地址,导致内部不一致错误。

可能有一个更好的方法来添加一个强大的(可能是一个__strong)它但是我试图保持这样的属性,好吧,属性和:

@property (nonatomic,strong) UICollectionView *collectionView_;

将保留对数据的强烈引用,希望能够阻止不一致的事情。

马丁

答案 5 :(得分:1)

这可能是相关的。

[NSIndexPath indexPathForRow: NSNotFound inSection: index]

使用NSNotFound而不是0。

答案 6 :(得分:1)

确保在viewWillAppear中加载了集合视图内容。当视图出现在屏幕上时,CollectionView / TableView将加载数据。尝试滚动到 viewDidAppear:方法中的项目或使用一些延迟。