mirror of
https://github.com/laosb/CropImage.git
synced 2025-05-01 08:01:09 +00:00
Working version.
This commit is contained in:
parent
645e4d6b2c
commit
45c2b0ac58
1 changed files with 104 additions and 14 deletions
|
@ -6,15 +6,62 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
#if os(iOS)
|
||||||
|
import UIKit
|
||||||
|
#endif
|
||||||
|
|
||||||
public struct CropImageView: View {
|
public struct CropImageView: View {
|
||||||
|
public enum RenderError: Error {
|
||||||
|
case imageRendererReturnedNil
|
||||||
|
}
|
||||||
|
|
||||||
var image: PlatformImage
|
var image: PlatformImage
|
||||||
var targetSize: CGSize
|
var targetSize: CGSize
|
||||||
var onCrop: (PlatformImage) -> Void
|
var targetScale: CGFloat = 1
|
||||||
|
var onCrop: (Result<PlatformImage, Error>) -> Void
|
||||||
|
|
||||||
@State private var offset: CGSize = .zero
|
@State private var offset: CGSize = .zero
|
||||||
@State private var scale: CGFloat = 1
|
@State private var scale: CGFloat = 1
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
func crop() throws -> PlatformImage {
|
||||||
|
let snapshotView = MoveAndScalableImageView(offset: $offset, scale: $scale, image: image)
|
||||||
|
.frame(width: targetSize.width, height: targetSize.height)
|
||||||
|
if #available(iOS 16.0, macOS 13.0, *) {
|
||||||
|
let renderer = ImageRenderer(content: snapshotView)
|
||||||
|
renderer.scale = targetScale
|
||||||
|
#if os(iOS)
|
||||||
|
if let image = renderer.uiImage {
|
||||||
|
return image
|
||||||
|
} else {
|
||||||
|
throw RenderError.imageRendererReturnedNil
|
||||||
|
}
|
||||||
|
#elseif os(macOS)
|
||||||
|
if let image = renderer.nsImage {
|
||||||
|
return image
|
||||||
|
} else {
|
||||||
|
throw RenderError.imageRendererReturnedNil
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
} else {
|
||||||
|
#if os(macOS)
|
||||||
|
fatalError("Cropping is not supported on macOS versions before Ventrura 13.0.")
|
||||||
|
#elseif os(iOS)
|
||||||
|
let window = UIWindow(frame: CGRect(origin: .zero, size: targetSize))
|
||||||
|
let hosting = UIHostingController(rootView: snapshotView)
|
||||||
|
hosting.view.frame = window.frame
|
||||||
|
window.addSubview(hosting.view)
|
||||||
|
window.makeKeyAndVisible()
|
||||||
|
UIGraphicsBeginImageContextWithOptions(hosting.view.bounds.size, false, targetScale)
|
||||||
|
let context = UIGraphicsGetCurrentContext()!
|
||||||
|
hosting.view.layer.render(in: context)
|
||||||
|
let image = UIGraphicsGetImageFromCurrentImageContext()!
|
||||||
|
UIGraphicsEndImageContext()
|
||||||
|
return image
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public var body: some View {
|
public var body: some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
MoveAndScalableImageView(offset: $offset, scale: $scale, image: image)
|
MoveAndScalableImageView(offset: $offset, scale: $scale, image: image)
|
||||||
|
@ -22,36 +69,79 @@ public struct CropImageView: View {
|
||||||
.fill(style: FillStyle(eoFill: true))
|
.fill(style: FillStyle(eoFill: true))
|
||||||
.foregroundColor(.black.opacity(0.6))
|
.foregroundColor(.black.opacity(0.6))
|
||||||
.allowsHitTesting(false)
|
.allowsHitTesting(false)
|
||||||
|
VStack {
|
||||||
|
Spacer()
|
||||||
|
HStack {
|
||||||
|
Spacer()
|
||||||
|
Button { Task {
|
||||||
|
do {
|
||||||
|
onCrop(.success(try crop()))
|
||||||
|
} catch {
|
||||||
|
onCrop(.failure(error))
|
||||||
|
}
|
||||||
|
} } label: {
|
||||||
|
Label("Crop", systemImage: "checkmark.circle.fill")
|
||||||
|
.font(.title2)
|
||||||
|
.foregroundColor(.accentColor)
|
||||||
|
.labelStyle(.iconOnly)
|
||||||
|
.padding(1)
|
||||||
|
.background(
|
||||||
|
Circle().fill(.white)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.buttonStyle(.plain)
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct CropImageView_Previews: PreviewProvider {
|
struct CropImageView_Previews: PreviewProvider {
|
||||||
struct PreviewView: View {
|
struct PreviewView: View {
|
||||||
@State private var croppedImage: PlatformImage? = nil
|
@State private var targetSize: CGSize = .init(width: 100, height: 100)
|
||||||
|
@State private var result: Result<PlatformImage, Error>? = nil
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack {
|
VStack {
|
||||||
CropImageView(
|
CropImageView(
|
||||||
image: .init(contentsOfFile: "/Users/laosb/Downloads/png.png")!,
|
image: .init(contentsOfFile: "/Users/laosb/Downloads/png.png")!,
|
||||||
targetSize: .init(width: 100, height: 100)
|
targetSize: targetSize
|
||||||
) { _ in
|
) { result = $0 }
|
||||||
|
Form {
|
||||||
}
|
Section {
|
||||||
if let croppedImage {
|
TextField("Width", value: $targetSize.width, formatter: NumberFormatter())
|
||||||
#if os(macOS)
|
TextField("Height", value: $targetSize.height, formatter: NumberFormatter())
|
||||||
Image(nsImage: croppedImage)
|
} header: { Text("Crop Target Size") }
|
||||||
#elseif os(iOS)
|
Section {
|
||||||
Image(uiImage: croppedImage)
|
if let result {
|
||||||
#endif
|
switch result {
|
||||||
} else {
|
case let .success(croppedImage):
|
||||||
Text("Press \(Image(systemName: "checkmark.circle.fill")) to crop.")
|
#if os(macOS)
|
||||||
|
Image(nsImage: croppedImage)
|
||||||
|
#elseif os(iOS)
|
||||||
|
Image(uiImage: croppedImage)
|
||||||
|
#endif
|
||||||
|
case let .failure(error):
|
||||||
|
Text(error.localizedDescription)
|
||||||
|
.foregroundColor(.red)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Text("Press \(Image(systemName: "checkmark.circle.fill")) to crop.")
|
||||||
|
}
|
||||||
|
} header: { Text("Result") }
|
||||||
}
|
}
|
||||||
|
#if os(macOS)
|
||||||
|
.formStyle(.grouped)
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
PreviewView()
|
PreviewView()
|
||||||
|
#if os(macOS)
|
||||||
|
.frame(minHeight: 750)
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue