Swift.自定制相册,实现首个cell是拍照功能

效果图

前言:

过去有个项目有了这个需求.当时选择从git上找了个框架直接来用,但是其中很多功能是我并不需要的,以及想要了解一下这一块的具体实现方式,就选择自己参考其重写了一下,主要运用的知识就是photos框架以及图片编辑那一块对view的操作逻辑.
如果对这些感兴趣的可以仔细看下项目参考下.如果只是有类似需求可以直接拉到最下方使用方式.

实现功能:

自定制相册.

实现相片展示页首个cell是拍照功能.

实现相片选中裁切功能.

实现思路:

  1. 使用photos框架获取手机内所有图片.

  2. 使用CollectionView将图片展示,并实现首个cell是相机按钮功能.

  3. 新建控制器实现选中照片编辑功能.

  4. 使用代理将编辑完照片回调.

实现方式:

  1. 在info.plist文件中添加相机与相册调用权限.

  2. 创建EWPickerManager类,用于获取以及存储数据.

  3. 获取手机内所有相片,PHFetchResult()格式

  4. 将相片转换成image格式,并将其存储为一个Array.

  5. 创建相册展示页ViewController.为其添加CollectionView.并将获取的相片数据展示,同时实现首个cell是相机展示.

  6. 创建相片选中编辑页.为其添加背景展示图片View,上层覆盖半透明View,下方取消确认按钮View.以及中间透明裁切区域View.

  7. 添加相片方向校正方法,保证相片正常展示.

  8. 为编辑页添加缩放手势以及拖拽手势.

  9. 添加frame验证方法,保证相片在缩放拖拽后展示效果.

  10. 添加相片裁切方法.

  11. 创建EWImageCropperDelegate将编辑完成的相片回调.

  12. 添加调用相机功能.

  13. 为相册展示页CollectionView.Cell添加点击方法.

  14. 使用自定制navigationController将交互串联.


0.在info.plist文件中添加相机与相册调用权限.

所有与相机相册相关的功能都需要添加权限说明,重要!

	<key>NSPhotoLibraryUsageDescription</key>
	<string>相册权限</string>
	<key>NSCameraUsageDescription</key>
	<string>相机权限</string>

1.创建EWPickerManager类,用于获取以及存储数据.

class EWPickerManager: NSObject {

    private(set) var photoAlbum  = PHFetchResult<PHAsset>()

    private var photoManage = PHCachingImageManager()
    /// 照片获取偏好设置
    private let photoOption = PHImageRequestOptions()
    //pickerCell照片的size
    class public var pickerPhotoSize: CGSize {
        let sreenBounds = ScreenInfo.Frame
        let screenWidth = sreenBounds.width > sreenBounds.height ? sreenBounds.height : sreenBounds.width
        let width = (screenWidth - CGFloat(9)) / CGFloat(4)
        return CGSize(width: width, height: width)
    }

    override init() {
        super.init()
        // 如何调整所请求的图像大小。
        self.photoOption.resizeMode   = .fast //最快速的调整图像大小,有可能比给定大小略大
        // 请求的图像质量和交付优先级。
        self.photoOption.deliveryMode = .opportunistic //平衡图像质量和响应速度
        // 是否同步处理一个图像请求.
        self.photoOption.isSynchronous = true
        getPhotoAlbums()
    }
}

2.获取手机内所有相片,PHFetchResult()格式

      /// 获取手机中所有相册照片源
    private func getPhotoAlbums(){
        //创建一个PHFetchOptions对象检索照片
        let options = PHFetchOptions()
        //通过创建时间来检索
        options.sortDescriptors = [NSSortDescriptor.init(key: "creationDate", ascending: false)]
        //通过数据类型来检索,这里为只检索照片
        options.predicate = NSPredicate.init(format: "mediaType in %@", [PHAssetMediaType.image.rawValue])
        //通过检索条件检索出符合检索条件的所有数据,也就是所有的照片
        let allResult = PHAsset.fetchAssets(with: options)
        //将获取的相片加入到相片的数组中
        photoAlbum = allResult
    }

