现代图片选择器(PHPicker)在 SwiftUI 应用

现代图片选择器(PHPicker)在 SwiftUI 应用,第1张

在 SwiftUI 中实现调用系统相册 0. 项目需求

许多 app 会要求用户上传照片到服务器或者 app 中进行后续处理,就会需要从设备相册中选取照片。这是一个相当普遍的需求。最终实现效果如下。

在 Xcode 预览模式下打开 live view 可以实时预览组件的运行情况,这里我们定义了一个选取照片按钮,而下方是一个 Image View,不过由于刚开始的时候没有选取任何照片,所以不显示任何内容。

当点击该按钮时,将会调出照片选取页面,也就是 Picker。

这个 Picker 可以访问到设备(这里是 Simulator)的图库相册。由于 iOS 的使用习惯,大部分人会把大部分照片直接存在图库中,而不是存储到 “文件” 中,所以可以认为选取设备的所有照片。Picker 下方长条的按钮可以在 Picker 内在线预览所选中的照片。预览时支持放大、旋转等 *** 作。

当选择完成后点击 “Add” 后,选取的照片加入到程序中,此时下方隐藏的 Image View 将显示选取的照片。

这就是这个组件所用到的功能。接下来介绍实现这个组件使用到的技术和实现方法。

1. 使用技术 1.1 SwiftUI

SwiftUI 是苹果在2019年新推出的 Swift 原生 UI 框架。通过这个 UI 框架,开发者可以轻松实现美观大气的界面设计并且在全平台(iOS、iPadOS、macOS、tvOS、watchOS)上获得相同且美观的页面。比如,可以实现不同尺寸屏幕自适应布局,自动实现黑夜模式等等。同时,在开发过程中,Xcode 针对 SwiftUI 还提供了预览功能,可以实时显示界面设计效果,也可以在预览区域选取控件,左边的代码区域也会自动选中并自动修改属性。目前还在发展的阶段,不断有新的、更易用的 API 面世。这两年 Apple 在下一波大棋,把所有平台的软硬件都打通,这个 SwiftUI 也是实现这个愿景的一个工具。Apple 官方的应用和一些精品应用已经从 UIKit 转移到了 SwiftUI,这应该是一个趋势,就像 iOS 开发会从 object-C 迁移到 Swift 一样。

1.2 PhotoKit & PhotosUI

PhotoKit 是苹果近年来提出用于执行和照片相关功能的现代 API,PhotosUI 是其中的一个框架,可以实现比如图片选择等功能,下面是 Apple 对于 PhotoKit 的说明。

PhotoKit 这几年功能不断完善,其中 PhotoUI 的各个组件由于美观的外观和异步执行、保护用户隐私的特性,逐步在取代原先 UIKit 中有关图片的组件,比如这篇文章介绍的 PHPickerViewController 就是 UIImagePickerViewController 的现代替代。

2. 代码 2.1 PhotoPicker 图库选取器的编写

在 SwiftUI 中,与其他框架或语言常见的用类定义不同,是用结构体定义的,不得不说,Swift 语言的结构体的功能很强大。

struct PhotoPicker: UIViewControllerRepresentable {
  
}

这里要提到在 SwiftUI 中集合 UIKit 功能的方法,就是在实现结构体时要实现 UIViewControllerRepresentable 协议(这里的协议可以理解为类似于接口一样的东西),这个协议可以帮助 SwiftUI 实现一个 UIKit 中 UIViewController 组件的组件。

要实现协议,根据文档,我们就要实现如下方法和类。

下面就开始实现各个方法和类,不过,在此之前,要定义几个需要的成员变量。

		let configuration: PHPickerConfiguration
    @Binding var pickerResult: [UIImage]
    @Binding var isPresented: Bool

这里定义了三个变量,第一个是对 PHPickerViewController 的配置,第二个和第三个分别是选取结果和是否显示图库选取器界面。注意后两个变量前的修饰符 @Binding,代表这两个变量与外部的变量绑定,由 SwiftUI 管理,当外部变量更新时或内部变量发生变化时,可以实时显示变化。

接着实现协议中规定的两个方法。

		func makeUIViewController(context: Context) -> PHPickerViewController {
        let controller = PHPickerViewController(configuration: configuration)
        controller.delegate = context.coordinator
        return controller
    }
    
    func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) { }

第一个方法是绘制 UIViewController 组件,这里我们要绘制一个 PHPickerViewController,所以返回值是这个组件,方法体内创建了一个组件,并且将该组件的委托交给上下文的协调器,最后返回这个建立的组件。

