Question

I am trying to create a card layout in my app very much like the multitasking UI in iOS 7. The engineering WWDC video Exploring Scroll Views on iOS 7, notes that they used nested scroll views to accomplish that effect in iOS 7, but did not go into any detail. I would imagine that they would have used a UICollectionView, since that seemed like the most intuitive way.

So far I have tried making a custom UICollectionViewFlowLayout, but it is quite difficult and doesn't provide the functionality I am looking for. I have also tried using a UICollectionView with a UICollectionViewFlowLayout wherein each of the custom UICollectionViewCells cells has a scrollview which has a card as its subview. I am using Autolayout, so I added the constraint that the bottom of the card be contiguous with the top of the scroll view. I then added extra space within the scrollview's content size so that the card will appear to scroll out of the screen as the user swipes up. Like so,

[self.contentView addConstraints: [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-70-[cardView(400)]-650-|"
                                                                          options:0
                                                                          metrics:nil
                                                                            views:views]];

Where the superview, specified by |, is a scrollview that fills the cell.

And also I cannot make the sizeForItemAtIndexPath large enough to cover the entire screen because I receive the error:

the behavior of the UICollectionViewFlowLayout is not defined because: the item height must be less that the height of the UICollectionView minus the section insets top and bottom values.

So the question is, should I make a custom UICollectionViewFlowLayout instead of trying to leverage nested scrollviews?

Était-ce utile?

La solution

In case anyone else stumbles upon this question, the answer was no I should not make a custom UICollectionViewFlowLayout to handle the gestures of the swipe out. Using UIScrollViews has a much more natural feel than implementing a custom layout. It provides the same scrolling and momentum that scroll views have throughout iOS.

I managed to resolve the problems that I was having in a very simple way with nested UIScrollViews. I seems that the warning that I was receiving was actually just a bug with putting the collection view in the first tab spot on a tab view controller and having the cell take up the entire screen. This bug was resolved by adding an extra view behind the collection view.

- (void)viewDidLoad
{
    [super viewDidLoad];
    [self.cardCollectionView registerClass:[SwipeCardCollectionViewCell class] forCellWithReuseIdentifier:kCollectionViewCellIdentifier];
    self.cardCollectionView.alwaysBounceVertical = NO;
    self.cardCollectionView.alwaysBounceHorizontal = YES;
}

- (void)loadView {
    self.view = [[UIView alloc] init];

    self.layout = [[UICollectionViewFlowLayout alloc] init]; 
    self.layout.scrollDirection = UICollectionViewScrollDirectionHorizontal
    self.cardCollectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:self.layout];
    self.cardCollectionView.translatesAutoresizingMaskIntoConstraints = NO;
    self.cardCollectionView.backgroundColor = [UIColor clearColor];
    self.cardCollectionView.delegate = self;
    self.cardCollectionView.dataSource = self;
    [self.view addSubview:self.cardCollectionView];

    NSDictionary *views = @{@"cardCollectionView": self.cardCollectionView}

    [self.view addConstraints: [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[cardCollectionView]|"
                                                                       options:0
                                                                       metrics:nil
                                                                         views:views]];

    [self.view addConstraints: [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[cardCollectionView]|"
                                                                       options:0
                                                                       metrics:nil
                                                                         views:views]];

                                                              views:views]];
}

-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{  
    SwipeCardCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kCollectionViewCellIdentifier forIndexPath:indexPath];
    cell.delegate = self;
    return cell;
}

I made the collection view's sizeForItemAtIndexPath: equal to the size of the screen

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
    return CGSizeMake(self.cardCollectionView.frame.size.width - CARD_WIDTH_INSET, self.cardCollectionView.frame.size.height);
}

-(CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section{
    return 0.0;
}

-(CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section {
    return CARD_SPACING;
}

I got the cards to page by the width of the cards instead of the width of the collection view's bounds by implementing scrollViewWillEndDragging:withVelocity:targetContentOffset: and adding an content inset on either side of the collection view.

- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section{
    CGFloat inset = CARD_WIDTH_INSET/2.0;
    return UIEdgeInsetsMake(0.0, inset, 0.0, inset);
}

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {
    CGFloat cardWidth = self.cardCollectionView.frame.size.width - CARD_WIDTH_INSET;
    NSInteger cardNumber = round(targetContentOffset->x/(cardWidth + CARD_SPACING));
    CGFloat finalOffset = cardNumber*(cardWidth + CARD_SPACING);

   targetContentOffset->x = finalOffset;
}

Lastly, I was able to get the cards to swipe off screen by adding a scroll view to the custom swipe cell that I made. Enabling paging allows the card to swipe right off the screen just like in the springboard multitasking UI. Like so,

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        self.cardContainerScrollView = [[UIScrollView alloc] init];

        self.cardContainerScrollView.delegate = self;
        self.cardContainerScrollView.pagingEnabled = YES;
        self.cardContainerScrollView.translatesAutoresizingMaskIntoConstraints = NO;
        self.cardContainerScrollView.alwaysBounceHorizontal = NO;
        self.cardContainerScrollView.alwaysBounceVertical = YES;
        self.cardContainerScrollView.showsHorizontalScrollIndicator = NO;
        self.cardContainerScrollView.showsVerticalScrollIndicator = NO;

        self.cardView = [[UIView alloc] init];
        self.cardView.translatesAutoresizingMaskIntoConstraints = NO;
        self.cardView.backgroundColor = [UIColor whiteColor];

        [self.cardContainerScrollView addSubview:self.cardView];

        [self.contentView addSubview:self.cardContainerScrollView];

        NSDictionary *views = @{@"cardView": self.cardView,
                                @"cardContainerScrollView": self.cardContainerScrollView};

        [self.contentView addConstraints: [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[cardContainerScrollView]|"
                                                                                  options:0
                                                                                  metrics:nil
                                                                                    views:views]];

        [self.contentView addConstraints: [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[cardContainerScrollView]|"
                                                                                  options:0
                                                                                  metrics:nil
                                                                                    views:views]];

        [self.contentView addConstraints: [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-70-[cardView(400)]-650-|"
                                                                                  options:0
                                                                                  metrics:nil
                                                                                    views:views]];

        [self.contentView addConstraints: [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[cardView(260)]|"
                                                                                  options:0
                                                                                  metrics:nil
                                                                                    views:views]];

    }
    return self;
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top