Skip to content

Commit 6a3f343

Browse files
authored
Merge pull request #34 from mosliem/fix-cropping-image-main-thread
Fix cropping image in the main thread
2 parents ef27522 + d91ed72 commit 6a3f343

File tree

4 files changed

+172
-79
lines changed

4 files changed

+172
-79
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,8 @@ Thanks to [@yefimtsev](https://github.com/yefimtsev) for adding the ability to c
223223

224224
Thanks to [@SuperY](https://github.com/SuperY) for adding the chinese localization 🇨🇳
225225

226+
Thanks to [@mosliem](https://github.com/mosliem) for adding the cropping in background thread 🧵
227+
226228
## ✍️ Author
227229

228230
Benedikt Betz

Sources/SwiftyCrop/Models/SwiftyCropConfiguration.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,19 @@ public struct SwiftyCropConfiguration {
1818
// We cannot use the localized values here because module access is not given in init
1919
cancelButton: String? = nil,
2020
interactionInstructions: String? = nil,
21-
saveButton: String? = nil
21+
saveButton: String? = nil,
22+
progressLayerText: String? = nil
2223
) {
2324
self.cancelButton = cancelButton
2425
self.interactionInstructions = interactionInstructions
2526
self.saveButton = saveButton
27+
self.progressLayerText = progressLayerText
2628
}
2729

2830
public let cancelButton: String?
2931
public let interactionInstructions: String?
3032
public let saveButton: String?
33+
public let progressLayerText: String?
3134
}
3235

3336
public struct Fonts {

Sources/SwiftyCrop/Resources/Localizable.xcstrings

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,24 @@
167167
}
168168
}
169169
},
170+
"processing_label" : {
171+
"extractionState" : "manual",
172+
"localizations" : {
173+
"en" : { "stringUnit" : { "state" : "translated", "value" : "Processing" } },
174+
"de" : { "stringUnit" : { "state" : "translated", "value" : "Verarbeitung" } },
175+
"es" : { "stringUnit" : { "state" : "translated", "value" : "Procesando" } },
176+
"fr" : { "stringUnit" : { "state" : "translated", "value" : "Traitement" } },
177+
"hu" : { "stringUnit" : { "state" : "translated", "value" : "Feldolgozás" } },
178+
"it" : { "stringUnit" : { "state" : "translated", "value" : "Elaborazione" } },
179+
"ja" : { "stringUnit" : { "state" : "translated", "value" : "処理中" } },
180+
"ko" : { "stringUnit" : { "state" : "translated", "value" : "처리 중" } },
181+
"pt-BR" : { "stringUnit" : { "state" : "translated", "value" : "Processando" } },
182+
"ru" : { "stringUnit" : { "state" : "translated", "value" : "Обработка" } },
183+
"tr" : { "stringUnit" : { "state" : "translated", "value" : "İşleniyor" } },
184+
"uk" : { "stringUnit" : { "state" : "translated", "value" : "Обробка" } },
185+
"zh" : { "stringUnit" : { "state" : "translated", "value" : "处理中" } }
186+
}
187+
},
170188
"save_button" : {
171189
"extractionState" : "manual",
172190
"localizations" : {

Sources/SwiftyCrop/View/CropView.swift

Lines changed: 148 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import SwiftUI
2+
import PhotosUI
23

34
struct CropView: View {
45
@Environment(\.dismiss) private var dismiss
56
@StateObject private var viewModel: CropViewModel
7+
8+
@State private var isCropping: Bool = false
69

710
private let image: UIImage
811
private let maskShape: MaskShape
@@ -31,8 +34,28 @@ struct CropView: View {
3134
localizableTableName = "Localizable"
3235
}
3336

37+
// MARK: - Body
3438
var body: some View {
35-
let magnificationGesture = MagnificationGesture()
39+
ZStack {
40+
VStack {
41+
instructionText
42+
.padding(.top, 16)
43+
Spacer()
44+
cropImageView
45+
Spacer()
46+
cropToolbar
47+
}
48+
.background(configuration.colors.background)
49+
50+
if isCropping {
51+
progressLayer
52+
}
53+
}
54+
}
55+
56+
// MARK: - Gestures
57+
private var magnificationGesture: some Gesture {
58+
MagnificationGesture()
3659
.onChanged { value in
3760
let sensitivity: CGFloat = 0.1 * configuration.zoomSensitivity
3861
let scaledValue = (value.magnitude - 1) * sensitivity + 1
@@ -46,8 +69,10 @@ struct CropView: View {
4669
viewModel.lastScale = viewModel.scale
4770
viewModel.lastOffset = viewModel.offset
4871
}
72+
}
4973

50-
let dragGesture = DragGesture()
74+
private var dragGesture: some Gesture {
75+
DragGesture()
5176
.onChanged { value in
5277
let maxOffsetPoint = viewModel.calculateDragGestureMax()
5378
let newX = min(
@@ -63,96 +88,140 @@ struct CropView: View {
6388
.onEnded { _ in
6489
viewModel.lastOffset = viewModel.offset
6590
}
91+
}
6692

67-
let rotationGesture = RotationGesture()
93+
private var rotationGesture: some Gesture {
94+
RotationGesture()
6895
.onChanged { value in
6996
viewModel.angle = viewModel.lastAngle + value
7097
}
7198
.onEnded { _ in
7299
viewModel.lastAngle = viewModel.angle
73100
}
101+
}
74102

75-
VStack {
76-
Text(
77-
configuration.texts.interactionInstructions ??
78-
NSLocalizedString("interaction_instructions", tableName: localizableTableName, bundle: .module, comment: "")
79-
)
80-
.font(configuration.fonts.interactionInstructions)
81-
.foregroundColor(configuration.colors.interactionInstructions)
82-
.padding(.top, 30)
83-
.zIndex(1)
84-
85-
ZStack {
86-
Image(uiImage: image)
87-
.resizable()
88-
.scaledToFit()
89-
.rotationEffect(viewModel.angle)
90-
.scaleEffect(viewModel.scale)
91-
.offset(viewModel.offset)
92-
.opacity(0.5)
93-
.overlay(
94-
GeometryReader { geometry in
95-
Color.clear
96-
.onAppear {
97-
viewModel.updateMaskDimensions(for: geometry.size)
98-
}
99-
}
100-
)
101-
102-
Image(uiImage: image)
103-
.resizable()
104-
.scaledToFit()
105-
.rotationEffect(viewModel.angle)
106-
.scaleEffect(viewModel.scale)
107-
.offset(viewModel.offset)
108-
.mask(
109-
MaskShapeView(maskShape: maskShape)
110-
.frame(width: viewModel.maskSize.width, height: viewModel.maskSize.height)
111-
)
112-
113-
VStack {
114-
Spacer()
115-
HStack {
116-
Button {
117-
dismiss()
118-
} label: {
119-
Text(
120-
configuration.texts.cancelButton ??
121-
NSLocalizedString("cancel_button", tableName: localizableTableName, bundle: .module, comment: "")
122-
)
123-
.padding()
124-
.font(configuration.fonts.cancelButton)
125-
.foregroundColor(configuration.colors.cancelButton)
126-
}
127-
.padding()
128-
129-
Spacer()
130-
131-
Button {
132-
onComplete(cropImage())
133-
dismiss()
134-
} label: {
135-
Text(
136-
configuration.texts.saveButton ??
137-
NSLocalizedString("save_button", tableName: localizableTableName, bundle: .module, comment: "")
138-
)
139-
.padding()
140-
.font(configuration.fonts.saveButton)
141-
.foregroundColor(configuration.colors.saveButton)
142-
}
143-
.padding()
103+
// MARK: - UI Components
104+
private var instructionText: some View {
105+
Text(
106+
configuration.texts.interactionInstructions ??
107+
NSLocalizedString("interaction_instructions", tableName: localizableTableName, bundle: .module, comment: "")
108+
)
109+
.font(configuration.fonts.interactionInstructions)
110+
.foregroundColor(configuration.colors.interactionInstructions)
111+
.padding(.top, 30)
112+
.zIndex(1)
113+
}
114+
115+
private var cropImageView: some View {
116+
ZStack {
117+
Image(uiImage: image)
118+
.resizable()
119+
.scaledToFit()
120+
.rotationEffect(viewModel.angle)
121+
.scaleEffect(viewModel.scale)
122+
.offset(viewModel.offset)
123+
.opacity(0.5)
124+
.overlay(
125+
GeometryReader { geometry in
126+
Color.clear
127+
.onAppear {
128+
viewModel.updateMaskDimensions(for: geometry.size)
129+
}
130+
}
131+
)
132+
133+
Image(uiImage: image)
134+
.resizable()
135+
.scaledToFit()
136+
.rotationEffect(viewModel.angle)
137+
.scaleEffect(viewModel.scale)
138+
.offset(viewModel.offset)
139+
.mask(
140+
MaskShapeView(maskShape: maskShape)
141+
.frame(width: viewModel.maskSize.width, height: viewModel.maskSize.height)
142+
)
143+
}
144+
.frame(maxWidth: .infinity, maxHeight: .infinity)
145+
.simultaneousGesture(magnificationGesture)
146+
.simultaneousGesture(dragGesture)
147+
.simultaneousGesture(configuration.rotateImage ? rotationGesture : nil)
148+
}
149+
150+
private var cropToolbar: some View {
151+
HStack {
152+
Button {
153+
dismiss()
154+
} label: {
155+
Text(
156+
configuration.texts.cancelButton ??
157+
NSLocalizedString("cancel_button", tableName: localizableTableName, bundle: .module, comment: "")
158+
)
159+
.padding()
160+
.font(configuration.fonts.cancelButton)
161+
.foregroundColor(configuration.colors.cancelButton)
162+
}
163+
.padding()
164+
165+
Spacer()
166+
167+
Button {
168+
Task {
169+
isCropping = true
170+
let result = cropImage()
171+
await MainActor.run {
172+
onComplete(result)
173+
dismiss()
144174
}
145-
.frame(maxWidth: .infinity, alignment: .bottom)
146175
}
176+
} label: {
177+
Text(
178+
configuration.texts.saveButton ??
179+
NSLocalizedString("save_button", tableName: localizableTableName, bundle: .module, comment: "")
180+
)
181+
.padding()
182+
.font(configuration.fonts.saveButton)
183+
.foregroundColor(configuration.colors.saveButton)
184+
}
185+
.padding()
186+
.disabled(isCropping)
187+
}
188+
.frame(maxWidth: .infinity, alignment: .bottom)
189+
}
190+
191+
private var progressLayer: some View {
192+
ZStack {
193+
configuration.colors.background.opacity(0.4)
194+
.ignoresSafeArea()
195+
196+
VStack(alignment: .center, spacing: 5) {
197+
198+
Spacer(minLength: 35)
199+
200+
ProgressView()
201+
.progressViewStyle(CircularProgressViewStyle(tint: configuration.colors.interactionInstructions))
202+
.scaleEffect(1.2)
203+
204+
Spacer()
205+
206+
Text(
207+
configuration.texts.progressLayerText ??
208+
NSLocalizedString("processing_label", tableName: localizableTableName, bundle: .module, comment: "")
209+
)
210+
.font(.body)
211+
.foregroundColor(configuration.colors.interactionInstructions)
212+
.padding(.bottom, 12)
213+
147214
}
148-
.frame(maxWidth: .infinity, maxHeight: .infinity)
149-
.simultaneousGesture(magnificationGesture)
150-
.simultaneousGesture(dragGesture)
151-
.simultaneousGesture(configuration.rotateImage ? rotationGesture : nil)
215+
.frame(width: 120, height: 110)
216+
.background(configuration.colors.background.opacity(0.8))
217+
.cornerRadius(12)
218+
.padding(.vertical, 5)
219+
.padding(.horizontal, 15)
152220
}
153-
.background(configuration.colors.background)
221+
.transition(.opacity)
154222
}
155223

224+
// MARK: - Helpers
156225
private func updateOffset() {
157226
let maxOffsetPoint = viewModel.calculateDragGestureMax()
158227
let newX = min(max(viewModel.offset.width, -maxOffsetPoint.x), maxOffsetPoint.x)
@@ -180,6 +249,7 @@ struct CropView: View {
180249
}
181250
}
182251

252+
// MARK: - Mask Shape View
183253
private struct MaskShapeView: View {
184254
let maskShape: MaskShape
185255

0 commit comments

Comments
 (0)