72979


長文になり申し訳ありません。  
いろいろ試したのですが、どうにも詰まってしまったのでどなたかにお助けいただければと思い、お聞きしています。

## 【実現したいこと】

NavigationLinkが複数あり、そこをタップしたら何かしらの方法でログイン処理をしてログインが成功したらNavigationLinkの遷移先に移動したい。

## 【前提】

ユーザ情報の管理としてFirestoreを用いていて以下のようなコードでユーザ情報を取得・変更監視しています。

  * 情報取得部分…変数dbの定義などは他の部分でしています、またUserInfoのドキュメントにGroupsというサブコレクションがあり同じように取得しています

[code]

    static func getUserInfo(uid: String, completion: @escaping (UserInfo) -> ()) {
        var result: UserInfo = UserInfo()
        db.collection("UserInfo").document(uid).addSnapshotListener{ documentSnapshot, err in
            if let err = err {
                print("Error getting documents: \(err)")
            } else {
                if let document = documentSnapshot, document.exists {
                    if let decodedData = try! document.data(as: UserInfo.self, decoder: Firestore.Decoder()) {
                        result = decodedData
                        completion(result)
                    } else {
                        print("Cannot decode")
                    }
                } else {
                    print("Document does not exist")
                }
            }
        }
    }
    
[/code]

  * ユーザ情報管理部分…classをObservableObjectに準拠して更新を管理しています。その上で各ViewではEnvironmentObjectととして持っています。

[code]

    class SessionStore: ObservableObject {
        @Published var userinfo: UserInfo?
        @Published var groupinfo: GroupInfo?
        var handle: AuthStateDidChangeListenerHandle?
    
        func listen () {
            handle = Auth.auth().addStateDidChangeListener { (auth, user) in
                if let user = user {
                    FirebaseController.getUserInfo(uid: user.uid) { uinfo in
                        self.userinfo = uinfo
                        FirebaseController.getUserInfoGroup(uid: user.uid) { uigs in
                            self.userinfo?.groups = uigs
                        }
                    }
                } else {
                    self.session = nil
                }
            }
        }
    }
    
[/code]

## 【試したこと】

### 1\. Sheetでログインし成功したらNavigationLinkの遷移先に移動

コード

  * 遷移元

[code]

    struct HomeView: View {
        @EnvironmentObject var session: SessionStore
        @State var groupinfos: [GroupInfo] = []
        @State var showSignedInView: Bool = false
        @State var selectedGroupInfo: GroupInfo = GroupInfo()
        
        var body: some View {
            NavigationView {
                VStack {
                    Text("以下からグループをお選びください")
                    ForEach(self.groupinfos) { gi in
                        NavigationLink(destination: GroupHomeView(groupInfo: gi)) {
                            GroupIconView(groupInfo: gi)
                                .padding()
                                .onTapGesture(perform: {
                                    self.selectedGroupInfo = gi
                                    if let userinfo = session.userinfo, let groups = userinfo.groups, let uginfo = groups[self.selectedGroupInfo.nameEng] {
                                        if uginfo.passwd == self.selectedGroupInfo.passwd {
                                            self.showSignedInView = false
                                        }
                                    } else {
                                        self.showSignedInView = true
                                    }
                                })
                                .sheet(isPresented: self.$showSignedInView, content: {
                                    GroupSignInView(showSignedInView: self.$showSignedInView, groupInfo: self.$selectedGroupInfo)
                                })
                        }
                    }
                }.onAppear(perform: {
                    FirebaseController.getAllGroups { gis in
                        self.groupinfos = gis
                    }
                })
            }
        }
    }
    
[/code]

  * ログイン用モーダル