3.将相片转换成image格式,并将其存储为一个Array.

     /// 获取手机相册内所有照片
    ///
    /// - Returns: 手机相册内所有照片
    public func getAllPhoto() -> [UIImage]{
        var imageArray = [UIImage]()
        let scale = UIScreen.main.scale
        /// 重要,不对size进行重置会使显示效果变差
        let photoScaleSize = CGSize(width: EWPickerManager.pickerPhotoSize.width * scale, height: EWPickerManager.pickerPhotoSize.height * scale)
        /// 将图片添加到数组
        for i in 0 ..< self.photoAlbum.count {
            /// 按顺序获取图片
            self.photoManage.requestImage(for: self.photoAlbum[i], targetSize: photoScaleSize, contentMode: .aspectFill, options: self.photoOption) { (image, infoDic) in
                if image != nil{
                    imageArray.append(image!)
                }
            }
        }
        return imageArray
    }

4.创建相册展示页ViewController.为其添加CollectionView.并将获取的相片数据展示,同时实现首个cell是相机展示.

import UIKit

class EWPhotoCollectionViewController: UIViewController {
    public var delegate: EWImageCropperDelegate?
    private let manager =  EWPickerManager()
    private var photoArray = [UIImage]()

    private let collectionView: UICollectionView = {
        let defaultLayout = UICollectionViewFlowLayout()
        defaultLayout.scrollDirection = UICollectionViewScrollDirection.vertical//设置垂直显示
        defaultLayout.minimumLineSpacing = 3 //每个相邻的layout的上下间隔
        defaultLayout.minimumInteritemSpacing = 3.0 //每个相邻layout的左右间隔
        let collectionView = UICollectionView(frame:CGRect(x: 0, y: 88, width: ScreenInfo.Width, height: ScreenInfo.Height - ScreenInfo.navigationHeight), collectionViewLayout: defaultLayout)
        collectionView.backgroundColor = UIColor.white
        return collectionView
    }()

    override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
    }
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.title = "相册"
        getPhotoData()
        drawMyView()
        drawMyNavigationBar()
    }
    private func drawMyView(){
        self.view.backgroundColor = UIColor.white
        collectionView.delegate = self
        collectionView.dataSource = self
        collectionView.alwaysBounceVertical = true
        /// 使用动态注册阻止collectionView重用
        for i in 0 ..< photoArray.count+1{
            collectionView.register(EWPhotoCollectionViewCell.self, forCellWithReuseIdentifier: "EWPhotoCollectionViewCell\(i)")
        }
        self.view.addSubview(collectionView)
    }
    private func drawMyNavigationBar(){
        let button = UIBarButtonItem(image: EWBundle.imageFromBundle("image_back"), style: .plain, target: self, action: #selector(onClickBackButton))
        self.navigationItem.leftBarButtonItem = button
    }
    /// 获取所有照片
    private func getPhotoData(){
        self.photoArray = manager.getAllPhoto()
    }
    @objc private func onClickBackButton(){
        self.dismiss(animated: true, completion: nil)
    }
}
//MARK: - CollectionViewDelegate
extension EWPhotoCollectionViewController: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout{
    func numberOfSections(in collectionView: UICollectionView) -> Int {
        return 1
    }
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        /// 返回数据数组.count 加一个新建按钮
        return self.photoArray.count + 1
    }
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier:  "EWPhotoCollectionViewCell\(indexPath.row)", for: indexPath) as? EWPhotoCollectionViewCell else {
            return EWPhotoCollectionViewCell()
        }
        guard indexPath.row > 0 else {
            cell.setData()
            return cell
        }
        cell.setData(image: photoArray[indexPath.row - 1])
        return cell
    }
    internal func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        return EWPickerManager.pickerPhotoSize
    }
}

5.创建相片选中编辑页.为其添加背景展示图片View,上层覆盖半透明View,下方取消确认按钮View.以及中间透明裁切区域View.

/// 选中图片后裁切控制器
class EWPhotoCropViewController: UIViewController {
    /// 初始frame
    private var oldFrame: CGRect?
    /// 最大frame
    private var largeFrame: CGRect?
    /// 裁切区域frame
    private var cropFrame: CGRect?
    /// 最后结果frame
    private var latestFrame: CGRect?
    /// 选中的照片
    private var selectedPhoto: UIImage = UIImage()

