Подтвердить что ты не робот

Прокрутка к элементу в представлении коллекции приводит к сбою приложения

Я хочу прокрутить до определенного элемента 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

Я напечатал это на viewWillAppear и viewDidLayoutSubviews (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;   
}
4b9b3361

Ответ 1

У меня была такая же проблема, и я мог ее решить. Прежде всего, когда вы создаете 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 и не используйте [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];
    }
}

Это все. Надеюсь, это поможет!

Ответ 2

Кадр вашего представления коллекции первоначально представляет собой CGRectZero, для того, чтобы макет потока работал, он должен иметь представление коллекции с фреймом. Это происходит, когда вы обновляете ограничения макета, которые слишком поздно в жизненном цикле просмотра.

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

Надеюсь, что это поможет.

Ответ 3

Я смог воспроизвести вашу проблему. Проблема в том, что 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 content size and about it frame
    // but on iOS 7 it does know about it 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];
}

Ответ 4

Кажется, что UIKit сработает, когда -scrollToItemAtIndexPath: atScrollPosition: Animated вызывается, когда UICollectionView еще не выложен, как вы могли видеть на Radar

Таким образом, вы можете поместить его только в viewDidAppear и и viewDidLayoutSubviews на iOS7 и только поместить его в viewDidAppear на iOS6. почему даже viewDidLayoutSubviews исключается в iOS6, отображается в вашем журнале:

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 будет аварийно завершен в первый раз.

Ответ 5

Здесь случайный дополнительный удержание будет работать в прежние дни. У меня было несколько раз, когда память освобождается и перераспределяется, поэтому приложение ищет что-то по адресу, который был переработан, что приводит к внутренней ошибке несогласованности.

Вероятно, гораздо лучший способ добавить сильный (вероятно, __strong), но я стараюсь сохранять такие свойства как свойства, а также:

@property (nonatomic,strong) UICollectionView *collectionView_;

Будет содержать сильную ссылку на данные, которые, надеюсь, остановят несостоятельность.

Martin

Ответ 6

Это может быть связано.

[NSIndexPath indexPathForRow: NSNotFound inSection: index]

Используйте NSNotFound вместо 0.

Ответ 7

Убедитесь, что содержимое коллекционного просмотра загружено в viewWillAppear. CollectionView/TableView загрузит данные, когда на экране появится представление. Попробуйте прокрутить элемент в режиме viewDidAppear: или используйте некоторую задержку.