Tạo slide show ảnh với UICollectionView và UIScrollView trong iOS

Introduction

Trong bài viết này, chúng ta sẽ cùng nhau sử dụng UICollectionViewUIScrollVIew để tạo một slide show ảnh cực kỳ đơn giản và dễ dàng, gần giống với slide show trên app Facebook iOS.

Let's start

Đầu tiên, mở Xcode lên và bắt đầu tạo project mới.

Trong Main.storyboard, View Controller Scene kéo thả 1 một UIButton mới, đổi text thành Go to slide show, thêm constraint center X, center Y.

Từ của sổ Show the Object libary, kéo thêm UIViewController mới vào storyboard. Chọn button ở UIViewController đầu, giữ Control và kéo thả vào UIViewController mới để tạo segue.

Tạo mới class SlideShowViewController và set custom class này với UIViewController thứ 2.

Trong Slide Show View Controller Scene, thêm một UICollectionView, constraint top, bottom, leading, trailing với super view.

Tiếp theo, set dataSource, delegate cho collectionView bằng cách giữ Control và chọn collectionView, kéo thả tới Slide Show View Controller, rồi chọn dataSource, delegate.

Đổi scroll đirection của collectionView từ Vertical thành Horizontal.

Để dễ thao tác, resize lại cell trong collectionView to bằng kích thước super view.

Sau đó, kéo thêm một UIScrollView vào trong cell và cũng set constraint top, bottom, leading, trailing với constant = 0 với contentView của cell.

Tiếp tục thêm 1 UIImageView vào trong UIScrollView mới tạo và set constraint top, bottom, leading, trailing super view với constant = 0. Set Content Mode của imageView thành Aspect Fit, Clip to Bounds.

Set background color cho UIImageView, UIScrollView, UICollectionViewCell trên thành màu đen.

Tạo mới class PhotoCollectionViewCell: UICollectionViewCell, set custom class cho cell trong UICollectionView. Set identifier cho cell là PhotoCell.

Sau đó, tạo các @IBOutlet tương ứng trong PhotoCollectionViewCell như sau:

import UIKit

class PhotoCollectionViewCell: UICollectionViewCell {

    @IBOutlet private weak var scrollView: UIScrollView!
    @IBOutlet private weak var imageView: UIImageView!

    @IBOutlet private weak var imageViewTopConstraint: NSLayoutConstraint!
    @IBOutlet private weak var imageViewBottomConstraint: NSLayoutConstraint!
    @IBOutlet private weak var imageViewLeadingConstraint: NSLayoutConstraint!
    @IBOutlet private weak var imageViewTrailingConstraint: NSLayoutConstraint!

}

Tiếp tục implement, hoàn thiện class PhotoCollectionViewCell.

class PhotoCollectionViewCell: UICollectionViewCell {

    @IBOutlet private weak var scrollView: UIScrollView!
    @IBOutlet fileprivate weak var imageView: UIImageView!

    @IBOutlet private weak var imageViewTopConstraint: NSLayoutConstraint!
    @IBOutlet private weak var imageViewBottomConstraint: NSLayoutConstraint!
    @IBOutlet private weak var imageViewLeadingConstraint: NSLayoutConstraint!
    @IBOutlet private weak var imageViewTrailingConstraint: NSLayoutConstraint!

    var scrollViewDidZoomed: (Bool) -> Void = { _ in }

