From 22ed64a199dec5d36b81937d04e7a19eb6a3623e Mon Sep 17 00:00:00 2001 From: Yaroslav Yefimtsev Date: Mon, 21 Oct 2024 01:08:28 +0300 Subject: [PATCH 1/3] Extend configuration to include colors and fonts * Add Colors struct to the configuration to handle color customization of the buttons, instruction and background * Add Fonts struct to the configuration to handle font customization of the buttons and instruction * Remove hardcoded color and font references in CropView * Fix trailing whitespaces in CropView.swift --- .../Models/SwiftyCropConfiguration.swift | 46 +++++++++++++++- Sources/SwiftyCrop/View/CropView.swift | 54 ++++++++++--------- 2 files changed, 72 insertions(+), 28 deletions(-) diff --git a/Sources/SwiftyCrop/Models/SwiftyCropConfiguration.swift b/Sources/SwiftyCrop/Models/SwiftyCropConfiguration.swift index 3683735..85ac796 100644 --- a/Sources/SwiftyCrop/Models/SwiftyCropConfiguration.swift +++ b/Sources/SwiftyCrop/Models/SwiftyCropConfiguration.swift @@ -1,4 +1,5 @@ import CoreGraphics +import SwiftUI /// `SwiftyCropConfiguration` is a struct that defines the configuration for cropping behavior. public struct SwiftyCropConfiguration { @@ -9,7 +10,9 @@ public struct SwiftyCropConfiguration { public let zoomSensitivity: CGFloat public let rectAspectRatio: CGFloat public let customTexts: Texts? - + public let fonts: Fonts + public let colors: Colors + public struct Texts { public init( cancelButtonText: String, @@ -26,6 +29,41 @@ public struct SwiftyCropConfiguration { public let saveButtonText: String } + public struct Colors { + public init( + cancelButton: Color = .white, + interactionInstructions: Color = .white, + saveButton: Color = .white, + background: Color = .black + ) { + self.cancelButton = cancelButton + self.interactionInstructions = interactionInstructions + self.saveButton = saveButton + self.background = background + } + + public let cancelButton: Color + public let interactionInstructions: Color + public let saveButton: Color + public let background: Color + } + + public struct Fonts { + public init( + cancelButton: Font? = nil, + interactionInstructions: Font? = nil, + saveButton: Font? = nil + ) { + self.cancelButton = cancelButton + self.interactionInstructions = interactionInstructions ?? .system(size: 16, weight: .regular) + self.saveButton = saveButton + } + + public let cancelButton: Font? + public let interactionInstructions: Font + public let saveButton: Font? + } + /// Creates a new instance of `SwiftyCropConfiguration`. /// /// - Parameters: @@ -49,7 +87,9 @@ public struct SwiftyCropConfiguration { rotateImage: Bool = false, zoomSensitivity: CGFloat = 1, rectAspectRatio: CGFloat = 4/3, - customTexts: Texts? = nil + customTexts: Texts? = nil, + fonts: Fonts = .init(), + colors: Colors = .init() ) { self.maxMagnificationScale = maxMagnificationScale self.maskRadius = maskRadius @@ -58,5 +98,7 @@ public struct SwiftyCropConfiguration { self.zoomSensitivity = zoomSensitivity self.rectAspectRatio = rectAspectRatio self.customTexts = customTexts + self.fonts = fonts + self.colors = colors } } diff --git a/Sources/SwiftyCrop/View/CropView.swift b/Sources/SwiftyCrop/View/CropView.swift index a2dd0f1..6b688d0 100644 --- a/Sources/SwiftyCrop/View/CropView.swift +++ b/Sources/SwiftyCrop/View/CropView.swift @@ -3,13 +3,13 @@ import SwiftUI struct CropView: View { @Environment(\.dismiss) private var dismiss @StateObject private var viewModel: CropViewModel - + private let image: UIImage private let maskShape: MaskShape private let configuration: SwiftyCropConfiguration private let onComplete: (UIImage?) -> Void private let localizableTableName: String - + init( image: UIImage, maskShape: MaskShape, @@ -30,23 +30,23 @@ struct CropView: View { ) localizableTableName = "Localizable" } - + var body: some View { let magnificationGesture = MagnificationGesture() .onChanged { value in let sensitivity: CGFloat = 0.1 * configuration.zoomSensitivity let scaledValue = (value.magnitude - 1) * sensitivity + 1 - + let maxScaleValues = viewModel.calculateMagnificationGestureMaxValues() viewModel.scale = min(max(scaledValue * viewModel.lastScale, maxScaleValues.0), maxScaleValues.1) - + updateOffset() } .onEnded { _ in viewModel.lastScale = viewModel.scale viewModel.lastOffset = viewModel.offset } - + let dragGesture = DragGesture() .onChanged { value in let maxOffsetPoint = viewModel.calculateDragGestureMax() @@ -63,7 +63,7 @@ struct CropView: View { .onEnded { _ in viewModel.lastOffset = viewModel.offset } - + let rotationGesture = RotationGesture() .onChanged { value in viewModel.angle = value @@ -71,17 +71,17 @@ struct CropView: View { .onEnded { _ in viewModel.lastAngle = viewModel.angle } - + VStack { Text( configuration.customTexts?.interactionInstructionsText ?? NSLocalizedString("interaction_instructions", tableName: localizableTableName, bundle: .module, comment: "") ) - .font(.system(size: 16, weight: .regular)) - .foregroundColor(.white) - .padding(.top, 30) - .zIndex(1) - + .font(configuration.fonts.interactionInstructions) + .foregroundColor(configuration.colors.interactionInstructions) + .padding(.top, 30) + .zIndex(1) + ZStack { Image(uiImage: image) .resizable() @@ -98,7 +98,7 @@ struct CropView: View { } } ) - + Image(uiImage: image) .resizable() .scaledToFit() @@ -114,7 +114,7 @@ struct CropView: View { .simultaneousGesture(magnificationGesture) .simultaneousGesture(dragGesture) .simultaneousGesture(configuration.rotateImage ? rotationGesture : nil) - + HStack { Button { dismiss() @@ -122,12 +122,13 @@ struct CropView: View { Text( configuration.customTexts?.cancelButtonText ?? NSLocalizedString("cancel_button", tableName: localizableTableName, bundle: .module, comment: "") - ) + ) } - .foregroundColor(.white) - + .font(configuration.fonts.cancelButton) + .foregroundColor(configuration.colors.cancelButton) + Spacer() - + Button { onComplete(cropImage()) dismiss() @@ -135,16 +136,17 @@ struct CropView: View { Text( configuration.customTexts?.saveButtonText ?? NSLocalizedString("save_button", tableName: localizableTableName, bundle: .module, comment: "") - ) + ) + .font(configuration.fonts.saveButton) } - .foregroundColor(.white) + .foregroundColor(configuration.colors.saveButton) } .frame(maxWidth: .infinity, alignment: .bottom) .padding() } - .background(.black) + .background(configuration.colors.background) } - + private func updateOffset() { let maxOffsetPoint = viewModel.calculateDragGestureMax() let newX = min(max(viewModel.offset.width, -maxOffsetPoint.x), maxOffsetPoint.x) @@ -152,7 +154,7 @@ struct CropView: View { viewModel.offset = CGSize(width: newX, height: newY) viewModel.lastOffset = viewModel.offset } - + private func cropImage() -> UIImage? { var editedImage: UIImage = image if configuration.rotateImage { @@ -171,10 +173,10 @@ struct CropView: View { return viewModel.cropToSquare(editedImage) } } - + private struct MaskShapeView: View { let maskShape: MaskShape - + var body: some View { Group { switch maskShape { From c740e4ed1265e9c48eac831effd1a4685471e540 Mon Sep 17 00:00:00 2001 From: Ben Date: Mon, 21 Oct 2024 13:09:03 +0200 Subject: [PATCH 2/3] Adjusted configuration for texts, fonts and colors --- README.md | 31 ++++++--- .../Models/SwiftyCropConfiguration.swift | 67 ++++++++++--------- Sources/SwiftyCrop/View/CropView.swift | 6 +- Tests/SwiftyCropTests/SwiftyCropTests.swift | 29 ++++++-- 4 files changed, 87 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index 26247d1..00b8f30 100644 --- a/README.md +++ b/README.md @@ -155,21 +155,34 @@ You can also configure `SwiftyCropView` by passing a `SwiftyCropConfiguration`. | `rotateImage` | `Bool`: Whether the image can be rotated when cropping using pinch gestures. Defaults to `false`. | | `zoomSensitivity` | `CGFloat`: Zoom sensitivity when cropping. Increase to make zoom faster / less sensitive. Defaults to `1.0`. | | `rectAspectRatio` | `CGFloat`: The aspect ratio to use when a rectangular mask shape is used. Defaults to `4:3`. | -| `customTexts` | `Texts`: Defines custom texts for the buttons and instructions. Defaults to `nil` using localized strings from resources. | +| `texts` | `Texts`: Defines custom texts for the buttons and instructions. Defaults to using localized strings from resources. | +| `fonts` | `Fonts`: Defines custom fonts for the buttons and instructions. Defaults to using system font. | +| `colors` | `Colors`: Defines custom colors for the texts and background. Defaults to white text and black background. | Create a configuration like this: ```swift let configuration = SwiftyCropConfiguration( - maxMagnificationScale = 4.0, + maxMagnificationScale: 4.0, maskRadius: 130, cropImageCircular: false, rotateImage: true, - zoomSensitivity = 1.0, - rectAspectRatio = 4/3, - customTexts = SwiftyCropConfiguration.Texts( - cancelButtonText: "Cancel", - interactionInstructionsText: "Custom instruction text", - saveButtonText: "Save" + zoomSensitivity: 1.0, + rectAspectRatio: 4/3, + texts: SwiftyCropConfiguration.Texts( + cancelButton: "Cancel", + interactionInstructions: "Custom instruction text", + saveButton: "Save" + ), + fonts: SwiftyCropConfiguration.Fonts( + cancelButton: Font.system(size: 12), + interactionInstructions: Font.system(size: 14), + saveButton: Font.system(size: 12) + ), + colors: SwiftyCropConfiguration.Colors( + cancelButton: Color.red, + interactionInstructions: Color.white, + saveButton: Color.blue, + background: Color.gray ) ) ``` @@ -205,6 +218,8 @@ Thanks to [@insub](https://github.com/insub4067) for adding the korean localizat Thanks to [@yhirano](https://github.com/yhirano) for adding the japanese localization 🇯🇵 +Thanks to [@yefimtsev](https://github.com/yefimtsev) for adding the ability to customize fonts and colors 🖼️ + ## ✍️ Author Benedikt Betz diff --git a/Sources/SwiftyCrop/Models/SwiftyCropConfiguration.swift b/Sources/SwiftyCrop/Models/SwiftyCropConfiguration.swift index 85ac796..499b1e9 100644 --- a/Sources/SwiftyCrop/Models/SwiftyCropConfiguration.swift +++ b/Sources/SwiftyCrop/Models/SwiftyCropConfiguration.swift @@ -9,26 +9,43 @@ public struct SwiftyCropConfiguration { public let rotateImage: Bool public let zoomSensitivity: CGFloat public let rectAspectRatio: CGFloat - public let customTexts: Texts? + public let texts: Texts public let fonts: Fonts public let colors: Colors public struct Texts { public init( - cancelButtonText: String, - interactionInstructionsText: String, - saveButtonText: String + // We cannot use the localized values here because module access is not given in init + cancelButton: String? = nil, + interactionInstructions: String? = nil, + saveButton: String? = nil ) { - self.cancelButtonText = cancelButtonText - self.interactionInstructionsText = interactionInstructionsText - self.saveButtonText = saveButtonText + self.cancelButton = cancelButton + self.interactionInstructions = interactionInstructions + self.saveButton = saveButton } - public let cancelButtonText: String - public let interactionInstructionsText: String - public let saveButtonText: String + public let cancelButton: String? + public let interactionInstructions: String? + public let saveButton: String? } + public struct Fonts { + public init( + cancelButton: Font? = nil, + interactionInstructions: Font? = nil, + saveButton: Font? = nil + ) { + self.cancelButton = cancelButton + self.interactionInstructions = interactionInstructions ?? .system(size: 16, weight: .regular) + self.saveButton = saveButton + } + + public let cancelButton: Font? + public let interactionInstructions: Font + public let saveButton: Font? + } + public struct Colors { public init( cancelButton: Color = .white, @@ -48,22 +65,6 @@ public struct SwiftyCropConfiguration { public let background: Color } - public struct Fonts { - public init( - cancelButton: Font? = nil, - interactionInstructions: Font? = nil, - saveButton: Font? = nil - ) { - self.cancelButton = cancelButton - self.interactionInstructions = interactionInstructions ?? .system(size: 16, weight: .regular) - self.saveButton = saveButton - } - - public let cancelButton: Font? - public let interactionInstructions: Font - public let saveButton: Font? - } - /// Creates a new instance of `SwiftyCropConfiguration`. /// /// - Parameters: @@ -79,7 +80,11 @@ public struct SwiftyCropConfiguration { /// /// - rectAspectRatio: The aspect ratio to use when a `.rectangle` mask shape is used. Defaults to `4:3`. /// - /// - customTexts: `Texts` object when using custom texts for the cropping view. + /// - texts: `Texts` object when using custom texts for the cropping view. + /// + /// - fonts: `Fonts` object when using custom fonts for the cropping view. Defaults to system. + /// + /// - colors: `Colors` object when using custom colors for the cropping view. Defaults to white text and black background. public init( maxMagnificationScale: CGFloat = 4.0, maskRadius: CGFloat = 130, @@ -87,9 +92,9 @@ public struct SwiftyCropConfiguration { rotateImage: Bool = false, zoomSensitivity: CGFloat = 1, rectAspectRatio: CGFloat = 4/3, - customTexts: Texts? = nil, - fonts: Fonts = .init(), - colors: Colors = .init() + texts: Texts = Texts(), + fonts: Fonts = Fonts(), + colors: Colors = Colors() ) { self.maxMagnificationScale = maxMagnificationScale self.maskRadius = maskRadius @@ -97,7 +102,7 @@ public struct SwiftyCropConfiguration { self.rotateImage = rotateImage self.zoomSensitivity = zoomSensitivity self.rectAspectRatio = rectAspectRatio - self.customTexts = customTexts + self.texts = texts self.fonts = fonts self.colors = colors } diff --git a/Sources/SwiftyCrop/View/CropView.swift b/Sources/SwiftyCrop/View/CropView.swift index 6b688d0..d645bcd 100644 --- a/Sources/SwiftyCrop/View/CropView.swift +++ b/Sources/SwiftyCrop/View/CropView.swift @@ -74,7 +74,7 @@ struct CropView: View { VStack { Text( - configuration.customTexts?.interactionInstructionsText ?? + configuration.texts.interactionInstructions ?? NSLocalizedString("interaction_instructions", tableName: localizableTableName, bundle: .module, comment: "") ) .font(configuration.fonts.interactionInstructions) @@ -120,7 +120,7 @@ struct CropView: View { dismiss() } label: { Text( - configuration.customTexts?.cancelButtonText ?? + configuration.texts.cancelButton ?? NSLocalizedString("cancel_button", tableName: localizableTableName, bundle: .module, comment: "") ) } @@ -134,7 +134,7 @@ struct CropView: View { dismiss() } label: { Text( - configuration.customTexts?.saveButtonText ?? + configuration.texts.saveButton ?? NSLocalizedString("save_button", tableName: localizableTableName, bundle: .module, comment: "") ) .font(configuration.fonts.saveButton) diff --git a/Tests/SwiftyCropTests/SwiftyCropTests.swift b/Tests/SwiftyCropTests/SwiftyCropTests.swift index c7996d0..c5d890c 100644 --- a/Tests/SwiftyCropTests/SwiftyCropTests.swift +++ b/Tests/SwiftyCropTests/SwiftyCropTests.swift @@ -8,10 +8,21 @@ final class SwiftyCropTests: XCTestCase { maskRadius: 1.0, cropImageCircular: true, rectAspectRatio: 4/3, - customTexts: SwiftyCropConfiguration.Texts( + texts: SwiftyCropConfiguration.Texts( cancelButtonText: "Test 1", interactionInstructionsText: "Test 2", saveButtonText: "Test 3" + ), + fonts: SwiftyCropConfiguration.Fonts( + cancelButton: Font.system(size: 12), + interactionInstructions: .systemFont(ofSize: 13), + saveButton: .systemFont(ofSize: 14) + ), + colors: SwiftyCropConfiguration.Colors( + cancelButton: .red, + interactionInstructions: .yellow, + saveButton: .green, + background: .gray ) ) @@ -19,8 +30,18 @@ final class SwiftyCropTests: XCTestCase { XCTAssertEqual(configuration.maskRadius, 1.0) XCTAssertEqual(configuration.cropImageCircular, true) XCTAssertEqual(configuration.rectAspectRatio, 4/3) - XCTAssertEqual(configuration.customTexts?.cancelButtonText, "Test 1") - XCTAssertEqual(configuration.customTexts?.interactionInstructionsText, "Test 2") - XCTAssertEqual(configuration.customTexts?.saveButtonText, "Test 3") + + XCTAssertEqual(configuration.texts.cancelButton, "Test 1") + XCTAssertEqual(configuration.texts.interactionInstructions, "Test 2") + XCTAssertEqual(configuration.texts.saveButton, "Test 3") + + XCTAssertEqual(configuration.fonts.cancelButton, Font.system(size: 12)) + XCTAssertEqual(configuration.fonts.interactionInstructions, Font.system(size: 13)) + XCTAssertEqual(configuration.fonts.saveButton, Font.system(size: 14)) + + XCTAssertEqual(configuration.colors.cancelButton, Color.red) + XCTAssertEqual(configuration.colors.interactionInstructions, Color.yellow) + XCTAssertEqual(configuration.colors.saveButton, Color.green) + XCTAssertEqual(configuration.colors.background, Color.gray) } } From 4bf6b14b295d963d43cf475e2510b4b9cec2e749 Mon Sep 17 00:00:00 2001 From: Ben Date: Mon, 21 Oct 2024 13:14:33 +0200 Subject: [PATCH 3/3] Fix tests --- Tests/SwiftyCropTests/SwiftyCropTests.swift | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Tests/SwiftyCropTests/SwiftyCropTests.swift b/Tests/SwiftyCropTests/SwiftyCropTests.swift index c5d890c..a0aecb9 100644 --- a/Tests/SwiftyCropTests/SwiftyCropTests.swift +++ b/Tests/SwiftyCropTests/SwiftyCropTests.swift @@ -1,4 +1,5 @@ import XCTest +import SwiftUI @testable import SwiftyCrop final class SwiftyCropTests: XCTestCase { @@ -9,14 +10,14 @@ final class SwiftyCropTests: XCTestCase { cropImageCircular: true, rectAspectRatio: 4/3, texts: SwiftyCropConfiguration.Texts( - cancelButtonText: "Test 1", - interactionInstructionsText: "Test 2", - saveButtonText: "Test 3" + cancelButton: "Test 1", + interactionInstructions: "Test 2", + saveButton: "Test 3" ), fonts: SwiftyCropConfiguration.Fonts( cancelButton: Font.system(size: 12), - interactionInstructions: .systemFont(ofSize: 13), - saveButton: .systemFont(ofSize: 14) + interactionInstructions: Font.system(size: 13), + saveButton: Font.system(size: 14) ), colors: SwiftyCropConfiguration.Colors( cancelButton: .red,