[code]

    struct GroupSignInView: View {
        @EnvironmentObject var session: SessionStore
        @State var password: String = ""
        @State var error: Bool = false
        @Binding var showSignedInView: Bool
        @Binding var groupInfo: GroupInfo
        
        var body: some View {
            Form {
                if(error){
                    Section {
                        Text("ログインに失敗しました")
                            .foregroundColor(.red)
                    }
                }
                Section {
                    Text(self.groupInfo.name)
                    SecureField("パスワード", text: $password)
                    Button("ログイン", action: {
                        self.showSignedInView = (self.password != groupInfo.passwd)
                        if self.showSignedInView {
                            self.error = true
                        } else {
                            // この関数はUserInfoのログインユーザのドキュメントのGroupサブコレクションにログイン済み情報を持たせています。
                            FirebaseController.addGroupsToUserInfo(groupNameEng: groupInfo.nameEng, userDocumentId: session.session!.uid, groupPasswd: groupInfo.passwd)
                        }
                    })
                }
            }
        }
    }
    
[/code]

  * 遷移先View

[code]

    struct GroupHomeView: View {
        @EnvironmentObject var session: SessionStore
        var groupInfo: GroupInfo
        
        var body: some View {
            Group {
                Text(groupInfo.nameEng)
            }
        }
    }
    
[/code]

### 2\. NavigationLinkの遷移先でログイン処理

  * 遷移元View

[code]

    struct HomeView: View {
        @EnvironmentObject var session: SessionStore
        @State var groupinfos: [GroupInfo] = []
        
        var body: some View {
            NavigationView {
                VStack {
                    Text("以下からグループをお選びください")
                        .font(.title)
                        .frame(width: 300, height: 50, alignment: .top)
                    ForEach(self.groupinfos) { gi in
                        NavigationLink(destination: GroupHomeView(groupInfo: gi)) {
                            GroupIconView(groupInfo: gi)
                                .padding()
                        }
                    }
                }.onAppear(perform: {
                    FirebaseController.getAllGroups { gis in
                        self.groupinfos = gis
                    }
                })
            }
        }
    }
    
[/code]

  * 遷移先View

[code]

    struct GroupHomeView: View {
        @EnvironmentObject var session: SessionStore
        @State var signedInGroup: Bool = false
        var groupInfo: GroupInfo
        
        var body: some View {
            // ここでログイン済かどうか分けています
            if signedInGroup {
                Text("ログイン成功")
            } else {
                GroupSignInView(signedInGroup: self.$signedInGroup, groupInfo: self.groupInfo)
            }
        }
    }
    
[/code]

  * ログイン用View…ほぼ1と同じです。

[code]

    struct GroupSignInView: View {
        @EnvironmentObject var session: SessionStore
        @State var password: String = ""
        @State var error: Bool = false
        @Binding var signedInGroup: Bool
        var groupInfo: GroupInfo
        
        var body: some View {
            Form {
                if(error){
                    Section {
                        Text("ログインに失敗しました")
                            .foregroundColor(.red)
                    }
                }
                Section {
                    Text(self.groupInfo.name)
                    SecureField("パスワード", text: $password)
                    Button("ログイン", action: {
                        self.signedInGroup = (self.password == groupInfo.passwd)
                        if !self.signedInGroup {
                            self.error = true
                        } else {
                            FirebaseController.addGroupsToUserInfo(groupNameEng: groupInfo.nameEng, userDocumentId: session.session!.uid, groupPasswd: groupInfo.passwd)
                        }
                    })
                }
            }.onAppear(perform: {
                if let userinfo = session.userinfo, let groups = userinfo.groups, let uginfo = groups[self.groupInfo.nameEng] {
                    if uginfo.passwd == groupInfo.passwd {
                        self.signedInGroup = true
                    }
                }
            })
        }
    }
    
[/code]

## 【発生した問題】

  1. モーダルでログイン後NavigationLinkの遷移先に移動しなかった。その後同グループを押すも、モーダルは出ない(= ログイン済み情報の取得はうまくいっている)が、遷移しない。
  2. ログイン後にHomeViewに戻されてしまう。firestore側のUserInfoのサブコレクションGroupsがログイン後に変更されるからリロードされた??

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

  1. 1の案についてこのようなmodal→NavigationLinkの遷移は可能でしょうか？NavigationLinkのisActiveを利用しようかと思ったのですが、NavigationLinkが複数ありそれぞれにフラグが必要かと思うので、他にもっとわかりやすい方法があればと思いそちらがいいなと思っています。それしかなかったら実装します。
  2. 2の案についてHomeViewに戻されてしまうのは何故でしょうか？
  3. ↑の問題を解決する方法はあるのでしょうか？
  4. 試した方法にこだわるつもりはないので、他の実現方法があったらお聞きしたいです。ただし、諸事情によりUserInfoのサブコレクションにログイン済みグループをGroupsとして保持するのは、変更が厳しくなっています(この構成は良くないと思うのですが申し訳ありません)。

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


