Mengadaptasi solusi bisnis Anda yang sudah ada untuk SwiftUI. Bagian 2

Hari baik untuk semua! Dengan Anda, saya, Anna Zharkova, pengembang seluler terkemuka Usetech
, di bagian ini, kita akan membahas kasus bagaimana mengadaptasi solusi turnkey untuk proyek di SwiftUI. Jika Anda tidak terlalu terbiasa dengan teknologi ini, saya menyarankan Anda untuk membiasakan diri dengan pengenalan singkat tentang topik ini .

Jadi, mari kita lihat contoh sederhana tentang bagaimana Anda dapat menggunakan pustaka siap pakai untuk aplikasi iOS standar dalam aplikasi di SwiftUI.

Mari kita ambil solusi klasik: unggah gambar secara asinkron menggunakan perpustakaan SDWebImage.



Untuk kenyamanan, bekerja dengan perpustakaan diringkas dalam ImageManager, yang memanggil:

  • SDWebImageDownloader
  • SDImageCache

untuk mengunduh gambar dan caching.

Secara tradisional, komunikasi dengan hasil UIImageView penerima diimplementasikan dalam 2 cara:

  • dengan melewatkan tautan lemah ke UIImageView yang sama ini;
  • dengan melewati blok penutupan ke metode ImageManager


Panggilan ke ImageManager biasanya dirangkum dalam ekstensi UIImageView:

extension UIImageView {
 func setup(by key: String) {
        ImageManager.sharedInstance.setImage(toImageView: self, forKey: key)
    }
}

baik di kelas penerus:

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)
    }
}

Sekarang mari kita coba mengacaukan solusi ini ke SwiftUI. Namun, ketika beradaptasi, kita harus mempertimbangkan fitur kerangka kerja berikut:

- Tampilan - struktur. Warisan tidak didukung


- Perpanjangan dalam arti biasa tidak berguna. Tentu saja, kita dapat menulis beberapa metode untuk memperluas fungsionalitas, tetapi kita harus mengikatnya entah bagaimana ke DataFlow ;

Kami mendapatkan masalah untuk mendapatkan umpan balik dan kebutuhan untuk mengadaptasi seluruh logika interaksi dengan UI ke DataDriven Flow.

Untuk solusinya, kita bisa pergi dari sisi Tampilan dan dari sisi adaptasi Aliran Data.

Mari kita mulai dengan View.

Untuk mulai dengan, ingat bahwa SwiftUI tidak ada dengan sendirinya, tetapi sebagai tambahan untuk UIKit. Pengembang SwiftUI telah menyediakan mekanisme untuk digunakan dalam SwiftUI UIView, yang analognya tidak ada di antara kontrol yang sudah jadi. Untuk kasus seperti itu, ada protokol UIViewRepresentable dan UIViewControllerRepresentable untuk mengadaptasi UIView dan UIViewController masing-masing.

Buat struktur tampilan yang mengimplementasikan UIViewRepresentable, di mana kami mendefinisikan kembali metode:

  • makeUiView;
  • perbaruiUIView

di mana kami menunjukkan UIView mana yang kami gunakan dan mengatur pengaturan dasar mereka. Dan jangan lupa PropertyWrappers untuk properti yang bisa berubah.

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
    }
}

Kita dapat menyematkan kontrol baru yang dihasilkan di View SwiftUI:



Pendekatan ini memiliki keuntungan:

  • Tidak perlu mengubah operasi perpustakaan yang ada
  • Logika dienkapsulasi dalam embedded UIView.

Tetapi ada tanggung jawab baru. Pertama, Anda perlu memantau manajemen memori dalam bundel View-UIView. Karena struktur View, maka semua pekerjaan dengan mereka dilakukan di latar belakang oleh kerangka itu sendiri. Namun pembersihan objek baru jatuh di pundak pengembang.

Kedua, langkah-langkah tambahan diperlukan untuk penyesuaian (ukuran, gaya). Jika untuk Lihat opsi ini diaktifkan secara default, maka mereka harus disinkronkan dengan UIView.

Misalnya, untuk menyesuaikan ukuran, kita dapat menggunakan GeometryReader sehingga gambar kita menempati seluruh lebar layar dan ketinggian yang ditentukan oleh kita:

 var body: some View {
      GeometryReader { geometry in 
        VStack {
           WrappedCachedImage(height:300, imageUrl: imageUrl) 
           .frame(minWidth: 0, maxWidth: geometry.size.width,
                     minHeight: 0, maxHeight: 300)
        }
    }
}

Pada prinsipnya, untuk kasus seperti itu, penggunaan UIView yang disematkan dapat dianggap sebagai rekayasa berlebihan . Jadi sekarang mari kita coba untuk menyelesaikan melalui DataFlow SwiftUI.

Pandangan yang kita miliki tergantung pada variabel keadaan atau sekelompok variabel, yaitu dari model tertentu, yang dengan sendirinya bisa menjadi variabel keadaan ini. Intinya, interaksi ini didasarkan pada pola MVVM.

Kami menerapkan sebagai berikut:

  • buat Tampilan kustom, di dalamnya kita akan menggunakan kontrol SwiftUI;
  • buat ViewModel di mana kita mentransfer logika bekerja dengan Model (ImageManager).


Agar ada koneksi antara View dan ViewModel, ViewModel harus mengimplementasikan protokol ObservableObject dan menyambungkan ke View sebagai 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
            }
        }
    }
}

Lihat di metode onAppear dari siklus-hidupnya memanggil metode ViewModel dan mendapatkan gambar akhir dari properti @Published-nya:

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()
        }
    }
    
}

Ada juga Combine API deklaratif untuk bekerja dengan DataFlow SwiftUI . Bekerja dengannya sangat mirip dengan bekerja dengan kerangka kerja reaktif (RxSwift yang sama): ada subjek, ada pelanggan, ada metode manajemen yang serupa, ada yang dapat dibatalkan (bukan 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)
 }

Jika ImageManager kami awalnya ditulis menggunakan Combine, maka solusinya akan terlihat seperti ini.

Tapi sejak itu ImageManager diimplementasikan dengan prinsip lain, maka kita akan mencoba cara lain. Untuk menghasilkan suatu acara, kami akan menggunakan mekanisme PasstroughSubject, yang mendukung pelengkapan langganan secara otomatis.

    var didChange = PassthroughSubject<UIImage, Never>()

Kami akan mengirimkan nilai baru ketika menetapkan nilai ke properti UIImage dari model kami:

var data = UIImage() {
        didSet {
            didChange.send(data)
        }
    }
 ,    .

Nilai akhir dari View kami "mendengarkan" dalam metode onReceive:

 var body: some View {
       Image(uiImage: image)
            .onReceive(imageLoader.didChange) { im in
            self.image = im
           //-   
        }
    }

Jadi, kami telah melihat contoh sederhana tentang bagaimana Anda dapat mengadaptasi kode yang ada ke SwiftUI.
Apa yang tersisa untuk ditambahkan. Jika solusi iOS yang ada lebih memengaruhi bagian UI, lebih baik menggunakan adaptasi melalui UIViewRepresentable. Dalam kasus lain, adaptasi dari model tampilan negara diperlukan.

Pada bagian berikut, kita akan melihat bagaimana mengadaptasi logika bisnis dari proyek yang ada untuk SwiftUI , bekerja dengan navigasi, dan kemudian menggali adaptasi untuk Menggabungkan sedikit lebih dalam.

Untuk informasi lebih lanjut tentang bekerja dengan Tampilan di bawah SwiftUI, lihat di sini .

All Articles