祝大家有美好的一天!有了你,我,安娜Zharkova,领先的移动开发者Usetech。在这一部分中,我们将讨论已经如何适应一个完整的解决方案,以在SwiftUI一个项目的情况。如果您对这项技术不是特别熟悉,建议您熟悉该主题的简要介绍。因此,让我们看一个简单的示例,说明如何在SwiftUI上的应用程序中为标准iOS应用程序使用现成的库。让我们采用经典的解决方案:使用SDWebImage库异步上传图像。
为了方便起见,将库使用的图像封装在ImageManager中,该图像调用:- SDWebImageDownloader
- SDImageCache
用于下载图像和缓存。按照传统,与接收UIImageView结果的通信以两种方式实现:- 通过将弱链接传递到同一UIImageView;
- 通过将闭包传递给ImageManager方法
通常,将对ImageManager的调用封装在UIImageView扩展中:extension UIImageView {
func setup(by key: String) {
ImageManager.sharedInstance.setImage(toImageView: self, forKey: key)
}
}
在后继类中:class CachedImageView : UIImageView {
private var _imageUrl: String?
var imageUrl: String? {
get {
return _imageUrl
}
set {
self._imageUrl = newValue
if let url = newValue, !url.isEmpty {
self.setup(by: url)
}
}
}
func setup(by key: String) {
ImageManager.sharedInstance.setImage(toImageView: self, forKey: key)
}
}
现在,让我们尝试将此解决方案固定到SwiftUI。但是,在进行调整时,我们必须考虑框架的以下功能:-视图-结构。不支持继承-通常意义上的扩展是没有用的。当然,我们可以编写一些方法来扩展功能,但是我们需要以某种方式将其绑定到DataFlow;我们遇到了获取反馈的问题,并且需要使与UI交互的整个逻辑适应于DataDriven Flow。对于解决方案,我们可以从“视图”端和“数据流”适配端进行操作。让我们从View开始。首先,请回想一下SwiftUI本身并不存在,而是作为UIKit的一个附加组件。SwiftUI开发人员提供了一种在SwiftUI UIView中使用的机制,其类似物不在现成的控件中。对于这种情况,存在UIViewRepresentable和UIViewControllerRepresentable协议,分别用于调整UIView和UIViewController。创建一个实现UIViewRepresentable的View结构,在其中我们重新定义方法:在其中,我们指示我们正在使用哪个UIView并设置其基本设置。并且不要忘记PropertyWrappers具有可变属性。struct WrappedCachedImage : UIViewRepresentable {
let height: CGFloat
@State var imageUrl: String
func makeUIView(context: Context) -> CachedImageView {
let frame = CGRect(x: 20, y: 0, width: UIScreen.main.bounds.size.width - 40,
height: height)
return CachedImageView(frame: frame)
}
func updateUIView(_ uiView: CachedImageView, context: Context) {
uiView.imageUrl = imageUrl
uiView.contentMode = .scaleToFill
}
}
我们可以将生成的新控件嵌入到View SwiftUI中:
这种方法具有以下优点:- 无需更改现有库的操作
- 逻辑封装在嵌入式UIView中。
但是有新的责任。首先,您需要在View-UIView捆绑软件中监视内存管理。由于采用了View结构,因此与它们一起进行的所有工作都是由框架本身在后台执行的。但是清洁新物体落在显影剂的肩膀上。其次,需要其他步骤来进行自定义(尺寸,样式)。如果默认情况下启用了View选项,则必须将它们与UIView同步。例如,要调整大小,我们可以使用GeometryReader,以便我们的图像占据屏幕的整个宽度和我们定义的高度: var body: some View {
GeometryReader { geometry in
VStack {
WrappedCachedImage(height:300, imageUrl: imageUrl)
.frame(minWidth: 0, maxWidth: geometry.size.width,
minHeight: 0, maxHeight: 300)
}
}
}
原则上,对于这种情况,可以将嵌入式UIView的使用视为过度设计。因此,现在让我们尝试通过DataFlow SwiftUI解决问题。我们拥有的视图取决于状态变量或一组变量,即 来自某个模型,该模型本身可以是此状态变量。本质上,这种交互是基于MVVM模式的。我们实现如下:- 创建一个自定义视图,在其中我们将使用SwiftUI控件;
- 创建一个ViewModel,我们将使用Model(ImageManager)的逻辑转移到其中。
为了在View和ViewModel之间建立连接,ViewModel必须实现ObservableObject协议,并以ObservedObject的身份连接到View 。class CachedImageModel : ObservableObject {
@Published var image: UIImage = UIImage()
private var urlString: String = ""
init(urlString:String) {
self.urlString = urlString
}
func loadImage() {
ImageManager.sharedInstance
.receiveImage(forKey: urlString) {[weak self] (im) in
guard let self = self else {return}
DispatchQueue.main.async {
self.image = im
}
}
}
}
在其生命周期的onAppear方法中的View调用ViewModel方法,并从其@Published属性获取最终图像:struct CachedLoaderImage : View {
@ObservedObject var model:CachedImageModel
init(withURL url:String) {
self.model = CachedImageModel(urlString: url)
}
var body: some View {
Image(uiImage: model.image)
.resizable()
.onAppear{
self.model.loadImage()
}
}
}
还有一个声明性的Combine API与DataFlow SwiftUI一起使用。使用它与使用响应式框架(相同的RxSwift)非常相似:有主题,有订阅者,有类似的管理方法,有可取消的(而不是Disposable)。class ImageLoader: ObservableObject {
@Published var image: UIImage?
private var cancellable: AnyCancellable?
func load(url: String) {
cancellable = ImageManager.sharedInstance.publisher(for: url)
.map { UIImage(data: $0.data) }
.replaceError(with: nil)
.receive(on: DispatchQueue.main)
.assign(to: \.image, on: self)
}
如果我们的ImageManager最初是使用Combine编写的,则解决方案将如下所示。但是由于 ImageManager是用其他原理实现的,那么我们将尝试另一种方法。为了生成事件,我们将使用PasstroughSubject机制,该机制支持订阅的自动完成。 var didChange = PassthroughSubject<UIImage, Never>()
在为模型的UIImage属性分配值时,我们将发送一个新值:var data = UIImage() {
didSet {
didChange.send(data)
}
}
, .
我们的View的最终值在onReceive方法中“侦听”: var body: some View {
Image(uiImage: image)
.onReceive(imageLoader.didChange) { im in
self.image = im
}
}
因此,我们看了一个简单的示例,说明如何使现有代码适应SwiftUI。还有什么要补充的。如果现有的iOS解决方案对UI部分的影响更大,则最好通过UIViewRepresentable使用适应功能。在其他情况下,需要从状态的视图模型进行调整。在以下各部分中,我们将研究如何使现有项目的业务逻辑适应SwiftUI,使用导航,然后深入适应以进行更深入的组合。有关在SwiftUI下使用View的更多信息,请参见此处。