图片浏览器

1. 引用的第三方库 SVProgressHUD,SDWebImage,SnapKit

2. 创建 PhotoBrowserController.swift

//  PhotoBrowserController.swift
//  Created by Hanyang Li on 2022/7/6.
//

import UIKit
import SVProgressHUD

///可重用 Cell 表示符号
private let PhotoBrowserViewCellId = "PhotoBrowserViewCellId"
//MARK: - 图片浏览器
class PhotoBrowserController: UIViewController {

    ///照片 URL 数组
    private var urls: [URL]
    ///当前选中照片索引
    private var currentIndexPath:IndexPath
    
    //MARK: - 监听方法
    @objc private func close(){
        dismiss(animated: true)
    }
    //MARK: - 保存
    @objc private func save(){
        //1.拿到图片
        let cell  = collectionView.visibleCells[0] as! PhotoBrowserCell
        //2. imageView 可能因为网络问题 没有图片 -> 下载需要提示
        guard let image = cell.imageView.image else{
            return
        }
        //3.保存图片
        UIImageWriteToSavedPhotosAlbum(image, self, #selector(image(image:didFinishSavingWithError:contextInfo:)), nil)
    }
    
    /// 保存图片回调
    /// - Parameters:
    ///   - image: 保存的图片
    ///   - error: 错误提示
    ///   - contextInfo: 相关信息
   @objc private func image(image:UIImage, didFinishSavingWithError error: NSError?, contextInfo: Any?){
        let message = (error == nil) ? "保存成功": "保存失败"
        SVProgressHUD.showSuccess(withStatus: message)
    }
    
    /// 构造函数 属性都可以是必选,不用在后续考虑解包的问题
    init(urls: [URL], indexPath: IndexPath){
        self.urls = urls
        self.currentIndexPath = indexPath
        //调用父类方法
        super.init(nibName: nil, bundle: nil)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    // 和 xib & sb 等价 职责: 创建视图层次结构 loadView 函数执行完毕,view上的元素要全部创建完成
    //如果 view == nil 系统会自动调用 view 的 getter 方法时, 自动调用 loadView 创建 view
    override func loadView() {
        //1.设置根视图
        var rect = UIScreen.main.bounds
        rect.size.width += 20
        view = UIView(frame: rect)
        //2.设置界面
        setupUI()
    }

    //视图加载完成被调用,loadView 执行完毕被执行
    //主要做数据加载,或者其他处理
    //目前很多程序没有实现 loadView ,所以在建立子控件的代码都在 viewDidLoad 中
    override func viewDidLoad() {
        super.viewDidLoad()
        //让 collectionView 滚动到指定的位置
        collectionView.scrollToItem(at: currentIndexPath, at: .centeredHorizontally, animated: false)
    }
    
    //MARK: - 懒加载控件
    private lazy var collectionView = UICollectionView(frame: CGRect.zero, collectionViewLayout: PhotoBrowserViewLayout())
    ///关闭按钮
    private lazy var closeButton = UIButton(title: "关闭", fontSize: 14, color: .white, imageName: nil, backColor: .darkGray)
    ///保存按钮
    private lazy var saveButton = UIButton(title: "保存", fontSize: 14, color: .white, imageName: nil, backColor: .darkGray)
    //MARK: - 自定义流水布局
    private class PhotoBrowserViewLayout: UICollectionViewFlowLayout{
        override func prepare() {
            super.prepare()
            itemSize = collectionView!.bounds.size
            minimumLineSpacing = 0
            minimumInteritemSpacing = 0
            scrollDirection = .horizontal
            collectionView?.isPagingEnabled = true
            collectionView?.bounces = false
            collectionView?.showsHorizontalScrollIndicator = false
        }
    }
}

//MARK: - 设置 UI
private extension PhotoBrowserController{
    //设置UI
    private func setupUI(){
        //1.添加控件
        view.addSubview(collectionView)
        view.addSubview(closeButton)
        view.addSubview(saveButton)
        
        //2.设置布局
        collectionView.frame = view.bounds
        collectionView.backgroundColor = UIColor(white: 0.1, alpha: 1.0)
        closeButton.snp.makeConstraints { make in
            make.bottom.equalTo(view.snp.bottom).offset(-8 - BottomSafeHeight)
            make.left.equalTo(view.snp.left).offset(8)
            make.size.equalTo(CGSize(width: 100, height: 36))
        }
        saveButton.snp.makeConstraints { make in
            make.bottom.equalTo(view.snp.bottom).offset(-8 - BottomSafeHeight)
            make.right.equalTo(view.snp.right).offset(-28)
            make.size.equalTo(CGSize(width: 100, height: 36))
        }
        
        //3.监听方法
        closeButton.addTarget(self, action: #selector(close), for: .touchUpInside)
        saveButton.addTarget(self, action: #selector(save), for: .touchUpInside)
        
        //4.准备控件
        prepareCollectionView()
    }
    
    /// 准备 CollectionView
    private func prepareCollectionView(){
       //1.注册可重用 Cell
        collectionView.register(PhotoBrowserCell.self, forCellWithReuseIdentifier: PhotoBrowserViewCellId)
       //2.设置数据源
        collectionView.dataSource = self
    }
}

//MARK: - UICollectionViewDataSource
extension PhotoBrowserController: UICollectionViewDataSource{
    ///数量
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return urls.count
    }
    
    ///cell
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: PhotoBrowserViewCellId, for: indexPath) as! PhotoBrowserCell
        cell.imageURL = urls[indexPath.item]
        //设置代理
        cell.photoDelegate = self
        return cell
    }
}

//MARK: - PhotoBrowserCellDelegate
extension PhotoBrowserController: PhotoBrowserCellDelegate{
    func photoBrowserCellDidTapImage() {
        close()
    }
}

//MARK: - 解除转场动画协议
extension PhotoBrowserController:PhotoBrowserDismissDelegate{
    
