Skip to content

Commit

Permalink
feat: support async crop (#232)
Browse files Browse the repository at this point in the history
* feat: support async crop

* fix: hide activityIndicator after the async crop is done

fix a typo

* fix: put UIKit related code in main thread
  • Loading branch information
guoyingtao committed Nov 14, 2022
1 parent 21604f0 commit 69e5640
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 21 deletions.
1 change: 1 addition & 0 deletions Example/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class ViewController: UIViewController, CropViewControllerDelegate {
}

var config = Mantis.Config()
config.cropMode = .async
config.cropToolbarConfig.toolbarButtonOptions = [.clockwiseRotate, .reset, .ratio, .horizontallyFlip]
let cropViewController = Mantis.cropViewController(image: image,
config: config)
Expand Down
8 changes: 8 additions & 0 deletions Sources/Mantis/Config.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@ public class LocalizationConfig {

// MARK: - Config
public struct Config {

public enum CropMode {
case sync
case async // We may need this mode when cropping big images
}

public var cropMode: CropMode = .sync

public var cropViewConfig = CropViewConfig()
public var cropToolbarConfig: CropToolbarConfigProtocol = CropToolbarConfig()

Expand Down
55 changes: 52 additions & 3 deletions Sources/Mantis/CropView/CropView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,23 @@ class CropView: UIView {

private var cropFrameKVO: NSKeyValueObservation?

lazy private var activityIndicator: UIActivityIndicatorView = {
let activityIndicator = UIActivityIndicatorView(frame: .zero)
activityIndicator.color = .white
let indicatorSize: CGFloat = 100
activityIndicator.transform = CGAffineTransform(scaleX: 2.0, y: 2.0)

addSubview(activityIndicator)
activityIndicator.translatesAutoresizingMaskIntoConstraints = false

activityIndicator.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
activityIndicator.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
activityIndicator.widthAnchor.constraint(equalToConstant: indicatorSize).isActive = true
activityIndicator.heightAnchor.constraint(equalToConstant: indicatorSize).isActive = true

return activityIndicator
} ()

deinit {
print("CropView deinit.")
}
Expand Down Expand Up @@ -577,8 +594,25 @@ extension CropView {
extension CropView {
func crop(_ image: UIImage) -> CropOutput {
let cropInfo = getCropInfo()
let cropOutput = (image.crop(by: cropInfo), makeTransformation(), cropInfo)
return addImageMask(to: cropOutput)
}

func asyncCrop(_ image: UIImage, completion: @escaping (CropOutput) -> Void) {
let cropInfo = getCropInfo()
let cropOutput = (image.crop(by: cropInfo), makeTransformation(), cropInfo)

let transformation = Transformation(
DispatchQueue.global(qos: .userInteractive).async {
let maskedCropOutput = self.addImageMask(to: cropOutput)
DispatchQueue.main.async {
self.activityIndicator.isHidden = true
completion(maskedCropOutput)
}
}
}

func makeTransformation() -> Transformation {
Transformation(
offset: scrollView.contentOffset,
rotation: getTotalRadians(),
scale: scrollView.zoomScale,
Expand All @@ -587,9 +621,13 @@ extension CropView {
maskFrame: gridOverlayView.frame,
scrollBounds: scrollView.bounds
)
}

func addImageMask(to cropOutput: CropOutput) -> CropOutput {
let (croppedImage, transformation, cropInfo) = cropOutput

guard let croppedImage = image.crop(by: cropInfo) else {
return (nil, transformation, cropInfo)
guard let croppedImage = croppedImage else {
return cropOutput
}

switch cropViewConfig.cropShapeType {
Expand Down Expand Up @@ -700,6 +738,17 @@ extension CropView {
func crop() -> CropOutput {
return crop(image)
}

/// completion is called in the main thread
func asyncCrop(completion: @escaping (_ cropOutput: CropOutput) -> Void ) {
activityIndicator.isHidden = false
activityIndicator.startAnimating()

asyncCrop(image) { [weak self] cropOutput in
self?.activityIndicator.isHidden = true
completion(cropOutput)
}
}

func handleDeviceRotated() {
viewModel.resetCropFrame(by: getInitialCropBoxRect())
Expand Down
36 changes: 18 additions & 18 deletions Sources/Mantis/CropViewController/CropViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -433,16 +433,7 @@ public class CropViewController: UIViewController {
}

private func handleCrop() {
let cropResult = cropView.crop()
guard let image = cropResult.croppedImage else {
delegate?.cropViewControllerDidFailToCrop(self, original: cropView.image)
return
}

delegate?.cropViewControllerDidCrop(self,
cropped: image,
transformation: cropResult.transformation,
cropInfo: cropResult.cropInfo)
crop()
}
}

Expand Down Expand Up @@ -569,16 +560,25 @@ extension CropViewController: CropToolbarDelegate {
// API
extension CropViewController {
public func crop() {
let cropResult = cropView.crop()
guard let image = cropResult.croppedImage else {
delegate?.cropViewControllerDidFailToCrop(self, original: cropView.image)
return
switch config.cropMode {
case .sync:
let cropOutput = cropView.crop()
handleCropOutput(cropOutput)
case .async:
cropView.asyncCrop(completion: handleCropOutput)
}

delegate?.cropViewControllerDidCrop(self,
cropped: image,
transformation: cropResult.transformation,
cropInfo: cropResult.cropInfo)
func handleCropOutput(_ cropOutput: CropOutput) {
guard let image = cropOutput.croppedImage else {
delegate?.cropViewControllerDidFailToCrop(self, original: cropView.image)
return
}

delegate?.cropViewControllerDidCrop(self,
cropped: image,
transformation: cropOutput.transformation,
cropInfo: cropOutput.cropInfo)
}
}

public func process(_ image: UIImage) -> UIImage? {
Expand Down

0 comments on commit 69e5640

Please sign in to comment.