Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: make rotation view customizable #308

Merged
merged 12 commits into from May 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions Example/ViewController.swift
Expand Up @@ -58,7 +58,7 @@ class ViewController: UIViewController, CropViewControllerDelegate {

var config = Mantis.Config()
config.cropMode = .async
config.cropViewConfig.showRotationDial = false
config.cropViewConfig.showAttachedRotationControlView = false
config.showAttachedCropToolbar = false
let cropViewController: CustomViewController = Mantis.cropViewController(image: image, config: config)
cropViewController.delegate = self
Expand Down Expand Up @@ -101,7 +101,7 @@ class ViewController: UIViewController, CropViewControllerDelegate {

var config = Mantis.Config()
config.showAttachedCropToolbar = false
config.cropViewConfig.showRotationDial = false
config.cropViewConfig.showAttachedRotationControlView = false
config.cropViewConfig.minimumZoomScale = 2.0
config.cropViewConfig.maximumZoomScale = 10.0

Expand Down
4 changes: 2 additions & 2 deletions README.md
Expand Up @@ -29,7 +29,7 @@
## Breaking Changes in 2.x.x
* Add CropViewConfig
* move some properties from Config to CropViewConfig
* make dialConfig as a property of CropViewConfig
* make roationControlViewConfig as a property of CropViewConfig
* Refactor CropToolbarConfigProtocol
* rename some properties

Expand Down Expand Up @@ -145,7 +145,7 @@ let cropViewController = Mantis.cropViewController(image: <Your Image>, config:

When choose alwaysUsingOnePresetFixedRatio, fixed-ratio setting button does not show.

* If you want to hide rotation dial, set Mantis.Config..cropViewConfig.dialConfig = nil
* If you want to hide rotation control view, set Mantis.Config.cropViewConfig.showAttachedRotationControlView = false
* If you want to use ratio list instead of presenter, set Mantis.CropToolbarConfig.ratioCandidatesShowType = .alwaysShowRatioList

```swift
Expand Down
9 changes: 4 additions & 5 deletions Sources/Mantis/CropView/CropView+Touches.swift
Expand Up @@ -12,8 +12,8 @@ extension CropView {
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let newPoint = convert(point, to: self)

if let rotationDial = rotationDial, rotationDial.frame.contains(newPoint) {
return rotationDial
if let rotationControlView = rotationControlView, rotationControlView.frame.contains(newPoint) {
return rotationControlView
}

if !cropViewConfig.disableCropBoxDeformation && isHitGridOverlayView(by: newPoint) {
Expand Down Expand Up @@ -44,8 +44,7 @@ extension CropView {
// A resize event has begun by grabbing the crop UI, so notify delegate
delegate?.cropViewDidBeginResize(self)

if touch.view is RotationDial {
viewModel.setTouchRotationBoardStatus()
if touch.view is RotationControlViewProtocol {
return
}

Expand All @@ -60,7 +59,7 @@ extension CropView {
return
}

if touch.view is RotationDial {
if touch.view is RotationControlViewProtocol {
return
}

Expand Down
130 changes: 72 additions & 58 deletions Sources/Mantis/CropView/CropView.swift
Expand Up @@ -32,8 +32,6 @@ protocol CropViewDelegate: AnyObject {
}

class CropView: UIView {
private let angleDashboardHeight: CGFloat = 60

var image: UIImage

let viewModel: CropViewModelProtocol
Expand All @@ -52,9 +50,11 @@ class CropView: UIView {
let cropWorkbenchView: CropWorkbenchViewProtocol
let cropMaskViewManager: CropMaskViewManagerProtocol

var rotationDial: RotationDialProtocol? {
var rotationControlView: RotationControlViewProtocol? {
didSet {
addSubview(rotationDial!)
if rotationControlView?.isAttachedToCropView == true {
addSubview(rotationControlView!)
}
}
}

Expand Down Expand Up @@ -150,27 +150,33 @@ class CropView: UIView {
case .degree90Rotating:
cropMaskViewManager.showVisualEffectBackground(animated: true)
cropAuxiliaryIndicatorView.isHidden = true
rotationDial?.isHidden = true
toggleRotationControlViewIsNeeded(isHidden: true)
case .touchImage:
cropMaskViewManager.showDimmingBackground(animated: true)
cropAuxiliaryIndicatorView.gridLineNumberType = .crop
cropAuxiliaryIndicatorView.gridHidden = false
case .touchCropboxHandle(let tappedEdge):
cropAuxiliaryIndicatorView.handleIndicatorHandleTouched(with: tappedEdge)
rotationDial?.isHidden = true
toggleRotationControlViewIsNeeded(isHidden: true)
cropMaskViewManager.showDimmingBackground(animated: true)
case .touchRotationBoard:
cropAuxiliaryIndicatorView.gridLineNumberType = .rotate
cropAuxiliaryIndicatorView.gridHidden = false
cropMaskViewManager.showDimmingBackground(animated: true)
case .betweenOperation:
cropAuxiliaryIndicatorView.handleEdgeUntouched()
rotationDial?.isHidden = false
adaptAngleDashboardToCropBox()
toggleRotationControlViewIsNeeded(isHidden: false)
adaptRotationControlViewToCropBoxIfNeeded()
cropMaskViewManager.showVisualEffectBackground(animated: true)
checkImageStatusChanged()
}
}

private func toggleRotationControlViewIsNeeded(isHidden: Bool) {
if rotationControlView?.isAttachedToCropView == true {
rotationControlView?.isHidden = isHidden
}
}

private func imageStatusChanged() -> Bool {
if viewModel.getTotalRadians() != 0 { return true }
Expand Down Expand Up @@ -208,7 +214,7 @@ class CropView: UIView {
cropWorkbenchView.resetImageContent(by: viewModel.cropBoxFrame)
cropAuxiliaryIndicatorView.bringSelfToFront()

setupRotationDial()
setupRotationDialIfNeeded()

if aspectRatioLockEnabled {
setFixedRatioCropBox()
Expand Down Expand Up @@ -238,55 +244,63 @@ class CropView: UIView {
addSubview(cropAuxiliaryIndicatorView)
}

private func setupRotationDial() {
guard cropViewConfig.showRotationDial, let rotationDial = rotationDial else {
private func setupRotationDialIfNeeded() {
guard let rotationControlView = rotationControlView else {
return
}

rotationDial.reset()

let boardLength = min(bounds.width, bounds.height) * 0.6
let dialFrame = CGRect(x: 0,
y: 0,
width: boardLength,
height: angleDashboardHeight)
rotationDial.setup(with: dialFrame)
rotationDial.isUserInteractionEnabled = true

rotationDial.setRotationCenter(by: cropAuxiliaryIndicatorView.center, of: self)

rotationDial.didRotate = { [unowned self] angle in
rotationControlView.reset()
rotationControlView.isUserInteractionEnabled = true

rotationControlView.didUpdateRotationValue = { [unowned self] angle in
self.viewModel.setTouchRotationBoardStatus()
self.viewModel.setRotatingStatus(by: angle)
}

rotationDial.didFinishedRotate = { [unowned self] in
rotationControlView.didFinishRotation = { [unowned self] in
self.viewModel.setBetweenOperationStatus()
}

if rotationControlView.isAttachedToCropView {
let boardLength = min(bounds.width, bounds.height) * 0.6
let dialFrame = CGRect(x: 0,
y: 0,
width: boardLength,
height: cropViewConfig.rotationControlViewHeight)

rotationControlView.setupUI(withAllowableFrame: dialFrame)
}

rotationDial.rotateDialPlate(by: Angle(radians: viewModel.radians))
rotationDial.bringSelfToFront()
if let rotationDial = rotationControlView as? RotationDialProtocol {
rotationDial.setRotationCenter(by: cropAuxiliaryIndicatorView.center, of: self)
}

adaptAngleDashboardToCropBox()
rotationControlView.updateRotationValue(by: Angle(radians: viewModel.radians))
viewModel.setBetweenOperationStatus()

adaptRotationControlViewToCropBoxIfNeeded()
rotationControlView.bringSelfToFront()
}

private func adaptAngleDashboardToCropBox() {
guard let rotationDial = rotationDial else { return }
private func adaptRotationControlViewToCropBoxIfNeeded() {
guard let rotationControlView = rotationControlView,
rotationControlView.isAttachedToCropView else { return }

if Orientation.treatAsPortrait {
rotationDial.transform = CGAffineTransform(rotationAngle: 0)
rotationDial.frame.origin.x = cropAuxiliaryIndicatorView.frame.origin.x +
(cropAuxiliaryIndicatorView.frame.width - rotationDial.frame.width) / 2
rotationDial.frame.origin.y = cropAuxiliaryIndicatorView.frame.maxY
rotationControlView.transform = CGAffineTransform(rotationAngle: 0)
rotationControlView.frame.origin.x = cropAuxiliaryIndicatorView.frame.origin.x +
(cropAuxiliaryIndicatorView.frame.width - rotationControlView.frame.width) / 2
rotationControlView.frame.origin.y = cropAuxiliaryIndicatorView.frame.maxY
} else if Orientation.isLandscapeLeft {
rotationDial.transform = CGAffineTransform(rotationAngle: -CGFloat.pi / 2)
rotationDial.frame.origin.x = cropAuxiliaryIndicatorView.frame.maxX
rotationDial.frame.origin.y = cropAuxiliaryIndicatorView.frame.origin.y +
(cropAuxiliaryIndicatorView.frame.height - rotationDial.frame.height) / 2
rotationControlView.transform = CGAffineTransform(rotationAngle: -CGFloat.pi / 2)
rotationControlView.frame.origin.x = cropAuxiliaryIndicatorView.frame.maxX
rotationControlView.frame.origin.y = cropAuxiliaryIndicatorView.frame.origin.y +
(cropAuxiliaryIndicatorView.frame.height - rotationControlView.frame.height) / 2
} else if Orientation.isLandscapeRight {
rotationDial.transform = CGAffineTransform(rotationAngle: CGFloat.pi / 2)
rotationDial.frame.origin.x = cropAuxiliaryIndicatorView.frame.minX - rotationDial.frame.width
rotationDial.frame.origin.y = cropAuxiliaryIndicatorView.frame.origin.y +
(cropAuxiliaryIndicatorView.frame.height - rotationDial.frame.height) / 2
rotationControlView.transform = CGAffineTransform(rotationAngle: CGFloat.pi / 2)
rotationControlView.frame.origin.x = cropAuxiliaryIndicatorView.frame.minX - rotationControlView.frame.width
rotationControlView.frame.origin.y = cropAuxiliaryIndicatorView.frame.origin.y +
(cropAuxiliaryIndicatorView.frame.height - rotationControlView.frame.height) / 2
}
}

Expand Down Expand Up @@ -407,21 +421,27 @@ extension CropView {
let rect = self.bounds
var contentRect = CGRect.zero

var rotationControlViewHeight: CGFloat = 0

if cropViewConfig.showAttachedRotationControlView && rotationControlView?.isAttachedToCropView == true {
rotationControlViewHeight = cropViewConfig.rotationControlViewHeight
}

if Orientation.treatAsPortrait {
contentRect.origin.x = rect.origin.x + cropViewPadding
contentRect.origin.y = rect.origin.y + cropViewPadding

contentRect.size.width = rect.width - 2 * cropViewPadding
contentRect.size.height = rect.height - 2 * cropViewPadding - angleDashboardHeight
contentRect.size.height = rect.height - 2 * cropViewPadding - rotationControlViewHeight
} else if Orientation.isLandscape {
contentRect.size.width = rect.width - 2 * cropViewPadding - angleDashboardHeight
contentRect.size.width = rect.width - 2 * cropViewPadding - rotationControlViewHeight
contentRect.size.height = rect.height - 2 * cropViewPadding

contentRect.origin.y = rect.origin.y + cropViewPadding
if Orientation.isLandscapeLeft {
contentRect.origin.x = rect.origin.x + cropViewPadding
} else {
contentRect.origin.x = rect.origin.x + cropViewPadding + angleDashboardHeight
contentRect.origin.x = rect.origin.x + cropViewPadding + rotationControlViewHeight
}
}

Expand Down Expand Up @@ -686,13 +706,7 @@ extension CropView {
func getTotalRadians() -> CGFloat {
return viewModel.getTotalRadians()
}

private func setRotation(byRadians radians: CGFloat) {
cropWorkbenchView.transform = CGAffineTransform(rotationAngle: radians)
updatePosition(by: radians)
rotationDial?.rotateDialPlate(to: Angle(radians: radians), animated: false)
}


func setFixedRatioCropBox(zoom: Bool = true, cropBox: CGRect? = nil) {
let refCropBox = cropBox ?? getInitialCropBoxRect()
let imageHorizontalToVerticalRatio = ImageHorizontalToVerticalRatio(ratio: getImageHorizontalToVerticalRatio())
Expand All @@ -707,7 +721,7 @@ extension CropView {
self.viewModel.setBetweenOperationStatus()
}

adaptAngleDashboardToCropBox()
adaptRotationControlViewToCropBoxIfNeeded()
cropWorkbenchView.updateMinZoomScale()
}

Expand Down Expand Up @@ -880,7 +894,7 @@ extension CropView: CropViewProtocol {
}
}

func transform(byTransformInfo transformation: Transformation, rotateDial: Bool = true) {
func transform(byTransformInfo transformation: Transformation, isUpdateRotationControlView: Bool = true) {
viewModel.setRotatingStatus(by: Angle(radians: transformation.rotation))

if transformation.cropWorkbenchViewBounds != .zero {
Expand All @@ -896,9 +910,9 @@ extension CropView: CropViewProtocol {
viewModel.cropBoxFrame = transformation.maskFrame
}

if rotateDial {
rotationDial?.rotateDialPlate(by: Angle(radians: viewModel.radians))
adaptAngleDashboardToCropBox()
if isUpdateRotationControlView {
rotationControlView?.updateRotationValue(by: Angle(radians: viewModel.radians))
adaptRotationControlViewToCropBoxIfNeeded()
}
}

Expand Down Expand Up @@ -975,7 +989,7 @@ extension CropView: CropViewProtocol {
var newTransform = getTransformInfo(byTransformInfo: transformInfo)

// The first transform is just for retrieving the final cropBoxFrame
transform(byTransformInfo: newTransform, rotateDial: false)
transform(byTransformInfo: newTransform, isUpdateRotationControlView: false)

// The second transform is for adjusting the scale of transformInfo
let adjustScale = (viewModel.cropBoxFrame.width / viewModel.cropBoxOriginFrame.width)
Expand Down
17 changes: 13 additions & 4 deletions Sources/Mantis/CropViewConfig.swift
Expand Up @@ -33,12 +33,19 @@ public struct CropViewConfig {
public var maximumZoomScale: CGFloat = 15

/**
Rotation Dial currently is tightly coupled with other parts of CropView, we see rotation dial as a part of CropView,
so we put dialConfig inside CropViewConfig
Rotation control view currently is tightly coupled with other parts of CropView, we see rotation control view as a part of CropView,
so we put RotationControlViewConfig inside CropViewConfig
*/
public var dialConfig = DialConfig()
public var rotationControlViewConfig = RotationControlViewConfig()

public var showRotationDial = true
@available(*, deprecated, message: "Use showAttachedRotationControlView instead")
public var showRotationDial = true {
didSet {
showAttachedRotationControlView = showRotationDial
}
}

public var showAttachedRotationControlView = true

public var padding: CGFloat = 14 {
didSet {
Expand All @@ -50,6 +57,8 @@ public struct CropViewConfig {

public var cropActivityIndicatorSize = CGSize(width: 100, height: 100)

public var rotationControlViewHeight: CGFloat = 60

var minimumCropBoxSize: CGFloat = 42

public var disableCropBoxDeformation = false
Expand Down