    func imageViewForDismiss() -> UIImageView {
        let iv = UIImageView()
        //设置填充模式
        iv.contentMode = .scaleAspectFill
        iv.clipsToBounds = true
        //设置图像 - 直接从当前显示的 cell 中获取
        let cell = collectionView.visibleCells[0] as! PhotoBrowserCell
        iv.image = cell.imageView.image
        //设置位置 - 坐标转换(由父视图进行转换)
        iv.frame = cell.scrollView.convert(cell.imageView.frame, to: keyWindow)
        //测试代码
        return iv
    }
    
    func indexPathForDismiss() -> IndexPath {
        return collectionView.indexPathsForVisibleItems[0]
    }
}

3. 创建 PhotoBrowserCell.swift

//
//  PhotoBrowserCell.swift
//  Created by Hanyang Li on 2022/7/6.
//

import UIKit
import SDWebImage
import SVProgressHUD

protocol PhotoBrowserCellDelegate: NSObjectProtocol{
    func photoBrowserCellDidTapImage()
}
//MARK: - 照片浏览 Cell
class PhotoBrowserCell: UICollectionViewCell {
    
    weak var photoDelegate:PhotoBrowserCellDelegate?
    
    //MARK: - 监听方法
    @objc private func tapImage(){
        photoDelegate?.photoBrowserCellDidTapImage()
    }
    
    /**
      手势识别是对 touch 的一个封装,UIScorllView 支持捏合手势,一般做过手势监听的控件,都会屏蔽掉 touch 事件
     */
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        print("touchesBegan")
    }
    
    //MARK: - 图像地址
    var imageURL: URL?{
        didSet{
            
            guard let url = imageURL else{
                return
            }
            
            //0.恢复scrollView
            resetScrollView()
            //1.url 缩略图的地址
            //1>从磁盘加载缩略图的图像
            let placeHolderImage = SDImageCache.shared.imageFromDiskCache(forKey: url.absoluteString)
            setPlaceHolder(image: placeHolderImage)
            //2.异步加载大图,sd_webImage 一旦设置了 URL ,准备异步加载
            //清除之前加载的图片,如果之前的图片也是异步下载,但没有完成,会取消之前的异步操作
            //4>异步加载大图
            //几乎所有的第三方框架,进度回调都是异步的
            //原因
            //1.不是所有的程序都需要进度回调
            //2.进度回调的频率非常高,如果是在主线程,会造成主线程的卡顿
            //3.使用进度回调,需求界面上跟进进度变化的 UI 不多,而且不会频繁更新
            imageView.sd_setImage(with: bmiddleURL(url: url), placeholderImage: placeHolderImage, options: [.retryFailed,.refreshCached]) { current, total, url in
                //更新进度
                DispatchQueue.main.async {
                    self.placeHolder.progress = CGFloat(current) / CGFloat(total)
                }
            } completed: { image, _, _, _ in
                //判断图像下载是否成功
                if image == nil{
                    SVProgressHUD.showError(withStatus: "您的网络不给力")
                    return
                }
                //隐藏占位图像
                self.placeHolder.isHidden = true
                //设置图像视图位置
                self.setPositon(image: image)
            }
        }
    }
    
    
    ///  设置占位图像视图的内容
    /// - Parameter image: 本地缓存的缩略图,如果缩略图下载失败,image 为nil
    private func setPlaceHolder(image: UIImage?){
        //显示
        placeHolder.isHidden = false
        placeHolder.image = image
        //2>设置大小
        placeHolder.sizeToFit()
        //3>设置中心点
        placeHolder.center = scrollView.center
    }
    
