Làm viewcontroller hiển thị photo giống facebook
1. Hiển thị photo giống facebook Nếu mọi ngươi dùng app facebook thì sẽ hay xem ảnh trên đó, khi ấn vào ảnh thì sẽ có 1 viewcontroller hiện lên kèm theo ảnh đó, chúng ta có thể zoom, double tap vào ảnh đó để nó có thể phóng to ra. Ngoài ra tùy vào kích thước của anh mà chúng ta sẽ thấy bức ...
1. Hiển thị photo giống facebook
Nếu mọi ngươi dùng app facebook thì sẽ hay xem ảnh trên đó, khi ấn vào ảnh thì sẽ có 1 viewcontroller hiện lên kèm theo ảnh đó, chúng ta có thể zoom, double tap vào ảnh đó để nó có thể phóng to ra. Ngoài ra tùy vào kích thước của anh mà chúng ta sẽ thấy bức ảnh đc hiển thị theo chiều ngang hoặc dọc, việc này làm cho bức ảnh hiển thị lên đẹp nhất có thể, hôm nay chúng ta sẽ làm viewcontroller tương tự vậy
2. Các components dùng trong viewcontroller
2.1 UIImageView
Có thể nói đây sẽ là thành phần chính được sử dụng trong viewcontroller, nó cho phép chúng ta hiển thị bức ảnh trong viewcontroller. Có 1 điểm khác biệt là ở đây chúng ta có thể zoom được ảnh, để làm được điều này chúng ta sẽ phải đặt UIImageView vào trong 1 UIScrollView, chúng ta sẽ đặt tên nó là class ScalingImageView: UIScrollView
class ScalingImageView: UIScrollView { }
class ScalingImageView: UIScrollView sẽ có 2 component chính là image chính là ảnh chúng ta cần hiển thị và 1 imageView để hiển thị ảnh đó
var image: UIImage? { didSet { if let image = image { self.updateWithImage(image) } } } lazy var imageView: UIImageView = { let view = UIImageView() view.contentMode = .scaleAspectFill view.clipsToBounds = true return view }()
Vì trong ScalingImageView sẽ cho phép zoom ảnh nên chúng ta sẽ có 1 hàm tính toán khả năng zoom ảnh dựa vào kích thước của ảnh, nếu kích thước bức ảnh quá bé, bé hơn kích thước của ScalingImageView thì sẽ không cho zoom
fileprivate func updateZoomScaleWithImage(_ image: UIImage) { let scrollViewFrame = bounds let imageSize = image.size let awidthScale = scrollViewFrame.awidth / imageSize.awidth let heightScale = scrollViewFrame.height / imageSize.height let minScale = min(awidthScale, heightScale) self.minimumZoomScale = minScale self.maximumZoomScale = minScale * 4 if (imageSize.height / imageSize.awidth) > (scrollViewFrame.height / scrollViewFrame.awidth) { self.maximumZoomScale = max(maximumZoomScale, awidthScale) } self.zoomScale = minimumZoomScale self.panGestureRecognizer.isEnabled = false }
2.2 Photo
Trong class này chúng ta có 2 component chính là image chúng ta cần hiển thị và 1 block updatedImage dùng để update image lên imageView sau khi nó đã download xong
protocol PhotoProtocol: class { var image: UIImage? { get set } var updatedImage: ((_ image: UIImage?) -> Void)? { get set } } class Photo: NSObject, PhotoProtocol { }
Khi tạo 1 object photo chúng ta có thể gán trực tiếp image cho nó hoặc chỉ gán url, từ đó mỗi khi kích vào object đó PhotoViewController sẽ tiến hành download rồi cache lại nó để hiển thị.
2.3 Kingfisher
Vì ở đây chúng ta dùng cả url của anh để hiển thị cho nên cần 1 framework download -> cache lại ảnh rồi hiển thị khi cần thiết, Kingfisher sẽ làm việc đó cho chúng ta. Khi init 1 object Photo chúng ta có thể gán trực tiếp image cần hiện thị vào nó thông qua property image hoặc gán url image để Kingfisher tự download và cache nó.
var image: UIImage? { didSet { self.updatedImage?(image) } } var updatedImage: ((UIImage?) -> Void)? init(_ imageUrl: String) { super.init() if let url = URL(string: imageUrl) { let iamgeResouce = ImageResource(downloadURL: url) KingfisherManager.shared.retrieveImage(with: iamgeResouce, options: nil, progressBlock: nil, completionHandler: {[weak self](image, error, cacheType, imageURL) in guard let weakSelf = self else { return } if let image = image { weakSelf.image = image } }) } }
Mỗi khi chúng ta tạo 1 object Photo kèm theo url Kingfisher sẽ download image rồi cache lại nó rồi trả về thông qua property image
3. Tạo PhotoViewController
PhotoViewController sẽ có 5 component chính
@IBOutlet weak var topView: UIView! @IBOutlet weak var bottomView: UIView! @IBOutlet weak var activityIndicator: UIActivityIndicatorView! lazy var scalingImageView: ScalingImageView = { let view = ScalingImageView(frame: self.view.bounds) view.delegate = self return view }() var photo: Photo?
Trong đó, topView sẽ chức các component nhỏ như button close, label information, avatar ..., còn bottomView chức các thông tin như like, share, comment của image, scalingImageView chứa image chính của chúng ta. saclingImageView có thể init trực tiếp trên storyboard hoặc qua code, ở đây mình init nó qua code
fileprivate func initImageView() { self.scalingImageView.frame = self.view.bounds self.scalingImageView.image = self.photo?.image self.view.insertSubview(scalingImageView, belowSubview: self.topView) self.photo?.updatedImage = {[weak self] image in guard let weakSelf = self else { return } weakSelf.scalingImageView.image = image if image != nil { weakSelf.activityIndicator.stopAnimating() } } if self.photo?.image == nil { self.activityIndicator.startAnimating() } }
Ở hàm initImageView chúng ta gọi thêm hàm updatedImage từ photo, để khi Kingfisher cache xong image nó sẽ gọi lại trong viewcontroller và gán image vào imageView, đồng thời stop animation của activity. Ở PhotoViewController chúng ta init thêm 1 func doubleTap cho imageView
@objc fileprivate func didDoubleTap(_ sender: UITapGestureRecognizer) { let scrollViewSize = self.scalingImageView.bounds.size var pointInView = sender.location(in: self.scalingImageView.imageView) var newZoomScale = min(scalingImageView.maximumZoomScale, self.scalingImageView.minimumZoomScale * 2) if let imageSize = scalingImageView.imageView.image?.size, (imageSize.height / imageSize.awidth) > (scrollViewSize.height / scrollViewSize.awidth) { pointInView.x = scalingImageView.imageView.bounds.awidth / 2 let awidthScale = scrollViewSize.awidth / imageSize.awidth newZoomScale = awidthScale } let isZoomIn = (scalingImageView.zoomScale >= newZoomScale) || (abs(scalingImageView.zoomScale - newZoomScale) <= 0.01) if isZoomIn { newZoomScale = scalingImageView.minimumZoomScale } scalingImageView.isDirectionalLockEnabled = !isZoomIn let awidth = scrollViewSize.awidth / newZoomScale let height = scrollViewSize.height / newZoomScale let originX = pointInView.x - (awidth / 2) let originY = pointInView.y - (height / 2) let rectToZoomTo = CGRect(x: originX, y: originY, awidth: awidth, height: height) self.scalingImageView.zoom(to: rectToZoomTo, animated: true) }
Hàm này sẽ có 2 chức năng, zoom in ảnh khi ở trạng thái chưa zoom và zoom out ảnh khi đang ở trạng thái zoom in.
4. Demo
https://github.com/pqhuy87it/MonthlyReport/tree/master/PhotoViewController