12/08/2018, 14:09

Sử dụng UICollectionView và UIScrollView để tạo slideshow ảnh như Facebook

Nhân dịp project đang cần làm một slideshow hiển thị ảnh từ news feed, nên trong bài viết này mình sẽ viết một tutorial "for beginner" để tạo slideshow ảnh bằng cách sử dụng UICollectionView và UIScrollVIew cực kỳ đơn giản và dễ dàng. Mở Xcode lên và bắt đầu tạo project mới thôi. Trong ...

Nhân dịp project đang cần làm một slideshow hiển thị ảnh từ news feed, nên trong bài viết này mình sẽ viết một tutorial "for beginner" để tạo slideshow ảnh bằng cách sử dụng UICollectionViewUIScrollVIew cực kỳ đơn giản và dễ dàng.

Mở Xcode lên và bắt đầu tạo project mới thôi.

Screen Shot 2016-10-31 at 13.14.49.png

Trong Main.storyboard kéo thả 1 một UIButton, đổi text thành Click to show images, thêm constraint chính giữa X, Y hoặc tuỳ bạn.

Từ của sổ Show the Object libary, kéo thêm UIViewController vào storyboard. Chọn button ở UIViewController đầu, control+kéo thả vào UIViewController mới tạo. Tạo mới class ImageDetailsViewController và set class này với UIViewController thứ 2.

Screen Shot 2016-10-31 at 13.38.02.png

Đừng quên set delegate, datasource cho collectionView nhé. Slideshow thì hiển thị ảnh theo chiều ngang phải không nào, vì vậy chúng ta đổi scroll đirection từ Vertical thành Horizontal.

Screen Shot 2016-10-31 at 14.48.12.png

Kéo thả 1 UICollectionView vào ImageDetailsViewController set constraint cho nó dính chặt với superview. Set identifier cho cell thành ImageDetailCell.

Screen Shot 2016-10-31 at 13.42.18.png

Kéo 1 UIScrollView vào trong cell và cũng set constraint top, bottom, leading, trailing với constant = 0. Trong Attributes inspector, set max zoom cho nó bằng 2 hoặc bao nhiêu tuỳ thích (mặc định là 1).

Tiếp tục thêm 1 UIImageView vào trong UIScrollView mới tạo và set constraint top, bottom, leading, trailing với constant = 0, center theo chiều x, center theo chiều y.

Tạo mới class ImageDetailCollectionViewCell kế thừa class UICollectionViewCell, set class này với cell trong UICollectionView.

Kéo thả các controll từ storyboad để tạo các IBOutlet tương úng trong ImageDetailCollectionViewCell như sau:

`@property (weak, nonatomic) IBOutlet UIImageView *imvDetail;
`@property (weak, nonatomic) IBOutlet UIScrollView *scrollView;
// 4 constraint top, bottom, leading, trailing tương úng của imvDetail
`@property (weak, nonatomic) IBOutlet NSLayoutConstraint *imageViewTrailingConstraint;
`@property (weak, nonatomic) IBOutlet NSLayoutConstraint *imageViewBottomConstraint;
`@property (weak, nonatomic) IBOutlet NSLayoutConstraint *imageViewLeadingConstraint;
`@property (weak, nonatomic) IBOutlet NSLayoutConstraint *imageViewTopConstraint;

Trong ImageDetailsViewController.m, thêm 1 NSArray để chứa các UIImage cần hiển thị.

@interface ImageDetailsViewController () {
    NSArray<UIImage *> *_images;
}

