73641


SwiftUIのCombineを用いたMVVMで以下のような動作をどう組むか考えていました。  
以下の実装方法についてお聞きしたいことがあります。

### 【実装したい動作】

  1. 人名のリストを表示
  2. そのうち一つを選ぶと確認画面がmodalで表示される
  3. modalで確認を押すと、その人物についてのページを表示する

### 【実装結果】

  * ContentView.swift

[code]

    import SwiftUI
    
    struct Person: Identifiable, Hashable {
      var id: UUID = UUID()
      var name: String
      var age: Int
    }
    
    struct ContentView: View {
      @ObservedObject var contentViewModel = ContentViewModel()
      var persons: [Person] = [Person(name: "taro", age: 10), Person(name: "hoge", age: 15)]
      
      var body: some View {
        NavigationView {
          VStack {
            ForEach(self.persons) { person in
              NavigationLink(
                destination: NavView(person: person),
                tag: person,
                selection: self.$contentViewModel.selectedPerson
              ){
                Text(person.name)
                  .onTapGesture(perform: {
                    self.contentViewModel.apply(.signIn(person: person))
                  })
                  .sheet(isPresented: self.$contentViewModel.isShow, content: {
                    ModalView(
                      modalViewModel: self.contentViewModel.modalViewModel,
                      person: self.contentViewModel.targetPerson!
                    )
                  })
              }
            }
          }
        }
      }
    }
    
    struct ModalView: View {
      @ObservedObject var modalViewModel: ModalViewModel
      var person: Person
      
      var body: some View {
        VStack {
          Text("\(person.name)です")
          Button("Confirm") {
            self.modalViewModel.apply(.confirm)
          }
        }
      }
    }
    
    struct NavView: View {
      var person: Person
      
      var body: some View {
        Text("\(person.name)です!!!!")
      }
    }
    
[/code]

  * ViewModel.swift

[code]

    import Combine
    import SwiftUI
    
    class ContentViewModel: ObservableObject {
      // MARK: - Inputs
      enum Inputs {
        case signIn(person: Person)
      }
      var targetPerson: Person?
      
      // MARK: - Outputs
      @Published var isShow = false
      @Published var isConfirmed = false
      @Published var selectedPerson: Person?
      
      // MARK: - Privates
      private var cancellables: Set<AnyCancellable> = []
      
      @ObservedObject var modalViewModel = ModalViewModel()
      
      init(){
        self.bind()
      }
      
      func apply(_ action: Inputs) {
        switch action {
          case .signIn(let person):
            self.targetPerson = person
            self.isShow = true
        }
      }
      
      func bind() {
        self.modalViewModel
          .$isConfirmed
          .print("変更されました→\(isConfirmed)")
          .sink { (isConfirmed) in
            if isConfirmed {
              self.selectedPerson = self.targetPerson
              self.isShow = false
            }
          }
          .store(in: &cancellables)
      }
    }
    
    class ModalViewModel: ObservableObject {
      // MARK: - Inputs
      enum Inputs {
        case confirm
      }
      
      // MARK: - Outputs
      @Published var isConfirmed = false
      
      init(){}
      
      func apply(_ action: Inputs) {
        switch action {
          case .confirm:
            self.isConfirmed = true
        }
      }
    }
    
[/code]

これらの実装で望む動作は実現できました。

### 【お聞きしたいこと】

  1. ContentViewModelとModalViewModelどちらもにisConfirmedがあるのは仕方ないのでしょうか？

よろしくお願いいたします。


CC BY-SA 4.0
2021-01-13T14:54:14.460
73277
2021-01-28T08:44:31.393
2021-01-14T11:04:18.837
24870
24870
question
swift swiftui mvvm combine
MVVMで設計した際のModalを挟んだ画面遷移の実装について


> ContentViewModelとModalViewModelどちらもにisConfirmedがあるのは仕方ないのでしょうか？

そもそも `ContentViewModel`の`isConfirmed`は `print` でしか使われていません。  
（そして値も変わることがなさそうです。つまり使われていないと言えます）

よって、根本的にこの質問は成り立たないように感じます（`ContentViewModel`の`isConfirmed`は削除しまってよいと考えます）。

下記は`ContentViewModel`の`isConfirmed`に関する部分をコメントアウトして実際に試したコードです（全コードを1ファイルに載せるのは、お作法的にはよろしくないですが...）。意図通りの動きをすると思います。宣言とprintの2箇所コメントアウトしています。

[code]

    import SwiftUI
    import Combine
    
    struct Person: Identifiable, Hashable {
      var id: UUID = UUID()
      var name: String
      var age: Int
    }
    
    struct ContentView: View {
      @ObservedObject var contentViewModel = ContentViewModel()
      var persons: [Person] = [Person(name: "taro", age: 10), Person(name: "hoge", age: 15)]
      
      var body: some View {
        NavigationView {
          VStack {
            ForEach(self.persons) { person in
              NavigationLink(
                destination: NavView(person: person),
                tag: person,
                selection: self.$contentViewModel.selectedPerson
              ){
                Text(person.name)
                  .onTapGesture(perform: {
                    self.contentViewModel.apply(.signIn(person: person))
                  })
                  .sheet(isPresented: self.$contentViewModel.isShow, content: {
                    ModalView(
                      modalViewModel: self.contentViewModel.modalViewModel,
                      person: self.contentViewModel.targetPerson!
                    )
                  })
              }
            }
          }
        }
      }
    }
    
    struct ModalView: View {
      @ObservedObject var modalViewModel: ModalViewModel
      var person: Person
      
      var body: some View {
        VStack {
          Text("\(person.name)です")
          Button("Confirm") {
            self.modalViewModel.apply(.confirm)
          }
        }
      }
    }
    
    struct NavView: View {
      var person: Person
      
      var body: some View {
        Text("\(person.name)です!!!!")
      }
    }
    
    class ContentViewModel: ObservableObject {
      // MARK: - Inputs
      enum Inputs {
        case signIn(person: Person)
      }
      var targetPerson: Person?
      
      // MARK: - Outputs
      @Published var isShow = false
    //  @Published var isConfirmed = false
      @Published var selectedPerson: Person?
      
      // MARK: - Privates
      private var cancellables: Set<AnyCancellable> = []
      
      @ObservedObject var modalViewModel = ModalViewModel()
      
      init(){
        self.bind()
      }
      
      func apply(_ action: Inputs) {
        switch action {
          case .signIn(let person):
            self.targetPerson = person
            self.isShow = true
        }
      }
      
      func bind() {
        self.modalViewModel
          .$isConfirmed
    //      .print("変更されました→\(isConfirmed)")
          .sink { (isConfirmed) in
            if isConfirmed {
              self.selectedPerson = self.targetPerson
              self.isShow = false
            }
          }
          .store(in: &cancellables)
      }
    }
    
    class ModalViewModel: ObservableObject {
      // MARK: - Inputs
      enum Inputs {
        case confirm
      }
      
      // MARK: - Outputs
      @Published var isConfirmed = false
      
      init(){}
      
      func apply(_ action: Inputs) {
        switch action {
          case .confirm:
            self.isConfirmed = true
        }
      }
    }
    
[/code]


CC BY-SA 4.0
2021-01-28T08:20:11.593
73641
2021-01-28T08:44:31.393
2021-01-28T08:44:31.393
9008
9008
73277
answer