Skip to content

Commit

Permalink
Merge pull request #666 from tsahi-deri/feature/crop-improvements
Browse files Browse the repository at this point in the history
Crop improvements and  toolbar overlapped fix
  • Loading branch information
NikKovIos committed Aug 14, 2021
2 parents c3e9343 + 21c694e commit 440865b
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 68 deletions.
2 changes: 1 addition & 1 deletion Source/Configuration/YPColors.swift
Expand Up @@ -51,7 +51,7 @@ public struct YPColors {
public var bottomMenuItemUnselectedTextColor: UIColor = .ypSecondaryLabel

/// The color of the crop overlay.
public var cropOverlayColor: UIColor = .ypSystemBackground
public var cropOverlayColor: UIColor = .ypSystemBackground.withAlphaComponent(0.4)

// MARK: - Trimmer

Expand Down
5 changes: 1 addition & 4 deletions Source/Configuration/YPImagePickerConfiguration.swift
Expand Up @@ -88,10 +88,7 @@ public struct YPImagePickerConfiguration {
public var showsCrop: YPCropType = .none

/// Controls the visibility of a grid on crop stage. Default it false
public var showsCropOverlayGrid = false

/// Makes the bottom toolbar on crop stage transparent. Defaults to true
public var isCropToolbarTransparent = true
public var showsCropGridOverlay = false

/// Ex: cappedTo:1024 will make sure images from the library or the camera will be
/// resized to fit in a 1024x1024 box. Defaults to original image size.
Expand Down
177 changes: 115 additions & 62 deletions Source/Filters/Crop/YPCropView.swift
Expand Up @@ -9,102 +9,122 @@
import UIKit
import Stevia

class YPCropAreaView: UIView {

var isCircle = false {
didSet {
setNeedsDisplay()
}
}

override func draw(_ rect: CGRect) {
super.draw(rect)

guard isCircle else {
return
}

backgroundColor?.setFill()
UIRectFill(rect)

let layer = CAShapeLayer()
let path = CGMutablePath()

path.addRoundedRect(in: bounds, cornerWidth: bounds.width/2, cornerHeight: bounds.width/2)
path.addRect(bounds)

layer.path = path
layer.fillRule = CAShapeLayerFillRule.evenOdd

self.layer.mask = layer
}
}

class YPCropView: UIView {

let containerView = UIView()
let imageView = UIImageView()
let topCurtain = UIView()
let cropArea = YPCropAreaView()
let bottomCurtain = UIView()
let leadingCurtain = UIView()
let trailingCurtain = UIView()
let toolbar = UIToolbar()
let cropArea = YPCropAreaView()
let grid = YPGridView()

private var isCircle = true


private let defaultCurtainPadding: CGFloat = 15
private var isCircle: Bool {
if case YPCropType.circle = YPConfig.showsCrop {
return true
}
return false
}

convenience init(image: UIImage) {

self.init(frame: .zero)
setupViewHierarchy()

let ratio: Double
switch YPConfig.showsCrop {
case .rectangle(ratio: let configuredRatio):
isCircle = false
ratio = configuredRatio
default:
ratio = 1
}

setupViewHierarchy()
setupLayout(with: image, ratio: ratio)
setupImage(image, with: ratio)
applyStyle()
imageView.image = image
grid.isHidden = !YPConfig.showsCropOverlayGrid
containerView.clipsToBounds = true
grid.isCircle = isCircle
grid.isHidden = !YPConfig.showsCropGridOverlay
}

private func setupViewHierarchy() {

sv(
imageView,
topCurtain,
cropArea,
bottomCurtain,
toolbar,
grid
containerView.sv(
imageView,
topCurtain,
leadingCurtain,
trailingCurtain,
bottomCurtain,
cropArea,
grid
),
toolbar
)
}

private func setupLayout(with image: UIImage, ratio: Double) {

let horizontalCurtainMinWidth: CGFloat = ratio < 1 ? defaultCurtainPadding : 0

containerView.leading(0)
containerView.trailing(0)

topCurtain.height(>=defaultCurtainPadding)
bottomCurtain.height(>=defaultCurtainPadding)

leadingCurtain.width(>=horizontalCurtainMinWidth)
leadingCurtain.Top == topCurtain.Bottom
leadingCurtain.Bottom == bottomCurtain.Top

trailingCurtain.width(>=horizontalCurtainMinWidth)
trailingCurtain.Top == topCurtain.Bottom
trailingCurtain.Bottom == bottomCurtain.Top

cropArea.Top == topCurtain.Bottom
cropArea.Bottom == bottomCurtain.Top

|leadingCurtain--0--cropArea--0--trailingCurtain|

grid.followEdges(cropArea)

layout(
0,
|containerView|,
|toolbar| ~ 44
)

layout(
0,
|topCurtain|,
|cropArea|,
|bottomCurtain|,
0
)
|toolbar|

if #available(iOS 11.0, *) {
toolbar.Bottom == safeAreaLayoutGuide.Bottom
} else {
toolbar.bottom(0)
}

let r: CGFloat = CGFloat(1.0 / ratio)
cropArea.Height == cropArea.Width * r
cropArea.centerVertically()

let complementRatio: CGFloat = CGFloat(1.0 / ratio)
cropArea.Height == cropArea.Width * complementRatio
cropArea.centerInContainer()
}

private func setupImage(_ image: UIImage, with ratio: Double) {

// Fit image differently depnding on its ratio.
let imageRatio: Double = Double(image.size.width / image.size.height)
if ratio > imageRatio {
let screenWidth = YPImagePickerConfiguration.screenWidth
let scaledDownRatio = screenWidth / image.size.width
imageView.width(image.size.width * scaledDownRatio )
imageView.width(image.size.width * scaledDownRatio)
imageView.centerInContainer()
} else if ratio < imageRatio {
imageView.Height == cropArea.Height
Expand All @@ -115,35 +135,68 @@ class YPCropView: UIView {

// Fit imageView to image's bounds
imageView.Width == imageView.Height * CGFloat(imageRatio)

grid.followEdges(cropArea)
}

private func applyStyle() {

backgroundColor = .ypSystemBackground
clipsToBounds = true

imageView.style { i in
i.isUserInteractionEnabled = true
i.isMultipleTouchEnabled = true
}

topCurtain.style(curtainStyle)
bottomCurtain.style(curtainStyle)
leadingCurtain.style(curtainStyle)
trailingCurtain.style(curtainStyle)

cropArea.style { v in
v.backgroundColor = isCircle ? YPConfig.colors.cropOverlayColor : .clear
v.backgroundColor = isCircle ? YPConfig.colors.cropOverlayColor : .clear
v.isCircle = isCircle
v.isUserInteractionEnabled = false
}
bottomCurtain.style(curtainStyle)

if YPConfig.isCropToolbarTransparent {
toolbar.style { t in
t.setBackgroundImage(UIImage(), forToolbarPosition: .any, barMetrics: .default)
t.setShadowImage(UIImage(), forToolbarPosition: .any)
}
toolbar.style { t in
t.setBackgroundImage(UIImage(), forToolbarPosition: .any, barMetrics: .default)
t.setShadowImage(UIImage(), forToolbarPosition: .any)
}
}

func curtainStyle(v: UIView) {
v.backgroundColor = YPConfig.colors.cropOverlayColor.withAlphaComponent(0.7)
private func curtainStyle(v: UIView) {

v.backgroundColor = YPConfig.colors.cropOverlayColor
v.isUserInteractionEnabled = false
}
}

class YPCropAreaView: UIView {

var isCircle = false {
didSet {
self.setNeedsDisplay()
}
}

override func draw(_ rect: CGRect) {

super.draw(rect)

guard isCircle else { return }

self.backgroundColor?.setFill()
UIRectFill(rect)

let layer = CAShapeLayer()
let path = CGMutablePath()

path.addRoundedRect(in: bounds, cornerWidth: bounds.width/2, cornerHeight: bounds.width/2)
path.addRect(bounds)

layer.path = path
layer.fillRule = CAShapeLayerFillRule.evenOdd

self.layer.mask = layer
}
}
2 changes: 1 addition & 1 deletion Source/Pages/Gallery/YPGridView.swift
Expand Up @@ -17,7 +17,7 @@ class YPGridView: UIView {

var isCircle = false {
didSet {
setNeedsDisplay()
self.setNeedsDisplay()
}
}

Expand Down

0 comments on commit 440865b

Please sign in to comment.