    public var delegate: EWImageCropperDelegate?
    /// 遮挡在选中imageView上层的半透明View
    private let overlayView: UIView = {
        let view = UIImageView(frame: CGRect(x: 0, y: 0, width: ScreenInfo.Width, height: ScreenInfo.Height))
        view.backgroundColor = UIColor.black
        view.alpha = 0.5
        view.isUserInteractionEnabled = false
        return view
    }()
    /// 裁切区域View
    private let cropView: UIView = {
        let view = UIView(frame: CGRect(x: 0 , y: (ScreenInfo.Height - ScreenInfo.Width) / 2, width: ScreenInfo.Width, height: ScreenInfo.Width))
        view.layer.borderWidth = 1
        view.layer.borderColor = UIColor.yellow.cgColor
        return view
    }()
    /// 展示图片背景View
    private let backImageView: UIImageView = {
        let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: ScreenInfo.Width, height: ScreenInfo.Height))
        imageView.isMultipleTouchEnabled = true
        imageView.isUserInteractionEnabled = true
        imageView.contentMode = .scaleAspectFit
        return imageView
    }()

    override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
    }
    convenience init(image: UIImage) {
        self.init(nibName: nil, bundle: nil)
        /// 保证图片方向
        self.selectedPhoto = self.fixOrientation(image)
        self.backImageView.image = selectedPhoto
    }
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        drawMyView()
        drawMyNavigationBar()
    }
    private func drawMyView(){
        self.view.backgroundColor = UIColor.black
        self.view.addSubview(backImageView)
        initViewsFrame()
        self.view.addSubview(overlayView)
        self.view.addSubview(cropView)
        overlayClipping()
        drawBottomButtonView()
        addGestureRecognizers()
    }
    private func drawMyNavigationBar(){
        let button = UIBarButtonItem(image: EWBundle.imageFromBundle("image_back"), style: .plain, target: self, action: #selector(onClickCancelbuton))
        self.navigationItem.leftBarButtonItem = button
    }
    private func initViewsFrame(){
        /// 裁切区域Width
        let cropWidth = UIScreen.main.bounds.width
        /// 获取选中图片width等于裁切区域时的height
        let photoOldHeight = selectedPhoto.size.height / selectedPhoto.size.width * cropWidth
        // 判断选中图片OldHeight
        if photoOldHeight > ScreenInfo.Height {
            // 大于屏幕尺寸,则正常展示
            self.backImageView.frame = CGRect(x: 0, y: 0, width: cropWidth, height: photoOldHeight)
        }else{
            // 小于屏幕尺寸,将其置于中心展示
            self.backImageView.frame = CGRect(x: 0, y: (ScreenInfo.Height - photoOldHeight) / 2 , width: cropWidth, height: photoOldHeight)
        }
        /// 获取照片展示oldFrame
        oldFrame = self.backImageView.frame
        /// 初始化lastestFrame,使其为oldFrame
        latestFrame = self.oldFrame
        /// 初始化最大frame.使其为oldFrame.size*3 也就是设定图片放大比例不能超过3倍
        largeFrame = CGRect(x: 0, y: 0, width: (oldFrame?.size.width)! * 3, height: (oldFrame?.size.height)! * 3)
        /// 裁切frame就是裁切View.frame
        cropFrame = self.cropView.frame
    }
    /// 添加下方取消与完成按钮
    private func drawBottomButtonView(){
        let centerView = UIView(frame:CGRect(x: 100, y: self.view.frame.size.height - 50.0, width: self.view.frame.size.width-200, height: 50))
        centerView.backgroundColor = UIColor.black
        centerView.alpha = 0.5
        self.view.addSubview(centerView)
        let cancelBtn = UIButton(frame: CGRect(x: 0, y: self.view.frame.size.height - 50.0, width: 100, height: 50))
        cancelBtn.backgroundColor = UIColor.black
        cancelBtn.alpha = 0.5
        cancelBtn.titleLabel?.textColor = UIColor.white
        cancelBtn.setTitle("取消", for: UIControlState())
        cancelBtn.titleLabel?.font = UIFont.systemFont(ofSize: 18.0)
        cancelBtn.titleLabel?.textAlignment = NSTextAlignment.center
        cancelBtn.titleLabel?.lineBreakMode = NSLineBreakMode.byWordWrapping
        cancelBtn.titleLabel?.numberOfLines = 0
        cancelBtn.titleEdgeInsets = UIEdgeInsetsMake(5.0, 5.0, 5.0, 5.0)
        cancelBtn.addTarget(self, action:#selector(onClickCancelbuton), for: UIControlEvents.touchUpInside)
        self.view.addSubview(cancelBtn)
        let confirmBtn:UIButton = UIButton(frame: CGRect(x: self.view.frame.size.width - 100.0, y: self.view.frame.size.height - 50.0, width: 100, height: 50))
        confirmBtn.backgroundColor = UIColor.black
        confirmBtn.alpha = 0.5
        confirmBtn.titleLabel?.textColor = UIColor.white
        confirmBtn.setTitle("确定", for: UIControlState())
        confirmBtn.titleLabel?.font = UIFont.systemFont(ofSize: 18.0)
        confirmBtn.titleLabel?.textAlignment = NSTextAlignment.center
        confirmBtn.titleLabel?.lineBreakMode = NSLineBreakMode.byWordWrapping
        confirmBtn.titleLabel?.numberOfLines = 0
        confirmBtn.titleEdgeInsets = UIEdgeInsetsMake(5.0, 5.0, 5.0, 5.0)
        confirmBtn.addTarget(self, action:#selector(onClickConfirmButton), for: UIControlEvents.touchUpInside)
        self.view.addSubview(confirmBtn)
    }
    /// 修改overlayView.layer.使cropView不被遮挡
    private func overlayClipping() {
        let maskLayer = CAShapeLayer()
        let path = CGMutablePath()
        // 裁切View左侧side
        path.addRect(CGRect(x: 0, y: 0, width: self.cropView.frame.origin.x, height: self.overlayView.frame.size.height))
        // 裁切View右侧side
        path.addRect(CGRect(
            x: self.cropView.frame.origin.x + self.cropView.frame.size.width, y: 0, width: self.overlayView.frame.size.width - self.cropView.frame.origin.x - self.cropView.frame.size.width, height: self.overlayView.frame.size.height))
        // 裁切View上方side
        path.addRect(CGRect(x: 0, y: 0, width: self.overlayView.frame.size.width, height: self.cropView.frame.origin.y))
        // 裁切View下方side
        path.addRect(CGRect(x: 0, y: self.cropView.frame.origin.y + self.cropView.frame.size.height, width: self.overlayView.frame.size.width, height: self.overlayView.frame.size.height - self.cropView.frame.origin.y + self.cropView.frame.size.height))
        maskLayer.path = path
        /// 修改overlayView.将裁切View区域空白出来
        self.overlayView.layer.mask = maskLayer
        path.closeSubpath()
    }
}

6.添加相片方向校正方法,保证相片正常展示.

    // 保证图片方向
    func fixOrientation(_ srcImg:UIImage) -> UIImage {
        if srcImg.imageOrientation == UIImageOrientation.up {
            return srcImg
        }
        var transform = CGAffineTransform.identity
        switch srcImg.imageOrientation {
        case UIImageOrientation.down, UIImageOrientation.downMirrored:
            transform = transform.translatedBy(x: srcImg.size.width, y: srcImg.size.height)
            transform = transform.rotated(by: .pi)
        case UIImageOrientation.left, UIImageOrientation.leftMirrored:
            transform = transform.translatedBy(x: srcImg.size.width, y: 0)
            transform = transform.rotated(by: .pi/2)
        case UIImageOrientation.right, UIImageOrientation.rightMirrored:
            transform = transform.translatedBy(x: 0, y: srcImg.size.height)
            transform = transform.rotated(by: -.pi/2)
        case UIImageOrientation.up, UIImageOrientation.upMirrored: break
        }
        switch srcImg.imageOrientation {
        case UIImageOrientation.upMirrored, UIImageOrientation.downMirrored:
            transform = transform.translatedBy(x: srcImg.size.width, y: 0)
            transform = transform.scaledBy(x: -1, y: 1)
        case UIImageOrientation.leftMirrored, UIImageOrientation.rightMirrored:
            transform = transform.translatedBy(x: srcImg.size.height, y: 0)
            transform = transform.scaledBy(x: -1, y: 1)
        case UIImageOrientation.up, UIImageOrientation.down, UIImageOrientation.left, UIImageOrientation.right:break
        }
        // 上下文
        let ctx:CGContext = CGContext(data: nil, width: Int(srcImg.size.width), height: Int(srcImg.size.height), bitsPerComponent: srcImg.cgImage!.bitsPerComponent, bytesPerRow: 0, space: srcImg.cgImage!.colorSpace!, bitmapInfo: srcImg.cgImage!.bitmapInfo.rawValue)!
        ctx.concatenate(transform)
        switch srcImg.imageOrientation {
        case UIImageOrientation.left, UIImageOrientation.leftMirrored, UIImageOrientation.right, UIImageOrientation.rightMirrored:
            ctx.draw(srcImg.cgImage!, in: CGRect(x: 0, y: 0, width: srcImg.size.height, height: srcImg.size.width))
        default:
            ctx.draw(srcImg.cgImage!, in: CGRect(x: 0, y: 0, width: srcImg.size.width, height: srcImg.size.height))
        }
        let cgImg:CGImage = ctx.makeImage()!
        let img:UIImage = UIImage(cgImage: cgImg)
        ctx.closePath()
        return img
    }

7.为编辑页添加缩放手势以及拖拽手势.

 /// 添加缩放,以及拖拽手势
    private func addGestureRecognizers() {
        // 缩放手势
        let pinchGestureRecognizer:UIPinchGestureRecognizer = UIPinchGestureRecognizer(target: self, action: #selector(pinchView(_:)))
        self.view.addGestureRecognizer(pinchGestureRecognizer)
        // 拖拽手势
        let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(panView(_:)))
        self.view.addGestureRecognizer(panGestureRecognizer)
    }
    /// 缩放手势方法
    @objc private func pinchView(_ pinchGestureRecognizer:UIPinchGestureRecognizer) {
        if pinchGestureRecognizer.state == .began || pinchGestureRecognizer.state == .changed{
            /// 当缩放手势开始,以及正在进行中时,根据手势缩放比例对应将展示照片的backImageView通过transform进行等比例缩放
            self.backImageView.transform = backImageView.transform.scaledBy(x: pinchGestureRecognizer.scale, y: pinchGestureRecognizer.scale)
            pinchGestureRecognizer.scale = 1
        }else if pinchGestureRecognizer.state == .ended{
            /// 获取手势结束后backImageView.frame,并通过验证方法对其修正
            var newFrame = self.backImageView.frame
            /// 修正比例
            newFrame = handleScaleOverflow(newFrame)
            /// 修正位置
            newFrame = handleBorderOverflow(newFrame)
            /// 使backImageView.frame变为修正后的frame,并添加动画效果
            UIView.animate(withDuration: 0.3) {
                self.backImageView.frame = newFrame
                self.latestFrame = newFrame
            }
        }
    }
    //拖拽手势方法
    @objc private func panView(_ panGestureRecognizer:UIPanGestureRecognizer) {
        let view = self.backImageView
        if panGestureRecognizer.state == .began || panGestureRecognizer.state == .changed{
            /// 当拖拽手势开始以及正在进行中时,根据拖拽位移以及图片比例,通过修改view.center实现拖拽效果
            let absCenterX = self.cropFrame!.origin.x + self.cropFrame!.size.width / 2
            let absCenterY = self.cropFrame!.origin.y + self.cropFrame!.size.height / 2
            let scaleRatio = self.backImageView.frame.size.width / self.cropFrame!.size.width
            let acceleratorX = 1 - abs(absCenterX - view.center.x) / (scaleRatio * absCenterX)
            let acceleratorY = 1 - abs(absCenterY - view.center.y) / (scaleRatio * absCenterY)
            let translation = panGestureRecognizer.translation(in: view.superview)
            view.center = CGPoint(x:view.center.x + translation.x * acceleratorX, y: view.center.y + translation.y * acceleratorY)
            panGestureRecognizer.setTranslation(CGPoint.zero, in: view.superview)
        }else if panGestureRecognizer.state == .ended{
            /// 获取手势结束后backImageView.frame,并通过验证方法对其修正
            var newFrame = self.backImageView.frame
            /// 修正位置
            newFrame = self.handleBorderOverflow(newFrame)
            UIView.animate(withDuration: 0.3) {
                /// 使backImageView.frame变为修正后的frame,并添加动画效果
                self.backImageView.frame = newFrame
                self.latestFrame = newFrame
            }
        }
    }

8.添加frame验证方法,保证相片在缩放拖拽后展示效果.

/// 修正size不小于初始值,不大于最大值
    ///
    /// - Parameter newFrame: 变更过的frame
    /// - Returns: 修正后frame
    private func handleScaleOverflow(_ newFrame:CGRect) -> CGRect {
        var newFrame = newFrame
        let oriCenter = CGPoint(x: newFrame.origin.x + newFrame.size.width / 2, y: newFrame.origin.y + newFrame.size
            .height / 2)
        /// 如果frame.size小于最小值则使其等于最小值
        if newFrame.size.width < self.oldFrame!.size.width {
            newFrame = self.oldFrame!
        }
        /// 如果frame.size大于最大值则使其等于最大值
        if newFrame.size.width > self.largeFrame!.size.width {
            newFrame = self.largeFrame!
        }
        newFrame.origin.x = oriCenter.x - newFrame.size.width / 2
        newFrame.origin.y = oriCenter.y - newFrame.size.height / 2
        return newFrame
    }
    /// 修正frame保证view展示不超过裁切区域
    private func handleBorderOverflow(_ newFrame:CGRect) -> CGRect {
        var newFrame = newFrame
        if newFrame.origin.x > self.cropFrame!.origin.x {
            newFrame.origin.x = self.cropFrame!.origin.x
        }
        if newFrame.maxX < self.cropFrame!.size.width {
            newFrame.origin.x = self.cropFrame!.size.width - newFrame.size.width
        }
        if newFrame.origin.y > self.cropFrame!.origin.y {
            newFrame.origin.y = self.cropFrame!.origin.y
        }
        if newFrame.maxY < self.cropFrame!.origin.y + self.cropFrame!.size.height {
            newFrame.origin.y = self.cropFrame!.origin.y + self.cropFrame!.size.height - newFrame.size.height
        }
        if self.backImageView.frame.size.width > self.backImageView.frame.size.height && newFrame.size.height <= self.cropFrame!.size.height {
            newFrame.origin.y = self.cropFrame!.origin.y + (self.cropFrame!.size.height - newFrame.size.height) / 2
        }
        return newFrame
    }

9.添加相片裁切方法.

     /// 获取截取图片
    private func getSubImage() -> UIImage {
        /// 获取截取位置Frame
        let squareFrame = self.cropFrame!
        /// 获取图片缩放比例
        let scaleRatio = self.latestFrame!.size.width / self.selectedPhoto.size.width
        var x = (squareFrame.origin.x - self.latestFrame!.origin.x) / scaleRatio
        var y = (squareFrame.origin.y - self.latestFrame!.origin.y) / scaleRatio
        var w = squareFrame.size.width / scaleRatio
        var h = squareFrame.size.height / scaleRatio
        if self.latestFrame!.size.width < self.cropFrame!.size.width {
            let newW = self.selectedPhoto.size.width
            let newH = newW * (self.cropFrame!.size.height / self.cropFrame!.size.width)
            x = 0;
            y = y + (h - newH) / 2
            w = newH
            h = newH
        }
        if self.latestFrame!.size.height < self.cropFrame!.size.height {
            let newH = self.selectedPhoto.size.height
            let newW = newH * (self.cropFrame!.size.width / self.cropFrame!.size.height)
            x = x + (w - newW) / 2
            y = 0
            w = newH
            h = newH
        }
        /// 获取截取图片的frame
        let myImageRect = CGRect(x: x, y: y, width: w, height: h)
        let imageRef = self.selectedPhoto.cgImage
        let subImageRef = imageRef?.cropping(to: myImageRect)
        let size:CGSize = CGSize(width: myImageRect.size.width, height: myImageRect.size.height)
        UIGraphicsBeginImageContext(size)
        let context:CGContext = UIGraphicsGetCurrentContext()!
        context.draw(subImageRef!, in: myImageRect)
        let smallImage = UIImage(cgImage: subImageRef!)
        UIGraphicsEndImageContext()
        return smallImage
    }

10.创建EWImageCropperDelegate将编辑完成的相片回调.

/// 裁切后的照片返回协议
@objc protocol EWImageCropperDelegate : NSObjectProtocol {
    func imageCropper(_ cropperViewController:EWPhotoCropViewController, didFinished editImg:UIImage)
}

11.添加调用相机功能.

// MARK: - UIImagePickerControllerDelegate & UINavigationControllerDelegate
extension EWPhotoCollectionViewController:UIImagePickerControllerDelegate& UINavigationControllerDelegate{
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
        picker.dismiss(animated: true, completion: { () -> Void in
        })
        //相册中还可能是视频,所以这里需要判断选择的是不是图片
        let type: String = (info[UIImagePickerControllerMediaType] as! String)
        //当选择的类型是图片
        if type == "public.image" {
            let image:UIImage = info[UIImagePickerControllerOriginalImage] as! UIImage
            //先把图片转成NSData
            let data = UIImageJPEGRepresentation(image, 0.4)
            //图片保存的路径 //这里将图片放在沙盒的documents文件夹中
            let DocumentsPath:String = NSHomeDirectory()+"/Documents"
            //文件管理器
            let fileManager = FileManager.default
            //把刚刚图片转换的data对象拷贝至沙盒中 并保存为image.png
            try! fileManager.createDirectory(atPath: DocumentsPath, withIntermediateDirectories: true, attributes: nil)
            fileManager.createFile(atPath: DocumentsPath + "/image.png", contents: data, attributes: nil)
            //得到选择后沙盒中图片的完整路径
            let filePath = DocumentsPath + "/image.png"
            let previewImage = UIImage(contentsOfFile: filePath)
            let pcvc = EWPhotoCropViewController(image: previewImage!)
            pcvc.delegate = self.delegate
            self.navigationController?.pushViewController(pcvc, animated: true)
        }
    }
}
 /// 调用相机
    private func cameraShow(){
        if UIImagePickerController.isSourceTypeAvailable(.camera) {
            let picker = UIImagePickerController()
            picker.sourceType = .camera
            picker.delegate = self
            picker.allowsEditing = false
            self.present(picker, animated: true, completion: nil)
        } else {
            print("模拟器中无法打开照相机,请在真机中使用")
        }
    }

12.为相册展示页CollectionView.Cell添加点击方法.

    /// cell点击方法
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        guard indexPath.row != 0 else {
            cameraShow()
            return
        }
        manager.getPhotoData(index: indexPath.row - 1) { (data, infoDic) in
            guard data != nil else { return }
            let image = UIImage(data: data!)
            let VC = EWPhotoCropViewController(image: image!)
            VC.delegate = self.delegate
            self.navigationController?.pushViewController(VC, animated: true)
        }
    }

