SwiftUI - EnvironmentObject nasıl View Modeline geçirilir?


16

Görünüm Modeli (yalnızca görünüm) tarafından erişilebilen bir EnvironmentObject oluşturmak için arıyorum.

Ortam nesnesi uygulama oturumu verilerini izler, örneğin oturum açıldı, erişim belirteci vb. Bu veriler, bir API'nin bu EnvironmentObjects'ten veri geçirmesine izin vermek için görünüm modellerine (veya gerektiğinde hizmet sınıflarına) iletilir.

Görünüm nesnesi görünümünden model sınıfının başlatıcısına oturum nesnesi geçmeye çalıştım ama bir hata alıyorum.

SwiftUI kullanarak EnvironmentObject'e görünüm modeline nasıl erişebilirim / geçebilirim?

Test projesi bağlantısına bakın: https://gofile.io/?c=vgHLVx


Neden EO olarak viewmodel'i geçmiyorsunuz?
E.Coms

Üstte göründüğü gibi, birçok görünüm modeli olacak, bağladığım yükleme sadece basitleştirilmiş bir örnek
Michael

2
Bu sorunun neden reddedildiğinden emin değilim, aynı şeyi merak ediyorum. Yaptığım şeylere cevap vereceğim, umarım başka biri daha iyi bir şey bulabilir.
Michael Ozeryansky

2
@ E.Coms EnvironmentObject öğesinin genellikle bir nesne olmasını bekledim. Birden fazla iş biliyorum, onları küresel olarak erişilebilir hale getirmek için bir kod kokusu gibi görünüyor.
Michael Ozeryansky

@Michael Buna bir çözüm buldunuz mu?
Brett

Yanıtlar:


3

Bir ViewModel olmamayı tercih ediyorum. (Belki yeni bir model için zaman?)

Projemi a RootViewve bazı çocuk görüşleri ile kurdum . Benim EnvironmentObject olarak RootViewbir Appnesne ile benim kurmak . Modellerime erişen ViewModel yerine tüm görünümlerim Uygulamadaki sınıflara erişir. Görünümü belirleyen ViewModel yerine, görünüm hiyerarşisi düzeni belirler. Bunu birkaç uygulama için pratikte yaptığımdan, görüşlerimin küçük ve spesifik kaldığını gördüm. Aşırı basitleştirme olarak:

class App {
   @Published var user = User()

   let networkManager: NetworkManagerProtocol
   lazy var userService = UserService(networkManager: networkManager)

   init(networkManager: NetworkManagerProtocol) {
      self.networkManager = networkManager
   }

   convenience init() {
      self.init(networkManager: NetworkManager())
   }
}
struct RootView {
    @EnvironmentObject var app: App

    var body: some View {
        if !app.user.isLoggedIn {
            LoginView()
        } else {
            HomeView()
        }
    }
}
struct HomeView: View {
    @EnvironmentObject var app: App

    var body: some View {
       VStack {
          Text("User name: \(app.user.name)")
          Button(action: { app.userService.logout() }) {
             Text("Logout")
          }
       }
    }
}

Önizlemelerimde, MockAppalt sınıfı olan bir App. MockApp, Mocked nesnesiyle belirtilen başlatıcıları başlatır. Burada UserService'in alay edilmesine gerek yoktur, ancak veri kaynağı (yani NetworkManagerProtocol) bunu yapar.

struct HomeView_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            HomeView()
                .environmentObject(MockApp() as App) // <- This is needed for EnvironmentObject to treat the MockApp as an App Type
        }
    }

}

Sadece bir not: Sanırım zincirlemekten kaçınmak daha iyi app.userService.logout(). userServiceözel olmalı ve yalnızca uygulama sınıfının içinden erişilmelidir. Yukarıdaki kod şöyle görünmelidir: Button(action: { app.logout() })Çıkış yapma işlevi doğrudan arayacaktır userService.logout().
pawello2222

@ pawello2222 Daha iyi değil, herhangi bir faydası olmayan sadece cephe deseni, ancak istediğiniz gibi yapabilirsiniz.
Michael Ozeryansky

3

Yapmamalısın. SwiftUI'nin MVVM ile en iyi şekilde çalıştığı yaygın bir yanlış anlamadır.

MVVM'nin SwfitUI'de yeri yok. Bir dikdörtgen dikebiliyorsanız,

bir üçgen şekli sığdırmak. Uygun olmaz.

