├── Demo-macOS
├── Demo-macOS
│ ├── Assets.xcassets
│ │ ├── Contents.json
│ │ ├── stock.imageset
│ │ │ ├── thom-holmes-530344-unsplash (1).jpg
│ │ │ ├── thom-holmes-530344-unsplash (1)-1.jpg
│ │ │ └── Contents.json
│ │ ├── AccentColor.colorset
│ │ │ └── Contents.json
│ │ └── AppIcon.appiconset
│ │ │ └── Contents.json
│ ├── Preview Content
│ │ └── Preview Assets.xcassets
│ │ │ └── Contents.json
│ ├── Demo_macOSApp.swift
│ ├── Demo_macOS.entitlements
│ ├── Info.plist
│ └── ContentView.swift
└── Demo-macOS.xcodeproj
│ ├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
│ └── project.pbxproj
├── Tests
├── LinuxMain.swift
└── ImageCropperTests
│ ├── XCTestManifests.swift
│ └── SwiftUI_ImageCropperTests.swift
├── .swiftpm
└── xcode
│ └── package.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ └── IDEWorkspaceChecks.plist
├── SwiftUI-ImageCropper.xcworkspace
├── contents.xcworkspacedata
└── xcshareddata
│ └── IDEWorkspaceChecks.plist
├── Package.swift
├── Sources
└── ImageCropper
│ ├── CropperRatio.swift
│ ├── ImageCropperView.swift
│ └── CropperView.swift
├── LICENSE
├── .gitignore
└── README.md
/Demo-macOS/Demo-macOS/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Demo-macOS/Demo-macOS/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Tests/LinuxMain.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | import SwiftUI_ImageCropperTests
4 |
5 | var tests = [XCTestCaseEntry]()
6 | tests += SwiftUI_ImageCropperTests.allTests()
7 | XCTMain(tests)
8 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Demo-macOS/Demo-macOS/Assets.xcassets/stock.imageset/thom-holmes-530344-unsplash (1).jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marshallino16/ImageCropper/HEAD/Demo-macOS/Demo-macOS/Assets.xcassets/stock.imageset/thom-holmes-530344-unsplash (1).jpg
--------------------------------------------------------------------------------
/Demo-macOS/Demo-macOS.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Demo-macOS/Demo-macOS/Assets.xcassets/stock.imageset/thom-holmes-530344-unsplash (1)-1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marshallino16/ImageCropper/HEAD/Demo-macOS/Demo-macOS/Assets.xcassets/stock.imageset/thom-holmes-530344-unsplash (1)-1.jpg
--------------------------------------------------------------------------------
/Demo-macOS/Demo-macOS/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Tests/ImageCropperTests/XCTestManifests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | #if !canImport(ObjectiveC)
4 | public func allTests() -> [XCTestCaseEntry] {
5 | return [
6 | testCase(SwiftUI_ImageCropperTests.allTests),
7 | ]
8 | }
9 | #endif
10 |
--------------------------------------------------------------------------------
/SwiftUI-ImageCropper.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/SwiftUI-ImageCropper.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Demo-macOS/Demo-macOS.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Demo-macOS/Demo-macOS/Demo_macOSApp.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Demo_macOSApp.swift
3 | // Demo-macOS
4 | //
5 | // Created by Anthony Fernandez on 12/18/20.
6 | //
7 |
8 | import SwiftUI
9 |
10 | @main
11 | struct Demo_macOSApp: App {
12 | var body: some Scene {
13 | WindowGroup {
14 | ContentView()
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Demo-macOS/Demo-macOS/Demo_macOS.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.files.user-selected.read-only
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Demo-macOS/Demo-macOS/Assets.xcassets/stock.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "thom-holmes-530344-unsplash (1)-1.jpg",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "filename" : "thom-holmes-530344-unsplash (1).jpg",
14 | "idiom" : "universal",
15 | "scale" : "3x"
16 | }
17 | ],
18 | "info" : {
19 | "author" : "xcode",
20 | "version" : 1
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Tests/ImageCropperTests/SwiftUI_ImageCropperTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import SwiftUI_ImageCropper
3 |
4 | final class SwiftUI_ImageCropperTests: XCTestCase {
5 | func testExample() {
6 | // This is an example of a functional test case.
7 | // Use XCTAssert and related functions to verify your tests produce the correct
8 | // results.
9 | XCTAssertEqual(SwiftUI_ImageCropper().text, "Hello, World!")
10 | }
11 |
12 | static var allTests = [
13 | ("testExample", testExample),
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.3
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "ImageCropper",
8 | platforms: [
9 | .iOS(.v13),
10 | .macOS(.v10_15)
11 | ],
12 | products: [
13 | .library(
14 | name: "ImageCropper",
15 | type: .dynamic,
16 | targets: ["ImageCropper"]),
17 | ],
18 | dependencies: [
19 | ],
20 | targets: [
21 | .target(
22 | name: "ImageCropper",
23 | dependencies: []),
24 | .testTarget(
25 | name: "ImageCropperTests",
26 | dependencies: ["ImageCropper"]),
27 | ],
28 | swiftLanguageVersions: [.v5]
29 | )
30 |
--------------------------------------------------------------------------------
/Sources/ImageCropper/CropperRatio.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CropperRatio.swift
3 | //
4 | //
5 | // Created by Anthony Fernandez on 12/18/20.
6 | //
7 |
8 | import Foundation
9 | import CoreGraphics
10 |
11 | public struct CropperRatio {
12 |
13 | let width: CGFloat
14 | let height: CGFloat
15 |
16 | public init(width: CGFloat, height: CGFloat) {
17 | self.width = width
18 | self.height = height
19 | }
20 |
21 | public static var r_1_1: Self {
22 | return .init(width: 1, height: 1)
23 | }
24 |
25 | public static var r_3_2: Self {
26 | return .init(width: 3, height: 2)
27 | }
28 |
29 | public static var r_4_3: Self {
30 | return .init(width: 4, height: 3)
31 | }
32 |
33 | public static var r_16_9: Self {
34 | return .init(width: 16, height: 9)
35 | }
36 |
37 | static var r_18_6: Self {
38 | return .init(width: 18, height: 6)
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Demo-macOS/Demo-macOS/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSMinimumSystemVersion
22 | $(MACOSX_DEPLOYMENT_TARGET)
23 |
24 |
25 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Fernandez Anthony
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Demo-macOS/Demo-macOS/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "mac",
5 | "scale" : "1x",
6 | "size" : "16x16"
7 | },
8 | {
9 | "idiom" : "mac",
10 | "scale" : "2x",
11 | "size" : "16x16"
12 | },
13 | {
14 | "idiom" : "mac",
15 | "scale" : "1x",
16 | "size" : "32x32"
17 | },
18 | {
19 | "idiom" : "mac",
20 | "scale" : "2x",
21 | "size" : "32x32"
22 | },
23 | {
24 | "idiom" : "mac",
25 | "scale" : "1x",
26 | "size" : "128x128"
27 | },
28 | {
29 | "idiom" : "mac",
30 | "scale" : "2x",
31 | "size" : "128x128"
32 | },
33 | {
34 | "idiom" : "mac",
35 | "scale" : "1x",
36 | "size" : "256x256"
37 | },
38 | {
39 | "idiom" : "mac",
40 | "scale" : "2x",
41 | "size" : "256x256"
42 | },
43 | {
44 | "idiom" : "mac",
45 | "scale" : "1x",
46 | "size" : "512x512"
47 | },
48 | {
49 | "idiom" : "mac",
50 | "scale" : "2x",
51 | "size" : "512x512"
52 | }
53 | ],
54 | "info" : {
55 | "author" : "xcode",
56 | "version" : 1
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/Sources/ImageCropper/ImageCropperView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ImageCropperView.swift
3 | //
4 | //
5 | // Created by Anthony Fernandez on 12/18/20.
6 | //
7 |
8 | import SwiftUI
9 |
10 | public struct ImageCropperView: View {
11 |
12 | let image: Image
13 | let ratio: CropperRatio
14 | let cropRect: CGRect?
15 |
16 | var onCropChanged : ((CGRect) -> Void)?
17 |
18 | public init(image: Image,
19 | cropRect: CGRect? = nil,
20 | ratio: CropperRatio) {
21 | self.image = image
22 | self.ratio = ratio
23 | self.cropRect = cropRect
24 | }
25 |
26 | public var body: some View {
27 | image
28 | .resizable()
29 | .scaledToFit()
30 | .overlay(
31 | GeometryReader { reader in
32 | CropperView(viewModel: CropperViewModel(parentProxy: reader,
33 | cropRect: cropRect,
34 | ratio: ratio,
35 | onCropChanged: onCropChanged))
36 | }
37 | )
38 | }
39 | }
40 |
41 |
42 | // MARK: - Public API
43 |
44 | extension ImageCropperView {
45 |
46 | public func onCropChanged(_ action: @escaping ((CGRect) -> Void)) -> Self {
47 | var copy = self
48 | copy.onCropChanged = action
49 | return copy
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/Demo-macOS/Demo-macOS/ContentView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ContentView.swift
3 | // Demo-macOS
4 | //
5 | // Created by Anthony Fernandez on 12/18/20.
6 | //
7 |
8 | import SwiftUI
9 | import ImageCropper
10 |
11 | struct ContentView: View {
12 |
13 | @State var ratio: CropperRatio = CropperRatio.r_1_1
14 | @State var cropRect: CGRect? = nil
15 |
16 | var body: some View {
17 | VStack {
18 | Text("Image Cropper Demo")
19 |
20 | Spacer()
21 |
22 | ImageCropperView(image: Image("stock"),
23 | cropRect: cropRect,
24 | ratio: ratio)
25 | .onCropChanged { (newCrop) in
26 | cropRect = newCrop
27 | }
28 |
29 |
30 | Spacer()
31 |
32 | HStack {
33 | Spacer()
34 |
35 | Button(action: {
36 | ratio = CropperRatio.r_1_1
37 | cropRect = nil
38 | }) {
39 | Text("1:1")
40 | }
41 |
42 | Button(action: {
43 | ratio = CropperRatio.r_3_2
44 | cropRect = nil
45 | }) {
46 | Text("3:2")
47 | }
48 |
49 | Button(action: {
50 | ratio = CropperRatio.r_4_3
51 | cropRect = nil
52 | }) {
53 | Text("4:3")
54 | }
55 |
56 | Spacer()
57 | }
58 | .padding(.vertical, 16)
59 | }
60 | .padding()
61 | }
62 | }
63 |
64 | struct ContentView_Previews: PreviewProvider {
65 | static var previews: some View {
66 | ContentView()
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 |
7 |
8 | !.gitignore
9 | *.cer
10 | fastlane/*.mobileprovision
11 | xcschememanagement.plist
12 | # Fastlane temporary profiling data
13 | fastlane/report.xml
14 | # Deliver temporary error output
15 | fastlane/Error*.png
16 | # Deliver temporary preview output
17 | fastlane/Preview.html
18 | fastlane/test_output
19 | # Xcode
20 | #
21 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
22 | ## Build generated
23 | */build/
24 | */DerivedData/
25 | ## Various settings
26 | */*.pbxuser
27 | */!default.pbxuser
28 | */*.mode1v3
29 | */!default.mode1v3
30 | */*.mode2v3
31 | */!default.mode2v3
32 | */*.perspectivev3
33 | */!default.perspectivev3
34 | */xcuserdata/
35 | ## Other
36 | *.moved-aside
37 | *.xccheckout
38 | *.xcscmblueprint
39 | ## Obj-C/Swift specific
40 | */*.hmap
41 | */*.ipa
42 | */*.dSYM.zip
43 | */*.dSYM
44 | ## Playgrounds
45 | */timeline.xctimeline
46 | */playground.xcworkspace
47 | # Swift Package Manager
48 | #
49 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
50 | # Packages/
51 | # Package.pins
52 | # Package.resolved
53 | */.build/
54 | # CocoaPods
55 | #
56 | # We recommend against adding the Pods directory to your .gitignore. However
57 | # you should judge for yourself, the pros and cons are mentioned at:
58 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
59 | #
60 | Pods/
61 | #
62 | # Add this line if you want to avoid checking in source code from the Xcode workspace
63 | */*.xcworkspace
64 | # Carthage
65 | #
66 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
67 | # Carthage/Checkouts
68 | #Carthage/Build
69 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | SwiftUI-ImageCropper
2 | =================
3 |
4 |
5 | 
6 | [](https://swift.org/package-manager)
7 | 
8 | 
9 | 
10 |
11 |
12 | Simple ratio based image cropper for SwiftUI
13 |
14 |
15 |
16 |
17 | ## Requirements
18 |
19 | - Xcode 12
20 | - Swift 5.3
21 |
22 |
23 | ## Integration
24 |
25 | ImageCropper is available via [Swift Package Manager](https://swift.org/package-manager/)
26 |
27 | Using Xcode 12, go to `File -> Swift Packages -> Add Package Dependency` and enter [https://github.com/marshallino16/ImageCropper](https://github.com/marshallino16/ImageCropper)
28 |
29 | ## Usage
30 |
31 | `ImageCropper` gives you access to a new view called `ImageCropperView`. It can be integrated within your view `body` like this :
32 |
33 | ```swift
34 | let ratio = CropperRatio(width: 1, height: 1)
35 |
36 | ImageCropperView(image: Image("stock"),
37 | cropRect: nil,
38 | ratio: ratio)
39 | .onCropChanged { (newCrop) in
40 | print(newCrop)
41 | }
42 | ```
43 |
44 | ## Details
45 |
46 |
47 | #### Parameters
48 |
49 | | Param | Type | Initial Value | Optional |
50 | |----------|--------------|---------------|----------|
51 | | image | Image | | no |
52 | | cropRect | CGRect | nil | yes |
53 | | ratio | CropperRatio | | no |
54 |
55 | #### Modifiers
56 |
57 | ImageCropperView comes with one modifier
58 |
59 | ```swift
60 | onCropChanged { ... }
61 | ```
62 |
63 | ## Next
64 |
65 | - Pinch to zoom
66 |
67 | ## License
68 |
69 | ImageCropper is available under the MIT license. See the LICENSE file for more info.
70 |
--------------------------------------------------------------------------------
/Sources/ImageCropper/CropperView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CropperView.swift
3 | //
4 | // Created by Anthony Fernandez on 17/12/2020.
5 | //
6 |
7 | import SwiftUI
8 |
9 | struct CropperView: View {
10 |
11 | @ObservedObject var viewModel: CropperViewModel
12 |
13 | @State var cropCornerSize : CGFloat = 20
14 | @State var cropCornerWeight : CGFloat = 2
15 |
16 | var body: some View {
17 | ZStack {
18 | Rectangle()
19 | .fill(Color.black.opacity(0.3))
20 | .overlay(
21 | croppOverlay
22 | )
23 | .frame(width: viewModel.cropperRect.width, height: viewModel.cropperRect.height, alignment: .center)
24 | .position(x: viewModel.cropperRect.origin.x, y: viewModel.cropperRect.origin.y)
25 | .gesture(
26 | DragGesture(minimumDistance: 0, coordinateSpace: .local)
27 | .onChanged({ newValue in
28 | viewModel.cropperRect.origin.x = max(viewModel.cropperRect.width / 2, min(newValue.location.x, viewModel.parentProxy.size.width - (viewModel.cropperRect.width / 2)))
29 | viewModel.cropperRect.origin.y = max(viewModel.cropperRect.height / 2, min(newValue.location.y, viewModel.parentProxy.size.height - (viewModel.cropperRect.height / 2)))
30 | })
31 | .onEnded({ (_) in
32 | viewModel.onCropChanged?(viewModel.cropViewToCropRect())
33 | })
34 | )
35 | }
36 | .frame(width: viewModel.parentProxy.size.width, height: viewModel.parentProxy.size.height, alignment: .center)
37 | }
38 |
39 | var croppOverlay : some View {
40 | VStack {
41 | HStack {
42 | VStack(alignment: .leading, spacing: 0) {
43 | Rectangle()
44 | .fill(Color.white)
45 | .frame(width: cropCornerSize, height: cropCornerWeight)
46 | HStack {
47 | Rectangle()
48 | .fill(Color.white)
49 | .frame(width: 2, height: cropCornerSize)
50 | Spacer()
51 | }
52 | }
53 | Spacer()
54 | VStack(alignment: .trailing, spacing: 0) {
55 | Rectangle()
56 | .fill(Color.white)
57 | .frame(width: cropCornerSize, height: cropCornerWeight)
58 | HStack {
59 | Spacer()
60 | Rectangle()
61 | .fill(Color.white)
62 | .frame(width: cropCornerWeight, height: cropCornerSize)
63 | }
64 | }
65 | }
66 | Spacer()
67 | HStack {
68 | VStack(alignment: .leading, spacing: 0) {
69 | HStack {
70 | Rectangle()
71 | .fill(Color.white)
72 | .frame(width: cropCornerWeight, height: cropCornerSize)
73 | Spacer()
74 | }
75 | Rectangle()
76 | .fill(Color.white)
77 | .frame(width: cropCornerSize, height: cropCornerWeight)
78 | }
79 | Spacer()
80 | VStack(alignment: .trailing, spacing: 0) {
81 | HStack {
82 | Spacer()
83 | Rectangle()
84 | .fill(Color.white)
85 | .frame(width: cropCornerWeight, height: cropCornerSize)
86 | }
87 | Rectangle()
88 | .fill(Color.white)
89 | .frame(width: cropCornerSize, height: cropCornerWeight)
90 | }
91 | }
92 | }
93 | }
94 | }
95 |
96 | final class CropperViewModel: ObservableObject {
97 |
98 | @Published var parentProxy: GeometryProxy
99 | @Published var cropperRect: CGRect = .zero
100 |
101 | var ratio: CropperRatio
102 |
103 | var onCropChanged : ((CGRect) -> Void)?
104 |
105 | init(parentProxy: GeometryProxy,
106 | cropRect: CGRect? = nil,
107 | ratio: CropperRatio,
108 | onCropChanged : ((CGRect) -> Void)? = nil) {
109 |
110 | self.onCropChanged = onCropChanged
111 | self.parentProxy = parentProxy
112 | self.ratio = ratio
113 |
114 | if let cropRect = cropRect {
115 | self.cropperRect = cropRectToCropView(with: cropRect)
116 | }
117 | else {
118 | initializeCropRect()
119 | }
120 | }
121 |
122 | fileprivate func initializeCropRect() {
123 | cropperRect = createCropperFrame(for: parentProxy.size)
124 |
125 | // Save initial crop
126 | DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.5) {
127 | self.onCropChanged?(self.cropViewToCropRect())
128 | }
129 |
130 | }
131 |
132 | fileprivate func cropViewToCropRect() -> CGRect {
133 |
134 | let leftPos = (cropperRect.origin.x - (cropperRect.size.width / 2))
135 | let topPos = (cropperRect.origin.y - (cropperRect.size.height / 2))
136 |
137 | let x1 = leftPos / parentProxy.size.width
138 | let y1 = topPos / parentProxy.size.height
139 | let x2 = (leftPos + cropperRect.size.width) / parentProxy.size.width
140 | let y2 = (topPos + cropperRect.size.height) / parentProxy.size.height
141 |
142 | return CGRect(x: x1, y: y1, width: x2, height: y2)
143 | }
144 |
145 | fileprivate func cropRectToCropView(with cropRect: CGRect) -> CGRect {
146 |
147 | var x1 = parentProxy.size.width * cropRect.origin.x
148 | var y1 = parentProxy.size.height * cropRect.origin.y
149 | let x2 = (parentProxy.size.width * cropRect.size.width) - x1
150 | let y2 = (parentProxy.size.height * cropRect.size.height) - y1
151 |
152 | // View position modifies the center of it
153 | x1 += x2 / 2
154 | y1 += y2 / 2
155 |
156 | return CGRect(x: x1, y: y1, width: x2, height: y2)
157 | }
158 |
159 | fileprivate func createCropperFrame(for imageSize: CGSize) -> CGRect {
160 | let previewSize = CGSize(width: ratio.width, height: ratio.height)
161 |
162 | var height : CGFloat = 0
163 | var width : CGFloat = 0
164 |
165 | let printRatio = previewSize.width / previewSize.height
166 | let imageRatio = imageSize.width / imageSize.height
167 |
168 | if printRatio > imageRatio {
169 | height = imageSize.height
170 | width = (previewSize.width * height) / previewSize.height
171 |
172 | while width > imageSize.width {
173 | height -= 1
174 | width = (previewSize.width * height) / previewSize.height
175 | }
176 | }
177 | else {
178 | width = imageSize.width
179 | height = (previewSize.height * width) / previewSize.width
180 |
181 | while height > imageSize.height {
182 | width -= 1
183 | height = (previewSize.height * width) / previewSize.width
184 | }
185 | }
186 |
187 | let x = (imageSize.width / 2)
188 | let y = (imageSize.height / 2)
189 |
190 | return CGRect(x: x, y: y, width: width, height: height)
191 | }
192 | }
193 |
--------------------------------------------------------------------------------
/Demo-macOS/Demo-macOS.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 52;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 794ADB86258CB87F0085161B /* Demo_macOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 794ADB85258CB87F0085161B /* Demo_macOSApp.swift */; };
11 | 794ADB88258CB87F0085161B /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 794ADB87258CB87F0085161B /* ContentView.swift */; };
12 | 794ADB8A258CB8810085161B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 794ADB89258CB8810085161B /* Assets.xcassets */; };
13 | 794ADB8D258CB8810085161B /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 794ADB8C258CB8810085161B /* Preview Assets.xcassets */; };
14 | 79C7C038258CEF1B0021E8B8 /* ImageCropper in Frameworks */ = {isa = PBXBuildFile; productRef = 79C7C037258CEF1B0021E8B8 /* ImageCropper */; };
15 | 79C7C039258CEF1B0021E8B8 /* ImageCropper in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 79C7C037258CEF1B0021E8B8 /* ImageCropper */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
16 | /* End PBXBuildFile section */
17 |
18 | /* Begin PBXCopyFilesBuildPhase section */
19 | 79C7C033258CE9680021E8B8 /* Embed Frameworks */ = {
20 | isa = PBXCopyFilesBuildPhase;
21 | buildActionMask = 2147483647;
22 | dstPath = "";
23 | dstSubfolderSpec = 10;
24 | files = (
25 | 79C7C039258CEF1B0021E8B8 /* ImageCropper in Embed Frameworks */,
26 | );
27 | name = "Embed Frameworks";
28 | runOnlyForDeploymentPostprocessing = 0;
29 | };
30 | /* End PBXCopyFilesBuildPhase section */
31 |
32 | /* Begin PBXFileReference section */
33 | 794ADB82258CB87F0085161B /* Demo-macOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Demo-macOS.app"; sourceTree = BUILT_PRODUCTS_DIR; };
34 | 794ADB85258CB87F0085161B /* Demo_macOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Demo_macOSApp.swift; sourceTree = ""; };
35 | 794ADB87258CB87F0085161B /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; };
36 | 794ADB89258CB8810085161B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
37 | 794ADB8C258CB8810085161B /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; };
38 | 794ADB8E258CB8810085161B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
39 | 794ADB8F258CB8810085161B /* Demo_macOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Demo_macOS.entitlements; sourceTree = ""; };
40 | /* End PBXFileReference section */
41 |
42 | /* Begin PBXFrameworksBuildPhase section */
43 | 794ADB7F258CB87F0085161B /* Frameworks */ = {
44 | isa = PBXFrameworksBuildPhase;
45 | buildActionMask = 2147483647;
46 | files = (
47 | 79C7C038258CEF1B0021E8B8 /* ImageCropper in Frameworks */,
48 | );
49 | runOnlyForDeploymentPostprocessing = 0;
50 | };
51 | /* End PBXFrameworksBuildPhase section */
52 |
53 | /* Begin PBXGroup section */
54 | 794ADB79258CB87F0085161B = {
55 | isa = PBXGroup;
56 | children = (
57 | 794ADB84258CB87F0085161B /* Demo-macOS */,
58 | 794ADB83258CB87F0085161B /* Products */,
59 | 79C7C02F258CE9680021E8B8 /* Frameworks */,
60 | );
61 | sourceTree = "";
62 | };
63 | 794ADB83258CB87F0085161B /* Products */ = {
64 | isa = PBXGroup;
65 | children = (
66 | 794ADB82258CB87F0085161B /* Demo-macOS.app */,
67 | );
68 | name = Products;
69 | sourceTree = "";
70 | };
71 | 794ADB84258CB87F0085161B /* Demo-macOS */ = {
72 | isa = PBXGroup;
73 | children = (
74 | 794ADB85258CB87F0085161B /* Demo_macOSApp.swift */,
75 | 794ADB87258CB87F0085161B /* ContentView.swift */,
76 | 794ADB89258CB8810085161B /* Assets.xcassets */,
77 | 794ADB8E258CB8810085161B /* Info.plist */,
78 | 794ADB8F258CB8810085161B /* Demo_macOS.entitlements */,
79 | 794ADB8B258CB8810085161B /* Preview Content */,
80 | );
81 | path = "Demo-macOS";
82 | sourceTree = "";
83 | };
84 | 794ADB8B258CB8810085161B /* Preview Content */ = {
85 | isa = PBXGroup;
86 | children = (
87 | 794ADB8C258CB8810085161B /* Preview Assets.xcassets */,
88 | );
89 | path = "Preview Content";
90 | sourceTree = "";
91 | };
92 | 79C7C02F258CE9680021E8B8 /* Frameworks */ = {
93 | isa = PBXGroup;
94 | children = (
95 | );
96 | name = Frameworks;
97 | sourceTree = "";
98 | };
99 | /* End PBXGroup section */
100 |
101 | /* Begin PBXNativeTarget section */
102 | 794ADB81258CB87F0085161B /* Demo-macOS */ = {
103 | isa = PBXNativeTarget;
104 | buildConfigurationList = 794ADB92258CB8810085161B /* Build configuration list for PBXNativeTarget "Demo-macOS" */;
105 | buildPhases = (
106 | 794ADB7E258CB87F0085161B /* Sources */,
107 | 794ADB7F258CB87F0085161B /* Frameworks */,
108 | 794ADB80258CB87F0085161B /* Resources */,
109 | 79C7C033258CE9680021E8B8 /* Embed Frameworks */,
110 | );
111 | buildRules = (
112 | );
113 | dependencies = (
114 | );
115 | name = "Demo-macOS";
116 | packageProductDependencies = (
117 | 79C7C037258CEF1B0021E8B8 /* ImageCropper */,
118 | );
119 | productName = "Demo-macOS";
120 | productReference = 794ADB82258CB87F0085161B /* Demo-macOS.app */;
121 | productType = "com.apple.product-type.application";
122 | };
123 | /* End PBXNativeTarget section */
124 |
125 | /* Begin PBXProject section */
126 | 794ADB7A258CB87F0085161B /* Project object */ = {
127 | isa = PBXProject;
128 | attributes = {
129 | LastSwiftUpdateCheck = 1230;
130 | LastUpgradeCheck = 1230;
131 | TargetAttributes = {
132 | 794ADB81258CB87F0085161B = {
133 | CreatedOnToolsVersion = 12.3;
134 | };
135 | };
136 | };
137 | buildConfigurationList = 794ADB7D258CB87F0085161B /* Build configuration list for PBXProject "Demo-macOS" */;
138 | compatibilityVersion = "Xcode 9.3";
139 | developmentRegion = en;
140 | hasScannedForEncodings = 0;
141 | knownRegions = (
142 | en,
143 | Base,
144 | );
145 | mainGroup = 794ADB79258CB87F0085161B;
146 | productRefGroup = 794ADB83258CB87F0085161B /* Products */;
147 | projectDirPath = "";
148 | projectRoot = "";
149 | targets = (
150 | 794ADB81258CB87F0085161B /* Demo-macOS */,
151 | );
152 | };
153 | /* End PBXProject section */
154 |
155 | /* Begin PBXResourcesBuildPhase section */
156 | 794ADB80258CB87F0085161B /* Resources */ = {
157 | isa = PBXResourcesBuildPhase;
158 | buildActionMask = 2147483647;
159 | files = (
160 | 794ADB8D258CB8810085161B /* Preview Assets.xcassets in Resources */,
161 | 794ADB8A258CB8810085161B /* Assets.xcassets in Resources */,
162 | );
163 | runOnlyForDeploymentPostprocessing = 0;
164 | };
165 | /* End PBXResourcesBuildPhase section */
166 |
167 | /* Begin PBXSourcesBuildPhase section */
168 | 794ADB7E258CB87F0085161B /* Sources */ = {
169 | isa = PBXSourcesBuildPhase;
170 | buildActionMask = 2147483647;
171 | files = (
172 | 794ADB88258CB87F0085161B /* ContentView.swift in Sources */,
173 | 794ADB86258CB87F0085161B /* Demo_macOSApp.swift in Sources */,
174 | );
175 | runOnlyForDeploymentPostprocessing = 0;
176 | };
177 | /* End PBXSourcesBuildPhase section */
178 |
179 | /* Begin XCBuildConfiguration section */
180 | 794ADB90258CB8810085161B /* Debug */ = {
181 | isa = XCBuildConfiguration;
182 | buildSettings = {
183 | ALWAYS_SEARCH_USER_PATHS = NO;
184 | CLANG_ANALYZER_NONNULL = YES;
185 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
186 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
187 | CLANG_CXX_LIBRARY = "libc++";
188 | CLANG_ENABLE_MODULES = YES;
189 | CLANG_ENABLE_OBJC_ARC = YES;
190 | CLANG_ENABLE_OBJC_WEAK = YES;
191 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
192 | CLANG_WARN_BOOL_CONVERSION = YES;
193 | CLANG_WARN_COMMA = YES;
194 | CLANG_WARN_CONSTANT_CONVERSION = YES;
195 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
196 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
197 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
198 | CLANG_WARN_EMPTY_BODY = YES;
199 | CLANG_WARN_ENUM_CONVERSION = YES;
200 | CLANG_WARN_INFINITE_RECURSION = YES;
201 | CLANG_WARN_INT_CONVERSION = YES;
202 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
203 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
204 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
205 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
206 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
207 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
208 | CLANG_WARN_STRICT_PROTOTYPES = YES;
209 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
210 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
211 | CLANG_WARN_UNREACHABLE_CODE = YES;
212 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
213 | COPY_PHASE_STRIP = NO;
214 | DEBUG_INFORMATION_FORMAT = dwarf;
215 | ENABLE_STRICT_OBJC_MSGSEND = YES;
216 | ENABLE_TESTABILITY = YES;
217 | GCC_C_LANGUAGE_STANDARD = gnu11;
218 | GCC_DYNAMIC_NO_PIC = NO;
219 | GCC_NO_COMMON_BLOCKS = YES;
220 | GCC_OPTIMIZATION_LEVEL = 0;
221 | GCC_PREPROCESSOR_DEFINITIONS = (
222 | "DEBUG=1",
223 | "$(inherited)",
224 | );
225 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
226 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
227 | GCC_WARN_UNDECLARED_SELECTOR = YES;
228 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
229 | GCC_WARN_UNUSED_FUNCTION = YES;
230 | GCC_WARN_UNUSED_VARIABLE = YES;
231 | MACOSX_DEPLOYMENT_TARGET = 11.0;
232 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
233 | MTL_FAST_MATH = YES;
234 | ONLY_ACTIVE_ARCH = YES;
235 | SDKROOT = macosx;
236 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
237 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
238 | };
239 | name = Debug;
240 | };
241 | 794ADB91258CB8810085161B /* Release */ = {
242 | isa = XCBuildConfiguration;
243 | buildSettings = {
244 | ALWAYS_SEARCH_USER_PATHS = NO;
245 | CLANG_ANALYZER_NONNULL = YES;
246 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
247 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
248 | CLANG_CXX_LIBRARY = "libc++";
249 | CLANG_ENABLE_MODULES = YES;
250 | CLANG_ENABLE_OBJC_ARC = YES;
251 | CLANG_ENABLE_OBJC_WEAK = YES;
252 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
253 | CLANG_WARN_BOOL_CONVERSION = YES;
254 | CLANG_WARN_COMMA = YES;
255 | CLANG_WARN_CONSTANT_CONVERSION = YES;
256 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
257 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
258 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
259 | CLANG_WARN_EMPTY_BODY = YES;
260 | CLANG_WARN_ENUM_CONVERSION = YES;
261 | CLANG_WARN_INFINITE_RECURSION = YES;
262 | CLANG_WARN_INT_CONVERSION = YES;
263 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
264 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
265 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
266 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
267 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
268 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
269 | CLANG_WARN_STRICT_PROTOTYPES = YES;
270 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
271 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
272 | CLANG_WARN_UNREACHABLE_CODE = YES;
273 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
274 | COPY_PHASE_STRIP = NO;
275 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
276 | ENABLE_NS_ASSERTIONS = NO;
277 | ENABLE_STRICT_OBJC_MSGSEND = YES;
278 | GCC_C_LANGUAGE_STANDARD = gnu11;
279 | GCC_NO_COMMON_BLOCKS = YES;
280 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
281 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
282 | GCC_WARN_UNDECLARED_SELECTOR = YES;
283 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
284 | GCC_WARN_UNUSED_FUNCTION = YES;
285 | GCC_WARN_UNUSED_VARIABLE = YES;
286 | MACOSX_DEPLOYMENT_TARGET = 11.0;
287 | MTL_ENABLE_DEBUG_INFO = NO;
288 | MTL_FAST_MATH = YES;
289 | SDKROOT = macosx;
290 | SWIFT_COMPILATION_MODE = wholemodule;
291 | SWIFT_OPTIMIZATION_LEVEL = "-O";
292 | };
293 | name = Release;
294 | };
295 | 794ADB93258CB8810085161B /* Debug */ = {
296 | isa = XCBuildConfiguration;
297 | buildSettings = {
298 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
299 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
300 | CODE_SIGN_ENTITLEMENTS = "Demo-macOS/Demo_macOS.entitlements";
301 | CODE_SIGN_STYLE = Automatic;
302 | COMBINE_HIDPI_IMAGES = YES;
303 | DEVELOPMENT_ASSET_PATHS = "\"Demo-macOS/Preview Content\"";
304 | DEVELOPMENT_TEAM = TQ9RME29ZJ;
305 | ENABLE_HARDENED_RUNTIME = YES;
306 | ENABLE_PREVIEWS = YES;
307 | INFOPLIST_FILE = "Demo-macOS/Info.plist";
308 | LD_RUNPATH_SEARCH_PATHS = (
309 | "$(inherited)",
310 | "@executable_path/../Frameworks",
311 | );
312 | MACOSX_DEPLOYMENT_TARGET = 11.0;
313 | PRODUCT_BUNDLE_IDENTIFIER = "com.anthonyfernandez.Demo-macOS";
314 | PRODUCT_NAME = "$(TARGET_NAME)";
315 | SWIFT_VERSION = 5.0;
316 | };
317 | name = Debug;
318 | };
319 | 794ADB94258CB8810085161B /* Release */ = {
320 | isa = XCBuildConfiguration;
321 | buildSettings = {
322 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
323 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
324 | CODE_SIGN_ENTITLEMENTS = "Demo-macOS/Demo_macOS.entitlements";
325 | CODE_SIGN_STYLE = Automatic;
326 | COMBINE_HIDPI_IMAGES = YES;
327 | DEVELOPMENT_ASSET_PATHS = "\"Demo-macOS/Preview Content\"";
328 | DEVELOPMENT_TEAM = TQ9RME29ZJ;
329 | ENABLE_HARDENED_RUNTIME = YES;
330 | ENABLE_PREVIEWS = YES;
331 | INFOPLIST_FILE = "Demo-macOS/Info.plist";
332 | LD_RUNPATH_SEARCH_PATHS = (
333 | "$(inherited)",
334 | "@executable_path/../Frameworks",
335 | );
336 | MACOSX_DEPLOYMENT_TARGET = 11.0;
337 | PRODUCT_BUNDLE_IDENTIFIER = "com.anthonyfernandez.Demo-macOS";
338 | PRODUCT_NAME = "$(TARGET_NAME)";
339 | SWIFT_VERSION = 5.0;
340 | };
341 | name = Release;
342 | };
343 | /* End XCBuildConfiguration section */
344 |
345 | /* Begin XCConfigurationList section */
346 | 794ADB7D258CB87F0085161B /* Build configuration list for PBXProject "Demo-macOS" */ = {
347 | isa = XCConfigurationList;
348 | buildConfigurations = (
349 | 794ADB90258CB8810085161B /* Debug */,
350 | 794ADB91258CB8810085161B /* Release */,
351 | );
352 | defaultConfigurationIsVisible = 0;
353 | defaultConfigurationName = Release;
354 | };
355 | 794ADB92258CB8810085161B /* Build configuration list for PBXNativeTarget "Demo-macOS" */ = {
356 | isa = XCConfigurationList;
357 | buildConfigurations = (
358 | 794ADB93258CB8810085161B /* Debug */,
359 | 794ADB94258CB8810085161B /* Release */,
360 | );
361 | defaultConfigurationIsVisible = 0;
362 | defaultConfigurationName = Release;
363 | };
364 | /* End XCConfigurationList section */
365 |
366 | /* Begin XCSwiftPackageProductDependency section */
367 | 79C7C037258CEF1B0021E8B8 /* ImageCropper */ = {
368 | isa = XCSwiftPackageProductDependency;
369 | productName = ImageCropper;
370 | };
371 | /* End XCSwiftPackageProductDependency section */
372 | };
373 | rootObject = 794ADB7A258CB87F0085161B /* Project object */;
374 | }
375 |
--------------------------------------------------------------------------------