CC BY-SA 4.0
2020-12-29T14:28:40.087
72971
2020-12-30T05:46:10.443
24870
question
swift ios firebase swiftui
SwiftUI: NavigationLinkの遷移前にログイン処理(Firestoreのデータ更新含む)を挟む実装がうまくいきません


## 案1の問題点

`NavigationLink`内の`label`として働くViewに対して、`onTapGesture`を設定しているため、タップイベントの処理が横取りされる形になって、`NavigationLink`としての動作が出来ないのだろうと思われます。

### 案1の解決方法

>
> 1の案についてこのようなmodal→NavigationLinkの遷移は可能でしょうか？NavigationLinkのisActiveを利用しようかと思ったのですが、NavigationLinkが複数ありそれぞれにフラグが必要かと思うので、他にもっとわかりやすい方法があればと思いそちらがいいなと思っています。それしかなかったら実装します。

`NavigationLink`には、`isActive:`の他に、`tag:selection:`を持つイニシャライザがあり、複数ある場合にはそちらを使用することが想定されています。

[code]

    struct HomeView: View {
        @EnvironmentObject var session: SessionStore
        @State var groupinfos: [GroupInfo] = []
        @State var showSignedInView: Bool = false
        @State var selectedGroupInfo: GroupInfo = GroupInfo()
        @State var selectdGroup: GroupInfo? = nil //<-
        
        var body: some View {
            NavigationView {
                VStack {
                    Text("以下からグループをお選びください")
                    ForEach(self.groupinfos) { gi in
                        NavigationLink(destination: GroupHomeView(groupInfo: gi), tag: gi, selection: $selectdGroup) { //<-
                            GroupIconView(groupInfo: gi)
                                .padding()
                                .onTapGesture(perform: {
                                    self.selectedGroupInfo = gi
                                    if let userinfo = session.userinfo, let groups = userinfo.groups, let uginfo = groups[self.selectedGroupInfo.nameEng] {
                                        if uginfo.passwd == self.selectedGroupInfo.passwd {
                                            self.showSignedInView = false
                                        }
                                    } else {
                                        self.showSignedInView = true
                                    }
                                })
                                .sheet(isPresented: self.$showSignedInView, content: {
                                    GroupSignInView(showSignedInView: self.$showSignedInView, groupInfo: self.$selectedGroupInfo)
                                        //↓
                                        .onDisappear {
                                            self.selectdGroup = self.selectedGroupInfo
                                        }
                                })
                        }
                    }
                }.onAppear(perform: {
                    FirebaseController.getAllGroups { gis in
                        self.groupinfos = gis
                    }
                })
            }
        }
    }
    
[/code]

`GroupInfo`を`Hashable`にしろなんて言うエラーが出たり、ログイン失敗のことが考えられていなかったりしますが、そこら辺は現在提示されている情報だけではどうしようもないので、ご自身で解決してください。

* * *

## 案2の問題点

提示された以外のコードをこちらで補った場合には、「ログイン後にHomeViewに戻されてしまう」と言う事象は確認できませんでした。

このような問題をご質問される場合、本来のプロジェクトとは別に、問題となる事象を再現するための **必要最小限**
の内容となるよう簡略化したプロジェクトを作成し、そのプロジェクトのコードを全て掲載することをお勧めします。(Firebaseのような要素は取り除いた方がより多くの方に試してもらえるでしょう。)


CC BY-SA 4.0
2020-12-30T05:46:10.443
72979
2020-12-30T05:46:10.443
13972
72971
answer