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

Extend configuration to include colors and fonts #26

Merged
merged 3 commits into from
Oct 21, 2024
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 23 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
)
```
Expand Down Expand Up @@ -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
Expand Down
75 changes: 61 additions & 14 deletions Sources/SwiftyCrop/Models/SwiftyCropConfiguration.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import CoreGraphics
import SwiftUI

/// `SwiftyCropConfiguration` is a struct that defines the configuration for cropping behavior.
public struct SwiftyCropConfiguration {
Expand All @@ -8,22 +9,60 @@ 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,
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
}

/// Creates a new instance of `SwiftyCropConfiguration`.
Expand All @@ -41,22 +80,30 @@ 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,
cropImageCircular: Bool = false,
rotateImage: Bool = false,
zoomSensitivity: CGFloat = 1,
rectAspectRatio: CGFloat = 4/3,
customTexts: Texts? = nil
texts: Texts = Texts(),
fonts: Fonts = Fonts(),
colors: Colors = Colors()
) {
self.maxMagnificationScale = maxMagnificationScale
self.maskRadius = maskRadius
self.cropImageCircular = cropImageCircular
self.rotateImage = rotateImage
self.zoomSensitivity = zoomSensitivity
self.rectAspectRatio = rectAspectRatio
self.customTexts = customTexts
self.texts = texts
self.fonts = fonts
self.colors = colors
}
}
60 changes: 31 additions & 29 deletions Sources/SwiftyCrop/View/CropView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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()
Expand All @@ -63,25 +63,25 @@ struct CropView: View {
.onEnded { _ in
viewModel.lastOffset = viewModel.offset
}

let rotationGesture = RotationGesture()
.onChanged { value in
viewModel.angle = value
}
.onEnded { _ in
viewModel.lastAngle = viewModel.angle
}

VStack {
Text(
configuration.customTexts?.interactionInstructionsText ??
configuration.texts.interactionInstructions ??
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()
Expand All @@ -98,7 +98,7 @@ struct CropView: View {
}
}
)

Image(uiImage: image)
.resizable()
.scaledToFit()
Expand All @@ -114,45 +114,47 @@ struct CropView: View {
.simultaneousGesture(magnificationGesture)
.simultaneousGesture(dragGesture)
.simultaneousGesture(configuration.rotateImage ? rotationGesture : nil)

HStack {
Button {
dismiss()
} label: {
Text(
configuration.customTexts?.cancelButtonText ??
configuration.texts.cancelButton ??
NSLocalizedString("cancel_button", tableName: localizableTableName, bundle: .module, comment: "")
)
)
}
.foregroundColor(.white)

.font(configuration.fonts.cancelButton)
.foregroundColor(configuration.colors.cancelButton)

Spacer()

Button {
onComplete(cropImage())
dismiss()
} label: {
Text(
configuration.customTexts?.saveButtonText ??
configuration.texts.saveButton ??
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)
let newY = min(max(viewModel.offset.height, -maxOffsetPoint.y), maxOffsetPoint.y)
viewModel.offset = CGSize(width: newX, height: newY)
viewModel.lastOffset = viewModel.offset
}

private func cropImage() -> UIImage? {
var editedImage: UIImage = image
if configuration.rotateImage {
Expand All @@ -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 {
Expand Down
36 changes: 29 additions & 7 deletions Tests/SwiftyCropTests/SwiftyCropTests.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import XCTest
import SwiftUI
@testable import SwiftyCrop

final class SwiftyCropTests: XCTestCase {
Expand All @@ -8,19 +9,40 @@ final class SwiftyCropTests: XCTestCase {
maskRadius: 1.0,
cropImageCircular: true,
rectAspectRatio: 4/3,
customTexts: SwiftyCropConfiguration.Texts(
cancelButtonText: "Test 1",
interactionInstructionsText: "Test 2",
saveButtonText: "Test 3"
texts: SwiftyCropConfiguration.Texts(
cancelButton: "Test 1",
interactionInstructions: "Test 2",
saveButton: "Test 3"
),
fonts: SwiftyCropConfiguration.Fonts(
cancelButton: Font.system(size: 12),
interactionInstructions: Font.system(size: 13),
saveButton: Font.system(size: 14)
),
colors: SwiftyCropConfiguration.Colors(
cancelButton: .red,
interactionInstructions: .yellow,
saveButton: .green,
background: .gray
)
)

XCTAssertEqual(configuration.maxMagnificationScale, 1.0)
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)
}
}
Loading