    //重置 ScrollView
    private func resetScrollView(){
        //重设 imageView 的内容属性 - scrollView 在处理缩放的时候,是调整代理方法返回视图的 transform 来实现的
        imageView.transform = .identity
        //重设 ScrollView 内容属性
        scrollView.contentInset = UIEdgeInsets.zero
        scrollView.contentOffset = CGPoint.zero
        scrollView.contentSize = CGSize.zero
    }
    
    /// 设置 imageView 的位置 长短图的显示
    /// - Parameter image: iamge
    private func setPositon(image: UIImage?){
        
        guard let image = image else{
            return
        }
        
        //自动设置大小
        let size = self.displaySize(image: image)
        //判断图片高度
        if size.height < scrollView.bounds.height{
            //上下居中显示
            self.imageView.frame = CGRect(x: 0, y: 0, width: size.width, height: size.height)
            //内容边距
            let y = (scrollView.bounds.height - size.height) * 0.5
            scrollView.contentInset = UIEdgeInsets(top: y, left: 0, bottom: 0, right: 0)
        }else{
            self.imageView.frame = CGRect(x: 0, y: 0, width: size.width, height: size.height)
            scrollView.contentSize = size
           
        }
        scrollView.contentInsetAdjustmentBehavior = .never
    }
    
    /// 根据 scrollView 的宽度计算等比例缩放之后的图片尺寸
    /// - Parameter image: image
    /// - Returns: 缩放之后的 size
    private func displaySize(image: UIImage) -> CGSize{
        let w = scrollView.bounds.width
        let h = image.size.height * w / image.size.width
        return CGSize(width: w, height: h)
    }
    