    override func awakeFromNib() {
        super.awakeFromNib()
        // Set delegate cho scrollView
        scrollView.delegate = self
        // Thêm một gesture double tap để zoom in và zoom out
        let zoomGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(zoomWhenDoubleTapped))
        zoomGestureRecognizer.numberOfTapsRequired = 2
        contentView.addGestureRecognizer(zoomGestureRecognizer	)
    }

    func setPhoto(_ photoName: String) {
        imageView.image = UIImage(named: photoName)
        // Calculate minimumZoomScale, maximumZoomScale
        updateMinZoomScale()
        // Centering imageView
        updateImageViewConstraints()
    }

    private func updateImageViewConstraints() {
        let xOffset = max(0.0, (contentView.frame.size.width - imageView.frame.size.width) / 2)
        imageViewLeadingConstraint.constant = xOffset
        imageViewTrailingConstraint.constant = xOffset
        let yOffset = max(0.0, (contentView.frame.size.height - imageView.frame.size.height) / 2)
        imageViewTopConstraint.constant = yOffset
        imageViewBottomConstraint.constant = yOffset
        layoutIfNeeded()
    }

    @objc private func zoomWhenDoubleTapped(_ gesture: UITapGestureRecognizer) {
        // 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 (scrollView.zoomScale > scrollView.minimumZoomScale) {
            scrollView.setZoomScale(scrollView.minimumZoomScale, animated: true)
        } 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
            scrollView.setZoomScale(scrollView.maximumZoomScale, animated: true)
        }
    }

    private func updateMinZoomScale() {
        guard let image = imageView.image else {
            return
        }
        // Tính toán tỉ lệ chiều rộng của ảnh với chiều rộng của contentView
        let widthScale = contentView.bounds.width / image.size.width
        // Tính toán tỉ lệ chiều cao của ảnh với chiều cao của contentView
        let heightScale = contentView.bounds.height / image.size.height
        let minScale = min(widthScale, heightScale)
        scrollView.minimumZoomScale = minScale
        scrollView.zoomScale = minScale
        scrollView.maximumZoomScale = max(1, minScale * 3)
    }

}

// MARK: - UIScrollViewDelegate

extension PhotoCollectionViewCell: UIScrollViewDelegate {

    // Set view cần zoom trong scrollView
    func viewForZooming(in scrollView: UIScrollView) -> UIView? {
        return imageView
    }

    // Nếu đang zoom thì call closure disable scrolling collectionView
    func scrollViewWillBeginZooming(_ scrollView: UIScrollView, with view: UIView?) {
        let isOriginalSize = scrollView.zoomScale == scrollView.minimumZoomScale
        scrollViewDidZoomed(isOriginalSize)
    }

    // Centering imageView
    func scrollViewDidZoom(_ scrollView: UIScrollView) {
        updateImageViewConstraints()
    }

}

Trong SlideShowViewController, thêm một mảng String lưu tên các ảnh sẽ hiển thị.

class SlideShowViewController: UIViewController {

    @IBOutlet fileprivate weak var slideShowCollectionView: UICollectionView!

    fileprivate let photos = [
        "photo1",
        "photo2",
        "photo3",
        "photo4",
        "photo5"
    ]

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        UIApplication.shared.isStatusBarHidden = true
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        UIApplication.shared.isStatusBarHidden = false
    }

    @IBAction private func closeButtonTapped(_ sender: Any) {
        dismiss(animated: true, completion: nil)
    }

}

Màn hình slide show, chúng ta sẽ ẩn status bar. Vì vậy trong Info.plist, hãy thêm property View controller-based status bar appearance với value NO.

Cuối cùng, implement các function cần thiết của 2 protocol UICollectionViewDataSourceUICollectionViewDelegateFlowLayout.

// MARK: - UICollectionViewDataSource

extension SlideShowViewController: UICollectionViewDataSource {

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return photos.count
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "PhotoCell", for: indexPath) as? PhotoCollectionViewCell else {
            return UICollectionViewCell()
        }
        // Set photo name cho cell
        cell.setPhoto(photos[indexPath.row])
        // Implement closure
        cell.scrollViewDidZoomed = { isOriginalSize in
            collectionView.isScrollEnabled = !isOriginalSize
        }
        return cell
    }

}

// MARK: - UICollectionViewDelegateFlowLayout

extension SlideShowViewController: UICollectionViewDelegateFlowLayout {

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout,
                        sizeForItemAt indexPath: IndexPath) -> CGSize {
        // Mỗi cell sẽ có kích thước full màn hình
        return view.frame.size
    }

}

Result

Kết quả, chúng ta được slide show ảnh như này.

Link final project: https://github.com/oNguyenXuanThanh/StudyReport082018