如何在 SwiftUI 中打开 ImagePicker?
How to open the ImagePicker in SwiftUI?
我需要使用 SwiftUI 在我的应用程序中打开一个图像选择器,我该怎么做?
我考虑过使用 UIImagePickerController
,但我不知道如何在 SwiftUI 中使用它。
您需要将 UIImagePickerController
包装在实现 UIViewControllerRepresentable
.
的结构中
有关 UIViewControllerRepresentable
的更多信息,请查看这个精彩的 WWDC 2019 演讲:
struct ImagePicker: UIViewControllerRepresentable {
@Environment(\.presentationMode)
private var presentationMode
let sourceType: UIImagePickerController.SourceType
let onImagePicked: (UIImage) -> Void
final class Coordinator: NSObject,
UINavigationControllerDelegate,
UIImagePickerControllerDelegate {
@Binding
private var presentationMode: PresentationMode
private let sourceType: UIImagePickerController.SourceType
private let onImagePicked: (UIImage) -> Void
init(presentationMode: Binding<PresentationMode>,
sourceType: UIImagePickerController.SourceType,
onImagePicked: @escaping (UIImage) -> Void) {
_presentationMode = presentationMode
self.sourceType = sourceType
self.onImagePicked = onImagePicked
}
func imagePickerController(_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
let uiImage = info[UIImagePickerController.InfoKey.originalImage] as! UIImage
onImagePicked(uiImage)
presentationMode.dismiss()
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
presentationMode.dismiss()
}
}
func makeCoordinator() -> Coordinator {
return Coordinator(presentationMode: presentationMode,
sourceType: sourceType,
onImagePicked: onImagePicked)
}
func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController {
let picker = UIImagePickerController()
picker.sourceType = sourceType
picker.delegate = context.coordinator
return picker
}
func updateUIViewController(_ uiViewController: UIImagePickerController,
context: UIViewControllerRepresentableContext<ImagePicker>) {
}
}
这里有一个简单的测试视图:
- 选取器显示在 sheet
- 所选图像出现时没有任何动画,并替换了
Show image picker
按钮
struct ContentView: View {
@State var showImagePicker: Bool = false
@State var image: Image? = nil
var body: some View {
ZStack {
VStack {
Button(action: {
self.showImagePicker.toggle()
}) {
Text("Show image picker")
}
image?.resizable().frame(width: 100, height: 100)
}
.sheet(isPresented: $showImagePicker) {
ImagePicker(sourceType: .photoLibrary) { image in
self.image = Image(uiImage: image)
}
}
}
}
}
我希望这有助于作为一个起点!
我相信一旦 SwiftUI
结束测试版,Apple 会让这件事变得更容易。
测试于 Xcode 11.4
错误:
- @JAHelia 发现当
sourceType
不是相机时拾取器上的错误。
你将无法拖下 sheet - 我还没有找到解决方案。
我是 Swift 的新手,但我能够通过以下方式获得它。
这将加载一个图像选择器模态并让您 select 一张照片,然后它将更新来自父项的 @State
变量。
如果这对您有用,您可以将 @State
替换为可以跨多个组件的内容,例如 @EnvironmentObject
,这样其他组件也可以得到更新。
希望对您有所帮助!
// ImagePicker.swift
struct ImagePicker : View {
@State var image: UIImage? = nil
var body: some View {
ImagePickerViewController(image: $image)
}
}
// ImagePickerViewController.swift
import UIKit
import AVFoundation
import SwiftUI
struct ImagePickerViewController: UIViewControllerRepresentable {
@Binding var image: UIImage?
func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<ImagePickerViewController>) {
}
func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePickerViewController>) -> UIImagePickerController {
let imagePicker = UIImagePickerController()
imagePicker.sourceType = UIImagePickerController.SourceType.photoLibrary
imagePicker.allowsEditing = false
imagePicker.delegate = context.coordinator
return imagePicker
}
func makeCoordinator() -> Coordinator {
return Coordinator(self)
}
class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate, AVCapturePhotoCaptureDelegate {
var parent: ImagePickerViewController
init(_ parent: ImagePickerViewController) {
self.parent = parent
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
let imagePicked = info[.originalImage] as! UIImage
parent.image = imagePicked
picker.dismiss(animated: true, completion: nil)
}
}
}
用法:
// SampleView.swift
struct SampleView : View {
var body: some View {
PresentationLink(destination: ImagePicker().environmentObject(self.userData), label: {
Text("Import Photo")
})
}
}
再一次,我刚接触 Swift 所以如果有人有任何意见,请告诉我!很高兴了解更多。
这是一个适用于 Xcode 11 beta 4 的版本。
它使用具有两个属性的 BindableObject 单例 (ImagePicker.shared):.view 和 .image。
请参阅下面的用法 (ImagePickerTestView)
import SwiftUI
import Combine
final class ImagePicker : BindableObject {
static let shared : ImagePicker = ImagePicker()
private init() {} //force using the singleton: ImagePicker.shared
let view = ImagePicker.View()
let coordinator = ImagePicker.Coordinator()
// Bindable Object part
let willChange = PassthroughSubject<Image?, Never>()
@Published var image: Image? = nil {
didSet {
if image != nil {
willChange.send(image)
}
}
}
}
extension ImagePicker {
class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
// UIImagePickerControllerDelegate
func imagePickerController(_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
let uiImage = info[UIImagePickerController.InfoKey.originalImage] as! UIImage
ImagePicker.shared.image = Image(uiImage: uiImage)
picker.dismiss(animated:true)
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
picker.dismiss(animated:true)
}
}
struct View: UIViewControllerRepresentable {
func makeCoordinator() -> Coordinator {
ImagePicker.shared.coordinator
}
func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker.View>) -> UIImagePickerController {
let picker = UIImagePickerController()
picker.delegate = context.coordinator
return picker
}
func updateUIViewController(_ uiViewController: UIImagePickerController,
context: UIViewControllerRepresentableContext<ImagePicker.View>) {
}
}
}
struct ImagePickerTestView: View {
@State var showingPicker = false
@State var image : Image? = nil
// you could use ImagePicker.shared.image directly
var body: some View {
VStack {
Button("Show image picker") {
self.showingPicker = true
}
image?
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 300)
}.sheet(isPresented: $showingPicker,
onDismiss: {
// do whatever you need here
}, content: {
ImagePicker.shared.view
})
.onReceive(ImagePicker.shared.$image) { image in
// This gets called when the image is picked.
// sheet/onDismiss gets called when the picker completely leaves the screen
self.image = image
}
}
}
#if DEBUG
struct ImagePicker_Previews : PreviewProvider {
static var previews: some View {
ImagePickerTestView()
}
}
#endif
基于@user:2890168 我做了一个版本:
- 检索
UIImage
而不是 Image
- 使用
.sheet
来呈现ImagePicker
。
- 显示
ActionSheet
以帮助用户删除或更改图像。
struct LibraryImage: View {
@State var showAction: Bool = false
@State var showImagePicker: Bool = false
@State var uiImage: UIImage? = nil
var sheet: ActionSheet {
ActionSheet(
title: Text("Action"),
message: Text("Quotemark"),
buttons: [
.default(Text("Change"), action: {
self.showAction = false
self.showImagePicker = true
}),
.cancel(Text("Close"), action: {
self.showAction = false
}),
.destructive(Text("Remove"), action: {
self.showAction = false
self.uiImage = nil
})
])
}
var body: some View {
VStack {
if (uiImage == nil) {
Image(systemName: "camera.on.rectangle")
.accentColor(Color.App.purple)
.background(
Color.App.gray
.frame(width: 100, height: 100)
.cornerRadius(6))
.onTapGesture {
self.showImagePicker = true
}
} else {
Image(uiImage: uiImage!)
.resizable()
.frame(width: 100, height: 100)
.cornerRadius(6)
.onTapGesture {
self.showAction = true
}
}
}
.sheet(isPresented: $showImagePicker, onDismiss: {
self.showImagePicker = false
}, content: {
ImagePicker(isShown: self.$showImagePicker, uiImage: self.$uiImage)
})
.actionSheet(isPresented: $showAction) {
sheet
}
}
}
LibraryImage
的默认 body
是一个 Image
,显示用户可点击的相机图标。
点击事件时,图像选择器显示为带有 sheet
修饰符。选择图像后,重新计算 LibraryImage
主体,现在显示在 else 语句中定义的 Image
(因为 uiImage
属性 现在包含用户选择的图像)。
现在,点击事件显示 ActionSheet
。
编辑后的图片选择器:
struct ImagePicker: UIViewControllerRepresentable {
@Binding var isShown: Bool
@Binding var uiImage: UIImage?
class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
@Binding var isShown: Bool
@Binding var uiImage: UIImage?
init(isShown: Binding<Bool>, uiImage: Binding<UIImage?>) {
_isShown = isShown
_uiImage = uiImage
}
func imagePickerController(_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
let imagePicked = info[UIImagePickerController.InfoKey.originalImage] as! UIImage
uiImage = imagePicked
isShown = false
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
isShown = false
}
}
func makeCoordinator() -> Coordinator {
return Coordinator(isShown: $isShown, uiImage: $uiImage)
}
func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController {
let picker = UIImagePickerController()
picker.delegate = context.coordinator
return picker
}
func updateUIViewController(_ uiViewController: UIImagePickerController,
context: UIViewControllerRepresentableContext<ImagePicker>) {
}
}
默认行为:
我实现了一个我认为更通用和可扩展的版本。我使用 Subject
而不是 Binding
来解决 undoable/inappropriate 在您的视图中添加另一个绑定的问题。
例如,您创建了一个 List
显示存储在基础存储中的一组图像,并且您想要使用图像选择器添加图像。在这种情况下,非常 hard/ugly 将该图像添加到您的基础存储中。
所以我使用了一个主题来传输图像,你可以简单地观察它并将新图像添加到一些存储中,或者如果你想让它表现得像一个绑定,它也是一行代码。 (在你的观察中修改你的状态)
然后我将首选项包装到 ViewModel
中,这样如果您想要更多主题或配置,它就不会变得混乱。
import SwiftUI
import Combine
struct ImagePickerView : UIViewControllerRepresentable {
@Binding var model: ImagePickerViewModel
typealias UIViewControllerType = UIImagePickerController
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIViewController(context: UIViewControllerRepresentableContext<Self>) -> UIImagePickerController {
let controller = UIImagePickerController()
controller.delegate = context.coordinator
controller.allowsEditing = false
controller.mediaTypes = ["public.image"]
controller.sourceType = .photoLibrary
return controller
}
func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<ImagePickerView>) {
// run right after making
}
class Coordinator : NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
var parentView: ImagePickerView
init(_ parentView: ImagePickerView) {
self.parentView = parentView
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
parentView.model.isPresented = false
}
func imagePickerController(_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
guard let uiImage = info[.originalImage] as? UIImage else { return }
let image = Image(uiImage: uiImage)
parentView.model.pickedImagesSubject?.send([image])
parentView.model.isPresented = false
}
}
}
struct ImagePickerViewModel {
var isPresented: Bool = false
let pickedImagesSubject: PassthroughSubject<[Image], Never>! = PassthroughSubject<[Image], Never>()
}
用法:
struct SomeView : View {
@EnvironmentObject var storage: Storage
@State var imagePickerViewModel = ImagePickerViewModel()
var body: some View {
Button(action: { self.imagePickerViewModel.isPresented.toggle() }) { ... }
.sheet(isPresented: $imagePickerViewModel.isPresented) {
ImagePickerView(model: self.$imagePickerViewModel)
}
.onReceive(imagePickerViewModel.pickedImagesSubject) { (images: [Image]) -> Void in
withAnimation {
// modify your storage here
self.storage.images += images
}
}
}
}
我是这样实现的:
import SwiftUI
final class ImagePickerCoordinator: NSObject {
@Binding var image: UIImage?
@Binding var takePhoto: Bool
init(image: Binding<UIImage?>, takePhoto: Binding<Bool>) {
_image = image
_takePhoto = takePhoto
}
}
struct ShowImagePicker: UIViewControllerRepresentable {
@Binding var image: UIImage?
@Binding var takePhoto: Bool
func makeCoordinator() -> ImagePickerCoordinator {
ImagePickerCoordinator(image: $image, takePhoto: $takePhoto)
}
func makeUIViewController(context: Context) -> UIImagePickerController {
let pickerController = UIImagePickerController()
pickerController.delegate = context.coordinator
guard UIImagePickerController.isSourceTypeAvailable(.camera) else { return pickerController }
switch self.takePhoto {
case true:
pickerController.sourceType = .camera
case false:
pickerController.sourceType = .photoLibrary
}
pickerController.allowsEditing = true
return pickerController
}
func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {}
}
extension ImagePickerCoordinator: UINavigationControllerDelegate, UIImagePickerControllerDelegate {
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
guard let uiImage = info[UIImagePickerController.InfoKey.originalImage] as? UIImage else { return }
self.image = uiImage
picker.dismiss(animated: true, completion: nil)
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
picker.dismiss(animated: true, completion: nil)
}
}
将仅两个按钮的逻辑添加到您的视图中就足够了...))
Xcode 12 的清理版本可通过 SPM 作为 Swift 包:
https://github.com/ralfebert/ImagePickerView
来源:
import SwiftUI
public struct ImagePickerView: UIViewControllerRepresentable {
private let sourceType: UIImagePickerController.SourceType
private let onImagePicked: (UIImage) -> Void
@Environment(\.presentationMode) private var presentationMode
public init(sourceType: UIImagePickerController.SourceType, onImagePicked: @escaping (UIImage) -> Void) {
self.sourceType = sourceType
self.onImagePicked = onImagePicked
}
public func makeUIViewController(context: Context) -> UIImagePickerController {
let picker = UIImagePickerController()
picker.sourceType = self.sourceType
picker.delegate = context.coordinator
return picker
}
public func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {}
public func makeCoordinator() -> Coordinator {
Coordinator(
onDismiss: { self.presentationMode.wrappedValue.dismiss() },
onImagePicked: self.onImagePicked
)
}
final public class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
private let onDismiss: () -> Void
private let onImagePicked: (UIImage) -> Void
init(onDismiss: @escaping () -> Void, onImagePicked: @escaping (UIImage) -> Void) {
self.onDismiss = onDismiss
self.onImagePicked = onImagePicked
}
public func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
if let image = info[.originalImage] as? UIImage {
self.onImagePicked(image)
}
self.onDismiss()
}
public func imagePickerControllerDidCancel(_: UIImagePickerController) {
self.onDismiss()
}
}
}
iOS 14 Xcode 12 - 照片选择器 SwiftUI 具有可重复使用的视图,允许限制
struct ImagePickerView: UIViewControllerRepresentable {
@Binding var images: [UIImage]
@Binding var showPicker: Bool
var selectionLimit: Int
func makeUIViewController(context: Context) -> some UIViewController {
var config = PHPickerConfiguration()
config.filter = .images
config.selectionLimit = selectionLimit
let picker = PHPickerViewController(configuration: config)
picker.delegate = context.coordinator
return picker
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) { }
func makeCoordinator() -> Coordinator {
Coordinator(parent: self)
}
class Coordinator: NSObject, PHPickerViewControllerDelegate {
var parent: ImagePickerView
init(parent: ImagePickerView) {
self.parent = parent
}
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
parent.showPicker.toggle()
for img in results {
if img.itemProvider.canLoadObject(ofClass: UIImage.self) {
img.itemProvider.loadObject(ofClass: UIImage.self) { (image, err) in
guard let image1 = image else { return }
DispatchQueue.main.async {
self.parent.images.append(image1 as! UIImage)
}
}
} else {
// Handle Error
parent.showPicker.toggle()
}
}
}
}
}
然后在视图中你可以做
VStack {
Image(systemName: "camera.viewfinder")
.resizable()
.aspectRatio(contentMode: .fit)
.onTapGesture {
self.viewModel.pickerBool.toggle()
}
}
.sheet(isPresented: self.$viewModel.pickerBool) {
ImagePickerView(images: self.$viewModel.images, showPicker: self.$viewModel.pickerBool, selectionLimit: 3)
}
我需要使用 SwiftUI 在我的应用程序中打开一个图像选择器,我该怎么做?
我考虑过使用 UIImagePickerController
,但我不知道如何在 SwiftUI 中使用它。
您需要将 UIImagePickerController
包装在实现 UIViewControllerRepresentable
.
有关 UIViewControllerRepresentable
的更多信息,请查看这个精彩的 WWDC 2019 演讲:
struct ImagePicker: UIViewControllerRepresentable {
@Environment(\.presentationMode)
private var presentationMode
let sourceType: UIImagePickerController.SourceType
let onImagePicked: (UIImage) -> Void
final class Coordinator: NSObject,
UINavigationControllerDelegate,
UIImagePickerControllerDelegate {
@Binding
private var presentationMode: PresentationMode
private let sourceType: UIImagePickerController.SourceType
private let onImagePicked: (UIImage) -> Void
init(presentationMode: Binding<PresentationMode>,
sourceType: UIImagePickerController.SourceType,
onImagePicked: @escaping (UIImage) -> Void) {
_presentationMode = presentationMode
self.sourceType = sourceType
self.onImagePicked = onImagePicked
}
func imagePickerController(_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
let uiImage = info[UIImagePickerController.InfoKey.originalImage] as! UIImage
onImagePicked(uiImage)
presentationMode.dismiss()
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
presentationMode.dismiss()
}
}
func makeCoordinator() -> Coordinator {
return Coordinator(presentationMode: presentationMode,
sourceType: sourceType,
onImagePicked: onImagePicked)
}
func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController {
let picker = UIImagePickerController()
picker.sourceType = sourceType
picker.delegate = context.coordinator
return picker
}
func updateUIViewController(_ uiViewController: UIImagePickerController,
context: UIViewControllerRepresentableContext<ImagePicker>) {
}
}
这里有一个简单的测试视图:
- 选取器显示在 sheet
- 所选图像出现时没有任何动画,并替换了
Show image picker
按钮
struct ContentView: View {
@State var showImagePicker: Bool = false
@State var image: Image? = nil
var body: some View {
ZStack {
VStack {
Button(action: {
self.showImagePicker.toggle()
}) {
Text("Show image picker")
}
image?.resizable().frame(width: 100, height: 100)
}
.sheet(isPresented: $showImagePicker) {
ImagePicker(sourceType: .photoLibrary) { image in
self.image = Image(uiImage: image)
}
}
}
}
}
我希望这有助于作为一个起点!
我相信一旦 SwiftUI
结束测试版,Apple 会让这件事变得更容易。
测试于 Xcode 11.4
错误:
- @JAHelia 发现当
sourceType
不是相机时拾取器上的错误。 你将无法拖下 sheet - 我还没有找到解决方案。
我是 Swift 的新手,但我能够通过以下方式获得它。
这将加载一个图像选择器模态并让您 select 一张照片,然后它将更新来自父项的 @State
变量。
如果这对您有用,您可以将 @State
替换为可以跨多个组件的内容,例如 @EnvironmentObject
,这样其他组件也可以得到更新。
希望对您有所帮助!
// ImagePicker.swift
struct ImagePicker : View {
@State var image: UIImage? = nil
var body: some View {
ImagePickerViewController(image: $image)
}
}
// ImagePickerViewController.swift
import UIKit
import AVFoundation
import SwiftUI
struct ImagePickerViewController: UIViewControllerRepresentable {
@Binding var image: UIImage?
func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<ImagePickerViewController>) {
}
func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePickerViewController>) -> UIImagePickerController {
let imagePicker = UIImagePickerController()
imagePicker.sourceType = UIImagePickerController.SourceType.photoLibrary
imagePicker.allowsEditing = false
imagePicker.delegate = context.coordinator
return imagePicker
}
func makeCoordinator() -> Coordinator {
return Coordinator(self)
}
class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate, AVCapturePhotoCaptureDelegate {
var parent: ImagePickerViewController
init(_ parent: ImagePickerViewController) {
self.parent = parent
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
let imagePicked = info[.originalImage] as! UIImage
parent.image = imagePicked
picker.dismiss(animated: true, completion: nil)
}
}
}
用法:
// SampleView.swift
struct SampleView : View {
var body: some View {
PresentationLink(destination: ImagePicker().environmentObject(self.userData), label: {
Text("Import Photo")
})
}
}
再一次,我刚接触 Swift 所以如果有人有任何意见,请告诉我!很高兴了解更多。
这是一个适用于 Xcode 11 beta 4 的版本。
它使用具有两个属性的 BindableObject 单例 (ImagePicker.shared):.view 和 .image。
请参阅下面的用法 (ImagePickerTestView)
import SwiftUI
import Combine
final class ImagePicker : BindableObject {
static let shared : ImagePicker = ImagePicker()
private init() {} //force using the singleton: ImagePicker.shared
let view = ImagePicker.View()
let coordinator = ImagePicker.Coordinator()
// Bindable Object part
let willChange = PassthroughSubject<Image?, Never>()
@Published var image: Image? = nil {
didSet {
if image != nil {
willChange.send(image)
}
}
}
}
extension ImagePicker {
class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
// UIImagePickerControllerDelegate
func imagePickerController(_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
let uiImage = info[UIImagePickerController.InfoKey.originalImage] as! UIImage
ImagePicker.shared.image = Image(uiImage: uiImage)
picker.dismiss(animated:true)
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
picker.dismiss(animated:true)
}
}
struct View: UIViewControllerRepresentable {
func makeCoordinator() -> Coordinator {
ImagePicker.shared.coordinator
}
func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker.View>) -> UIImagePickerController {
let picker = UIImagePickerController()
picker.delegate = context.coordinator
return picker
}
func updateUIViewController(_ uiViewController: UIImagePickerController,
context: UIViewControllerRepresentableContext<ImagePicker.View>) {
}
}
}
struct ImagePickerTestView: View {
@State var showingPicker = false
@State var image : Image? = nil
// you could use ImagePicker.shared.image directly
var body: some View {
VStack {
Button("Show image picker") {
self.showingPicker = true
}
image?
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 300)
}.sheet(isPresented: $showingPicker,
onDismiss: {
// do whatever you need here
}, content: {
ImagePicker.shared.view
})
.onReceive(ImagePicker.shared.$image) { image in
// This gets called when the image is picked.
// sheet/onDismiss gets called when the picker completely leaves the screen
self.image = image
}
}
}
#if DEBUG
struct ImagePicker_Previews : PreviewProvider {
static var previews: some View {
ImagePickerTestView()
}
}
#endif
基于@user:2890168 我做了一个版本:
- 检索
UIImage
而不是Image
- 使用
.sheet
来呈现ImagePicker
。 - 显示
ActionSheet
以帮助用户删除或更改图像。
struct LibraryImage: View {
@State var showAction: Bool = false
@State var showImagePicker: Bool = false
@State var uiImage: UIImage? = nil
var sheet: ActionSheet {
ActionSheet(
title: Text("Action"),
message: Text("Quotemark"),
buttons: [
.default(Text("Change"), action: {
self.showAction = false
self.showImagePicker = true
}),
.cancel(Text("Close"), action: {
self.showAction = false
}),
.destructive(Text("Remove"), action: {
self.showAction = false
self.uiImage = nil
})
])
}
var body: some View {
VStack {
if (uiImage == nil) {
Image(systemName: "camera.on.rectangle")
.accentColor(Color.App.purple)
.background(
Color.App.gray
.frame(width: 100, height: 100)
.cornerRadius(6))
.onTapGesture {
self.showImagePicker = true
}
} else {
Image(uiImage: uiImage!)
.resizable()
.frame(width: 100, height: 100)
.cornerRadius(6)
.onTapGesture {
self.showAction = true
}
}
}
.sheet(isPresented: $showImagePicker, onDismiss: {
self.showImagePicker = false
}, content: {
ImagePicker(isShown: self.$showImagePicker, uiImage: self.$uiImage)
})
.actionSheet(isPresented: $showAction) {
sheet
}
}
}
LibraryImage
的默认 body
是一个 Image
,显示用户可点击的相机图标。
点击事件时,图像选择器显示为带有 sheet
修饰符。选择图像后,重新计算 LibraryImage
主体,现在显示在 else 语句中定义的 Image
(因为 uiImage
属性 现在包含用户选择的图像)。
现在,点击事件显示 ActionSheet
。
编辑后的图片选择器:
struct ImagePicker: UIViewControllerRepresentable {
@Binding var isShown: Bool
@Binding var uiImage: UIImage?
class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
@Binding var isShown: Bool
@Binding var uiImage: UIImage?
init(isShown: Binding<Bool>, uiImage: Binding<UIImage?>) {
_isShown = isShown
_uiImage = uiImage
}
func imagePickerController(_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
let imagePicked = info[UIImagePickerController.InfoKey.originalImage] as! UIImage
uiImage = imagePicked
isShown = false
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
isShown = false
}
}
func makeCoordinator() -> Coordinator {
return Coordinator(isShown: $isShown, uiImage: $uiImage)
}
func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController {
let picker = UIImagePickerController()
picker.delegate = context.coordinator
return picker
}
func updateUIViewController(_ uiViewController: UIImagePickerController,
context: UIViewControllerRepresentableContext<ImagePicker>) {
}
}
默认行为:
我实现了一个我认为更通用和可扩展的版本。我使用 Subject
而不是 Binding
来解决 undoable/inappropriate 在您的视图中添加另一个绑定的问题。
例如,您创建了一个 List
显示存储在基础存储中的一组图像,并且您想要使用图像选择器添加图像。在这种情况下,非常 hard/ugly 将该图像添加到您的基础存储中。
所以我使用了一个主题来传输图像,你可以简单地观察它并将新图像添加到一些存储中,或者如果你想让它表现得像一个绑定,它也是一行代码。 (在你的观察中修改你的状态)
然后我将首选项包装到 ViewModel
中,这样如果您想要更多主题或配置,它就不会变得混乱。
import SwiftUI
import Combine
struct ImagePickerView : UIViewControllerRepresentable {
@Binding var model: ImagePickerViewModel
typealias UIViewControllerType = UIImagePickerController
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIViewController(context: UIViewControllerRepresentableContext<Self>) -> UIImagePickerController {
let controller = UIImagePickerController()
controller.delegate = context.coordinator
controller.allowsEditing = false
controller.mediaTypes = ["public.image"]
controller.sourceType = .photoLibrary
return controller
}
func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<ImagePickerView>) {
// run right after making
}
class Coordinator : NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
var parentView: ImagePickerView
init(_ parentView: ImagePickerView) {
self.parentView = parentView
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
parentView.model.isPresented = false
}
func imagePickerController(_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
guard let uiImage = info[.originalImage] as? UIImage else { return }
let image = Image(uiImage: uiImage)
parentView.model.pickedImagesSubject?.send([image])
parentView.model.isPresented = false
}
}
}
struct ImagePickerViewModel {
var isPresented: Bool = false
let pickedImagesSubject: PassthroughSubject<[Image], Never>! = PassthroughSubject<[Image], Never>()
}
用法:
struct SomeView : View {
@EnvironmentObject var storage: Storage
@State var imagePickerViewModel = ImagePickerViewModel()
var body: some View {
Button(action: { self.imagePickerViewModel.isPresented.toggle() }) { ... }
.sheet(isPresented: $imagePickerViewModel.isPresented) {
ImagePickerView(model: self.$imagePickerViewModel)
}
.onReceive(imagePickerViewModel.pickedImagesSubject) { (images: [Image]) -> Void in
withAnimation {
// modify your storage here
self.storage.images += images
}
}
}
}
我是这样实现的:
import SwiftUI
final class ImagePickerCoordinator: NSObject {
@Binding var image: UIImage?
@Binding var takePhoto: Bool
init(image: Binding<UIImage?>, takePhoto: Binding<Bool>) {
_image = image
_takePhoto = takePhoto
}
}
struct ShowImagePicker: UIViewControllerRepresentable {
@Binding var image: UIImage?
@Binding var takePhoto: Bool
func makeCoordinator() -> ImagePickerCoordinator {
ImagePickerCoordinator(image: $image, takePhoto: $takePhoto)
}
func makeUIViewController(context: Context) -> UIImagePickerController {
let pickerController = UIImagePickerController()
pickerController.delegate = context.coordinator
guard UIImagePickerController.isSourceTypeAvailable(.camera) else { return pickerController }
switch self.takePhoto {
case true:
pickerController.sourceType = .camera
case false:
pickerController.sourceType = .photoLibrary
}
pickerController.allowsEditing = true
return pickerController
}
func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {}
}
extension ImagePickerCoordinator: UINavigationControllerDelegate, UIImagePickerControllerDelegate {
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
guard let uiImage = info[UIImagePickerController.InfoKey.originalImage] as? UIImage else { return }
self.image = uiImage
picker.dismiss(animated: true, completion: nil)
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
picker.dismiss(animated: true, completion: nil)
}
}
将仅两个按钮的逻辑添加到您的视图中就足够了...))
Xcode 12 的清理版本可通过 SPM 作为 Swift 包:
https://github.com/ralfebert/ImagePickerView
来源:
import SwiftUI
public struct ImagePickerView: UIViewControllerRepresentable {
private let sourceType: UIImagePickerController.SourceType
private let onImagePicked: (UIImage) -> Void
@Environment(\.presentationMode) private var presentationMode
public init(sourceType: UIImagePickerController.SourceType, onImagePicked: @escaping (UIImage) -> Void) {
self.sourceType = sourceType
self.onImagePicked = onImagePicked
}
public func makeUIViewController(context: Context) -> UIImagePickerController {
let picker = UIImagePickerController()
picker.sourceType = self.sourceType
picker.delegate = context.coordinator
return picker
}
public func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {}
public func makeCoordinator() -> Coordinator {
Coordinator(
onDismiss: { self.presentationMode.wrappedValue.dismiss() },
onImagePicked: self.onImagePicked
)
}
final public class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
private let onDismiss: () -> Void
private let onImagePicked: (UIImage) -> Void
init(onDismiss: @escaping () -> Void, onImagePicked: @escaping (UIImage) -> Void) {
self.onDismiss = onDismiss
self.onImagePicked = onImagePicked
}
public func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
if let image = info[.originalImage] as? UIImage {
self.onImagePicked(image)
}
self.onDismiss()
}
public func imagePickerControllerDidCancel(_: UIImagePickerController) {
self.onDismiss()
}
}
}
iOS 14 Xcode 12 - 照片选择器 SwiftUI 具有可重复使用的视图,允许限制
struct ImagePickerView: UIViewControllerRepresentable {
@Binding var images: [UIImage]
@Binding var showPicker: Bool
var selectionLimit: Int
func makeUIViewController(context: Context) -> some UIViewController {
var config = PHPickerConfiguration()
config.filter = .images
config.selectionLimit = selectionLimit
let picker = PHPickerViewController(configuration: config)
picker.delegate = context.coordinator
return picker
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) { }
func makeCoordinator() -> Coordinator {
Coordinator(parent: self)
}
class Coordinator: NSObject, PHPickerViewControllerDelegate {
var parent: ImagePickerView
init(parent: ImagePickerView) {
self.parent = parent
}
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
parent.showPicker.toggle()
for img in results {
if img.itemProvider.canLoadObject(ofClass: UIImage.self) {
img.itemProvider.loadObject(ofClass: UIImage.self) { (image, err) in
guard let image1 = image else { return }
DispatchQueue.main.async {
self.parent.images.append(image1 as! UIImage)
}
}
} else {
// Handle Error
parent.showPicker.toggle()
}
}
}
}
}
然后在视图中你可以做
VStack {
Image(systemName: "camera.viewfinder")
.resizable()
.aspectRatio(contentMode: .fit)
.onTapGesture {
self.viewModel.pickerBool.toggle()
}
}
.sheet(isPresented: self.$viewModel.pickerBool) {
ImagePickerView(images: self.$viewModel.images, showPicker: self.$viewModel.pickerBool, selectionLimit: 3)
}