@@ -11,7 +11,7 @@ import UIKit
11
11
#endif
12
12
13
13
/// A view that allows the user to crop an image.
14
- public struct CropImageView : View {
14
+ public struct CropImageView < Controls : View > : View {
15
15
/// Errors that could happen during the cropping process.
16
16
public enum CropError : Error {
17
17
/// SwiftUI `ImageRenderer` returned nil when calling `nsImage` or `uiImage`.
@@ -28,6 +28,29 @@ public struct CropImageView: View {
28
28
case failedToGetImageFromCurrentUIGraphicsImageContext
29
29
}
30
30
31
+ private static func defaultControlsView( crop: @escaping ( ) async -> ( ) ) -> AnyView { AnyView (
32
+ VStack {
33
+ Spacer ( )
34
+ HStack {
35
+ Spacer ( )
36
+ Button { Task {
37
+ await crop ( )
38
+ } } label: {
39
+ Label ( " Crop " , systemImage: " checkmark.circle.fill " )
40
+ . font ( . title2)
41
+ . foregroundColor ( . accentColor)
42
+ . labelStyle ( . iconOnly)
43
+ . padding ( 1 )
44
+ . background (
45
+ Circle ( ) . fill ( . white)
46
+ )
47
+ }
48
+ . buttonStyle ( . plain)
49
+ . padding ( )
50
+ }
51
+ }
52
+ ) }
53
+
31
54
/// The image to crop.
32
55
public var image : PlatformImage
33
56
/// The intended size of the cropped image, in points.
@@ -40,6 +63,41 @@ public struct CropImageView: View {
40
63
///
41
64
/// The error should be a ``CropError``.
42
65
public var onCrop : ( Result < PlatformImage , Error > ) -> Void
66
+ /// A custom view overlaid on the image cropper.
67
+ ///
68
+ /// - Parameters:
69
+ /// - crop: An async function to trigger crop action. Result will be delivered via ``onCrop``.
70
+ public var controls : ( _ crop: @escaping ( ) async -> ( ) ) -> Controls
71
+
72
+ /// Create a ``CropImageView`` with a custom ``controls`` view.
73
+ public init (
74
+ image: PlatformImage ,
75
+ targetSize: CGSize ,
76
+ targetScale: CGFloat = 1 ,
77
+ onCrop: @escaping ( Result < PlatformImage , Error > ) -> Void ,
78
+ @ViewBuilder controls: @escaping ( _ crop: ( ) async -> ( ) ) -> Controls
79
+ ) {
80
+ self . image = image
81
+ self . targetSize = targetSize
82
+ self . targetScale = targetScale
83
+ self . onCrop = onCrop
84
+ self . controls = controls
85
+ }
86
+ /// Create a ``CropImageView`` with the default ``controls`` view.
87
+ ///
88
+ /// The default ``controls`` view is a simple overlay with a checkmark icon on the bottom-trailing corner to trigger crop action.
89
+ public init (
90
+ image: PlatformImage ,
91
+ targetSize: CGSize ,
92
+ targetScale: CGFloat = 1 ,
93
+ onCrop: @escaping ( Result < PlatformImage , Error > ) -> Void
94
+ ) where Controls == AnyView {
95
+ self . image = image
96
+ self . targetSize = targetSize
97
+ self . targetScale = targetScale
98
+ self . onCrop = onCrop
99
+ self . controls = Self . defaultControlsView
100
+ }
43
101
44
102
@State private var offset : CGSize = . zero
45
103
@State private var scale : CGFloat = 1
@@ -94,28 +152,11 @@ public struct CropImageView: View {
94
152
. fill ( style: FillStyle ( eoFill: true ) )
95
153
. foregroundColor ( . black. opacity ( 0.6 ) )
96
154
. allowsHitTesting ( false )
97
- VStack {
98
- Spacer ( )
99
- HStack {
100
- Spacer ( )
101
- Button { Task {
102
- do {
103
- onCrop ( . success( try crop ( ) ) )
104
- } catch {
105
- onCrop ( . failure( error) )
106
- }
107
- } } label: {
108
- Label ( " Crop " , systemImage: " checkmark.circle.fill " )
109
- . font ( . title2)
110
- . foregroundColor ( . accentColor)
111
- . labelStyle ( . iconOnly)
112
- . padding ( 1 )
113
- . background (
114
- Circle ( ) . fill ( . white)
115
- )
116
- }
117
- . buttonStyle ( . plain)
118
- . padding ( )
155
+ controls {
156
+ do {
157
+ onCrop ( . success( try crop ( ) ) )
158
+ } catch {
159
+ onCrop ( . failure( error) )
119
160
}
120
161
}
121
162
}
0 commit comments