Introduces limited iOS 13 support – wip

This commit is contained in:
Shaps Benkau 2023-01-30 14:08:59 +00:00
parent 19a7cf0b27
commit 0ad99191d9
5 changed files with 563 additions and 143 deletions

View File

@ -29,7 +29,6 @@ import ObjectiveC
/// }
///
public struct Backport<Wrapped> {
/// The underlying content this backport represents.
public let content: Wrapped
@ -38,7 +37,12 @@ public struct Backport<Wrapped> {
public init(_ content: Wrapped) {
self.content = content
}
}
public extension Backport where Wrapped == Any {
init(_ content: Wrapped) {
self.content = content
}
}
public extension View {

View File

@ -165,7 +165,6 @@ struct PasteButtonStyle: PrimitiveButtonStyle {
}
}
}
// .opacity(configuration.isPressed ? 0.5 : 1)
}
}
#endif

View File

@ -14,6 +14,9 @@ internal extension View {
library: PHPhotoLibrary
) -> some View {
if #available(iOS 14, *) {
sheet(isPresented: isPresented) {
}
// var config = PHPickerConfiguration(photoLibrary: library)
// config.preferredAssetRepresentationMode = preferredItemEncoding.mode
// config.selectionLimit = maxSelectionCount ?? 0
@ -29,13 +32,15 @@ internal extension View {
// configuration: config
// )
} else {
EmptyView()
backport.background {
LegacyPhotosViewController(isPresented: isPresented, selection: selection, filter: filter)
}
}
}
}
@available(iOS 14, *)
private struct PickerViewController: UIViewControllerRepresentable {
private struct PhotosViewController: UIViewControllerRepresentable {
@Binding var isPresented: Bool
@Binding var selection: [Backport<Any>.PhotosPickerItem]
@ -57,7 +62,7 @@ private struct PickerViewController: UIViewControllerRepresentable {
}
@available(iOS 14, *)
private extension PickerViewController {
private extension PhotosViewController {
final class Representable: UIViewController, PHPickerViewControllerDelegate, UIAdaptivePresentationControllerDelegate {
private weak var controller: PHPickerViewController?
@ -105,17 +110,121 @@ private extension PickerViewController {
}
private func dismissController() {
isPresented.wrappedValue = false
guard let controller else { return }
controller.presentedViewController?.dismiss(animated: true)
}
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
print(results)
dismissController()
}
func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
dismissController()
}
}
}
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
print(results)
#warning("TBD")
@available(iOS 13, *)
private struct LegacyPhotosViewController: UIViewControllerRepresentable {
@Binding var isPresented: Bool
@Binding var selection: [Backport<Any>.PhotosPickerItem]
let filter: Backport<Any>.PHPickerFilter?
func makeUIViewController(context: Context) -> Representable {
Representable(
isPresented: $isPresented,
selection: $selection,
filter: filter
)
}
func updateUIViewController(_ controller: Representable, context: Context) {
controller.selection = $selection
controller.filter = filter
}
}
@available(iOS 13, *)
private extension LegacyPhotosViewController {
final class Representable: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate, UIAdaptivePresentationControllerDelegate {
private weak var controller: UIImagePickerController?
var isPresented: Binding<Bool> {
didSet {
updateControllerLifecycle(
from: oldValue.wrappedValue,
to: isPresented.wrappedValue
)
}
}
var selection: Binding<[Backport<Any>.PhotosPickerItem]>
var filter: Backport<Any>.PHPickerFilter?
init(isPresented: Binding<Bool>, selection: Binding<[Backport<Any>.PhotosPickerItem]>, filter: Backport<Any>.PHPickerFilter?) {
self.isPresented = isPresented
self.selection = selection
self.filter = filter
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func updateControllerLifecycle(from oldValue: Bool, to newValue: Bool) {
switch (oldValue, newValue) {
case (false, true):
presentController()
case (true, false):
dismissController()
case (true, true), (false, false):
break
}
}
private func presentController() {
let controller = UIImagePickerController()
if let filter {
controller.mediaTypes = filter.mediaTypes
} else if let types = UIImagePickerController.availableMediaTypes(for: .photoLibrary) {
controller.mediaTypes = types
}
controller.allowsEditing = false
controller.sourceType = .photoLibrary
controller.videoQuality = .typeHigh
controller.presentationController?.delegate = self
controller.delegate = self
present(controller, animated: true)
self.controller = controller
}
private func dismissController() {
isPresented.wrappedValue = false
guard let controller else { return }
controller.presentedViewController?.dismiss(animated: true)
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
print("TBD")
print(info)
dismissController()
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
dismissController()
}
func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
dismissController()
}
}
}

View File

@ -2,22 +2,22 @@
import SwiftUI
import PhotosUI
@available(iOS, introduced: 14, deprecated: 16)
public extension Backport where Wrapped == Any {
// Available when SwiftUI is imported with PhotosUI
@available(iOS, introduced: 13, deprecated: 16)
public extension Backport<Any> {
/// Available when SwiftUI is imported with PhotosUI
/// A control that allows a user to choose photos and/or videos from the photo library.
///
/// The user explicitly grants access only to items they choose, so photo library access authorization is not needed.
struct PhotosPicker<Label>: View where Label: View {
@State private var isPresented: Bool = false
@Binding var selection: [Backport<Any>.PhotosPickerItem]
@Binding var selection: [Backport.PhotosPickerItem]
private let filter: Backport<Any>.PHPickerFilter?
private let maxSelection: Int?
private let selectionBehavior: Backport<Any>.PhotosPickerSelectionBehavior
private let encoding: Backport<Any>.PhotosPickerItem.EncodingDisambiguationPolicy
private let library: PHPh otoLibrary
private let label: Label
let filter: Backport.PHPickerFilter?
let maxSelection: Int?
let selectionBehavior: Backport.PhotosPickerSelectionBehavior
let encoding: Backport.PhotosPickerItem.EncodingDisambiguationPolicy
let library: PHPhotoLibrary
let label: Label
public var body: some View {
Button {
@ -38,24 +38,11 @@ public extension Backport where Wrapped == Any {
}
}
@available(iOS 14, *)
public extension Backport.PhotosPicker where Wrapped == Any {
/// Creates a Photos picker that selects a `PhotosPickerItem` from a given photo library.
///
/// The user explicitly grants access only to items they choose, so photo library access authorization is not needed.
///
/// - Parameters:
/// - selection: The item being shown and selected in the Photos picker.
/// - filter: Types of items that can be shown. Default is `nil`. Setting it to `nil` means all supported types can be shown.
/// - preferredItemEncoding: The encoding disambiguation policy of the selected item. Default is `.automatic`. Setting it to `.automatic` means the best encoding determined by the system will be used.
/// - photoLibrary: The photo library to choose from.
/// - label: The view that describes the action of choosing an item from the photo library.
@available(iOS, introduced: 13, deprecated: 14)
public extension Backport<Any>.PhotosPicker {
init(
selection: Binding<Backport<Any>.PhotosPickerItem?>,
maxSelectionCount: Int? = nil,
matching filter: Backport<Any>.PHPickerFilter? = nil,
preferredItemEncoding: Backport<Any>.PhotosPickerItem.EncodingDisambiguationPolicy = .automatic,
photoLibrary: PHPhotoLibrary = .shared(),
selection: Binding<Backport.PhotosPickerItem?>,
matching filter: Backport.PHPickerFilter? = nil,
@ViewBuilder label: () -> Label
) {
_selection = .init(
@ -67,32 +54,20 @@ public extension Backport.PhotosPicker where Wrapped == Any {
}
)
self.filter = filter
self.maxSelection = maxSelectionCount
self.maxSelection = 1
self.selectionBehavior = .default
self.encoding = preferredItemEncoding
self.library = photoLibrary
self.encoding = .automatic
self.library = .shared()
self.label = label()
}
}
@available(iOS 14, *)
public extension Backport.PhotosPicker where Wrapped == Any, Label == Text {
/// Creates a Photos picker with its label generated from a localized string key that selects a `PhotosPickerItem`.
///
/// The user explicitly grants access only to items they choose, so photo library access authorization is not needed.
///
/// - Parameters:
/// - titleKey: A localized string key that describes the purpose of showing the picker.
/// - selection: The item being shown and selected in the Photos picker.
/// - filter: Types of items that can be shown. Default is `nil`. Setting it to `nil` means all supported types can be shown.
/// - preferredItemEncoding: The encoding disambiguation policy of the selected item. Default is `.automatic`. Setting it to `.automatic` means the best encoding determined by the system will be used.
@available(iOS, introduced: 13, deprecated: 14)
public extension Backport<Any>.PhotosPicker<Text> {
init(
_ title: String,
selection: Binding<Backport<Any>.PhotosPickerItem?>,
maxSelectionCount: Int? = nil,
matching filter: Backport<Any>.PHPickerFilter? = nil,
preferredItemEncoding: Backport<Any>.PhotosPickerItem.EncodingDisambiguationPolicy = .automatic,
photoLibrary: PHPhotoLibrary = .shared()
_ titleKey: LocalizedStringKey,
selection: Binding<Backport.PhotosPickerItem?>,
matching filter: Backport.PHPickerFilter? = nil
) {
_selection = .init(
get: {
@ -103,16 +78,267 @@ public extension Backport.PhotosPicker where Wrapped == Any, Label == Text {
}
)
self.filter = filter
self.maxSelection = maxSelectionCount
self.maxSelection = 1
self.selectionBehavior = .default
self.encoding = preferredItemEncoding
self.library = photoLibrary
label = Text(title)
self.encoding = .automatic
self.library = .shared()
self.label = Text(titleKey)
}
init<S>(
_ title: S,
selection: Binding<Backport.PhotosPickerItem?>,
matching filter: Backport.PHPickerFilter? = nil
) where S: StringProtocol {
_selection = .init(
get: {
[selection.wrappedValue].compactMap { $0 }
},
set: { newValue in
selection.wrappedValue = newValue.first
}
)
self.filter = filter
self.maxSelection = 1
self.selectionBehavior = .default
self.encoding = .automatic
self.library = .shared()
self.label = Text(title)
}
}
@available(iOS 14, *)
public extension Backport<Any>.PhotosPicker {
/// Creates a Photos picker that selects a `PhotosPickerItem`.
///
/// The user explicitly grants access only to items they choose, so photo library access authorization is not needed.
///
/// - Parameters:
/// - selection: The item being shown and selected in the Photos picker.
/// - filter: Types of items that can be shown. Default is `nil`. Setting it to `nil` means all supported types can be shown.
/// - preferredItemEncoding: The encoding disambiguation policy of the selected item. Default is `.automatic`. Setting it to `.automatic` means the best encoding determined by the system will be used.
/// - label: The view that describes the action of choosing an item from the photo library.
init(
selection: Binding<Backport.PhotosPickerItem?>,
matching filter: Backport.PHPickerFilter? = nil,
preferredItemEncoding: Backport.PhotosPickerItem.EncodingDisambiguationPolicy = .automatic,
@ViewBuilder label: () -> Label
) {
_selection = .init(
get: {
[selection.wrappedValue].compactMap { $0 }
},
set: { newValue in
selection.wrappedValue = newValue.first
}
)
self.filter = filter
self.maxSelection = 1
self.selectionBehavior = .default
self.encoding = preferredItemEncoding
self.library = .shared()
self.label = label()
}
/// Creates a Photos picker that selects a `PhotosPickerItem` from a given photo library.
///
/// The user explicitly grants access only to items they choose, so photo library access authorization is not needed.
///
/// - Parameters:
/// - selection: The item being shown and selected in the Photos picker.
/// - filter: Types of items that can be shown. Default is `nil`. Setting it to `nil` means all supported types can be shown.
/// - preferredItemEncoding: The encoding disambiguation policy of the selected item. Default is `.automatic`. Setting it to `.automatic` means the best encoding determined by the system will be used.
/// - photoLibrary: The photo library to choose from.
/// - label: The view that describes the action of choosing an item from the photo library.
init(
selection: Binding<Backport.PhotosPickerItem?>,
matching filter: Backport.PHPickerFilter? = nil,
preferredItemEncoding: Backport.PhotosPickerItem.EncodingDisambiguationPolicy = .automatic,
photoLibrary: PHPhotoLibrary,
@ViewBuilder label: () -> Label
) {
_selection = .init(
get: {
[selection.wrappedValue].compactMap { $0 }
},
set: { newValue in
selection.wrappedValue = newValue.first
}
)
self.filter = filter
self.maxSelection = 1
self.selectionBehavior = .default
self.encoding = preferredItemEncoding
self.library = photoLibrary
self.label = label()
}
}
// MARK: Single selection
@available(iOS 14, *)
public extension Backport<Any>.PhotosPicker<Text> {
/// Creates a Photos picker with its label generated from a localized string key that selects a `PhotosPickerItem`.
///
/// The user explicitly grants access only to items they choose, so photo library access authorization is not needed.
///
/// - Parameters:
/// - titleKey: A localized string key that describes the purpose of showing the picker.
/// - selection: The item being shown and selected in the Photos picker.
/// - filter: Types of items that can be shown. Default is `nil`. Setting it to `nil` means all supported types can be shown.
/// - preferredItemEncoding: The encoding disambiguation policy of the selected item. Default is `.automatic`. Setting it to `.automatic` means the best encoding determined by the system will be used.
init(
_ titleKey: LocalizedStringKey,
selection: Binding<Backport.PhotosPickerItem?>,
matching filter: Backport.PHPickerFilter? = nil,
preferredItemEncoding: Backport.PhotosPickerItem.EncodingDisambiguationPolicy = .automatic
) {
_selection = .init(
get: {
[selection.wrappedValue].compactMap { $0 }
},
set: { newValue in
selection.wrappedValue = newValue.first
}
)
self.filter = filter
self.maxSelection = 1
self.selectionBehavior = .default
self.encoding = preferredItemEncoding
self.library = .shared()
self.label = Text(titleKey)
}
/// Creates a Photos picker with its label generated from a string that selects a `PhotosPickerItem`.
///
/// The user explicitly grants access only to items they choose, so photo library access authorization is not needed.
///
/// - Parameters:
/// - title: A string that describes the purpose of showing the picker.
/// - selection: The item being shown and selected in the Photos picker.
/// - filter: Types of items that can be shown. Default is `nil`. Setting it to `nil` means all supported types can be shown.
/// - preferredItemEncoding: The encoding disambiguation policy of the selected item. Default is `.automatic`. Setting it to `.automatic` means the best encoding determined by the system will be used.
init<S>(
_ title: S,
selection: Binding<Backport.PhotosPickerItem?>,
matching filter: Backport.PHPickerFilter? = nil,
preferredItemEncoding: Backport.PhotosPickerItem.EncodingDisambiguationPolicy = .automatic
) where S: StringProtocol {
_selection = .init(
get: {
[selection.wrappedValue].compactMap { $0 }
},
set: { newValue in
selection.wrappedValue = newValue.first
}
)
self.filter = filter
self.maxSelection = 1
self.selectionBehavior = .default
self.encoding = preferredItemEncoding
self.library = .shared()
self.label = Text(title)
}
/// Creates a Photos picker with its label generated from a localized string key that selects a `PhotosPickerItem` from a given photo library.
///
/// The user explicitly grants access only to items they choose, so photo library access authorization is not needed.
///
/// - Parameters:
/// - titleKey: A localized string key that describes the purpose of showing the picker.
/// - selection: The item being shown and selected in the Photos picker.
/// - filter: Types of items that can be shown. Default is `nil`. Setting it to `nil` means all supported types can be shown.
/// - preferredItemEncoding: The encoding disambiguation policy of the selected item. Default is `.automatic`. Setting it to `.automatic` means the best encoding determined by the system will be used.
/// - photoLibrary: The photo library to choose from.
init(
_ titleKey: LocalizedStringKey,
selection: Binding<Backport.PhotosPickerItem?>,
matching filter: Backport.PHPickerFilter? = nil,
preferredItemEncoding: Backport.PhotosPickerItem.EncodingDisambiguationPolicy = .automatic,
photoLibrary: PHPhotoLibrary
) {
_selection = .init(
get: {
[selection.wrappedValue].compactMap { $0 }
},
set: { newValue in
selection.wrappedValue = newValue.first
}
)
self.filter = filter
self.maxSelection = 1
self.selectionBehavior = .default
self.encoding = preferredItemEncoding
self.library = photoLibrary
self.label = Text(titleKey)
}
/// Creates a Photos picker with its label generated from a string that selects a `PhotosPickerItem` from a given photo library.
///
/// The user explicitly grants access only to items they choose, so photo library access authorization is not needed.
///
/// - Parameters:
/// - title: A string that describes the purpose of showing the picker.
/// - selection: The item being shown and selected in the Photos picker.
/// - filter: Types of items that can be shown. Default is `nil`. Setting it to `nil` means all supported types can be shown.
/// - preferredItemEncoding: The encoding disambiguation policy of the selected item. Default is `.automatic`. Setting it to `.automatic` means the best encoding determined by the system will be used.
/// - photoLibrary: The photo library to choose from.
init<S>(
_ title: S,
selection: Binding<Backport.PhotosPickerItem?>,
matching filter: Backport.PHPickerFilter? = nil,
preferredItemEncoding: Backport.PhotosPickerItem.EncodingDisambiguationPolicy = .automatic,
photoLibrary: PHPhotoLibrary
) where S: StringProtocol {
_selection = .init(
get: {
[selection.wrappedValue].compactMap { $0 }
},
set: { newValue in
selection.wrappedValue = newValue.first
}
)
self.filter = filter
self.maxSelection = 1
self.selectionBehavior = .default
self.encoding = preferredItemEncoding
self.library = photoLibrary
self.label = Text(title)
}
}
// MARK: Multiple selection
@available(iOS 15, *)
public extension Backport.PhotosPicker where Wrapped == Any {
public extension Backport<Any>.PhotosPicker {
/// Creates a Photos picker that selects a collection of `PhotosPickerItem`.
///
/// The user explicitly grants access only to items they choose, so photo library access authorization is not needed.
///
/// - Parameters:
/// - selection: All items being shown and selected in the Photos picker.
/// - maxSelectionCount: The maximum number of items that can be selected. Default is `nil`. Setting it to `nil` means maximum supported by the system.
/// - selectionBehavior: The selection behavior of the Photos picker. Default is `.default`.
/// - filter: Types of items that can be shown. Default is `nil`. Setting it to `nil` means all supported types can be shown.
/// - preferredItemEncoding: The encoding disambiguation policy of selected items. Default is `.automatic`. Setting it to `.automatic` means the best encoding determined by the system will be used.
/// - label: The view that describes the action of choosing items from the photo library.
init(
selection: Binding<[Backport.PhotosPickerItem]>,
maxSelectionCount: Int? = nil,
selectionBehavior: Backport.PhotosPickerSelectionBehavior = .default,
matching filter: Backport.PHPickerFilter? = nil,
preferredItemEncoding: Backport.PhotosPickerItem.EncodingDisambiguationPolicy = .automatic,
@ViewBuilder label: () -> Label
) {
_selection = selection
self.filter = filter
self.maxSelection = maxSelectionCount
self.selectionBehavior = selectionBehavior
self.encoding = preferredItemEncoding
self.library = .shared()
self.label = label()
}
/// Creates a Photos picker that selects a collection of `PhotosPickerItem` from a given photo library.
///
/// The user explicitly grants access only to items they choose, so photo library access authorization is not needed.
@ -126,12 +352,12 @@ public extension Backport.PhotosPicker where Wrapped == Any {
/// - photoLibrary: The photo library to choose from.
/// - label: The view that describes the action of choosing items from the photo library.
init(
selection: Binding<[Backport<Any>.PhotosPickerItem]>,
selection: Binding<[Backport.PhotosPickerItem]>,
maxSelectionCount: Int? = nil,
selectionBehavior: Backport<Any>.PhotosPickerSelectionBehavior = .default,
matching filter: Backport<Any>.PHPickerFilter? = nil,
preferredItemEncoding: Backport<Any>.PhotosPickerItem.EncodingDisambiguationPolicy = .automatic,
photoLibrary: PHPhotoLibrary = .shared(),
selectionBehavior: Backport.PhotosPickerSelectionBehavior = .default,
matching filter: Backport.PHPickerFilter? = nil,
preferredItemEncoding: Backport.PhotosPickerItem.EncodingDisambiguationPolicy = .automatic,
photoLibrary: PHPhotoLibrary,
@ViewBuilder label: () -> Label
) {
_selection = selection
@ -144,45 +370,94 @@ public extension Backport.PhotosPicker where Wrapped == Any {
}
}
@available(iOS 14, *)
public extension Backport.PhotosPicker where Wrapped == Any, Label == Text {
/// Creates a Photos picker with its label generated from a string that selects a `PhotosPickerItem` from a given photo library.
@available(iOS 15, *)
public extension Backport<Any>.PhotosPicker<Text> {
/// Creates a Photos picker with its label generated from a localized string key that selects a collection of `PhotosPickerItem`.
///
/// The user explicitly grants access only to items they choose, so photo library access authorization is not needed.
///
/// - Parameters:
/// - titleKey: A localized string key that describes the purpose of showing the picker.
/// - selection: All items being shown and selected in the Photos picker.
/// - maxSelectionCount: The maximum number of items that can be selected. Default is `nil`. Setting it to `nil` means maximum supported by the system.
/// - selectionBehavior: The selection behavior of the Photos picker. Default is `.default`.
/// - filter: Types of items that can be shown. Default is `nil`. Setting it to `nil` means all supported types can be shown.
/// - preferredItemEncoding: The encoding disambiguation policy of selected items. Default is `.automatic`. Setting it to `.automatic` means the best encoding determined by the system will be used.
init(
_ titleKey: LocalizedStringKey,
selection: Binding<[Backport.PhotosPickerItem]>,
maxSelectionCount: Int? = nil,
selectionBehavior: Backport.PhotosPickerSelectionBehavior = .default,
matching filter: Backport.PHPickerFilter? = nil,
preferredItemEncoding: Backport.PhotosPickerItem.EncodingDisambiguationPolicy = .automatic
) {
_selection = selection
self.filter = filter
self.maxSelection = maxSelectionCount
self.selectionBehavior = selectionBehavior
self.encoding = preferredItemEncoding
self.library = .shared()
self.label = Text(titleKey)
}
/// Creates a Photos picker with its label generated from a string that selects a collection of `PhotosPickerItem`.
///
/// The user explicitly grants access only to items they choose, so photo library access authorization is not needed.
///
/// - Parameters:
/// - title: A string that describes the purpose of showing the picker.
/// - selection: The item being shown and selected in the Photos picker.
/// - selection: All items being shown and selected in the Photos picker.
/// - maxSelectionCount: The maximum number of items that can be selected. Default is `nil`. Setting it to `nil` means maximum supported by the system.
/// - selectionBehavior: The selection behavior of the Photos picker. Default is `.default`.
/// - filter: Types of items that can be shown. Default is `nil`. Setting it to `nil` means all supported types can be shown.
/// - preferredItemEncoding: The encoding disambiguation policy of the selected item. Default is `.automatic`. Setting it to `.automatic` means the best encoding determined by the system will be used.
/// - photoLibrary: The photo library to choose from.
/// - preferredItemEncoding: The encoding disambiguation policy of selected items. Default is `.automatic`. Setting it to `.automatic` means the best encoding determined by the system will be used.
init<S>(
_ title: S,
selection: Binding<Backport<Any>.PhotosPickerItem?>,
selection: Binding<[Backport.PhotosPickerItem]>,
maxSelectionCount: Int? = nil,
matching filter: Backport<Any>.PHPickerFilter? = nil,
preferredItemEncoding: Backport<Any>.PhotosPickerItem.EncodingDisambiguationPolicy = .automatic,
photoLibrary: PHPhotoLibrary = .shared()
selectionBehavior: Backport.PhotosPickerSelectionBehavior = .default,
matching filter: Backport.PHPickerFilter? = nil,
preferredItemEncoding: Backport.PhotosPickerItem.EncodingDisambiguationPolicy = .automatic
) where S: StringProtocol {
_selection = .init(
get: {
[selection.wrappedValue].compactMap { $0 }
},
set: { newValue in
selection.wrappedValue = newValue.first
}
)
_selection = selection
self.filter = filter
self.maxSelection = maxSelectionCount
self.selectionBehavior = .default
self.selectionBehavior = selectionBehavior
self.encoding = preferredItemEncoding
self.library = photoLibrary
self.library = .shared()
self.label = Text(title)
}
/// Creates a Photos picker with its label generated from a localized string key that selects a collection of `PhotosPickerItem` from a given photo library.
///
/// The user explicitly grants access only to items they choose, so photo library access authorization is not needed.
///
/// - Parameters:
/// - titleKey: A localized string key that describes the purpose of showing the picker.
/// - selection: All items being shown and selected in the Photos picker.
/// - maxSelectionCount: The maximum number of items that can be selected. Default is `nil`. Setting it to `nil` means maximum supported by the system.
/// - selectionBehavior: The selection behavior of the Photos picker. Default is `.default`.
/// - filter: Types of items that can be shown. Default is `nil`. Setting it to `nil` means all supported types can be shown.
/// - preferredItemEncoding: The encoding disambiguation policy of selected items. Default is `.automatic`. Setting it to `.automatic` means the best encoding determined by the system will be used.
/// - photoLibrary: The photo library to choose from.
init(
_ titleKey: LocalizedStringKey,
selection: Binding<[Backport.PhotosPickerItem]>,
maxSelectionCount: Int? = nil,
selectionBehavior: Backport.PhotosPickerSelectionBehavior = .default,
matching filter: Backport.PHPickerFilter? = nil,
preferredItemEncoding: Backport.PhotosPickerItem.EncodingDisambiguationPolicy = .automatic,
photoLibrary: PHPhotoLibrary
) {
_selection = selection
self.filter = filter
self.maxSelection = maxSelectionCount
self.selectionBehavior = selectionBehavior
self.encoding = preferredItemEncoding
self.library = photoLibrary
self.label = Text(titleKey)
}
@available(iOS 15, *)
public extension Backport.PhotosPicker where Wrapped == Any, Label == Text {
/// Creates a Photos picker with its label generated from a string that selects a collection of `PhotosPickerItem` from a given photo library.
///
/// The user explicitly grants access only to items they choose, so photo library access authorization is not needed.
@ -197,12 +472,12 @@ public extension Backport.PhotosPicker where Wrapped == Any, Label == Text {
/// - photoLibrary: The photo library to choose from.
init<S>(
_ title: S,
selection: Binding<[Backport<Any>.PhotosPickerItem]>,
selection: Binding<[Backport.PhotosPickerItem]>,
maxSelectionCount: Int? = nil,
selectionBehavior: Backport<Any>.PhotosPickerSelectionBehavior = .default,
matching filter: Backport<Any>.PHPickerFilter? = nil,
preferredItemEncoding: Backport<Any>.PhotosPickerItem.EncodingDisambiguationPolicy = .automatic,
photoLibrary: PHPhotoLibrary = .shared()
selectionBehavior: Backport.PhotosPickerSelectionBehavior = .default,
matching filter: Backport.PHPickerFilter? = nil,
preferredItemEncoding: Backport.PhotosPickerItem.EncodingDisambiguationPolicy = .automatic,
photoLibrary: PHPhotoLibrary
) where S: StringProtocol {
_selection = selection
self.filter = filter

View File

@ -1,80 +1,113 @@
#if os(iOS)
import SwiftUI
import PhotosUI
import CoreServices
@available(iOS, introduced: 13, deprecated: 16)
public extension Backport where Wrapped == Any {
/// A filter that restricts which types of assets to show
struct PHPickerFilter: Equatable, Hashable {
let predicate: NSPredicate
internal let predicate: NSPredicate
// this enables us to support iOS 13 for images vs videos
internal let mediaTypes: [String]
internal init(mediaTypes: [String]) {
self.predicate = .init(value: true)
self.mediaTypes = mediaTypes
}
internal init(predicate: NSPredicate) {
self.predicate = predicate
self.mediaTypes = []
}
}
}
@available(iOS 13, *)
public extension Backport<Any>.PHPickerFilter {
/// The filter for images.
public static var images: Self {
.init(predicate: NSPredicate(format: "(mediaSubtypes & %d) != 0", argumentArray: [PHAssetMediaType.image]))
static var images: Self {
if #available(iOS 14, *) {
return .init(predicate: NSPredicate(format: "(mediaSubtypes & %d) != 0", argumentArray: [PHAssetMediaType.image]))
} else {
return .init(mediaTypes: [String(kUTTypeImage)])
}
}
/// The filter for videos.
public static var videos: Self {
.init(predicate: NSPredicate(format: "(mediaSubtypes & %d) != 0", argumentArray: [PHAssetMediaType.video]))
static var videos: Self {
if #available(iOS 14, *) {
return .init(predicate: NSPredicate(format: "(mediaSubtypes & %d) != 0", argumentArray: [PHAssetMediaType.video]))
} else {
return .init(mediaTypes: [String(kUTTypeMovie)])
}
}
/// The filter for live photos.
public static var livePhotos: Self {
.init(predicate: NSPredicate(format: "(mediaSubtypes & %d) != 0", argumentArray: [PHAssetMediaSubtype.photoLive]))
static var livePhotos: Self {
if #available(iOS 14, *) {
return .init(predicate: NSPredicate(format: "(mediaSubtypes & %d) != 0", argumentArray: [PHAssetMediaSubtype.photoLive]))
} else {
return .init(mediaTypes: [String(kUTTypeMovie), String(kUTTypeLivePhoto)])
}
}
}
@available(iOS 14, *)
public extension Backport<Any>.PHPickerFilter {
/// The filter for Depth Effect photos.
public static var depthEffectPhotos: Self {
static var depthEffectPhotos: Self {
.init(predicate: NSPredicate(format: "(mediaSubtypes & %d) != 0", argumentArray: [PHAssetMediaSubtype.photoDepthEffect]))
}
/// The filter for panorama photos.
public static var panoramas: Self {
static var panoramas: Self {
.init(predicate: NSPredicate(format: "(mediaSubtypes & %d) != 0", argumentArray: [PHAssetMediaSubtype.photoPanorama]))
}
/// The filter for screenshots.
public static var screenshots: Self {
static var screenshots: Self {
.init(predicate: NSPredicate(format: "(mediaSubtypes & %d) != 0", argumentArray: [PHAssetMediaSubtype.photoScreenshot]))
}
/// The filter for Slow-Mo videos.
public static var slomoVideos: Self {
static var slomoVideos: Self {
.init(predicate: NSPredicate(format: "(mediaSubtypes & %d) != 0", argumentArray: [PHAssetMediaSubtype.videoHighFrameRate]))
}
/// The filter for time-lapse videos.
public static var timelapseVideos: Self {
static var timelapseVideos: Self {
.init(predicate: NSPredicate(format: "(mediaSubtypes & %d) != 0", argumentArray: [PHAssetMediaSubtype.videoTimelapse]))
}
/// The filter for Cinematic videos.
@available(iOS 15.0, *)
public static var cinematicVideos: Self {
.init(predicate: NSPredicate(format: "(mediaSubtypes & %d) != 0", argumentArray: [PHAssetMediaSubtype.videoCinematic]))
}
/// Returns a new filter based on the asset playback style.
#warning("NEEDS TESTING!")
public static func playbackStyle(_ playbackStyle: PHAsset.PlaybackStyle) -> Self {
static func playbackStyle(_ playbackStyle: PHAsset.PlaybackStyle) -> Self {
.init(predicate: NSPredicate(format: "(playbackStyle & %d) != 0", argumentArray: [playbackStyle.rawValue]))
}
/// Returns a new filter formed by OR-ing the filters in a given array.
public static func any(of subfilters: [Self]) -> Self {
static func any(of subfilters: [Self]) -> Self {
.init(predicate: NSCompoundPredicate(orPredicateWithSubpredicates: subfilters.map { $0.predicate }))
}
/// Returns a new filter formed by AND-ing the filters in a given array.
public static func all(of subfilters: [Self]) -> Self {
static func all(of subfilters: [Self]) -> Self {
.init(predicate: NSCompoundPredicate(andPredicateWithSubpredicates: subfilters.map { $0.predicate }))
}
/// Returns a new filter formed by negating the given filter.
public static func not(_ filter: Self) -> Self {
static func not(_ filter: Self) -> Self {
.init(predicate: NSCompoundPredicate(notPredicateWithSubpredicate: filter.predicate))
}
}
@available(iOS 15.0, *)
public extension Backport<Any>.PHPickerFilter {
/// The filter for Cinematic videos.
static var cinematicVideos: Self {
.init(predicate: NSPredicate(format: "(mediaSubtypes & %d) != 0", argumentArray: [PHAssetMediaSubtype.videoCinematic]))
}
}
#endif