This commit is contained in:
Shibo Lyu 2023-07-21 18:52:11 +08:00
parent 1477b29bc8
commit 645e4d6b2c
7 changed files with 196 additions and 21 deletions

View file

@ -5,6 +5,10 @@ import PackageDescription
let package = Package(
name: "CropImage",
platforms: [
.iOS(.v14),
.macOS(.v13)
],
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
@ -20,9 +24,6 @@ let package = Package(
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "CropImage",
dependencies: []),
.testTarget(
name: "CropImageTests",
dependencies: ["CropImage"]),
dependencies: [])
]
)

View file

@ -1,6 +0,0 @@
public struct CropImage {
public private(set) var text = "Hello, World!"
public init() {
}
}

View file

@ -0,0 +1,57 @@
//
// CropImageView.swift
//
//
// Created by Shibo Lyu on 2023/7/21.
//
import SwiftUI
public struct CropImageView: View {
var image: PlatformImage
var targetSize: CGSize
var onCrop: (PlatformImage) -> Void
@State private var offset: CGSize = .zero
@State private var scale: CGFloat = 1
public var body: some View {
ZStack {
MoveAndScalableImageView(offset: $offset, scale: $scale, image: image)
RectHoleShape(size: targetSize)
.fill(style: FillStyle(eoFill: true))
.foregroundColor(.black.opacity(0.6))
.allowsHitTesting(false)
}
}
}
struct CropImageView_Previews: PreviewProvider {
struct PreviewView: View {
@State private var croppedImage: PlatformImage? = nil
var body: some View {
VStack {
CropImageView(
image: .init(contentsOfFile: "/Users/laosb/Downloads/png.png")!,
targetSize: .init(width: 100, height: 100)
) { _ in
}
if let croppedImage {
#if os(macOS)
Image(nsImage: croppedImage)
#elseif os(iOS)
Image(uiImage: croppedImage)
#endif
} else {
Text("Press \(Image(systemName: "checkmark.circle.fill")) to crop.")
}
}
}
}
static var previews: some View {
PreviewView()
}
}

View file

@ -0,0 +1,73 @@
//
// MoveAndScalableImageView.swift
//
//
// Created by Shibo Lyu on 2023/7/21.
//
import SwiftUI
private extension CGSize {
static func + (lhs: CGSize, rhs: CGSize) -> CGSize {
.init(width: lhs.width + rhs.width, height: lhs.height + rhs.height)
}
}
struct MoveAndScalableImageView: View {
@Binding var offset: CGSize
@Binding var scale: CGFloat
var image: PlatformImage
@State private var tempOffset: CGSize = .zero
@State private var tempScale: CGFloat = 1
var body: some View {
ZStack {
#if os(macOS)
Image(nsImage: image)
.scaleEffect(scale * tempScale)
.offset(offset + tempOffset)
#elseif os(iOS)
Image(uiImage: image)
.scaleEffect(scale * tempScale)
.offset(offset + tempOffset)
#endif
Color.white.opacity(0.0001)
.gesture(
DragGesture()
.onChanged { value in
tempOffset = value.translation
}
.onEnded { value in
offset = offset + tempOffset
tempOffset = .zero
}
)
.gesture(
MagnificationGesture()
.onChanged { value in
tempScale = value.magnitude
}
.onEnded { value in
scale = scale * tempScale
tempScale = 1
}
)
}
}
}
struct MoveAndScalableImageView_Previews: PreviewProvider {
struct PreviewView: View {
@State private var offset: CGSize = .zero
@State private var scale: CGFloat = 1
var body: some View {
MoveAndScalableImageView(offset: $offset, scale: $scale, image: .init(contentsOfFile: "/Users/laosb/Downloads/png.png")!)
}
}
static var previews: some View {
PreviewView()
}
}

View file

@ -0,0 +1,16 @@
//
// PlatformImage.swift
//
//
// Created by Shibo Lyu on 2023/7/21.
//
import Foundation
#if os(macOS)
import AppKit
public typealias PlatformImage = NSImage
#elseif os(iOS)
import UIKit
public typealias PlatformImage = UIImage
#endif

View file

@ -0,0 +1,45 @@
//
// RectHoleShape.swift
//
//
// Created by Shibo Lyu on 2023/7/21.
//
import SwiftUI
struct RectHoleShape: Shape {
let size: CGSize
func path(in rect: CGRect) -> Path {
let path = CGMutablePath()
path.move(to: rect.origin)
path.addLine(to: .init(x: rect.maxX, y: rect.minY))
path.addLine(to: .init(x: rect.maxX, y: rect.maxY))
path.addLine(to: .init(x: rect.minX, y: rect.maxY))
path.addLine(to: rect.origin)
path.closeSubpath()
let newRect = CGRect(origin: .init(
x: rect.midX - size.width / 2.0,
y: rect.midY - size.height / 2.0
), size: size)
path.move(to: newRect.origin)
path.addLine(to: .init(x: newRect.maxX, y: newRect.minY))
path.addLine(to: .init(x: newRect.maxX, y: newRect.maxY))
path.addLine(to: .init(x: newRect.minX, y: newRect.maxY))
path.addLine(to: newRect.origin)
path.closeSubpath()
return Path(path)
}
}
struct RectHoleShape_Previews: PreviewProvider {
static var previews: some View {
VStack {
RectHoleShape(size: .init(width: 100, height: 100))
.fill(style: FillStyle(eoFill: true))
.foregroundColor(.black.opacity(0.6))
}
}
}

View file

@ -1,11 +0,0 @@
import XCTest
@testable import CropImage
final class CropImageTests: XCTestCase {
func testExample() throws {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct
// results.
XCTAssertEqual(CropImage().text, "Hello, World!")
}
}