SwiftUI - Як передати EnvironmentObject у модель перегляду?


16

Я хочу створити EnvironmentObject, доступ до якого може отримати модель перегляду (не лише вид).

Об'єкт Environment відслідковує дані сеансу програми, наприклад loggedIn, маркер доступу тощо, ці дані будуть передані в моделі перегляду (або класи класів обслуговування, де це потрібно), щоб дозволити виклику API передавати дані з цього EnvironmentObjects.

Я намагався передати об’єкт сеансу ініціалізатору класу моделі view з виду, але отримав помилку.

як я можу отримати доступ / передати EnvironmentObject у модель перегляду за допомогою SwiftUI?

Дивіться посилання на тестовий проект: https://gofile.io/?c=vgHLVx


Чому б не передати viewmodel як ЕО?
E.Coms

Здається, вгорі буде багато моделей перегляду, завантаження, яке я пов’язав, - лише спрощений приклад
Майкл

2
Я не впевнений, чому це питання було скасовано, мені цікаво те саме. Я відповім, що я зробив, сподіваюся, хтось інший може придумати щось краще.
Михайло Озерянський

2
@ E.Coms Я очікував, що EnvironmentObject взагалі буде одним об’єктом. Я знаю багаторазові роботи, схоже на запах коду, щоб зробити їх такими глобально доступними.
Михайло Озерянський

@Michael Ви навіть знайшли рішення для цього?
Бретт

Відповіді:


3

Я вирішу не мати ViewModel. (Може, час для нового зразка?)

Я налаштував свій проект із RootViewдеякими та дитячими переглядами. Я насторіть RootViewз Appоб'єктом як EnvironmentObject. Замість того, щоб ViewModel отримував доступ до Моделей, усі мої погляди мають доступ до класів у додатку. Замість того, щоб ViewModel визначав макет, ієрархія перегляду визначає макет. Здійснюючи це на практиці для кількох додатків, я виявив, що мої погляди залишаються невеликими та конкретними. Як надмірне спрощення:

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

У своїх попередніх переглядах я ініціалізую a, MockAppщо є підкласом App. MockApp ініціалізує призначені ініціалізатори об'єктом Mocked. Тут UserService не потрібно глузувати, але джерело даних (тобто NetworkManagerProtocol) робить.

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

}

Лише зауваження: я думаю, що краще уникати подібних прикувань app.userService.logout(). userServiceмає бути приватним та доступ до нього лише з класу додатків. Код, наведений вище, повинен виглядати так: Button(action: { app.logout() })і функція виходу буде безпосередньо дзвонити userService.logout().
pawello2222

@ pawello2222 Це не краще, це просто візерунок фасаду без будь-якої користі, але ви можете робити як хочете.
Михайло Озерянський

3

Ви не повинні. Загальна помилка, що SwiftUI найкраще працює з MVVM.

MVVM не має місця в SwfitUI. Ви просите, чи можете ви засунути прямокутник до

підходять під форму трикутника. Це не підходило б.

Почнемо з деяких фактів і покроково працюємо:

  1. ViewModel - модель в MVVM.

  2. MVVM не приймає до уваги тип значення (наприклад, такого немає у Java).

  3. Модель типу цінності (модель без стану) вважається більш безпечною, ніж еталонною

    модель типу (модель зі станом) у сенсі незмінюваності.

Тепер, MVVM вимагає, щоб ви створили модель таким чином, що коли б вона не змінювалася, вона змінювалась

оновлення перегляду певним чином. Це відомо як зв'язування.

Без прив’язки у вас не буде приємного роз'єднання питань, наприклад; рефакторинг

модель та асоційовані стани та зберігаючи їх окремо від перегляду.

Це дві речі, у яких більшості розробників iOS MVVM не вдається:

  1. iOS не має механізму "прив'язки" в традиційному сенсі Java.

    Деякі просто ігнорують прив'язку та думають викликати об’єкт ViewModel

    автоматично вирішує все; деякі з них запровадять Rx на базі KVO та

    ускладнити все, коли MVVM повинен спростити справи.

  2. модель зі станом просто надто небезпечна

    тому що MVVM приділяє занадто багато уваги ViewModel, занадто мало - державному управлінню

    та загальні дисципліни з управління контролю; більшість розробників в кінцевому підсумку

    мислення модель зі станом, яка використовується для оновлення перегляду, є багаторазовою і

    перевіряється .

    саме тому Swift вводить в першу чергу тип значення; модель без

    держава.

Тепер до вашого питання: ви запитуєте, чи може ваш ViewModel мати доступ до EnvironmentObject (EO)?

Ви не повинні. Тому що в SwiftUI є модель, яка відповідає програмі View автоматично

посилання на ЕО. Наприклад;

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

Я сподіваюся, що люди зможуть оцінити, наскільки компактний SDK розроблений.

У SwiftUI MVVM працює автоматично . Немає потреби в окремому об’єкті ViewModel

що вручну прив'язується до перегляду, що вимагає переданої на нього посилання на ЕО.

Наведений вище код - MVVM. Наприклад; модель із прив’язкою для перегляду.

Але оскільки модель є значенням типу, то замість того, щоб переробляти модель і стан як

Перегляньте модель, ви рефакторируете контроль (наприклад, у розширенні протоколу).

Це офіційний SDK, адаптуючи модель дизайну до мовної функції, а не просто

її виконання. Речовина над формою.

Подивіться на ваше рішення, ви повинні використовувати одиночну, яка в основному є глобальною. ви

Потрібно знати, наскільки небезпечним є доступ до глобального місця будь-де без захисту

незмінність, якої у вас немає, оскільки ви повинні використовувати модель еталонного типу!

TL; DR

Ви не робите MVVM на Java у SwiftUI. І швидкий спосіб зробити це не потрібно

щоб це зробити, це вже вбудовано.

Сподіваюся, більше розробників побачить це, оскільки це здавалося популярним питанням.


1

Нижче представлений підхід, який працює для мене. Тестований з багатьма рішеннями розпочався з Xcode 11.1.

Проблема виникла з того, як вводиться EnvironmentObject, із загальної схеми

SomeView().environmentObject(SomeEO())

тобто, спочатку - створений вид, у другому створений об'єкт оточення, у третьому об'єкт середовища, який вводиться у вигляд

Таким чином, якщо мені потрібно створити / налаштувати модель перегляду в конструкторі перегляду, об'єкт середовища ще не присутній.

Рішення: розбийте все на частини і використовуйте явну ін'єкцію залежності

Ось як це виглядає в коді (загальна схема)

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

Тут немає ніяких компромісів, тому що ViewModel та EnvironmentObject за задумом є типовими типами посилань (насправді ObservableObject), тому я тут і там передаю лише посилання (ака-покажчики).

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")
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.