    /// 返回中等尺寸图片 URL
    /// - Parameter url: 缩略图 url
    /// - Returns: 中等尺寸 URL
    private func bmiddleURL(url: URL) -> URL{
        //1.转换成 string
        var urlString = url.absoluteString
        //2.替换单词 large 大等尺寸图片地址 thumbnail:缩略图片地址  bmiddle:中等尺寸图片地址  original:原始图片地址
        urlString = urlString.replacingOccurrences(of: "/thumbnail/", with: "/large/")
        //print(urlString)
        return URL(string: urlString)!
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupUI()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    private func setupUI(){
        //1.添加控件
        contentView.addSubview(scrollView)
        scrollView.addSubview(imageView)
        scrollView.addSubview(placeHolder)
        
        //2.设置位置
        var rect = bounds
        rect.size.width -= 20
        scrollView.frame = rect
        
        //3.设置 scrollView 缩放
        scrollView.delegate = self
        scrollView.maximumZoomScale = 2.0
        scrollView.minimumZoomScale = 0.5
        
        //4.添加手势识别
        let tap = UITapGestureRecognizer(target: self, action: #selector(tapImage))
        imageView.isUserInteractionEnabled = true
        imageView.addGestureRecognizer(tap)
    }
    
    //MARK: - 懒加载控件
    lazy var scrollView = UIScrollView()
    lazy var imageView = UIImageView()
    /// 占位图像
    private lazy var placeHolder = ProgressImageView()
}

//MARK: - UIScrollViewDelegate
extension PhotoBrowserCell: UIScrollViewDelegate{
    
    ///返回被缩放的视图
    func viewForZooming(in scrollView: UIScrollView) -> UIView? {
        return imageView
    }
    
    /// 缩放完成执行一次
    /// - Parameters:
    ///   - scrollView: scrollView
    ///   - view: view 被缩放的视图
    ///   - scale: 被缩放的比例
    func scrollViewDidEndZooming(_ scrollView: UIScrollView, with view: UIView?, atScale scale: CGFloat) {
        //print("缩放完成\(view) \(view?.bounds)")
        var offsetY = (scrollView.bounds.height - view!.frame.height) * 0.5
        offsetY = offsetY < 0 ? 0 : offsetY
        var offsetX = (scrollView.bounds.width - view!.frame.width) * 0.5
        offsetX = offsetX < 0 ? 0 : offsetX
        //设置间距
        scrollView.contentInset = UIEdgeInsets(top: offsetY, left: offsetX, bottom: 0, right: 0)
    }
    
    
    /// 只要缩放就会被调用
    /**
       a d  => 缩放比例
       a b c d => 共同决定旋转
       tx ty => 设置位移
       定义控件位置 frame  = center + bounds + transforms
     */
    func scrollViewDidZoom(_ scrollView: UIScrollView) {
        //print(imageView.transform)
    }
}

4. 创建 ProgressImageView.swift

//
//  ProgressImageView.swift
//  Created by Hanyang Li on 2022/7/7.
//

import UIKit


/// 带进度的图像视图 不会执行 drawRect 函数
class ProgressImageView: UIImageView {
    
    //外部传递的进度值 0~1
    var progress: CGFloat = 0{
        didSet{
            progressView.progress = progress
        }
    }
    
    //一旦给构造函数指定了参数,系统不在提供默认的构造函数,默认的构造函数会被覆盖
    init(){
        super.init(frame: CGRect.zero)
        setupUI()
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupUI()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    //设置布局
    private func setupUI(){
        addSubview(progressView)
        progressView.backgroundColor = .clear
        //自动布局
        progressView.snp.makeConstraints { make in
            make.edges.equalTo(self.snp.edges)
        }
    }
    
   //MARK: - 懒加载控件
    private lazy var progressView =  ProgressView()
    
}

//进度视图
private class ProgressView: UIView{
    //内部使用的进度值 0~1
    var progress: CGFloat = 0{
        didSet{
            //重绘视图
            setNeedsDisplay()
        }
    }
    
    override func draw(_ rect: CGRect) {
        //中心点
        let center = CGPoint(x: rect.width * 0.5, y: rect.height * 0.5)
        let r = min(rect.width, rect.height)  * 0.5
        let start  = -.pi * 0.5
        let end = start + progress * 2 * .pi
        /**
           参数
            1.中心点
            2.半径
            3.起始弧度
            4.结束弧度
            5.是否顺时针
         */
        let path = UIBezierPath(arcCenter: center, radius: r, startAngle: start, endAngle: end, clockwise: true)
        //添加到中心点点连线
        path.addLine(to: center)
        path.close()
        UIColor(white: 1.0, alpha: 0.3).setFill()
        path.fill()
    }
}

5. 创建 PhotoBrowserAnimator.swift

//
//  PhotoBrowserAnimator.swift
//  Created by Hanyang Li on 2022/7/14.
//

import UIKit

//MARK: - 展现动画的协议
protocol PhotoBrowserPresentDelegate: NSObjectProtocol{
    /// 指定 indexPath 对应的 imageView,用来做动画效果
    func imageViewForPresent(indexPath: IndexPath) -> UIImageView
    
    /// 动画转场的起始位置
    func photoBrowserPresentFromRect(indexPath: IndexPath) -> CGRect
    
    /// 动画转场的目标位置
    func photoBrowserPresentToRect(indexPath: IndexPath) -> CGRect
}

//MARK: - 解除动画协议
protocol PhotoBrowserDismissDelegate: NSObjectProtocol{
    /// 解除转场到图像视图 (包含起始位置)
    func imageViewForDismiss() -> UIImageView
    
    /// 解除转场的图像索引
    func indexPathForDismiss() -> IndexPath
}

//MARK: - 提供动画转场的 "代理"
class PhotoBrowserAnimator: NSObject, UIViewControllerTransitioningDelegate{
    
    //展现代理
    weak var presentDelegate: PhotoBrowserPresentDelegate?
    //解除代理
    weak var dismissDelegate: PhotoBrowserDismissDelegate?
    
    //动画图像的索引
    var indexPath:IndexPath?
    ///是否 modal 展现的标记
    private var isPresented = false
    
    ///  设置代理相关属性
    /// - Parameters:
    ///   - presentDelegate: 展现代理对象
    ///   - indexPath:  图像的索引
    func setDelegateParams(presentDelegate:PhotoBrowserPresentDelegate, indexPath: IndexPath, dismissDelegate: PhotoBrowserDismissDelegate){
        self.presentDelegate = presentDelegate
        self.indexPath = indexPath
        self.dismissDelegate = dismissDelegate
    }
    
    //返回提供 modal 展现的 ’动画对象‘
    func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        isPresented = true
        return self
    }
    
    //返回提供 dismiss 的 ‘动画对象’
    func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        isPresented = false
        return self
    }
}


//MARK: - UIViewControllerAnimatedTransitioning
//实现具体的动画时长
extension PhotoBrowserAnimator: UIViewControllerAnimatedTransitioning{
    
    
    //动画时长
    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return 0.5
    }
    
