├── .gitignore
├── Demo
├── MediaPickerDemo.swiftpm
│ ├── .swiftpm
│ │ ├── playgrounds
│ │ │ ├── CachedManifest.plist
│ │ │ ├── DocumentThumbnail.plist
│ │ │ ├── DocumentThumbnail.png
│ │ │ └── Workspace.plist
│ │ └── xcode
│ │ │ └── package.xcworkspace
│ │ │ └── contents.xcworkspacedata
│ ├── App
│ │ ├── Assets.xcassets
│ │ │ ├── AccentColor.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── AppIcon.appiconset
│ │ │ │ ├── Contents.json
│ │ │ │ ├── PlaceholderAppIcon-images-1024.png
│ │ │ │ ├── PlaceholderAppIcon-images-60@2x.png
│ │ │ │ ├── PlaceholderAppIcon-images-60@3x.png
│ │ │ │ ├── PlaceholderAppIcon-images-76@2x.png
│ │ │ │ └── PlaceholderAppIcon-images-83.5@2x.png
│ │ │ └── Contents.json
│ │ ├── ContentView.swift
│ │ └── MyApp.swift
│ ├── License.txt
│ ├── Package.resolved
│ └── Package.swift
├── MediaPickerDemo.xcodeproj
│ ├── project.pbxproj
│ └── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
├── Shared
│ ├── Assets.xcassets
│ │ ├── AccentColor.colorset
│ │ │ └── Contents.json
│ │ ├── AppIcon.appiconset
│ │ │ └── Contents.json
│ │ └── Contents.json
│ ├── ContentView.swift
│ ├── MediaPickerDemoApp.swift
│ └── NativeImage.swift
└── macOS
│ └── macOS.entitlements
├── LICENSE
├── Package.swift
├── README.md
├── Sources
└── MediaPicker
│ ├── MediaOptions.swift
│ ├── MediaPicker-PhotosUI.swift
│ ├── MediaPicker-iOS.swift
│ ├── MediaPicker-macOS.swift
│ └── MediaPickerErrors.swift
└── Tests
└── MediaPickerTests
└── MediaPickerTests.swift
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## User settings
6 | xcuserdata/
7 |
8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
9 | *.xcscmblueprint
10 | *.xccheckout
11 |
12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
13 | build/
14 | DerivedData/
15 | *.moved-aside
16 | *.pbxuser
17 | !default.pbxuser
18 | *.mode1v3
19 | !default.mode1v3
20 | *.mode2v3
21 | !default.mode2v3
22 | *.perspectivev3
23 | !default.perspectivev3
24 |
25 | ## Obj-C/Swift specific
26 | *.hmap
27 |
28 | ## App packaging
29 | *.ipa
30 | *.dSYM.zip
31 | *.dSYM
32 |
33 | ## Playgrounds
34 | timeline.xctimeline
35 | playground.xcworkspace
36 |
37 | # Swift Package Manager
38 | #
39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
40 | # Packages/
41 | # Package.pins
42 | # Package.resolved
43 | # *.xcodeproj
44 | #
45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
46 | # hence it is not needed unless you have added a package configuration file to your project
47 | # .swiftpm
48 |
49 | .swiftpm/*
50 | !.swiftpm/playgrounds/
51 | .build/
52 |
53 | # CocoaPods
54 | #
55 | # We recommend against adding the Pods directory to your .gitignore. However
56 | # you should judge for yourself, the pros and cons are mentioned at:
57 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
58 | #
59 | # Pods/
60 | #
61 | # Add this line if you want to avoid checking in source code from the Xcode workspace
62 | # *.xcworkspace
63 |
64 | # Carthage
65 | #
66 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
67 | # Carthage/Checkouts
68 |
69 | Carthage/Build/
70 |
71 | # Accio dependency management
72 | Dependencies/
73 | .accio/
74 |
75 | # fastlane
76 | #
77 | # It is recommended to not store the screenshots in the git repo.
78 | # Instead, use fastlane to re-generate the screenshots whenever they are needed.
79 | # For more information about the recommended setup visit:
80 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
81 |
82 | fastlane/report.xml
83 | fastlane/Preview.html
84 | fastlane/screenshots/**/*.png
85 | fastlane/test_output
86 |
87 | # Code Injection
88 | #
89 | # After new code Injection tools there's a generated folder /iOSInjectionProject
90 | # https://github.com/johnno1962/injectionforxcode
91 |
92 | iOSInjectionProject/
93 |
94 | .DS_Store
95 |
--------------------------------------------------------------------------------
/Demo/MediaPickerDemo.swiftpm/.swiftpm/playgrounds/CachedManifest.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CachedManifest
6 |
7 | manifestData
8 |
9 | eyJkZXBlbmRlbmNpZXMiOlt7InNjbSI6W3siaWRlbnRpdHkiOiJzd2lmdHVp
10 | LW1lZGlhcGlja2VyIiwibG9jYXRpb24iOiJodHRwczpcL1wvZ2l0aHViLmNv
11 | bVwvVVdBcHBEZXZcL1N3aWZ0VUktTWVkaWFQaWNrZXIuZ2l0IiwicHJvZHVj
12 | dEZpbHRlciI6bnVsbCwicmVxdWlyZW1lbnQiOnsicmFuZ2UiOlt7Imxvd2Vy
13 | Qm91bmQiOiIwLjAuMSIsInVwcGVyQm91bmQiOiIxLjAuMCJ9XX19XX1dLCJu
14 | YW1lIjoiTWVkaWFQaWNrZXJEZW1vIiwicGFja2FnZUtpbmQiOiJyb290Iiwi
15 | cGxhdGZvcm1zIjpbeyJvcHRpb25zIjpbXSwicGxhdGZvcm1OYW1lIjoiaW9z
16 | IiwidmVyc2lvbiI6IjE1LjIifV0sInByb2R1Y3RzIjpbeyJuYW1lIjoiTWVk
17 | aWFQaWNrZXJEZW1vIiwic2V0dGluZ3MiOlt7ImJ1bmRsZUlkZW50aWZpZXIi
18 | OlsiZGV2LnV3YXBwLk1lZGlhUGlja2VyLkRlbW8iXX0seyJ0ZWFtSWRlbnRp
19 | ZmllciI6WyIySDg2NkYyMlc3Il19LHsiZGlzcGxheVZlcnNpb24iOlsiMS4w
20 | LjEiXX0seyJidW5kbGVWZXJzaW9uIjpbIjEiXX0seyJpT1NBcHBJbmZvIjpb
21 | eyJhY2NlbnRDb2xvckFzc2V0TmFtZSI6IkFjY2VudENvbG9yIiwiY2FwYWJp
22 | bGl0aWVzIjpbXSwiaWNvbkFzc2V0TmFtZSI6IkFwcEljb24iLCJzdXBwb3J0
23 | ZWREZXZpY2VGYW1pbGllcyI6WyJwYWQiLCJwaG9uZSJdLCJzdXBwb3J0ZWRJ
24 | bnRlcmZhY2VPcmllbnRhdGlvbnMiOlt7InBvcnRyYWl0Ijp7fX0seyJsYW5k
25 | c2NhcGVSaWdodCI6e319LHsibGFuZHNjYXBlTGVmdCI6e319LHsicG9ydHJh
26 | aXRVcHNpZGVEb3duIjp7ImNvbmRpdGlvbiI6eyJkZXZpY2VGYW1pbGllcyI6
27 | WyJwYWQiXX19fV19XX1dLCJ0YXJnZXRzIjpbIkFwcE1vZHVsZSJdLCJ0eXBl
28 | Ijp7ImV4ZWN1dGFibGUiOm51bGx9fV0sInRhcmdldE1hcCI6eyJBcHBNb2R1
29 | bGUiOnsiZGVwZW5kZW5jaWVzIjpbeyJwcm9kdWN0IjpbIk1lZGlhUGlja2Vy
30 | Iiwic3dpZnR1aS1tZWRpYXBpY2tlciIsbnVsbF19XSwiZXhjbHVkZSI6W10s
31 | Im5hbWUiOiJBcHBNb2R1bGUiLCJwYXRoIjoiQXBwIiwicmVzb3VyY2VzIjpb
32 | XSwic2V0dGluZ3MiOltdLCJ0eXBlIjoiZXhlY3V0YWJsZSJ9fSwidGFyZ2V0
33 | cyI6W3siZGVwZW5kZW5jaWVzIjpbeyJwcm9kdWN0IjpbIk1lZGlhUGlja2Vy
34 | Iiwic3dpZnR1aS1tZWRpYXBpY2tlciIsbnVsbF19XSwiZXhjbHVkZSI6W10s
35 | Im5hbWUiOiJBcHBNb2R1bGUiLCJwYXRoIjoiQXBwIiwicmVzb3VyY2VzIjpb
36 | XSwic2V0dGluZ3MiOltdLCJ0eXBlIjoiZXhlY3V0YWJsZSJ9XSwidG9vbHNW
37 | ZXJzaW9uIjp7Il92ZXJzaW9uIjoiNS41LjAifX0=
38 |
39 | manifestHash
40 |
41 | TUtxSwwY7XSia8Kdh/KVbSjX/aguNuSGbe8v56xeShQ=
42 |
43 | schemaVersion
44 | 3
45 | swiftPMVersionString
46 | 5.5.0
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/Demo/MediaPickerDemo.swiftpm/.swiftpm/playgrounds/DocumentThumbnail.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | DocumentThumbnailConfiguration
6 |
7 | accentColorHash
8 |
9 | G0yRM9pzpxEyJAQxRAJ2WrDSP9NioWfW8MZbshURPZQ=
10 |
11 | appIconHash
12 |
13 | IbLu0eMoosYv5MF9URiL3qc0UPKZVtxcjJVCkxPd1yw=
14 |
15 | thumbnailIsPrerendered
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/Demo/MediaPickerDemo.swiftpm/.swiftpm/playgrounds/DocumentThumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UWAppDev/SwiftUI-MediaPicker/0e9342d80e944aa16768671b809186b9532ebe20/Demo/MediaPickerDemo.swiftpm/.swiftpm/playgrounds/DocumentThumbnail.png
--------------------------------------------------------------------------------
/Demo/MediaPickerDemo.swiftpm/.swiftpm/playgrounds/Workspace.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | AppSettings
6 |
7 | appIconPlaceholderGlyphName
8 | images
9 | appSettingsVersion
10 | 1
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/Demo/MediaPickerDemo.swiftpm/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Demo/MediaPickerDemo.swiftpm/App/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "platform" : "universal",
6 | "reference" : "systemOrangeColor"
7 | },
8 | "idiom" : "universal"
9 | }
10 | ],
11 | "info" : {
12 | "author" : "xcode",
13 | "version" : 1
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Demo/MediaPickerDemo.swiftpm/App/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "PlaceholderAppIcon-images-60@2x.png",
5 | "idiom" : "iphone",
6 | "scale" : "2x",
7 | "size" : "60x60"
8 | },
9 | {
10 | "filename" : "PlaceholderAppIcon-images-60@3x.png",
11 | "idiom" : "iphone",
12 | "scale" : "3x",
13 | "size" : "60x60"
14 | },
15 | {
16 | "filename" : "PlaceholderAppIcon-images-76@2x.png",
17 | "idiom" : "ipad",
18 | "scale" : "2x",
19 | "size" : "76x76"
20 | },
21 | {
22 | "filename" : "PlaceholderAppIcon-images-83.5@2x.png",
23 | "idiom" : "ipad",
24 | "scale" : "2x",
25 | "size" : "83.5x83.5"
26 | },
27 | {
28 | "filename" : "PlaceholderAppIcon-images-1024.png",
29 | "idiom" : "ios-marketing",
30 | "scale" : "1x",
31 | "size" : "1024x1024"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Demo/MediaPickerDemo.swiftpm/App/Assets.xcassets/AppIcon.appiconset/PlaceholderAppIcon-images-1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UWAppDev/SwiftUI-MediaPicker/0e9342d80e944aa16768671b809186b9532ebe20/Demo/MediaPickerDemo.swiftpm/App/Assets.xcassets/AppIcon.appiconset/PlaceholderAppIcon-images-1024.png
--------------------------------------------------------------------------------
/Demo/MediaPickerDemo.swiftpm/App/Assets.xcassets/AppIcon.appiconset/PlaceholderAppIcon-images-60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UWAppDev/SwiftUI-MediaPicker/0e9342d80e944aa16768671b809186b9532ebe20/Demo/MediaPickerDemo.swiftpm/App/Assets.xcassets/AppIcon.appiconset/PlaceholderAppIcon-images-60@2x.png
--------------------------------------------------------------------------------
/Demo/MediaPickerDemo.swiftpm/App/Assets.xcassets/AppIcon.appiconset/PlaceholderAppIcon-images-60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UWAppDev/SwiftUI-MediaPicker/0e9342d80e944aa16768671b809186b9532ebe20/Demo/MediaPickerDemo.swiftpm/App/Assets.xcassets/AppIcon.appiconset/PlaceholderAppIcon-images-60@3x.png
--------------------------------------------------------------------------------
/Demo/MediaPickerDemo.swiftpm/App/Assets.xcassets/AppIcon.appiconset/PlaceholderAppIcon-images-76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UWAppDev/SwiftUI-MediaPicker/0e9342d80e944aa16768671b809186b9532ebe20/Demo/MediaPickerDemo.swiftpm/App/Assets.xcassets/AppIcon.appiconset/PlaceholderAppIcon-images-76@2x.png
--------------------------------------------------------------------------------
/Demo/MediaPickerDemo.swiftpm/App/Assets.xcassets/AppIcon.appiconset/PlaceholderAppIcon-images-83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UWAppDev/SwiftUI-MediaPicker/0e9342d80e944aa16768671b809186b9532ebe20/Demo/MediaPickerDemo.swiftpm/App/Assets.xcassets/AppIcon.appiconset/PlaceholderAppIcon-images-83.5@2x.png
--------------------------------------------------------------------------------
/Demo/MediaPickerDemo.swiftpm/App/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Demo/MediaPickerDemo.swiftpm/App/ContentView.swift:
--------------------------------------------------------------------------------
1 | //===----------------------------------------------------------------------===//
2 | //
3 | // This source file is part of the SwiftUI FoodTracker tutorial series
4 | //
5 | // Copyright (c) 2020-2022 AppDev@UW.edu and the SwiftUI MediaPicker authors
6 | // Licensed under MIT License
7 | //
8 | // See https://github.com/UWAppDev/SwiftUI-MediaPicker/blob/main/LICENSE
9 | // for license information
10 | // See https://github.com/UWAppDev/SwiftUI-MediaPicker/graphs/contributors
11 | // for the list of SwiftUI MediaPicker project authors
12 | //
13 | //===----------------------------------------------------------------------===//
14 |
15 | import SwiftUI
16 | import AVKit
17 | import MediaPicker
18 |
19 | struct ContentView: View {
20 | @State var urls: [URL] = []
21 | @State var isShowingMediaPicker = false
22 |
23 | var selectButton: some View {
24 | Button("Select Media") {
25 | isShowingMediaPicker = true
26 | }
27 | .mediaImporter(isPresented: $isShowingMediaPicker,
28 | allowedMediaTypes: .all,
29 | allowsMultipleSelection: true) { result in
30 | switch result {
31 | case .success(let urls):
32 | self.urls = urls
33 | case .failure(let error):
34 | print(error)
35 | self.urls = []
36 | }
37 | }
38 | }
39 |
40 | var body: some View {
41 | List {
42 | Section {
43 | ForEach(urls, id: \.absoluteString) { url in
44 | switch try! url.resourceValues(forKeys: [.contentTypeKey]).contentType! {
45 | case let contentType where contentType.conforms(to: .image):
46 | AsyncImage(url: url) { image in
47 | image
48 | .resizable()
49 | .scaledToFit()
50 | } placeholder: {
51 | ProgressView()
52 | }
53 | case let contentType where contentType.conforms(to: .audiovisualContent):
54 | VideoPlayer(player: AVPlayer(url: url))
55 | .scaledToFit()
56 | default:
57 | Text("Can't display \(url.lastPathComponent)")
58 | }
59 | }
60 | } header: {
61 | selectButton
62 | }
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/Demo/MediaPickerDemo.swiftpm/App/MyApp.swift:
--------------------------------------------------------------------------------
1 | //===----------------------------------------------------------------------===//
2 | //
3 | // This source file is part of the SwiftUI FoodTracker tutorial series
4 | //
5 | // Copyright (c) 2020-2022 AppDev@UW.edu and the SwiftUI MediaPicker authors
6 | // Licensed under MIT License
7 | //
8 | // See https://github.com/UWAppDev/SwiftUI-MediaPicker/blob/main/LICENSE
9 | // for license information
10 | // See https://github.com/UWAppDev/SwiftUI-MediaPicker/graphs/contributors
11 | // for the list of SwiftUI MediaPicker project authors
12 | //
13 | //===----------------------------------------------------------------------===//
14 |
15 | import SwiftUI
16 |
17 | @main
18 | struct MyApp: App {
19 | var body: some Scene {
20 | WindowGroup {
21 | ContentView()
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Demo/MediaPickerDemo.swiftpm/License.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020-2022 AppDev@UW.edu and the SwiftUI MediaPicker authors.
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/MediaPickerDemo.swiftpm/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "object": {
3 | "pins": [
4 | {
5 | "package": "SwiftUI-MediaPicker",
6 | "repositoryURL": "https://github.com/UWAppDev/SwiftUI-MediaPicker.git",
7 | "state": {
8 | "branch": null,
9 | "revision": "8c6c7f3acbe4df234b383cfaac5996945b547aab",
10 | "version": "0.1.0"
11 | }
12 | }
13 | ]
14 | },
15 | "version": 1
16 | }
17 |
--------------------------------------------------------------------------------
/Demo/MediaPickerDemo.swiftpm/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version: 5.5
2 |
3 | // WARNING:
4 | // This file is automatically generated.
5 | // Do not edit it by hand because the contents will be replaced.
6 |
7 | import PackageDescription
8 | import AppleProductTypes
9 |
10 | let package = Package(
11 | name: "SwiftUI Media Picker",
12 | platforms: [
13 | .iOS("15.2")
14 | ],
15 | products: [
16 | .iOSApplication(
17 | name: "SwiftUI Media Picker",
18 | targets: ["AppModule"],
19 | bundleIdentifier: "dev.uwapp.MediaPicker.Demo",
20 | teamIdentifier: "2H866F22W7",
21 | displayVersion: "1.0.2",
22 | bundleVersion: "1",
23 | iconAssetName: "AppIcon",
24 | accentColorAssetName: "AccentColor",
25 | supportedDeviceFamilies: [
26 | .pad,
27 | .phone
28 | ],
29 | supportedInterfaceOrientations: [
30 | .portrait,
31 | .landscapeRight,
32 | .landscapeLeft,
33 | .portraitUpsideDown(.when(deviceFamilies: [.pad]))
34 | ]
35 | )
36 | ],
37 | dependencies: [
38 | .package(url: "https://github.com/UWAppDev/SwiftUI-MediaPicker.git", "0.0.1"..<"1.0.0")
39 | ],
40 | targets: [
41 | .executableTarget(
42 | name: "AppModule",
43 | dependencies: [
44 | .product(name: "MediaPicker", package: "swiftui-mediapicker")
45 | ],
46 | path: "App"
47 | )
48 | ]
49 | )
--------------------------------------------------------------------------------
/Demo/MediaPickerDemo.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 55;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | D2A0C5F92768268E00CB4F6F /* MediaPickerDemoApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A0C5E92768268C00CB4F6F /* MediaPickerDemoApp.swift */; };
11 | D2A0C5FA2768268E00CB4F6F /* MediaPickerDemoApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A0C5E92768268C00CB4F6F /* MediaPickerDemoApp.swift */; };
12 | D2A0C5FB2768268E00CB4F6F /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A0C5EA2768268C00CB4F6F /* ContentView.swift */; };
13 | D2A0C5FC2768268E00CB4F6F /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A0C5EA2768268C00CB4F6F /* ContentView.swift */; };
14 | D2A0C5FD2768268E00CB4F6F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D2A0C5EB2768268D00CB4F6F /* Assets.xcassets */; };
15 | D2A0C5FE2768268E00CB4F6F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D2A0C5EB2768268D00CB4F6F /* Assets.xcassets */; };
16 | D2A0C60A276826B400CB4F6F /* MediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = D2A0C609276826B400CB4F6F /* MediaPicker */; };
17 | D2A0C60C276826B900CB4F6F /* MediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = D2A0C60B276826B900CB4F6F /* MediaPicker */; };
18 | D2A0C60F2768281300CB4F6F /* NativeImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A0C60E2768281300CB4F6F /* NativeImage.swift */; };
19 | D2A0C6102768281300CB4F6F /* NativeImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A0C60E2768281300CB4F6F /* NativeImage.swift */; };
20 | /* End PBXBuildFile section */
21 |
22 | /* Begin PBXFileReference section */
23 | D2A0C5E92768268C00CB4F6F /* MediaPickerDemoApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPickerDemoApp.swift; sourceTree = ""; };
24 | D2A0C5EA2768268C00CB4F6F /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; };
25 | D2A0C5EB2768268D00CB4F6F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
26 | D2A0C5F02768268D00CB4F6F /* MediaPickerDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MediaPickerDemo.app; sourceTree = BUILT_PRODUCTS_DIR; };
27 | D2A0C5F62768268E00CB4F6F /* MediaPickerDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MediaPickerDemo.app; sourceTree = BUILT_PRODUCTS_DIR; };
28 | D2A0C5F82768268E00CB4F6F /* macOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = macOS.entitlements; sourceTree = ""; };
29 | D2A0C607276826AA00CB4F6F /* SwiftUI-MediaPicker */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = "SwiftUI-MediaPicker"; path = ..; sourceTree = ""; };
30 | D2A0C60D276826BF00CB4F6F /* MediaPickerDemo.swiftpm */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = MediaPickerDemo.swiftpm; sourceTree = ""; };
31 | D2A0C60E2768281300CB4F6F /* NativeImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeImage.swift; sourceTree = ""; };
32 | /* End PBXFileReference section */
33 |
34 | /* Begin PBXFrameworksBuildPhase section */
35 | D2A0C5ED2768268D00CB4F6F /* Frameworks */ = {
36 | isa = PBXFrameworksBuildPhase;
37 | buildActionMask = 2147483647;
38 | files = (
39 | D2A0C60A276826B400CB4F6F /* MediaPicker in Frameworks */,
40 | );
41 | runOnlyForDeploymentPostprocessing = 0;
42 | };
43 | D2A0C5F32768268D00CB4F6F /* Frameworks */ = {
44 | isa = PBXFrameworksBuildPhase;
45 | buildActionMask = 2147483647;
46 | files = (
47 | D2A0C60C276826B900CB4F6F /* MediaPicker in Frameworks */,
48 | );
49 | runOnlyForDeploymentPostprocessing = 0;
50 | };
51 | /* End PBXFrameworksBuildPhase section */
52 |
53 | /* Begin PBXGroup section */
54 | D2A0C5E32768268C00CB4F6F = {
55 | isa = PBXGroup;
56 | children = (
57 | D2A0C607276826AA00CB4F6F /* SwiftUI-MediaPicker */,
58 | D2A0C60D276826BF00CB4F6F /* MediaPickerDemo.swiftpm */,
59 | D2A0C5E82768268C00CB4F6F /* Shared */,
60 | D2A0C5F72768268E00CB4F6F /* macOS */,
61 | D2A0C5F12768268D00CB4F6F /* Products */,
62 | D2A0C608276826B400CB4F6F /* Frameworks */,
63 | );
64 | sourceTree = "";
65 | };
66 | D2A0C5E82768268C00CB4F6F /* Shared */ = {
67 | isa = PBXGroup;
68 | children = (
69 | D2A0C5EB2768268D00CB4F6F /* Assets.xcassets */,
70 | D2A0C5EA2768268C00CB4F6F /* ContentView.swift */,
71 | D2A0C5E92768268C00CB4F6F /* MediaPickerDemoApp.swift */,
72 | D2A0C60E2768281300CB4F6F /* NativeImage.swift */,
73 | );
74 | path = Shared;
75 | sourceTree = "";
76 | };
77 | D2A0C5F12768268D00CB4F6F /* Products */ = {
78 | isa = PBXGroup;
79 | children = (
80 | D2A0C5F02768268D00CB4F6F /* MediaPickerDemo.app */,
81 | D2A0C5F62768268E00CB4F6F /* MediaPickerDemo.app */,
82 | );
83 | name = Products;
84 | sourceTree = "";
85 | };
86 | D2A0C5F72768268E00CB4F6F /* macOS */ = {
87 | isa = PBXGroup;
88 | children = (
89 | D2A0C5F82768268E00CB4F6F /* macOS.entitlements */,
90 | );
91 | path = macOS;
92 | sourceTree = "";
93 | };
94 | D2A0C608276826B400CB4F6F /* Frameworks */ = {
95 | isa = PBXGroup;
96 | children = (
97 | );
98 | name = Frameworks;
99 | sourceTree = "";
100 | };
101 | /* End PBXGroup section */
102 |
103 | /* Begin PBXNativeTarget section */
104 | D2A0C5EF2768268D00CB4F6F /* MediaPickerDemo (iOS) */ = {
105 | isa = PBXNativeTarget;
106 | buildConfigurationList = D2A0C6012768268E00CB4F6F /* Build configuration list for PBXNativeTarget "MediaPickerDemo (iOS)" */;
107 | buildPhases = (
108 | D2A0C5EC2768268D00CB4F6F /* Sources */,
109 | D2A0C5ED2768268D00CB4F6F /* Frameworks */,
110 | D2A0C5EE2768268D00CB4F6F /* Resources */,
111 | );
112 | buildRules = (
113 | );
114 | dependencies = (
115 | );
116 | name = "MediaPickerDemo (iOS)";
117 | packageProductDependencies = (
118 | D2A0C609276826B400CB4F6F /* MediaPicker */,
119 | );
120 | productName = "MediaPickerDemo (iOS)";
121 | productReference = D2A0C5F02768268D00CB4F6F /* MediaPickerDemo.app */;
122 | productType = "com.apple.product-type.application";
123 | };
124 | D2A0C5F52768268D00CB4F6F /* MediaPickerDemo (macOS) */ = {
125 | isa = PBXNativeTarget;
126 | buildConfigurationList = D2A0C6042768268E00CB4F6F /* Build configuration list for PBXNativeTarget "MediaPickerDemo (macOS)" */;
127 | buildPhases = (
128 | D2A0C5F22768268D00CB4F6F /* Sources */,
129 | D2A0C5F32768268D00CB4F6F /* Frameworks */,
130 | D2A0C5F42768268D00CB4F6F /* Resources */,
131 | );
132 | buildRules = (
133 | );
134 | dependencies = (
135 | );
136 | name = "MediaPickerDemo (macOS)";
137 | packageProductDependencies = (
138 | D2A0C60B276826B900CB4F6F /* MediaPicker */,
139 | );
140 | productName = "MediaPickerDemo (macOS)";
141 | productReference = D2A0C5F62768268E00CB4F6F /* MediaPickerDemo.app */;
142 | productType = "com.apple.product-type.application";
143 | };
144 | /* End PBXNativeTarget section */
145 |
146 | /* Begin PBXProject section */
147 | D2A0C5E42768268C00CB4F6F /* Project object */ = {
148 | isa = PBXProject;
149 | attributes = {
150 | BuildIndependentTargetsInParallel = 1;
151 | LastSwiftUpdateCheck = 1320;
152 | LastUpgradeCheck = 1320;
153 | TargetAttributes = {
154 | D2A0C5EF2768268D00CB4F6F = {
155 | CreatedOnToolsVersion = 13.2;
156 | };
157 | D2A0C5F52768268D00CB4F6F = {
158 | CreatedOnToolsVersion = 13.2;
159 | };
160 | };
161 | };
162 | buildConfigurationList = D2A0C5E72768268C00CB4F6F /* Build configuration list for PBXProject "MediaPickerDemo" */;
163 | compatibilityVersion = "Xcode 13.0";
164 | developmentRegion = en;
165 | hasScannedForEncodings = 0;
166 | knownRegions = (
167 | en,
168 | Base,
169 | );
170 | mainGroup = D2A0C5E32768268C00CB4F6F;
171 | productRefGroup = D2A0C5F12768268D00CB4F6F /* Products */;
172 | projectDirPath = "";
173 | projectRoot = "";
174 | targets = (
175 | D2A0C5EF2768268D00CB4F6F /* MediaPickerDemo (iOS) */,
176 | D2A0C5F52768268D00CB4F6F /* MediaPickerDemo (macOS) */,
177 | );
178 | };
179 | /* End PBXProject section */
180 |
181 | /* Begin PBXResourcesBuildPhase section */
182 | D2A0C5EE2768268D00CB4F6F /* Resources */ = {
183 | isa = PBXResourcesBuildPhase;
184 | buildActionMask = 2147483647;
185 | files = (
186 | D2A0C5FD2768268E00CB4F6F /* Assets.xcassets in Resources */,
187 | );
188 | runOnlyForDeploymentPostprocessing = 0;
189 | };
190 | D2A0C5F42768268D00CB4F6F /* Resources */ = {
191 | isa = PBXResourcesBuildPhase;
192 | buildActionMask = 2147483647;
193 | files = (
194 | D2A0C5FE2768268E00CB4F6F /* Assets.xcassets in Resources */,
195 | );
196 | runOnlyForDeploymentPostprocessing = 0;
197 | };
198 | /* End PBXResourcesBuildPhase section */
199 |
200 | /* Begin PBXSourcesBuildPhase section */
201 | D2A0C5EC2768268D00CB4F6F /* Sources */ = {
202 | isa = PBXSourcesBuildPhase;
203 | buildActionMask = 2147483647;
204 | files = (
205 | D2A0C5FB2768268E00CB4F6F /* ContentView.swift in Sources */,
206 | D2A0C5F92768268E00CB4F6F /* MediaPickerDemoApp.swift in Sources */,
207 | D2A0C60F2768281300CB4F6F /* NativeImage.swift in Sources */,
208 | );
209 | runOnlyForDeploymentPostprocessing = 0;
210 | };
211 | D2A0C5F22768268D00CB4F6F /* Sources */ = {
212 | isa = PBXSourcesBuildPhase;
213 | buildActionMask = 2147483647;
214 | files = (
215 | D2A0C5FC2768268E00CB4F6F /* ContentView.swift in Sources */,
216 | D2A0C5FA2768268E00CB4F6F /* MediaPickerDemoApp.swift in Sources */,
217 | D2A0C6102768281300CB4F6F /* NativeImage.swift in Sources */,
218 | );
219 | runOnlyForDeploymentPostprocessing = 0;
220 | };
221 | /* End PBXSourcesBuildPhase section */
222 |
223 | /* Begin XCBuildConfiguration section */
224 | D2A0C5FF2768268E00CB4F6F /* Debug */ = {
225 | isa = XCBuildConfiguration;
226 | buildSettings = {
227 | ALWAYS_SEARCH_USER_PATHS = NO;
228 | CLANG_ANALYZER_NONNULL = YES;
229 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
230 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
231 | CLANG_CXX_LIBRARY = "libc++";
232 | CLANG_ENABLE_MODULES = YES;
233 | CLANG_ENABLE_OBJC_ARC = YES;
234 | CLANG_ENABLE_OBJC_WEAK = YES;
235 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
236 | CLANG_WARN_BOOL_CONVERSION = YES;
237 | CLANG_WARN_COMMA = YES;
238 | CLANG_WARN_CONSTANT_CONVERSION = YES;
239 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
240 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
241 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
242 | CLANG_WARN_EMPTY_BODY = YES;
243 | CLANG_WARN_ENUM_CONVERSION = YES;
244 | CLANG_WARN_INFINITE_RECURSION = YES;
245 | CLANG_WARN_INT_CONVERSION = YES;
246 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
247 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
248 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
249 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
250 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
251 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
252 | CLANG_WARN_STRICT_PROTOTYPES = YES;
253 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
254 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
255 | CLANG_WARN_UNREACHABLE_CODE = YES;
256 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
257 | COPY_PHASE_STRIP = NO;
258 | DEBUG_INFORMATION_FORMAT = dwarf;
259 | ENABLE_STRICT_OBJC_MSGSEND = YES;
260 | ENABLE_TESTABILITY = YES;
261 | GCC_C_LANGUAGE_STANDARD = gnu11;
262 | GCC_DYNAMIC_NO_PIC = NO;
263 | GCC_NO_COMMON_BLOCKS = YES;
264 | GCC_OPTIMIZATION_LEVEL = 0;
265 | GCC_PREPROCESSOR_DEFINITIONS = (
266 | "DEBUG=1",
267 | "$(inherited)",
268 | );
269 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
270 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
271 | GCC_WARN_UNDECLARED_SELECTOR = YES;
272 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
273 | GCC_WARN_UNUSED_FUNCTION = YES;
274 | GCC_WARN_UNUSED_VARIABLE = YES;
275 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
276 | MTL_FAST_MATH = YES;
277 | ONLY_ACTIVE_ARCH = YES;
278 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
279 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
280 | };
281 | name = Debug;
282 | };
283 | D2A0C6002768268E00CB4F6F /* Release */ = {
284 | isa = XCBuildConfiguration;
285 | buildSettings = {
286 | ALWAYS_SEARCH_USER_PATHS = NO;
287 | CLANG_ANALYZER_NONNULL = YES;
288 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
289 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
290 | CLANG_CXX_LIBRARY = "libc++";
291 | CLANG_ENABLE_MODULES = YES;
292 | CLANG_ENABLE_OBJC_ARC = YES;
293 | CLANG_ENABLE_OBJC_WEAK = YES;
294 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
295 | CLANG_WARN_BOOL_CONVERSION = YES;
296 | CLANG_WARN_COMMA = YES;
297 | CLANG_WARN_CONSTANT_CONVERSION = YES;
298 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
299 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
300 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
301 | CLANG_WARN_EMPTY_BODY = YES;
302 | CLANG_WARN_ENUM_CONVERSION = YES;
303 | CLANG_WARN_INFINITE_RECURSION = YES;
304 | CLANG_WARN_INT_CONVERSION = YES;
305 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
306 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
307 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
308 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
309 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
310 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
311 | CLANG_WARN_STRICT_PROTOTYPES = YES;
312 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
313 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
314 | CLANG_WARN_UNREACHABLE_CODE = YES;
315 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
316 | COPY_PHASE_STRIP = NO;
317 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
318 | ENABLE_NS_ASSERTIONS = NO;
319 | ENABLE_STRICT_OBJC_MSGSEND = YES;
320 | GCC_C_LANGUAGE_STANDARD = gnu11;
321 | GCC_NO_COMMON_BLOCKS = YES;
322 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
323 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
324 | GCC_WARN_UNDECLARED_SELECTOR = YES;
325 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
326 | GCC_WARN_UNUSED_FUNCTION = YES;
327 | GCC_WARN_UNUSED_VARIABLE = YES;
328 | MTL_ENABLE_DEBUG_INFO = NO;
329 | MTL_FAST_MATH = YES;
330 | SWIFT_COMPILATION_MODE = wholemodule;
331 | SWIFT_OPTIMIZATION_LEVEL = "-O";
332 | };
333 | name = Release;
334 | };
335 | D2A0C6022768268E00CB4F6F /* Debug */ = {
336 | isa = XCBuildConfiguration;
337 | buildSettings = {
338 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
339 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
340 | CODE_SIGN_STYLE = Automatic;
341 | CURRENT_PROJECT_VERSION = 1;
342 | DEVELOPMENT_TEAM = 2H866F22W7;
343 | ENABLE_PREVIEWS = YES;
344 | GENERATE_INFOPLIST_FILE = YES;
345 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
346 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
347 | INFOPLIST_KEY_UILaunchScreen_Generation = YES;
348 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
349 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
350 | IPHONEOS_DEPLOYMENT_TARGET = 14.0;
351 | LD_RUNPATH_SEARCH_PATHS = (
352 | "$(inherited)",
353 | "@executable_path/Frameworks",
354 | );
355 | MARKETING_VERSION = 1.0;
356 | PRODUCT_BUNDLE_IDENTIFIER = dev.uwapp.MediaPicker.Demo;
357 | PRODUCT_NAME = MediaPickerDemo;
358 | SDKROOT = iphoneos;
359 | SWIFT_EMIT_LOC_STRINGS = YES;
360 | SWIFT_VERSION = 5.0;
361 | TARGETED_DEVICE_FAMILY = "1,2";
362 | };
363 | name = Debug;
364 | };
365 | D2A0C6032768268E00CB4F6F /* Release */ = {
366 | isa = XCBuildConfiguration;
367 | buildSettings = {
368 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
369 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
370 | CODE_SIGN_STYLE = Automatic;
371 | CURRENT_PROJECT_VERSION = 1;
372 | DEVELOPMENT_TEAM = 2H866F22W7;
373 | ENABLE_PREVIEWS = YES;
374 | GENERATE_INFOPLIST_FILE = YES;
375 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
376 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
377 | INFOPLIST_KEY_UILaunchScreen_Generation = YES;
378 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
379 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
380 | IPHONEOS_DEPLOYMENT_TARGET = 14.0;
381 | LD_RUNPATH_SEARCH_PATHS = (
382 | "$(inherited)",
383 | "@executable_path/Frameworks",
384 | );
385 | MARKETING_VERSION = 1.0;
386 | PRODUCT_BUNDLE_IDENTIFIER = dev.uwapp.MediaPicker.Demo;
387 | PRODUCT_NAME = MediaPickerDemo;
388 | SDKROOT = iphoneos;
389 | SWIFT_EMIT_LOC_STRINGS = YES;
390 | SWIFT_VERSION = 5.0;
391 | TARGETED_DEVICE_FAMILY = "1,2";
392 | VALIDATE_PRODUCT = YES;
393 | };
394 | name = Release;
395 | };
396 | D2A0C6052768268E00CB4F6F /* Debug */ = {
397 | isa = XCBuildConfiguration;
398 | buildSettings = {
399 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
400 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
401 | CODE_SIGN_ENTITLEMENTS = macOS/macOS.entitlements;
402 | CODE_SIGN_STYLE = Automatic;
403 | COMBINE_HIDPI_IMAGES = YES;
404 | CURRENT_PROJECT_VERSION = 1;
405 | DEVELOPMENT_TEAM = 2H866F22W7;
406 | ENABLE_HARDENED_RUNTIME = YES;
407 | ENABLE_PREVIEWS = YES;
408 | GENERATE_INFOPLIST_FILE = YES;
409 | INFOPLIST_KEY_NSHumanReadableCopyright = "";
410 | LD_RUNPATH_SEARCH_PATHS = (
411 | "$(inherited)",
412 | "@executable_path/../Frameworks",
413 | );
414 | MACOSX_DEPLOYMENT_TARGET = 11.0;
415 | MARKETING_VERSION = 1.0;
416 | PRODUCT_BUNDLE_IDENTIFIER = dev.uwapp.MediaPicker.Demo;
417 | PRODUCT_NAME = MediaPickerDemo;
418 | SDKROOT = macosx;
419 | SWIFT_EMIT_LOC_STRINGS = YES;
420 | SWIFT_VERSION = 5.0;
421 | };
422 | name = Debug;
423 | };
424 | D2A0C6062768268E00CB4F6F /* Release */ = {
425 | isa = XCBuildConfiguration;
426 | buildSettings = {
427 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
428 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
429 | CODE_SIGN_ENTITLEMENTS = macOS/macOS.entitlements;
430 | CODE_SIGN_STYLE = Automatic;
431 | COMBINE_HIDPI_IMAGES = YES;
432 | CURRENT_PROJECT_VERSION = 1;
433 | DEVELOPMENT_TEAM = 2H866F22W7;
434 | ENABLE_HARDENED_RUNTIME = YES;
435 | ENABLE_PREVIEWS = YES;
436 | GENERATE_INFOPLIST_FILE = YES;
437 | INFOPLIST_KEY_NSHumanReadableCopyright = "";
438 | LD_RUNPATH_SEARCH_PATHS = (
439 | "$(inherited)",
440 | "@executable_path/../Frameworks",
441 | );
442 | MACOSX_DEPLOYMENT_TARGET = 11.0;
443 | MARKETING_VERSION = 1.0;
444 | PRODUCT_BUNDLE_IDENTIFIER = dev.uwapp.MediaPicker.Demo;
445 | PRODUCT_NAME = MediaPickerDemo;
446 | SDKROOT = macosx;
447 | SWIFT_EMIT_LOC_STRINGS = YES;
448 | SWIFT_VERSION = 5.0;
449 | };
450 | name = Release;
451 | };
452 | /* End XCBuildConfiguration section */
453 |
454 | /* Begin XCConfigurationList section */
455 | D2A0C5E72768268C00CB4F6F /* Build configuration list for PBXProject "MediaPickerDemo" */ = {
456 | isa = XCConfigurationList;
457 | buildConfigurations = (
458 | D2A0C5FF2768268E00CB4F6F /* Debug */,
459 | D2A0C6002768268E00CB4F6F /* Release */,
460 | );
461 | defaultConfigurationIsVisible = 0;
462 | defaultConfigurationName = Release;
463 | };
464 | D2A0C6012768268E00CB4F6F /* Build configuration list for PBXNativeTarget "MediaPickerDemo (iOS)" */ = {
465 | isa = XCConfigurationList;
466 | buildConfigurations = (
467 | D2A0C6022768268E00CB4F6F /* Debug */,
468 | D2A0C6032768268E00CB4F6F /* Release */,
469 | );
470 | defaultConfigurationIsVisible = 0;
471 | defaultConfigurationName = Release;
472 | };
473 | D2A0C6042768268E00CB4F6F /* Build configuration list for PBXNativeTarget "MediaPickerDemo (macOS)" */ = {
474 | isa = XCConfigurationList;
475 | buildConfigurations = (
476 | D2A0C6052768268E00CB4F6F /* Debug */,
477 | D2A0C6062768268E00CB4F6F /* Release */,
478 | );
479 | defaultConfigurationIsVisible = 0;
480 | defaultConfigurationName = Release;
481 | };
482 | /* End XCConfigurationList section */
483 |
484 | /* Begin XCSwiftPackageProductDependency section */
485 | D2A0C609276826B400CB4F6F /* MediaPicker */ = {
486 | isa = XCSwiftPackageProductDependency;
487 | productName = MediaPicker;
488 | };
489 | D2A0C60B276826B900CB4F6F /* MediaPicker */ = {
490 | isa = XCSwiftPackageProductDependency;
491 | productName = MediaPicker;
492 | };
493 | /* End XCSwiftPackageProductDependency section */
494 | };
495 | rootObject = D2A0C5E42768268C00CB4F6F /* Project object */;
496 | }
497 |
--------------------------------------------------------------------------------
/Demo/MediaPickerDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Demo/MediaPickerDemo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Demo/Shared/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 |
--------------------------------------------------------------------------------
/Demo/Shared/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "scale" : "2x",
6 | "size" : "20x20"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "scale" : "3x",
11 | "size" : "20x20"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "scale" : "2x",
16 | "size" : "29x29"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "scale" : "3x",
21 | "size" : "29x29"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "scale" : "2x",
26 | "size" : "40x40"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "scale" : "3x",
31 | "size" : "40x40"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "scale" : "2x",
36 | "size" : "60x60"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "scale" : "3x",
41 | "size" : "60x60"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "scale" : "1x",
46 | "size" : "20x20"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "scale" : "2x",
51 | "size" : "20x20"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "scale" : "1x",
56 | "size" : "29x29"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "scale" : "2x",
61 | "size" : "29x29"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "scale" : "1x",
66 | "size" : "40x40"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "scale" : "2x",
71 | "size" : "40x40"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "scale" : "1x",
76 | "size" : "76x76"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "scale" : "2x",
81 | "size" : "76x76"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "scale" : "2x",
86 | "size" : "83.5x83.5"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "scale" : "1x",
91 | "size" : "1024x1024"
92 | },
93 | {
94 | "idiom" : "mac",
95 | "scale" : "1x",
96 | "size" : "16x16"
97 | },
98 | {
99 | "idiom" : "mac",
100 | "scale" : "2x",
101 | "size" : "16x16"
102 | },
103 | {
104 | "idiom" : "mac",
105 | "scale" : "1x",
106 | "size" : "32x32"
107 | },
108 | {
109 | "idiom" : "mac",
110 | "scale" : "2x",
111 | "size" : "32x32"
112 | },
113 | {
114 | "idiom" : "mac",
115 | "scale" : "1x",
116 | "size" : "128x128"
117 | },
118 | {
119 | "idiom" : "mac",
120 | "scale" : "2x",
121 | "size" : "128x128"
122 | },
123 | {
124 | "idiom" : "mac",
125 | "scale" : "1x",
126 | "size" : "256x256"
127 | },
128 | {
129 | "idiom" : "mac",
130 | "scale" : "2x",
131 | "size" : "256x256"
132 | },
133 | {
134 | "idiom" : "mac",
135 | "scale" : "1x",
136 | "size" : "512x512"
137 | },
138 | {
139 | "idiom" : "mac",
140 | "scale" : "2x",
141 | "size" : "512x512"
142 | }
143 | ],
144 | "info" : {
145 | "author" : "xcode",
146 | "version" : 1
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/Demo/Shared/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Demo/Shared/ContentView.swift:
--------------------------------------------------------------------------------
1 | //===----------------------------------------------------------------------===//
2 | //
3 | // This source file is part of the SwiftUI FoodTracker tutorial series
4 | //
5 | // Copyright (c) 2020-2022 AppDev@UW.edu and the SwiftUI MediaPicker authors
6 | // Licensed under MIT License
7 | //
8 | // See https://github.com/UWAppDev/SwiftUI-MediaPicker/blob/main/LICENSE
9 | // for license information
10 | // See https://github.com/UWAppDev/SwiftUI-MediaPicker/graphs/contributors
11 | // for the list of SwiftUI MediaPicker project authors
12 | //
13 | //===----------------------------------------------------------------------===//
14 |
15 | import SwiftUI
16 | import AVKit
17 | import MediaPicker
18 |
19 | struct ContentView: View {
20 | @State var urls: [URL] = []
21 | @State var isShowingMediaPicker = false
22 |
23 | var selectButton: some View {
24 | Button("Select Media") {
25 | isShowingMediaPicker = true
26 | }
27 | .mediaImporter(isPresented: $isShowingMediaPicker,
28 | allowedMediaTypes: .all,
29 | allowsMultipleSelection: true) { result in
30 | switch result {
31 | case .success(let urls):
32 | self.urls = urls
33 | case .failure(let error):
34 | switch error {
35 | case MediaPickerErrors.imageURL(let urls, errors: let errors):
36 | print(errors)
37 | self.urls = urls
38 | default:
39 | print(error)
40 | self.urls = []
41 | }
42 | }
43 | }
44 | }
45 |
46 | var body: some View {
47 | List {
48 | Section {
49 | ForEach(urls, id: \.absoluteString) { url in
50 | switch try! url.resourceValues(forKeys: [.contentTypeKey]).contentType! {
51 | case let contentType where contentType.conforms(to: .image):
52 | if #available(iOS 15, macOS 12, *) {
53 | AsyncImage(url: url) { image in
54 | image
55 | .resizable()
56 | .scaledToFit()
57 | } placeholder: {
58 | ProgressView()
59 | }
60 | } else {
61 | if let image = NativeImage(contentsOfFile: url.path) {
62 | Image(nativeImage: image)
63 | .resizable()
64 | .scaledToFit()
65 | } else {
66 | Text("Can't load contents of \(url)")
67 | }
68 | }
69 | case let contentType where contentType.conforms(to: .audiovisualContent):
70 | VideoPlayer(player: AVPlayer(url: url))
71 | .scaledToFit()
72 | default:
73 | Text("Can't display \(url.lastPathComponent)")
74 | }
75 | }
76 | } header: {
77 | selectButton
78 | }
79 | }
80 | }
81 | }
82 |
83 | struct ContentView_Previews: PreviewProvider {
84 | static var previews: some View {
85 | ContentView()
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/Demo/Shared/MediaPickerDemoApp.swift:
--------------------------------------------------------------------------------
1 | //===----------------------------------------------------------------------===//
2 | //
3 | // This source file is part of the SwiftUI FoodTracker tutorial series
4 | //
5 | // Copyright (c) 2020-2022 AppDev@UW.edu and the SwiftUI MediaPicker authors
6 | // Licensed under MIT License
7 | //
8 | // See https://github.com/UWAppDev/SwiftUI-MediaPicker/blob/main/LICENSE
9 | // for license information
10 | // See https://github.com/UWAppDev/SwiftUI-MediaPicker/graphs/contributors
11 | // for the list of SwiftUI MediaPicker project authors
12 | //
13 | //===----------------------------------------------------------------------===//
14 |
15 | import SwiftUI
16 |
17 | @main
18 | struct MediaPickerDemoApp: App {
19 | var body: some Scene {
20 | WindowGroup {
21 | ContentView()
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Demo/Shared/NativeImage.swift:
--------------------------------------------------------------------------------
1 | //===----------------------------------------------------------------------===//
2 | //
3 | // This source file is part of the SwiftUI FoodTracker tutorial series
4 | //
5 | // Copyright (c) 2020-2022 AppDev@UW.edu and the SwiftUI MediaPicker authors
6 | // Licensed under MIT License
7 | //
8 | // See https://github.com/UWAppDev/SwiftUI-MediaPicker/blob/main/LICENSE
9 | // for license information
10 | // See https://github.com/UWAppDev/SwiftUI-MediaPicker/graphs/contributors
11 | // for the list of SwiftUI MediaPicker project authors
12 | //
13 | //===----------------------------------------------------------------------===//
14 |
15 | import SwiftUI
16 |
17 | #if canImport(UIKit)
18 | import UIKit
19 |
20 | typealias NativeImage = UIImage
21 |
22 | extension Image {
23 | init(nativeImage: NativeImage) {
24 | self.init(uiImage: nativeImage)
25 | }
26 | }
27 | #else
28 | import AppKit
29 |
30 | typealias NativeImage = NSImage
31 |
32 | extension Image {
33 | init(nativeImage: NativeImage) {
34 | self.init(nsImage: nativeImage)
35 | }
36 | }
37 | #endif
38 |
--------------------------------------------------------------------------------
/Demo/macOS/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 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020-2022 AppDev@UW.edu and the SwiftUI MediaPicker authors.
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 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.5
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: "SwiftUI-MediaPicker",
8 | platforms: [
9 | .iOS(.v14), .macOS(.v11),
10 | ],
11 | products: [
12 | .library(
13 | name: "MediaPicker",
14 | targets: ["MediaPicker"]),
15 | ],
16 | targets: [
17 | .target(
18 | name: "MediaPicker",
19 | dependencies: []),
20 | .testTarget(
21 | name: "MediaPickerTests",
22 | dependencies: ["MediaPicker"]),
23 | ]
24 | )
25 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SwiftUI Media Picker
2 |
3 | iOS/macOS media picker for importing images and videos in SwiftUI.
4 |
5 | ## Adding SwiftUI Media Picker as a Dependency
6 |
7 | 1. If you are using Xcode and have a minimum deployment target of iOS 14/macOS 11, make sure you are using Xcode 13.2 or later
8 | 2. Add the following line to the Package's `dependencies` array in your `Package.swift` file:
9 |
10 | ```swift
11 | .package(url: "https://github.com/UWAppDev/SwiftUI-MediaPicker", from: "0.2.0"),
12 | ```
13 |
14 | 3. Include this library as a dependency for your executable target:
15 |
16 | ```swift
17 | .target(name: "", dependencies: [
18 | .product(name: "MediaPicker", package: "swiftui-mediapicker"),
19 | ]),
20 | ```
21 |
22 | 4. Add `import MediaPicker` to your source code.
23 |
24 | ## Example
25 |
26 | ```swift
27 | import SwiftUI
28 | import AVKit
29 | import MediaPicker
30 |
31 | struct ContentView: View {
32 | @State var urls: [URL] = []
33 | @State var isShowingMediaPicker = false
34 |
35 | var selectButton: some View {
36 | Button("Select Media") {
37 | isShowingMediaPicker = true
38 | }
39 | .mediaImporter(isPresented: $isShowingMediaPicker,
40 | allowedMediaTypes: .all,
41 | allowsMultipleSelection: true) { result in
42 | switch result {
43 | case .success(let urls):
44 | self.urls = urls
45 | case .failure(let error):
46 | print(error)
47 | self.urls = []
48 | }
49 | }
50 | }
51 |
52 | var body: some View {
53 | List {
54 | Section {
55 | ForEach(urls, id: \.absoluteString) { url in
56 | switch try! url.resourceValues(forKeys: [.contentTypeKey]).contentType! {
57 | case let contentType where contentType.conforms(to: .image):
58 | AsyncImage(url: url) { image in
59 | image
60 | .resizable()
61 | .scaledToFit()
62 | } placeholder: {
63 | ProgressView()
64 | }
65 | case let contentType where contentType.conforms(to: .audiovisualContent):
66 | VideoPlayer(player: AVPlayer(url: url))
67 | .scaledToFit()
68 | default:
69 | Text("Can't display \(url.lastPathComponent)")
70 | }
71 | }
72 | } header: {
73 | selectButton
74 | }
75 | }
76 | }
77 | }
78 | ```
79 |
--------------------------------------------------------------------------------
/Sources/MediaPicker/MediaOptions.swift:
--------------------------------------------------------------------------------
1 | //===----------------------------------------------------------------------===//
2 | //
3 | // This source file is part of the SwiftUI FoodTracker tutorial series
4 | //
5 | // Copyright (c) 2020-2022 AppDev@UW.edu and the SwiftUI MediaPicker authors
6 | // Licensed under MIT License
7 | //
8 | // See https://github.com/UWAppDev/SwiftUI-MediaPicker/blob/main/LICENSE
9 | // for license information
10 | // See https://github.com/UWAppDev/SwiftUI-MediaPicker/graphs/contributors
11 | // for the list of SwiftUI MediaPicker project authors
12 | //
13 | //===----------------------------------------------------------------------===//
14 |
15 | @_implementationOnly import UniformTypeIdentifiers
16 |
17 | public struct MediaTypeOptions: OptionSet {
18 | public let rawValue: Int
19 |
20 | public init(rawValue: Int) {
21 | self.rawValue = rawValue
22 | }
23 |
24 | /// Only Live Photos.
25 | public static let livePhotos = MediaTypeOptions(rawValue: 1 << 0)
26 | /// Images, including Live Photos.
27 | public static let images: MediaTypeOptions = [MediaTypeOptions(rawValue: 1 << 1), livePhotos]
28 | /// Only videos.
29 | public static let videos = MediaTypeOptions(rawValue: 1 << 2)
30 |
31 | /// All media types.
32 | public static let all: MediaTypeOptions = [.images, .videos]
33 |
34 | /// All uniform type identifiers for contained media types.
35 | internal var typeIdentifiers: [UTType] {
36 | var types = [UTType]()
37 | if contains(.images) {
38 | types += [.heic, .heif, .image]
39 | } else if contains(.livePhotos) {
40 | types += [.livePhoto, .heic]
41 | }
42 | if contains(.videos) {
43 | types.append(.audiovisualContent)
44 | }
45 | return types
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Sources/MediaPicker/MediaPicker-PhotosUI.swift:
--------------------------------------------------------------------------------
1 | //===----------------------------------------------------------------------===//
2 | //
3 | // This source file is part of the SwiftUI FoodTracker tutorial series
4 | //
5 | // Copyright (c) 2020-2022 AppDev@UW.edu and the SwiftUI MediaPicker authors
6 | // Licensed under MIT License
7 | //
8 | // See https://github.com/UWAppDev/SwiftUI-MediaPicker/blob/main/LICENSE
9 | // for license information
10 | // See https://github.com/UWAppDev/SwiftUI-MediaPicker/graphs/contributors
11 | // for the list of SwiftUI MediaPicker project authors
12 | //
13 | //===----------------------------------------------------------------------===//
14 |
15 | #if canImport(PhotosUI) && os(iOS)
16 | import SwiftUI
17 | import PhotosUI
18 |
19 | public extension View {
20 | /// - Important: You are responsible for setting `isPresented` to false.
21 | func mediaImporter(
22 | isPresented: Binding,
23 | allowedMediaTypes: MediaTypeOptions,
24 | allowsMultipleSelection: Bool,
25 | onCompletion: @escaping (Result<[PHPickerResult], Error>) -> Void,
26 | @ViewBuilder loadingOverlay: @escaping () -> LoadingOverlay
27 | ) -> some View {
28 | var configuration = PHPickerConfiguration(photoLibrary: .shared())
29 | configuration.selectionLimit = allowsMultipleSelection ? 0 : 1
30 | configuration.filter = PHPickerFilter.from(allowedMediaTypes)
31 |
32 | return sheet(isPresented: isPresented) {
33 | MediaPickerWrapper(
34 | isPresented: isPresented,
35 | allowedContentTypes: allowedMediaTypes.typeIdentifiers,
36 | configuration: configuration,
37 | onCompletion: onCompletion,
38 | makeLoadingOverlay: loadingOverlay
39 | )
40 | }
41 | }
42 | }
43 |
44 | fileprivate struct MediaPickerWrapper: View {
45 | @Binding var isPresented: Bool
46 | @State var isLoading: Bool = false
47 | let allowedContentTypes: [UTType]
48 | let configuration: PHPickerConfiguration
49 | let onCompletion: (Result<[PHPickerResult], Error>) -> Void
50 | let makeLoadingOverlay: () -> LoadingOverlay
51 |
52 | var body: some View {
53 | MediaPicker(
54 | isPresented: $isPresented,
55 | isLoading: $isLoading,
56 | allowedContentTypes: allowedContentTypes,
57 | configuration: configuration,
58 | onCompletion: onCompletion
59 | )
60 | .overlay(isLoading ? makeLoadingOverlay() : nil)
61 | }
62 | }
63 |
64 | fileprivate extension PHPickerFilter {
65 | static func from(_ mediaOptions: MediaTypeOptions) -> Self {
66 | var filters = [PHPickerFilter]()
67 | if mediaOptions.contains(.images) {
68 | filters.append(.images)
69 | } else if mediaOptions.contains(.livePhotos) {
70 | filters.append(.livePhotos)
71 | }
72 | if mediaOptions.contains(.videos) {
73 | filters.append(.videos)
74 | }
75 | return PHPickerFilter.any(of: filters)
76 | }
77 | }
78 |
79 | // Meet the new Photos picker
80 | // https://developer.apple.com/wwdc20/10652
81 | fileprivate struct MediaPicker: UIViewControllerRepresentable {
82 | @Binding var isPresented: Bool
83 | @Binding var isLoading: Bool
84 | let allowedContentTypes: [UTType]
85 | let configuration: PHPickerConfiguration
86 | let onCompletion: (Result<[PHPickerResult], Error>) -> Void
87 |
88 | func makeUIViewController(context: Context) -> PHPickerViewController {
89 | let controller = PHPickerViewController(configuration: configuration)
90 | controller.delegate = context.coordinator
91 | return controller
92 | }
93 |
94 | func updateUIViewController(_ uiViewController: PHPickerViewController,
95 | context: Context) {
96 | // do nothing
97 | }
98 |
99 | func makeCoordinator() -> Coordinator {
100 | return Coordinator(for: self)
101 | }
102 |
103 | class Coordinator: PHPickerViewControllerDelegate {
104 | let coordinated: MediaPicker
105 |
106 | init(for picker: MediaPicker) {
107 | self.coordinated = picker
108 | }
109 |
110 | func picker(_ picker: PHPickerViewController,
111 | didFinishPicking results: [PHPickerResult]) {
112 | guard !results.isEmpty else {
113 | coordinated.isPresented = false
114 | return
115 | }
116 | Task { @MainActor in
117 | withAnimation {
118 | coordinated.isLoading = true
119 | }
120 | }
121 | coordinated.onCompletion(.success(results))
122 | }
123 | }
124 | }
125 | #endif
126 |
--------------------------------------------------------------------------------
/Sources/MediaPicker/MediaPicker-iOS.swift:
--------------------------------------------------------------------------------
1 | //===----------------------------------------------------------------------===//
2 | //
3 | // This source file is part of the SwiftUI FoodTracker tutorial series
4 | //
5 | // Copyright (c) 2020-2022 AppDev@UW.edu and the SwiftUI MediaPicker authors
6 | // Licensed under MIT License
7 | //
8 | // See https://github.com/UWAppDev/SwiftUI-MediaPicker/blob/main/LICENSE
9 | // for license information
10 | // See https://github.com/UWAppDev/SwiftUI-MediaPicker/graphs/contributors
11 | // for the list of SwiftUI MediaPicker project authors
12 | //
13 | //===----------------------------------------------------------------------===//
14 |
15 | #if os(iOS)
16 | import SwiftUI
17 | import struct PhotosUI.PHPickerResult
18 | @_implementationOnly import UniformTypeIdentifiers
19 | @_implementationOnly import struct AVFoundation.AVError
20 | @_implementationOnly import os
21 |
22 | private func OSLog(category: String) -> os.OSLog {
23 | #if DEBUG
24 | return OSLog(subsystem: "dev.uwapp.MediaPicker", category: category)
25 | #else
26 | return .disabled
27 | #endif
28 | }
29 |
30 | public extension View {
31 | /// Presents a system interface for allowing the user to import an existing
32 | /// media.
33 | ///
34 | /// In order for the interface to appear, `isPresented` must be `true`. When
35 | /// the operation is finished, `isPresented` will be set to `false` before
36 | /// `onCompletion` is called. If the user cancels the operation,
37 | /// `isPresented` will be set to `false` and `onCompletion` will not be
38 | /// called.
39 | ///
40 | /// - Note: Changing `allowedMediaTypes` while the media importer is
41 | /// presented will have no immediate effect, however will apply the next
42 | /// time it is presented.
43 | ///
44 | /// - Parameters:
45 | /// - isPresented: A binding to whether the interface should be shown.
46 | /// - allowedMediaTypes: The list of supported media types which can
47 | /// be imported.
48 | /// - onCompletion: A callback that will be invoked when the operation has
49 | /// succeeded or failed.
50 | /// - result: A `Result` indicating whether the operation succeeded or
51 | /// failed.
52 | func mediaImporter(
53 | isPresented: Binding,
54 | allowedMediaTypes: MediaTypeOptions,
55 | onCompletion: @escaping (Result) -> Void
56 | ) -> some View {
57 | self.mediaImporter(
58 | isPresented: isPresented,
59 | allowedMediaTypes: allowedMediaTypes,
60 | onCompletion: onCompletion,
61 | loadingOverlay: DefaultLoadingOverlay.init
62 | )
63 | }
64 |
65 | func mediaImporter(
66 | isPresented: Binding,
67 | allowedMediaTypes: MediaTypeOptions,
68 | onCompletion: @escaping (Result) -> Void,
69 | @ViewBuilder loadingOverlay: @escaping (Progress) -> LoadingOverlay
70 | ) -> some View {
71 | self.mediaImporter(
72 | isPresented: isPresented,
73 | allowedMediaTypes: allowedMediaTypes,
74 | allowsMultipleSelection: false,
75 | onCompletion: { result in
76 | onCompletion(result.map { $0.first! })
77 | },
78 | loadingOverlay: loadingOverlay
79 | )
80 | }
81 |
82 | /// Presents a system interface for allowing the user to import multiple
83 | /// medium.
84 | ///
85 | /// In order for the interface to appear, `isPresented` must be `true`. When
86 | /// the operation is finished, `isPresented` will be set to `false` before
87 | /// `onCompletion` is called. If the user cancels the operation,
88 | /// `isPresented` will be set to `false` and `onCompletion` will not be
89 | /// called.
90 | ///
91 | /// - Note: Changing `allowedMediaTypes` or `allowsMultipleSelection`
92 | /// while the media importer is presented will have no immediate effect,
93 | /// however will apply the next time it is presented.
94 | ///
95 | /// - Parameters:
96 | /// - isPresented: A binding to whether the interface should be shown.
97 | /// - allowedMediaTypes: The list of supported media types which can
98 | /// be imported.
99 | /// - allowsMultipleSelection: Whether the importer allows the user to
100 | /// select more than one media to import.
101 | /// - onCompletion: A callback that will be invoked when the operation has
102 | /// succeeded or failed.
103 | /// - result: A `Result` indicating whether the operation succeeded or
104 | /// failed.
105 | func mediaImporter(
106 | isPresented: Binding,
107 | allowedMediaTypes: MediaTypeOptions,
108 | allowsMultipleSelection: Bool,
109 | onCompletion: @escaping (Result<[URL], Error>) -> Void
110 | ) -> some View {
111 | self.mediaImporter(
112 | isPresented: isPresented,
113 | allowedMediaTypes: allowedMediaTypes,
114 | allowsMultipleSelection: allowsMultipleSelection,
115 | onCompletion: onCompletion,
116 | loadingOverlay: DefaultLoadingOverlay.init
117 | )
118 | }
119 |
120 | func mediaImporter(
121 | isPresented: Binding,
122 | allowedMediaTypes: MediaTypeOptions,
123 | allowsMultipleSelection: Bool,
124 | onCompletion: @escaping (Result<[URL], Error>) -> Void,
125 | @ViewBuilder loadingOverlay: @escaping (Progress) -> LoadingOverlay
126 | ) -> some View {
127 | let progress = Progress()
128 | return self.mediaImporter(
129 | isPresented: isPresented,
130 | allowedMediaTypes: allowedMediaTypes,
131 | allowsMultipleSelection: allowsMultipleSelection
132 | ) { (result: Result<[PHPickerResult], Error>) in
133 | switch result {
134 | case .success(let phPickerResults):
135 | importAsURLs(phPickerResults,
136 | allowedMediaTypes: allowedMediaTypes,
137 | progress: progress) { result in
138 | isPresented.wrappedValue = false
139 | onCompletion(result)
140 | }
141 | case .failure(let error):
142 | isPresented.wrappedValue = false
143 | onCompletion(.failure(error))
144 | }
145 | } loadingOverlay: {
146 | loadingOverlay(progress)
147 | }
148 | }
149 | }
150 |
151 | fileprivate struct DefaultLoadingOverlay: View {
152 | let progress: Progress
153 | var body: some View {
154 | NavigationView {
155 | ProgressView(progress)
156 | .padding()
157 | .navigationTitle("Importing Media...")
158 | }
159 | .transition(.move(edge: .bottom).combined(with: .opacity))
160 | }
161 | }
162 |
163 | fileprivate func importAsURLs(_ phPickerResults: [PHPickerResult],
164 | allowedMediaTypes: MediaTypeOptions,
165 | progress: Progress,
166 | onCompletion: @escaping (Result<[URL], Error>) -> Void) {
167 | let log = OSLog(category: "imageURLs")
168 | let signpostID = OSSignpostID(log: log, object: phPickerResults as NSArray)
169 | os_signpost(.begin, log: log, name: "imageURLs task group", signpostID: signpostID,
170 | "Loading %d results", phPickerResults.count)
171 |
172 | var imageURLs = [URL?](repeating: nil, count: phPickerResults.count)
173 | var errors = [Error]()
174 | var finishedCount = 0
175 | let queue = DispatchQueue(label: UUID().uuidString)
176 | progress.totalUnitCount = Int64(phPickerResults.count)
177 | progress.completedUnitCount = 0
178 |
179 | func recordResult(_ result: Result, for index: Int) {
180 | queue.sync {
181 | finishedCount += 1
182 |
183 | switch result {
184 | case .success(let url):
185 | os_signpost(.event, log: log, name: "imageURLs add url", signpostID: signpostID,
186 | "Adding %d out of %d results, url: %{public}@",
187 | finishedCount, phPickerResults.count, url.path)
188 | imageURLs[index] = url
189 | case .failure(let error):
190 | os_signpost(.event, log: log, name: "imageURLs add url", signpostID: signpostID,
191 | "Adding %d out of %d results, error: %{public}@",
192 | finishedCount, phPickerResults.count, error.localizedDescription)
193 | errors.append(error)
194 | }
195 |
196 | guard finishedCount == phPickerResults.count else {
197 | return
198 | }
199 |
200 | let resultImageURLs = imageURLs.compactMap { $0 }
201 | if errors.isEmpty {
202 | os_signpost(.end, log: log, name: "imageURLs task group", signpostID: signpostID,
203 | "success")
204 | onCompletion(.success(resultImageURLs))
205 | } else {
206 | os_signpost(.end, log: log, name: "imageURLs task group", signpostID: signpostID,
207 | "errored: %d", errors.count)
208 | onCompletion(.failure(MediaPickerErrors.imageURL(resultImageURLs, errors: errors)))
209 | }
210 | }
211 | }
212 |
213 | os_signpost(.begin, log: log, name: "imageURLs add task", signpostID: signpostID,
214 | "Adding %d tasks", phPickerResults.count)
215 | pickerResultsLoop:
216 | for (index, result) in phPickerResults.enumerated() {
217 | let provider = result.itemProvider
218 | // TOOD: investigate should we instead use/consider
219 | // provider.registeredTypeIdentifiers
220 | for type in allowedMediaTypes.typeIdentifiers {
221 | if provider.hasItemConformingToTypeIdentifier(type.identifier) {
222 | os_signpost(.event, log: log, name: "imageURLs add task", signpostID: signpostID,
223 | "Adding %d out of %d tasks: '%{public}@' of type %{public}@",
224 | index + 1, phPickerResults.count, provider.suggestedName ?? "", type.identifier)
225 |
226 | let signpostID = OSSignpostID(log: log, object: provider)
227 | os_signpost(.begin, log: log, name: "fileURL loadFileRepresentation", signpostID: signpostID,
228 | "%{public}@", provider.suggestedName ?? "")
229 | // https://developer.apple.com/forums/thread/652496
230 | let loadingProgress = provider.loadFileRepresentation(forTypeIdentifier: type.identifier) { url, error in
231 | guard let src = url else {
232 | os_signpost(.end, log: log, name: "fileURL loadFileRepresentation", signpostID: signpostID,
233 | "errored, no src url")
234 | return recordResult(.failure(error!), for: index)
235 | }
236 | os_signpost(.end, log: log, name: "fileURL loadFileRepresentation", signpostID: signpostID)
237 |
238 | // Because the src/url will be deleted once we return,
239 | // will copy the stored image to a different temp url.
240 | let dst = FileManager.default.temporaryDirectory
241 | .appendingPathComponent(src.lastPathComponent)
242 | os_signpost(.begin, log: log, name: "fileURL copy", signpostID: signpostID,
243 | "fileURL copy from %@ to %@", src.path, dst.path)
244 | if FileManager.default.fileExists(atPath: dst.path) {
245 | os_signpost(.end, log: log, name: "fileURL copy", signpostID: signpostID,
246 | "already exists")
247 | return recordResult(.success(dst), for: index)
248 | } else {
249 | do {
250 | try FileManager.default.copyItem(at: src, to: dst)
251 | os_signpost(.end, log: log, name: "fileURL copy", signpostID: signpostID,
252 | "copied")
253 | return recordResult(.success(dst), for: index)
254 | } catch {
255 | os_signpost(.end, log: log, name: "fileURL copy", signpostID: signpostID,
256 | "errored: %{public}d", error.localizedDescription)
257 | return recordResult(.failure(error), for: index)
258 | }
259 | }
260 | }
261 | progress.addChild(loadingProgress, withPendingUnitCount: 1)
262 | continue pickerResultsLoop
263 | }
264 | }
265 | os_signpost(.event, log: log, name: "imageURLs add task", signpostID: signpostID,
266 | "Adding %d out of %d tasks: '%{public}@' can't be loaded, only has %{public}@",
267 | index + 1, phPickerResults.count, provider.suggestedName ?? "", provider.registeredTypeIdentifiers)
268 | recordResult(.failure(MediaPickerErrors.missingFileRepresentation), for: index)
269 | }
270 | os_signpost(.end, log: log, name: "imageURLs add task", signpostID: signpostID)
271 | }
272 | #endif
273 |
--------------------------------------------------------------------------------
/Sources/MediaPicker/MediaPicker-macOS.swift:
--------------------------------------------------------------------------------
1 | //===----------------------------------------------------------------------===//
2 | //
3 | // This source file is part of the SwiftUI FoodTracker tutorial series
4 | //
5 | // Copyright (c) 2020-2022 AppDev@UW.edu and the SwiftUI MediaPicker authors
6 | // Licensed under MIT License
7 | //
8 | // See https://github.com/UWAppDev/SwiftUI-MediaPicker/blob/main/LICENSE
9 | // for license information
10 | // See https://github.com/UWAppDev/SwiftUI-MediaPicker/graphs/contributors
11 | // for the list of SwiftUI MediaPicker project authors
12 | //
13 | //===----------------------------------------------------------------------===//
14 |
15 | #if os(macOS)
16 | import SwiftUI
17 |
18 | public extension View {
19 | /// Presents a system interface for allowing the user to import an existing
20 | /// media.
21 | ///
22 | /// In order for the interface to appear, `isPresented` must be `true`. When
23 | /// the operation is finished, `isPresented` will be set to `false` before
24 | /// `onCompletion` is called. If the user cancels the operation,
25 | /// `isPresented` will be set to `false` and `onCompletion` will not be
26 | /// called.
27 | ///
28 | /// - Note: Changing `allowedMediaTypes` while the media importer is
29 | /// presented will have no immediate effect, however will apply the next
30 | /// time it is presented.
31 | ///
32 | /// - Parameters:
33 | /// - isPresented: A binding to whether the interface should be shown.
34 | /// - allowedMediaTypes: The list of supported media types which can
35 | /// be imported.
36 | /// - onCompletion: A callback that will be invoked when the operation has
37 | /// succeeded or failed.
38 | /// - result: A `Result` indicating whether the operation succeeded or
39 | /// failed.
40 | func mediaImporter(
41 | isPresented: Binding,
42 | allowedMediaTypes: MediaTypeOptions,
43 | onCompletion: @escaping (Result) -> Void
44 | ) -> some View {
45 | self.fileImporter(isPresented: isPresented,
46 | allowedContentTypes: allowedMediaTypes.typeIdentifiers,
47 | onCompletion: onCompletion)
48 | }
49 |
50 | /// Presents a system interface for allowing the user to import multiple
51 | /// medium.
52 | ///
53 | /// In order for the interface to appear, `isPresented` must be `true`. When
54 | /// the operation is finished, `isPresented` will be set to `false` before
55 | /// `onCompletion` is called. If the user cancels the operation,
56 | /// `isPresented` will be set to `false` and `onCompletion` will not be
57 | /// called.
58 | ///
59 | /// - Note: Changing `allowedMediaTypes` or `allowsMultipleSelection`
60 | /// while the media importer is presented will have no immediate effect,
61 | /// however will apply the next time it is presented.
62 | ///
63 | /// - Parameters:
64 | /// - isPresented: A binding to whether the interface should be shown.
65 | /// - allowedMediaTypes: The list of supported media types which can
66 | /// be imported.
67 | /// - allowsMultipleSelection: Whether the importer allows the user to
68 | /// select more than one media to import.
69 | /// - onCompletion: A callback that will be invoked when the operation has
70 | /// succeeded or failed.
71 | /// - result: A `Result` indicating whether the operation succeeded or
72 | /// failed.
73 | func mediaImporter(
74 | isPresented: Binding,
75 | allowedMediaTypes: MediaTypeOptions,
76 | allowsMultipleSelection: Bool,
77 | onCompletion: @escaping (Result<[URL], Error>) -> Void
78 | ) -> some View {
79 | self.fileImporter(isPresented: isPresented,
80 | allowedContentTypes: allowedMediaTypes.typeIdentifiers,
81 | allowsMultipleSelection: allowsMultipleSelection,
82 | onCompletion: onCompletion)
83 | }
84 | }
85 | #endif
86 |
--------------------------------------------------------------------------------
/Sources/MediaPicker/MediaPickerErrors.swift:
--------------------------------------------------------------------------------
1 | //===----------------------------------------------------------------------===//
2 | //
3 | // This source file is part of the SwiftUI FoodTracker tutorial series
4 | //
5 | // Copyright (c) 2020-2022 AppDev@UW.edu and the SwiftUI MediaPicker authors
6 | // Licensed under MIT License
7 | //
8 | // See https://github.com/UWAppDev/SwiftUI-MediaPicker/blob/main/LICENSE
9 | // for license information
10 | // See https://github.com/UWAppDev/SwiftUI-MediaPicker/graphs/contributors
11 | // for the list of SwiftUI MediaPicker project authors
12 | //
13 | //===----------------------------------------------------------------------===//
14 |
15 | import Foundation
16 |
17 | public enum MediaPickerErrors: Error {
18 | case imageURL([URL], errors: [Error])
19 | case missingFileRepresentation
20 | }
21 |
--------------------------------------------------------------------------------
/Tests/MediaPickerTests/MediaPickerTests.swift:
--------------------------------------------------------------------------------
1 | //===----------------------------------------------------------------------===//
2 | //
3 | // This source file is part of the SwiftUI FoodTracker tutorial series
4 | //
5 | // Copyright (c) 2020-2022 AppDev@UW.edu and the SwiftUI MediaPicker authors
6 | // Licensed under MIT License
7 | //
8 | // See https://github.com/UWAppDev/SwiftUI-MediaPicker/blob/main/LICENSE
9 | // for license information
10 | // See https://github.com/UWAppDev/SwiftUI-MediaPicker/graphs/contributors
11 | // for the list of SwiftUI MediaPicker project authors
12 | //
13 | //===----------------------------------------------------------------------===//
14 |
15 | import XCTest
16 | @testable import MediaPicker
17 |
18 | final class MediaPickerTests: XCTestCase {
19 | }
20 |
--------------------------------------------------------------------------------