13.使用自定制navigationController将交互串联.

import UIKit
/// 通用navigationController
class EWPhotoPickerViewController: UINavigationController {

    override func viewDidLoad() {
        super.viewDidLoad()
    }
    override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
    }
    /// 将delegate传入,可自定制tintColor,默认黑色
    init(photoDelegate: EWImageCropperDelegate, tintColor: UIColor = UIColor.black) {
        let vc = EWPhotoCollectionViewController()
        vc.delegate = photoDelegate
        super.init(rootViewController: vc)
        self.navigationBar.tintColor = tintColor
    }
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

添加方法:

  1. 使用CocoaPods加载.在podfile文件中添加
pod 'EWPhotoPicker'
  1. 直接从github下载项目将EWPhotoPicker文件夹拖入项目.

调用方法:

  1. 首先给项目info.plist文件中添加相机与相册权限,权限提示自行修改
	<key>NSPhotoLibraryUsageDescription</key>
	<string>相册权限</string>
	<key>NSCameraUsageDescription</key>
	<string>相机权限</string>
  1. 调用相册时:
    先让弹出相册的控制器遵循EWImageCropperDelegate.并在代理方法中获取image.
    当需要弹出相册时直接初始化EWPhotoPickerViewController,调用present方法.
/// 弹出控制器时时直接present就可以
@objc private func onClickPhotoButton(){
    let nnvc = EWPhotoPickerViewController(photoDelegate: self)
    /// 可以传入navigationBar.tintColor以保证与项目相同展示效果.默认不传为UIColor.black
//        let PPVC = EWPhotoPickerViewController(photoDelegate: self, tintColor: UIColor.gray)
    self.present(nnvc, animated: true, completion: nil)
}

/// 调用控制器遵循EWImageCropperDelegate,实现唯一的方法.
extension ViewController: EWImageCropperDelegate{
    func imageCropper(_ cropperViewController: EWPhotoCropViewController, didFinished editImg: UIImage) {
        cropperViewController.navigationController?.dismiss(animated: true, completion: nil)
        ///对选取并编辑后的图片直接使用
        self.imageView.image = editImg
    }
}

github地址: EWPhotoPicker

有问题欢迎探讨.

猜你喜欢

转载自blog.csdn.net/weixin_43566445/article/details/84333572