`@end

Thêm các function sau vào ImageDetailsViewController.m (nhớ conform thêm 2 protocol UICollectionViewDataSourceUICollectionViewDelegateFlowLayout trong header nhé).

- (void)viewDidLoad {
    [super viewDidLoad];
    // Sample data
    _images = @[
        [UIImage imageNamed:@"anh1"],
        [UIImage imageNamed:@"anh2"],
        [UIImage imageNamed:@"anh3"],
        [UIImage imageNamed:@"anh4"],
        [UIImage imageNamed:@"anh5"]
    ];
}

#pragma mark - UICollectionViewDataSource, UICollectionViewDelegateFlowLayout

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

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
    cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    ImageDetailCollectionViewCell *cell = [collectionView
        dequeueReusableCellWithReuseIdentifier:@"ImageDetailCell" forIndexPath:indexPath];
    // Set image cho cell và 1 block callback để xử lý khi ảnh đang zoom
    [cell setImageDetail:_images[indexPath.row] withHandler:^(BOOL isZooming){
        // Ảnh đang zoom thì không cho phép người dùng scroll collection view
        [collectionView setScrollEnabled:isZooming];
    }];
    return cell;
}

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout
    sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
    // Set kích thước một view full màn hình
    return self.view.frame.size;
}

Giờ đến cài đặt ImageDetailCollectionViewCell.m nhé. Thêm một biến block để nhận block call back từ ViewController.

@interface ImageDetailCollectionViewCell () {
    void (^_handleZooming)(BOOL);
}

Sau đó thêm các function sau (cần thêm phần define function setImageDetail và conform thêm protocol UIScrollViewDelegate trong file header).

- (void)setImageDetail:(UIImage *)image withHandler:(void(^)(BOOL isZooming))handler {
    // Set delegate cho scrollView
    self.scrollView.delegate = self;
    // Set image cho cell
    self.imvDetail.image = image;
    // Layout lại các subview
    [self layoutIfNeeded];
    // Set minZoomScale cho scrollView khít với ảnh cần hiển thị
    [self updateMinZoomScale];
    // Update các constraint của UIImageView để hiển thị ảnh chính giữa scrollView
    [self updateConstraints];
    // Thêm một gesture double tap để zoom in và zoom out
    UITapGestureRecognizer *zoomTapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self
        action:@selector(zoomWhenDoubleTapped:)];
    zoomTapGestureRecognizer.numberOfTapsRequired = 2;
    [self.scrollView addGestureRecognizer:zoomTapGestureRecognizer];
    _handleZooming = handler;
}

#pragma  mark - UIScrollViewDelegate

// Set view cần zoom trong scrollView. Chúng ta cần zoom ảnh nên set một UIImageView
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
    return self.imvDetail;
}

// Nếu UIImageView hiện tại đang zoom thì call back block không cho collectionView scroll chuyển ảnh
- (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view {
    BOOL isZooming = self.scrollView.zoomScale > self.scrollView.minimumZoomScale;
    if (_handleZooming) {
        _handleZooming(isZooming);
    }
}

// Mỗi khi scrollView bị zoom thì update constraint ép ảnh về chính giữa
- (void)scrollViewDidZoom:(UIScrollView *)scrollView {
    [self updateConstraints];
}

- (void)updateMinZoomScale {
    // Tính toán tỉ lệ chiều rộng của ảnh với chiều rộng của scrollView
    CGFloat awidthScale = CGRectGetWidth(self.contentView.bounds) / CGRectGetWidth(self.imvDetail.bounds);
    // Tính toán tỉ lệ chiều cao của ảnh với chiều cao của scrollView
    CGFloat heightScale = CGRectGetWidth(self.contentView.bounds) / CGRectGetWidth(self.imvDetail.bounds);
    CGFloat minScale = awidthScale < heightScale ? awidthScale : heightScale;
    self.scrollView.minimumZoomScale = minScale;
    self.scrollView.zoomScale = minScale;
}

// Set ảnh chính giữa scrollView
- (void)updateConstraints {
    CGFloat xOffset = MAX(0.0f, (self.contentView.bounds.size.awidth - self.imvDetail.frame.size.awidth) / 2);
    self.imageViewLeadingConstraint.constant = xOffset;
    self.imageViewTrailingConstraint.constant = xOffset;
    CGFloat yOffset = MAX(0.0f, (self.contentView.bounds.size.height - self.imvDetail.frame.size.height) / 2);
    self.imageViewTopConstraint.constant = yOffset;
    self.imageViewBottomConstraint.constant = yOffset;
    [self layoutIfNeeded];
}

- (void)zoomWhenDoubleTapped:(UIGestureRecognizer *)gesture {
    // Nếu zoomScale hiện tại > minimumZoomScale tức là ảnh đang bị zoom, douple tap sẽ zoom out về kích thước nhỏ nhất
    if (self.scrollView.zoomScale > self.scrollView.minimumZoomScale) {
        [self.scrollView setZoomScale:self.scrollView.minimumZoomScale animated:YES];
    } else {
    // Nếu zoomScale hiện tại > minimumZoomScale tức là ảnh đang bị zoom, douple tap sẽ zoom in đến kích thước lớn nhất
        [self.scrollView setZoomScale:self.scrollView.maximumZoomScale animated:YES];
    }
}

Và đây là kết quả.

result

0