    /// 实现具体的动画效果 - 一旦实现了此方法,所有的动画代码都交由程序员负责
    /// - Parameter transitionContext: 转场动画的上下文,提供动画所需要的素材
    /**
       1.容器视图 - 会将 Modal 要展现的视图包装在容器视图中
          存放的视图要显示 - 必须自己指定大小! 不会通过自动布局填满屏幕
       2.transitionContext.viewController: Key.from / to
       3.transitionContext.view: Key.from/to
       4.completeTransition: 无论转场是否被被取消,都必须调用,否则,系统不做其他事件处理!
     */
    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
       
        //自动布局系统不会对根视图做任何约束
        //let fromVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)
        //print(fromVC)
        //let toVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)
        ///print(toVC)
        
        //let fromView = transitionContext.view(forKey: UITransitionContextViewKey.from)
        //print(fromView)
        
        //let toView = transitionContext.view(forKey: UITransitionContextViewKey.to)
        //print(toView)
       
        isPresented ? presentAnimation(using: transitionContext) : dismissAnimation(using: transitionContext)
    }
    
    //解除转场动画
    private func dismissAnimation(using transitionContext: UIViewControllerContextTransitioning){
        
        //判断参数是否存在 guard let 会把属性变成局部变量,后续的闭包中不需要 self,也不需要考虑解包
        guard let presentDelegate = presentDelegate, let dismissDelegate = dismissDelegate else{
            return
        }
        
        //1.获取要 dismiss 的控制器的视图
        let fromView = transitionContext.view(forKey: UITransitionContextViewKey.from)!
        fromView.removeFromSuperview()
        
        //2.获取图像视图
        let iv = dismissDelegate.imageViewForDismiss()
        //3.添加到容器视图
        transitionContext.containerView.addSubview(iv)
        //4.获取 dismiss 的indexPath
        let indexPath = dismissDelegate.indexPathForDismiss()
        
        UIView.animate(withDuration: transitionDuration(using: transitionContext)) {
           //让 iv 运动到目标位置
            iv.frame = presentDelegate.photoBrowserPresentFromRect(indexPath: indexPath)
        } completion: { _ in
            //将 fromView 从父视图中删除
            iv.removeFromSuperview()
            //告诉系统,动画已完成
            transitionContext.completeTransition(true)
        }
    }
    
    /// 展现动画
    private func presentAnimation(using transitionContext: UIViewControllerContextTransitioning){
        
        //0.判断参数是否存在
        guard let presentDelegate = presentDelegate, let indexPath = indexPath else{
            return
        }
        
        //1.目标视图
        //1>.获取 modal 要展现的控制器的根视图
        let toView = transitionContext.view(forKey: UITransitionContextViewKey.to)!
        
        // 2> 添加到容器视图
        //transitionContext.containerView.addSubview(toView)
        // 3> 设置透明度
        //toView.alpha = 0
        
        //2.图像视图
        //1>.能够拿到参数与动画的图像视图 /  / 目标位置
        let iv = presentDelegate.imageViewForPresent(indexPath: indexPath)
        //2>.制定图像视图位置 /起始位置
        iv.frame = presentDelegate.photoBrowserPresentFromRect(indexPath: indexPath)
        //3>.将视图添加到容器中
        transitionContext.containerView.addSubview(iv)
        
        //3.开始动画
        UIView.animate(withDuration: transitionDuration(using: transitionContext)) {
            iv.frame = presentDelegate.photoBrowserPresentToRect(indexPath: indexPath)
            //toView.alpha = 1.0
        } completion: { _ in
            //图像视图删除
            iv.removeFromSuperview()
            transitionContext.containerView.addSubview(toView)
            //4.告诉系统转场动画完成
            transitionContext.completeTransition(true)
        }
    }
}

