Skip to content

Commit

Permalink
Merge pull request #26 from yefimtsev/ui-customization
Browse files Browse the repository at this point in the history
Extend configuration to include colors and fonts
  • Loading branch information
benedom authored Oct 21, 2024
2 parents 8b6fc8b + 4bf6b14 commit 454d0a0
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 58 deletions.
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)
}
}

0 comments on commit 454d0a0

Please sign in to comment.