aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNavan Chauhan <navanchauhan@gmail.com>2024-08-12 23:59:40 -0600
committerNavan Chauhan <navanchauhan@gmail.com>2024-08-13 00:00:24 -0600
commitfb3e562581a8367e0c83bf5b4c1548a5216281cf (patch)
treefcc7ada95f409807863bf29b0ee2e435712d6fbb
parent24a67bf73595994ccf4b60cdad0021b37ca45c45 (diff)
add support for macOSHEADmaster
-rw-r--r--Demo/SwiftyCropDemo.xcodeproj/project.pbxproj8
-rw-r--r--Demo/SwiftyCropDemo/ContentView.swift53
-rw-r--r--Demo/SwiftyCropDemo/UIElements/DecimalTextField.swift2
-rw-r--r--Package.swift2
-rw-r--r--Sources/SwiftyCrop/Models/CropViewModel.swift90
-rw-r--r--Sources/SwiftyCrop/SwiftyCrop.swift21
-rw-r--r--Sources/SwiftyCrop/View/CropView.swift38
7 files changed, 170 insertions, 44 deletions
diff --git a/Demo/SwiftyCropDemo.xcodeproj/project.pbxproj b/Demo/SwiftyCropDemo.xcodeproj/project.pbxproj
index 5609787..dcf869d 100644
--- a/Demo/SwiftyCropDemo.xcodeproj/project.pbxproj
+++ b/Demo/SwiftyCropDemo.xcodeproj/project.pbxproj
@@ -309,9 +309,13 @@
"$(inherited)",
"@executable_path/Frameworks",
);
+ MACOSX_DEPLOYMENT_TARGET = 12.0;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = "--PRODUCT-BUNDLE-IDENTIFIER-";
PRODUCT_NAME = "$(TARGET_NAME)";
+ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
+ SUPPORTS_MACCATALYST = NO;
+ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
@@ -337,9 +341,13 @@
"$(inherited)",
"@executable_path/Frameworks",
);
+ MACOSX_DEPLOYMENT_TARGET = 12.0;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = "--PRODUCT-BUNDLE-IDENTIFIER-";
PRODUCT_NAME = "$(TARGET_NAME)";
+ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
+ SUPPORTS_MACCATALYST = NO;
+ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
diff --git a/Demo/SwiftyCropDemo/ContentView.swift b/Demo/SwiftyCropDemo/ContentView.swift
index 08b1060..d4a7d5d 100644
--- a/Demo/SwiftyCropDemo/ContentView.swift
+++ b/Demo/SwiftyCropDemo/ContentView.swift
@@ -1,9 +1,15 @@
import SwiftUI
import SwiftyCrop
+#if canImport(UIKit)
+typealias PlatformImage = UIImage
+#elseif canImport(AppKit)
+typealias PlatformImage = NSImage
+#endif
+
struct ContentView: View {
@State private var showImageCropper: Bool = false
- @State private var selectedImage: UIImage?
+ @State private var selectedImage: PlatformImage?
@State private var selectedShape: MaskShape = .square
@State private var cropImageCircular: Bool
@State private var rotateImage: Bool
@@ -27,8 +33,7 @@ struct ContentView: View {
Group {
if let selectedImage = selectedImage {
- Image(uiImage: selectedImage)
- .resizable()
+ PlatformImageView(image: selectedImage)
.aspectRatio(contentMode: .fit)
.cornerRadius(8)
} else {
@@ -84,7 +89,15 @@ struct ContentView: View {
.frame(maxWidth: .infinity, alignment: .leading)
Button {
+#if canImport(UIKit)
maskRadius = min(UIScreen.main.bounds.width, UIScreen.main.bounds.height) / 2
+#elseif canImport(AppKit)
+ if let screen = NSScreen.main {
+ maskRadius = min(screen.frame.width, screen.frame.height) / 2
+ } else {
+ maskRadius = 200 // Default value if no screen is available
+ }
+#endif
} label: {
Image(systemName: "arrow.up.left.and.arrow.down.right")
.font(.footnote)
@@ -119,7 +132,19 @@ struct ContentView: View {
.onAppear {
loadImage()
}
+ #if os(macOS)
+ .sheet(isPresented: $showImageCropper) {
+ imageCropperView
+ }
+ #else
.fullScreenCover(isPresented: $showImageCropper) {
+ imageCropperView
+ }
+ #endif
+ }
+
+ private var imageCropperView: some View {
+ Group {
if let selectedImage = selectedImage {
SwiftyCropView(
imageToCrop: selectedImage,
@@ -137,6 +162,9 @@ struct ContentView: View {
}
}
}
+ #if canImport(AppKit)
+ .frame(width: 600, height: 400) // Adjust size as needed for macOS
+ #endif
}
private func loadImage() {
@@ -146,19 +174,34 @@ struct ContentView: View {
}
// Example function for downloading an image
- private func downloadExampleImage() async -> UIImage? {
+ private func downloadExampleImage() async -> PlatformImage? {
let portraitUrlString = "https://picsum.photos/1000/1200"
let landscapeUrlString = "https://picsum.photos/2000/1000"
let urlString = Int.random(in: 0...1) == 0 ? portraitUrlString : landscapeUrlString
guard let url = URL(string: urlString),
let (data, _) = try? await URLSession.shared.data(from: url),
- let image = UIImage(data: data)
+ let image = PlatformImage(data: data)
else { return nil }
return image
}
}
+struct PlatformImageView: View {
+ let image: PlatformImage
+
+ var body: some View {
+ #if canImport(UIKit)
+ Image(uiImage: image)
+ .resizable()
+ #elseif canImport(AppKit)
+ Image(nsImage: image)
+ .resizable()
+ #endif
+ }
+}
+
+
#Preview {
ContentView()
}
diff --git a/Demo/SwiftyCropDemo/UIElements/DecimalTextField.swift b/Demo/SwiftyCropDemo/UIElements/DecimalTextField.swift
index f616413..9758f0f 100644
--- a/Demo/SwiftyCropDemo/UIElements/DecimalTextField.swift
+++ b/Demo/SwiftyCropDemo/UIElements/DecimalTextField.swift
@@ -15,6 +15,8 @@ struct DecimalTextField: View {
TextField("maxMagnification", value: $value, formatter: decimalFormatter)
.textFieldStyle(RoundedBorderTextFieldStyle())
.multilineTextAlignment(.trailing)
+ #if canImport(UIKit)
.keyboardType(.decimalPad)
+ #endif
}
}
diff --git a/Package.swift b/Package.swift
index d233735..9d9a9b1 100644
--- a/Package.swift
+++ b/Package.swift
@@ -6,7 +6,7 @@ import PackageDescription
let package = Package(
name: "SwiftyCrop",
defaultLocalization: "en",
- platforms: [.iOS(.v16)],
+ platforms: [.iOS(.v16), .macOS(.v12)],
products: [
// Products define the executables and libraries a package produces, making them visible to other packages.
.library(
diff --git a/Sources/SwiftyCrop/Models/CropViewModel.swift b/Sources/SwiftyCrop/Models/CropViewModel.swift
index 869d4f8..1e9914b 100644
--- a/Sources/SwiftyCrop/Models/CropViewModel.swift
+++ b/Sources/SwiftyCrop/Models/CropViewModel.swift
@@ -1,5 +1,15 @@
import SwiftUI
+#if canImport(UIKit)
import UIKit
+#elseif canImport(AppKit)
+import AppKit
+#endif
+
+#if canImport(UIKit)
+typealias PlatformImage = UIImage
+#elseif canImport(AppKit)
+typealias PlatformImage = NSImage
+#endif
class CropViewModel: ObservableObject {
private let maxMagnificationScale: CGFloat
@@ -53,19 +63,28 @@ class CropViewModel: ObservableObject {
- image: The UIImage to crop
- Returns: A cropped UIImage if the cropping operation is successful; otherwise nil.
*/
- func cropToSquare(_ image: UIImage) -> UIImage? {
+ func cropToSquare(_ image: PlatformImage) -> PlatformImage? {
guard let orientedImage = image.correctlyOriented else {
return nil
}
let cropRect = calculateCropRect(orientedImage)
+ #if canImport(UIKit)
guard let cgImage = orientedImage.cgImage,
let result = cgImage.cropping(to: cropRect) else {
return nil
}
-
return UIImage(cgImage: result)
+ #elseif canImport(AppKit)
+ guard let cgImage = orientedImage.cgImage(forProposedRect: nil, context: nil, hints: nil) else {
+ return nil
+ }
+ guard let croppedCGImage = cgImage.cropping(to: cropRect) else {
+ return nil
+ }
+ return NSImage(cgImage: croppedCGImage, size: cropRect.size)
+ #endif
}
/**
@@ -74,13 +93,14 @@ class CropViewModel: ObservableObject {
- image: The UIImage to crop
- Returns: A cropped UIImage if the cropping operation is successful; otherwise nil.
*/
- func cropToCircle(_ image: UIImage) -> UIImage? {
+ func cropToCircle(_ image: PlatformImage) -> PlatformImage? {
guard let orientedImage = image.correctlyOriented else {
return nil
}
let cropRect = calculateCropRect(orientedImage)
-
+
+ #if canImport(UIKit)
// A circular crop results in some transparency in the
// cropped image, so set opaque to false to ensure the
// cropped image does not include a background fill
@@ -124,6 +144,21 @@ class CropViewModel: ObservableObject {
}
return circleCroppedImage
+ #elseif canImport(AppKit)
+ let circleCroppedImage = NSImage(size: cropRect.size)
+ circleCroppedImage.lockFocus()
+ let drawRect = NSRect(origin: .zero, size: cropRect.size)
+ NSBezierPath(ovalIn: drawRect).addClip()
+ let drawImageRect = NSRect(
+ origin: NSPoint(x: -cropRect.origin.x, y: -cropRect.origin.y),
+ size: orientedImage.size
+ )
+ orientedImage.draw(in: drawImageRect)
+ circleCroppedImage.unlockFocus()
+ return circleCroppedImage
+ #endif
+
+
}
/**
@@ -133,39 +168,42 @@ class CropViewModel: ObservableObject {
- angle: The Angle to rotate to
- Returns: A rotated UIImage if the rotating operation is successful; otherwise nil.
*/
- func rotate(_ image: UIImage, _ angle: Angle) -> UIImage? {
+ func rotate(_ image: PlatformImage, _ angle: Angle) -> PlatformImage? {
guard let orientedImage = image.correctlyOriented else {
return nil
}
+ #if canImport(UIKit)
guard let cgImage = orientedImage.cgImage else {
return nil
}
+ #elseif canImport(AppKit)
+ guard let cgImage = orientedImage.cgImage(forProposedRect: nil, context: nil, hints: nil) else {
+ return nil
+ }
+ #endif
let ciImage = CIImage(cgImage: cgImage)
// Prepare filter
- let filter = CIFilter.straightenFilter(
- image: ciImage,
- radians: angle.radians
- )
-
- // Get output image
- guard let output = filter?.outputImage else {
- return nil
- }
+ guard let filter = CIFilter.straightenFilter(image: ciImage, radians: angle.radians),
+ // Get output image
+ let output = filter.outputImage else {
+ return nil
+ }
// Create resulting image
let context = CIContext()
- guard let result = context.createCGImage(
- output,
- from: output.extent
- ) else {
+ guard let result = context.createCGImage(output, from: output.extent) else {
return nil
}
+ #if canImport(UIKit)
return UIImage(cgImage: result)
- }
+ #elseif canImport(AppKit)
+ return NSImage(cgImage: result, size: NSSize(width: result.width, height: result.height))
+ #endif
+ }
/**
Calculates the rectangle to crop.
@@ -173,7 +211,7 @@ class CropViewModel: ObservableObject {
- image: The UIImage to calculate the rectangle to crop for
- Returns: A CGRect representing the rectangle to crop.
*/
- private func calculateCropRect(_ orientedImage: UIImage) -> CGRect {
+ private func calculateCropRect(_ orientedImage: PlatformImage) -> CGRect {
// The relation factor of the originals image width/height
// and the width/height of the image displayed in the view (initial)
let factor = min(
@@ -206,21 +244,25 @@ class CropViewModel: ObservableObject {
}
}
-private extension UIImage {
+extension PlatformImage {
/**
- A UIImage instance with corrected orientation.
+ For iOS, A UIImage instance with corrected orientation.
If the instance's orientation is already `.up`, it simply returns the original.
- Returns: An optional UIImage that represents the correctly oriented image.
*/
- var correctlyOriented: UIImage? {
+ var correctlyOriented: PlatformImage? {
+ #if canImport(UIKit)
if imageOrientation == .up { return self }
-
+
UIGraphicsBeginImageContextWithOptions(size, false, scale)
draw(in: CGRect(origin: .zero, size: size))
let normalizedImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return normalizedImage
+ #elseif canImport(AppKit)
+ return self
+ #endif
}
}
diff --git a/Sources/SwiftyCrop/SwiftyCrop.swift b/Sources/SwiftyCrop/SwiftyCrop.swift
index 22268a9..bb6164c 100644
--- a/Sources/SwiftyCrop/SwiftyCrop.swift
+++ b/Sources/SwiftyCrop/SwiftyCrop.swift
@@ -11,11 +11,17 @@ import SwiftUI
/// - onComplete: A closure that's called when the cropping is complete. This closure returns the cropped `UIImage?`.
/// If an error occurs the return value is nil.
public struct SwiftyCropView: View {
- private let imageToCrop: UIImage
private let maskShape: MaskShape
private let configuration: SwiftyCropConfiguration
+ #if canImport(UIKit)
+ private let imageToCrop: UIImage
private let onComplete: (UIImage?) -> Void
+ #elseif canImport(AppKit)
+ private let imageToCrop: NSImage
+ private let onComplete: (NSImage?) -> Void
+ #endif
+ #if canImport(UIKit)
public init(
imageToCrop: UIImage,
maskShape: MaskShape,
@@ -27,6 +33,19 @@ public struct SwiftyCropView: View {
self.configuration = configuration
self.onComplete = onComplete
}
+ #elseif canImport(AppKit)
+ public init(
+ imageToCrop: NSImage,
+ maskShape: MaskShape,
+ configuration: SwiftyCropConfiguration = SwiftyCropConfiguration(),
+ onComplete: @escaping (NSImage?) -> Void
+ ) {
+ self.imageToCrop = imageToCrop
+ self.maskShape = maskShape
+ self.configuration = configuration
+ self.onComplete = onComplete
+ }
+ #endif
public var body: some View {
CropView(
diff --git a/Sources/SwiftyCrop/View/CropView.swift b/Sources/SwiftyCrop/View/CropView.swift
index b8ca961..a587281 100644
--- a/Sources/SwiftyCrop/View/CropView.swift
+++ b/Sources/SwiftyCrop/View/CropView.swift
@@ -4,17 +4,17 @@ struct CropView: View {
@Environment(\.dismiss) private var dismiss
@StateObject private var viewModel: CropViewModel
- private let image: UIImage
+ private let image: PlatformImage
private let maskShape: MaskShape
private let configuration: SwiftyCropConfiguration
- private let onComplete: (UIImage?) -> Void
+ private let onComplete: (PlatformImage?) -> Void
private let localizableTableName: String
init(
- image: UIImage,
+ image: PlatformImage,
maskShape: MaskShape,
configuration: SwiftyCropConfiguration,
- onComplete: @escaping (UIImage?) -> Void
+ onComplete: @escaping (PlatformImage?) -> Void
) {
self.image = image
self.maskShape = maskShape
@@ -81,9 +81,7 @@ struct CropView: View {
.zIndex(1)
ZStack {
- Image(uiImage: image)
- .resizable()
- .scaledToFit()
+ PlatformImageView(image: image)
.rotationEffect(viewModel.angle)
.scaleEffect(viewModel.scale)
.offset(viewModel.offset)
@@ -97,9 +95,7 @@ struct CropView: View {
}
)
- Image(uiImage: image)
- .resizable()
- .scaledToFit()
+ PlatformImageView(image: image)
.rotationEffect(viewModel.angle)
.scaleEffect(viewModel.scale)
.offset(viewModel.offset)
@@ -137,10 +133,10 @@ struct CropView: View {
.background(.black)
}
- private func cropImage() -> UIImage? {
- var editedImage: UIImage = image
+ private func cropImage() -> PlatformImage? {
+ var editedImage: PlatformImage = image
if configuration.rotateImage {
- if let rotatedImage: UIImage = viewModel.rotate(
+ if let rotatedImage: PlatformImage = viewModel.rotate(
editedImage,
viewModel.lastAngle
) {
@@ -170,3 +166,19 @@ struct CropView: View {
}
}
}
+
+struct PlatformImageView: View {
+ let image: PlatformImage
+
+ var body: some View {
+ #if canImport(UIKit)
+ Image(uiImage: image)
+ .resizable()
+ .scaledToFit()
+ #elseif canImport(AppKit)
+ Image(nsImage: image)
+ .resizable()
+ .scaledToFit()
+ #endif
+ }
+}