第二个方法是更新 UIViewController 组件,这里我们不需要更新组件,就将方法体空着就好。

然后给用户提供一个协调对象。要实现 makeCoordinator 方法,并定义一个 Coordinator 类。请看如下代码:

		func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
    
    class Coordinator: PHPickerViewControllerDelegate {
        private let parent: PhotoPicker
        
        init(_ parent: PhotoPicker) {
            self.parent = parent
        }
        
        func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
            for image in results {
                if image.itemProvider.canLoadObject(ofClass: UIImage.self) {
                    image.itemProvider.loadObject(ofClass: UIImage.self) {
                        (newImage, error) in
                        if let error = error {
                            print(error.localizedDescription)
                        } else {
                            self.parent.pickerResult.append(newImage as! UIImage)
                        }
                    }
                } else {
                    print("Loaded Asset is not a Image")
                }
            }
            parent.isPresented = false
        }
    }

首先是 makeCoordinator 方法,创建一个协调器,并绑定调用的父类对象。这里要将协调的父类设为这个结构体(PhotoPicker),所以要写为 Coordinator(self)

然后我们要定义一个 Coordinator 类,这个类要负责和 ViewController 与其他 SwiftUI 协调。在委托模式中,这个类当然要设置为 PHPickerViewControllerDelegate

这个类里有一个变量 parent,负责指定委托的父对象,这里的父对象是结构体 PhotoPicker,所以我们这样定义变量并且在初始化类时必须要传入一个有效的 PhotoPicker 对象。

在 Apple Developer 查询 PHPickerViewControllerDelegate 的文档,要实现 picker 方法去定义当用户在选择器在选择好以后或者点击取消(dismiss)后要执行的动作。通过查询文档,isPresented 变量是 SwiftUI 定义的一个变量,用来指示现在是否呈现拥有这个环境的组件是否呈现。

我们看 picker 方法。

		func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
            for image in results {
                if image.itemProvider.canLoadObject(ofClass: UIImage.self) {
                    image.itemProvider.loadObject(ofClass: UIImage.self) {
                        (newImage, error) in
                        if let error = error {
                            print(error.localizedDescription)
                        } else {
                            self.parent.pickerResult.append(newImage as! UIImage)
                        }
                    }
                } else {
                    print("Loaded Asset is not a Image")
                }
            }
            parent.isPresented = false
        }
    }

当点击成功或者取消时,都会返回一个 results,我们每次都需要处理这个 results,这里我们处理结果,并将结果中可读取的对象添加到 pickerResult 中。最后我们将父类的 isPresented 设置为 false,这样就能在执行完 *** 作以后自动将图库选择器关闭。这里我们不需要对 UI 动画进行任何设置,所有动画都会由 SwiftUI 自动渲染。(不会有人觉得自己做动画能做过 Apple 吧…)

通过上边的代码,我们就成功定义了一个图库选择器。下面在 SwiftUI 中对该 PhotoPicker 进行测试。

2.2 测试 PhotoPicker

写一个按钮测试 PhotoPicker 的功能。语法都是 SwiftUI 的基本语法,代码如下。

struct PhotoPickerDemo: View {
    @State private var isPresented: Bool = false
    @State var pickerResult: [UIImage] = []
    var config: PHPickerConfiguration {
        var config = PHPickerConfiguration(photoLibrary: PHPhotoLibrary.shared())
        config.filter = .images
        config.selectionLimit = 0
        return config
    }
    
    var body: some View {
        ScrollView {
            LazyVStack {
                Spacer()
                Button(action: { isPresented.toggle()}) {
                    Text("选取照片")
                        .font(.largeTitle)
                        .padding()
                        .background(Color.blue)
                        .foregroundColor(.white)
                        .cornerRadius(15)
                        .shadow(radius: 12)
                }
                .sheet(isPresented: $isPresented) {
                    PhotoPicker(configuration: self.config, pickerResult: $pickerResult, isPresented: $isPresented)
                }
                ForEach(pickerResult, id: \.self) {
                    image in
                    Image.init(uiImage: image)
                        .resizable()
                        .frame(alignment: .center)
                        .aspectRatio(contentMode: .fit)
                }
            }
        }
    }
}

就可以实现上面展示的效果了。

欢迎分享,转载请注明来源:内存溢出

原文地址:https://www.54852.com/web/996793.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2022-05-21
下一篇2022-05-21

发表评论

登录后才能评论

评论列表(0条)

    保存