Bazı gerçeklerle başlayalım ve adım adım çalışalım:

  1. ViewModel, MVVM'de bir modeldir.

  2. MVVM, değer türünü (örneğin; java'da böyle bir şey yok) dikkate almaz.

  3. Bir değer tipi model (durumsuz model) referanstan daha güvenli kabul edilir

    değişmezlik anlamında tip modeli (durumlu model).

Şimdi, MVVM, her değiştiğinde,

görünümü önceden belirlenmiş bir şekilde günceller. Bu bağlama olarak bilinir.

Bağlanma olmadan, endişelerin hoş bir şekilde ayrılması olmaz, örneğin; yeniden düzenleme

ve ilişkili durumları modellemek ve bunları görünümden ayrı tutmak.

Çoğu iOS MVVM geliştiricisinin başarısız olduğu iki şey şunlardır:

  1. iOS'un geleneksel java anlamında "bağlayıcı" bir mekanizması yoktur.

    Bazıları sadece bağlamayı görmezden gelir ve ViewModel nesnesini çağırmayı düşünür

    her şeyi otomatik olarak çözer; bazıları KVO tabanlı Rx'i tanıtır ve

    MVVM'nin işleri daha basit hale getirmesi gerektiğinde her şeyi karmaşık hale getirir.

  2. durumlu model çok tehlikeli

    çünkü MVVM ViewModel'e çok fazla, devlet yönetimine çok az önem verdi

    ve Kontrol yönetimindeki genel disiplinler; geliştiricilerin çoğu

    görünümü güncellemek için kullanılan durumu olan bir modeli yeniden kullanmak ve

    test edilebilir .

    bu yüzden Swift ilk etapta değer türünü ortaya koyuyor; olmayan bir model

    durum.

Şimdi sorunuza bakın: ViewModel'inizin EnvironmentObject'e (EO) erişip erişemeyeceğini mi soruyorsunuz?

Yapmamalısın. Çünkü SwiftUI'de View'a uygun bir model otomatik olarak

EO referansı. Örneğin;

struct Model: View {
    @EnvironmentObject state: State
    // automatic binding in body
    var body: some View {...}
}

Umarım insanlar kompakt SDK'nın nasıl tasarlandığını takdir edebilirler.

SwiftUI'de MVVM otomatiktir . Ayrı bir ViewModel nesnesine gerek yoktur

manuel olarak bağlanan ve ona iletilen bir EO referansı gerektiren.

Yukarıdaki kod olan MVVM. Örneğin; görüntülemek için bağlama ile bir model.

Ancak model değer türü olduğundan, modeli ve durumu yeniden düzenlemek yerine

görünümü modeli, kontrol refactor dışarı (örneğin, protokol uzantısı).

Bu, tasarım desenini sadece dil özelliklerine uyarlayan resmi SDK'dır.

zorlamak. Özün önceliği.

Çözümünüze bakın, temelde küresel olan singleton kullanmanız gerekir. Sen

koruması olmadan herhangi bir yere küresel erişimin ne kadar tehlikeli olduğunu bilmeli

referans tip model kullanmak zorunda olduğunuz için sahip olmadığınız değişmezlik!

TL; DR

SwiftUI'de MVVM'yi java olarak yapmazsınız. Ve bunu yapmanın Swift-y yolu gerekmiyor

Bunu yapmak için zaten yerleşik.

Umarım daha fazla geliştirici bunu görür çünkü bu popüler bir soru gibi görünüyordu.


1

Aşağıda benim için çalışan bir yaklaşım sağlanmıştır. Xcode 11.1 ile başlayan birçok çözümle test edilmiştir.

Sorun, EnvironmentObject'in enjekte edilmesinden kaynaklanıyor, genel şema

SomeView().environmentObject(SomeEO())

yani, ilk oluşturulan görünümde, ikinci oluşturulan ortam nesnesine, üçüncü ortam nesnesine görünüme enjekte edilir

Bu yüzden görünüm oluşturucu görünüm modeli oluşturmak / ayarlamak gerekirse çevre nesnesi henüz orada yok.

Çözüm: Her şeyi ayırın ve açık bağımlılık enjeksiyonu kullanın

Kodda nasıl göründüğü (genel şema)

// somewhere, say, in SceneDelegate

let someEO = SomeEO()                            // create environment object
let someVM = SomeVM(eo: someEO)                  // create view model
let someView = SomeView(vm: someVM)              // create view 
                   .environmentObject(someEO)

Burada herhangi bir değiş tokuş yoktur, çünkü ViewModel ve EnvironmentObject tasarım gereği referans türleridir (aslında ObservableObject), bu yüzden buraya geçiyorum ve sadece referanslar (aka işaretçiler)

class SomeEO: ObservableObject {
}

class BaseVM: ObservableObject {
    let eo: SomeEO
    init(eo: SomeEO) {
       self.eo = eo
    }
}

class SomeVM: BaseVM {
}

class ChildVM: BaseVM {
}

struct SomeView: View {
    @EnvironmentObject var eo: SomeEO
    @ObservedObject var vm: SomeVM

    init(vm: SomeVM) {
       self.vm = vm
    }

    var body: some View {
        // environment object will be injected automatically if declared inside ChildView
        ChildView(vm: ChildVM(eo: self.eo)) 
    }
}

struct ChildView: View {
    @EnvironmentObject var eo: SomeEO
    @ObservedObject var vm: ChildVM

    init(vm: ChildVM) {
       self.vm = vm
    }

    var body: some View {
        Text("Just demo stub")
    }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.