Skip to content
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
48 changes: 46 additions & 2 deletions Demo/SwiftyCropDemo/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ struct ContentView: View {
@State private var minAspectRatio: CGFloat
@State private var maxAspectRatio: CGFloat
@State private var dismissesOnCompletion: Bool
@State private var backgroundColor: PresetBackgroundColors = .black
@FocusState private var textFieldFocused: Bool
@EnvironmentObject private var cropSession: CropSession
#if os(macOS)
Expand All @@ -44,7 +45,34 @@ struct ContentView: View {
}
}
}


enum PresetBackgroundColors: String, CaseIterable {
case black = "Black"
case white = "White"
case gray = "Gray"
case blue = "Blue"
case primary = "Primary"

func getValue() -> Color {
switch self {
case .black:
.black

case .white:
.white

case .gray:
.gray

case .blue:
.blue

case .primary:
.primary
}
}
}

init() {
let defaultConfiguration = SwiftyCropConfiguration()
_cropImageCircular = State(initialValue: defaultConfiguration.cropImageCircular)
Expand Down Expand Up @@ -147,6 +175,19 @@ struct ContentView: View {

Toggle("Dismiss on completion", isOn: $dismissesOnCompletion)

HStack {
Text("Background color")
.frame(maxWidth: .infinity, alignment: .leading)

Picker("backgroundColor", selection: $backgroundColor) {
ForEach(PresetBackgroundColors.allCases, id: \.self) { color in
Text(color.rawValue)
}
}
.pickerStyle(.menu)
.labelsHidden()
}

HStack {
Text("Max magnification")
.frame(maxWidth: .infinity, alignment: .leading)
Expand Down Expand Up @@ -274,7 +315,10 @@ struct ContentView: View {
allowAspectRatioResizing: allowAspectRatioResizing,
minAspectRatio: minAspectRatio,
maxAspectRatio: maxAspectRatio,
dismissesOnCompletion: dismissesOnCompletion
dismissesOnCompletion: dismissesOnCompletion,
colors: SwiftyCropConfiguration.Colors(
background: backgroundColor.getValue()
)
)
}

Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,8 @@ You can also configure `SwiftyCropView` by passing a `SwiftyCropConfiguration`.
| `minAspectRatio` | `CGFloat`: The minimum allowed aspect ratio (width / height) when `allowAspectRatioResizing` is enabled. Defaults to `0.1`. |
| `maxAspectRatio` | `CGFloat`: The maximum allowed aspect ratio (width / height) when `allowAspectRatioResizing` is enabled. Defaults to `10.0`. |
| `dismissesOnCompletion` | `Bool`: Whether the cropping view dismisses itself after the save or cancel button was tapped. Set to `false` if the presenting view handles the dismissal itself. Defaults to `true`. |
| `showsProgressLayer` | `Bool`: Whether a progress layer is shown while the cropped image is generated. Defaults to `true`. |
| `progressLayerDelay` | `Duration`: How long cropping must remain in progress before the progress layer is shown. Use a short delay to avoid flashing it for fast crops. Defaults to `.zero`. |
| `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, buttons and background. Defaults to white text and black background. See [iOS 26 & Liquid Glass](#-ios-26--liquid-glass) for how the button colors behave with and without Liquid Glass. |
Expand All @@ -199,6 +201,8 @@ let configuration = SwiftyCropConfiguration(
showsZoomSlider: false,
zoomSensitivity: 1.0,
rectAspectRatio: 4/3,
showsProgressLayer: true,
progressLayerDelay: .milliseconds(200),
texts: SwiftyCropConfiguration.Texts(
cancelButton: "Cancel",
interactionInstructions: "Custom instruction text",
Expand Down Expand Up @@ -344,6 +348,8 @@ Another thanks to [@andrewhanshaw](https://github.com/andrewhanshaw) for overhau

Thanks to [@ezathashim](https://github.com/ezathashim) for adding the zoom slider for input devices without pinch-to-zoom and for making the dismissal optional 🔍

Thanks to [@jacklieblich](https://github.com/jacklieblich) for restoring the configured crop view background and for making the progress layer hideable and delayable ⏳

## 📃 License

`SwiftyCrop` is available under the MIT license. See the [LICENSE](https://github.com/benedom/SwiftyCrop/blob/master/LICENSE.md) file for more info.
11 changes: 11 additions & 0 deletions Sources/SwiftyCrop/Models/SwiftyCropConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ public struct SwiftyCropConfiguration {
public let minAspectRatio: CGFloat
public let maxAspectRatio: CGFloat
public let dismissesOnCompletion: Bool
public let showsProgressLayer: Bool
public let progressLayerDelay: Duration
public let texts: Texts
public let fonts: Fonts
public let colors: Colors
Expand Down Expand Up @@ -172,6 +174,11 @@ public struct SwiftyCropConfiguration {
/// - dismissesOnCompletion: Whether the cropping view dismisses itself after the save or cancel button was tapped.
/// Set this to `false` if the presenting view handles the dismissal itself. Defaults to `true`.
///
/// - showsProgressLayer: Whether a progress layer is shown while the cropped image is generated. Defaults to `true`.
///
/// - progressLayerDelay: How long cropping must remain in progress before the progress layer is shown.
/// Use a short delay to avoid flashing the progress layer for fast crops. Defaults to `.zero`.
///
/// - 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.
Expand All @@ -190,6 +197,8 @@ public struct SwiftyCropConfiguration {
minAspectRatio: CGFloat = 0.1,
maxAspectRatio: CGFloat = 10.0,
dismissesOnCompletion: Bool = true,
showsProgressLayer: Bool = true,
progressLayerDelay: Duration = .zero,
texts: Texts = Texts(),
fonts: Fonts = Fonts(),
colors: Colors = Colors()
Expand All @@ -206,6 +215,8 @@ public struct SwiftyCropConfiguration {
self.minAspectRatio = minAspectRatio
self.maxAspectRatio = maxAspectRatio
self.dismissesOnCompletion = dismissesOnCompletion
self.showsProgressLayer = showsProgressLayer
self.progressLayerDelay = progressLayerDelay
self.texts = texts
self.fonts = fonts
self.colors = colors
Expand Down
13 changes: 11 additions & 2 deletions Sources/SwiftyCrop/View/CropView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,22 @@ struct CropView: View {
ZStack {
cropImageView

if isCropping {
ProgressLayer(configuration: configuration, localizableTableName: localizableTableName)
if isCropping && configuration.showsProgressLayer {
if configuration.progressLayerDelay == .zero {
ProgressLayer(configuration: configuration, localizableTableName: localizableTableName)
} else {
DelayedProgressLayer(
configuration: configuration,
localizableTableName: localizableTableName,
delay: configuration.progressLayerDelay
)
}
}
}
// Scrolls the view up slightly so the blurred background of the toolbar is shown on a non-scrolling view
// Helps with contrast between the toolbar title and the content behind it
.scrollOffsetToolbarTrigger()
.background(configuration.colors.background)
.toolbar {
toolbarView
}
Expand Down
29 changes: 29 additions & 0 deletions Sources/SwiftyCrop/View/ProgressLayer.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,34 @@
import SwiftUI

struct DelayedProgressLayer: View {
let configuration: SwiftyCropConfiguration
let localizableTableName: String
let delay: Duration

@State private var isVisible = false

var body: some View {
Group {
if isVisible {
ProgressLayer(configuration: configuration, localizableTableName: localizableTableName)
}
}
.task {
guard delay > .zero else {
isVisible = true
return
}

do {
try await Task.sleep(for: delay)
isVisible = true
} catch {
// Removing this view cancels the delay when cropping finishes quickly.
}
}
}
}

struct ProgressLayer: View {
let configuration: SwiftyCropConfiguration
let localizableTableName: String
Expand Down
6 changes: 6 additions & 0 deletions Tests/SwiftyCropTests/SwiftyCropTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ final class SwiftyCropTests: XCTestCase {
zoomSensitivity: 2,
rectAspectRatio: 4/3,
dismissesOnCompletion: false,
showsProgressLayer: false,
progressLayerDelay: .milliseconds(200),
texts: SwiftyCropConfiguration.Texts(
cancelButton: "Test 1",
interactionInstructions: "Test 2",
Expand Down Expand Up @@ -43,6 +45,8 @@ final class SwiftyCropTests: XCTestCase {
XCTAssertEqual(configuration.zoomSensitivity, 2)
XCTAssertEqual(configuration.rectAspectRatio, 4/3)
XCTAssertEqual(configuration.dismissesOnCompletion, false)
XCTAssertEqual(configuration.showsProgressLayer, false)
XCTAssertEqual(configuration.progressLayerDelay, .milliseconds(200))

XCTAssertEqual(configuration.texts.cancelButton, "Test 1")
XCTAssertEqual(configuration.texts.interactionInstructions, "Test 2")
Expand All @@ -64,6 +68,8 @@ final class SwiftyCropTests: XCTestCase {
let configuration = SwiftyCropConfiguration()

XCTAssertEqual(configuration.dismissesOnCompletion, true)
XCTAssertEqual(configuration.showsProgressLayer, true)
XCTAssertEqual(configuration.progressLayerDelay, .zero)
}

func testZoomSliderDefaults() {
Expand Down
Loading