يوم جيد للجميع! معك أنا ، أنا زاركوفا ، مطور جوال رائد في Usetech.في هذا الجزء ، سنناقش بالفعل حالة كيفية تكييف حل جاهز لمشروع مع SwiftUI. إذا لم تكن على دراية خاصة بهذه التكنولوجيا ، أنصحك بأن تتعرف على مقدمة موجزة للموضوع .لذا ، دعنا نلقي نظرة على مثال بسيط لكيفية استخدام مكتبة جاهزة لتطبيق iOS قياسي في تطبيق على SwiftUI.لنأخذ الحل الكلاسيكي: تحميل الصور بشكل غير متزامن باستخدام مكتبة 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 ؛لدينا مشكلة في الحصول على التعليقات والحاجة إلى تكييف منطق التفاعل بالكامل مع واجهة المستخدم مع DataDriven Flow.بالنسبة للحل ، يمكننا الانتقال من جانب العرض ومن جانب التكيف مع تدفق البيانات.لنبدأ مع العرض.بادئ ذي بدء ، تذكر أن SwiftUI غير موجود بمفرده ، ولكن كإضافة إلى UIKit. قدم مطورو SwiftUI آلية للاستخدام في SwiftUI UIView ، وهي ليست من بين عناصر التحكم الجاهزة. لمثل هذه الحالات ، هناك بروتوكولات UIViewRepresentable و UIViewControllerRepresentable لتكييف UIView و UIViewController على التوالي.إنشاء بنية عرض تطبق UIViewRpresentable ، حيث نعيد تعريف الأساليب:حيث نشير إلى 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. منذ هيكل العرض ، يتم تنفيذ كل العمل معهم في الخلفية بواسطة الإطار نفسه. لكن تنظيف الأشياء الجديدة يقع على أكتاف المطور.ثانيًا ، يلزم اتخاذ خطوات إضافية للتخصيص (الأحجام والأنماط). إذا تم تمكين عرض هذه الخيارات افتراضيًا ، فيجب تزامنها مع 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).
لكي يكون هناك اتصال بين العرض و ViewModel ، يجب أن يقوم ViewModel بتطبيق بروتوكول ObservableObject والاتصال بـ View as a ObservedObject .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 لدورة حياتها طريقة 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()
}
}
}
هناك أيضًا واجهة برمجة تطبيقات موحّدة للعمل مع DataFlow SwiftUI . العمل معها يشبه إلى حد كبير العمل مع أطر العمل التفاعلية (نفس RxSwift): هناك مواضيع ، هناك مشتركون ، هناك طرق إدارة مماثلة ، هناك يمكن إلغاؤه (بدلاً من المتاح).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)
}
}
, .
القيمة النهائية للعرض الخاص بنا "تستمع" في طريقة onReceive: var body: some View {
Image(uiImage: image)
.onReceive(imageLoader.didChange) { im in
self.image = im
}
}
لذا ، نظرنا إلى مثال بسيط لكيفية تكييف الشفرة الحالية مع SwiftUI.ما تبقى للإضافة. إذا كان حل iOS الحالي يؤثر على جزء واجهة المستخدم أكثر ، فمن الأفضل استخدام التكيف من خلال UIViewRepresentable. في حالات أخرى ، هناك حاجة إلى التكيف من نموذج عرض الدولة.في الأجزاء التالية ، سنلقي نظرة على كيفية تكييف منطق الأعمال لمشروع حالي مع SwiftUI ، والعمل مع التنقل ، ثم البحث في التكيف للدمج بشكل أعمق.لمزيد من المعلومات حول العمل مع عرض تحت SwiftUI ، انظر هنا .