6. 照片查看器的展现协议

//MARK: - 照片查看器的展现协议
extension StatusPictureView: PhotoBrowserPresentDelegate{
    
    //创建一个 ImageView 在参与动画
    func imageViewForPresent(indexPath: IndexPath) -> UIImageView {
        let iv = UIImageView()
        //1.设置内容填充模式
        iv.contentMode = .scaleAspectFill
        iv.clipsToBounds = true
        //2.设置图像 (缩略图的缓存) - SDWebImage 如果已经存在本地缓存,不会发起网络请求
        if let url = viewModel?.thumbnailUrls?[indexPath.item]{
            iv.sd_setImage(with: url)
        }
        return iv
    }
   

    ///动画的起始位置
    func photoBrowserPresentFromRect(indexPath: IndexPath) -> CGRect {
        //1.根据 indexPath 获得当前用户选择的 cell
        let cell = self.cellForItem(at: indexPath)!
        //2.通过 cell 知道 cell 对应在屏幕上的准确位置
        //在不同视图之间的 ‘坐标系的转换’ self 是 cell 的父视图
        //由 collectionView 将 cell 的 frame 位置转换的 keyWindow 对应的 frame 位置
        let rect =  self.convert(cell.frame, to: keyWindow)
        return rect
    }
    
    ///动画的目标位置
    func photoBrowserPresentToRect(indexPath: IndexPath) -> CGRect {
        //根据缩略图的大小,等比例计算目标位置
        guard let key = viewModel?.thumbnailUrls?[indexPath.item].absoluteString else{
            return .zero
        }
     
        //从 sdwebImage 获取本地缓存图片
        guard let image = SDImageCache.shared.imageFromDiskCache(forKey: key) else{
            return .zero
        }
        //根据图像大小,计算全屏的大小
        let w = screenWidth
        let h = image.size.height * w / image.size.width
        //对高度进行额外的处理
        var y:CGFloat = 0
        if h < screenHeight { //图片短,垂直居中显示
            y = (screenHeight - h) * 0.5
        }
        
        return CGRect(x: 0, y: y, width: w, height: h)
    }
}

7. 调用方法

 //[weak self] 弱引用
guard let indexPath = notification.userInfo?[StatusSelectPhotoIndexPathKey] as? IndexPath else{
    return
}
guard let urls = notification.userInfo?[StatusSelectPhotoURLsKey] as? [URL] else{
    return
}
//判断 cell 是否遵守了展现动画协议
guard let cell = notification.object as? PhotoBrowserPresentDelegate else{
    return
}

//Modal 展现
let vc = PhotoBrowserController(urls: urls, indexPath: indexPath)
vc.modalPresentationStyle = .fullScreen
//1.设置 modal 的类型是自定义类型 Transition (转场)
vc.modalPresentationStyle = .custom
//2.设置动画代理
vc.transitioningDelegate = self?.photoBrowserAnimator

//3.设置 animator 的代理参数
self?.photoBrowserAnimator.setDelegateParams(presentDelegate: cell, indexPath: indexPath, dismissDelegate: vc)
//参数设置所有权交给调用方 (一旦调用方失误漏传参数,可能造成不必要的麻烦)
//会有一系列的 ...
//self?.photoBrowserAnimator.presentDelegate = cell
//self?.photoBrowserAnimator.indexPath = indexPath
//self?.photoBrowserAnimator.dismissDelegate = vc
//4. model 展现
self?.present(vc, animated: true, completion: nil)

8. 效果图片

      

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hanyang Li

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值