LoginSignup
16
7

More than 3 years have passed since last update.

SwiftUIでList内のViewに複数の遷移先を持つ

Last updated at Posted at 2019-10-01

iOSアプリを開発する上で、UIKitのUITableViewCellでタップしたUIViewによって遷移先を変えたいことは、よくあると思います。
同じことをSwiftUIの ListNavigationView で実現しようとした際に試したことを備忘録として残しておきます。
環境はXcode11.0 (11A420a)、iOS13.0です。

NavigationLink を2つ指定した場合

import SwiftUI

struct ContentView: View {
    var body: some View {
        NavigationView {
            List {
                Row(city: "Twentynine Palms", state: "California")
                Row(city: "Port Alsworth", state: "Alaska")
                Row(city: "Skagway", state: "Alaska")
            }
        }
    }
}

struct Row: View {
    var city: String
    var state: String
    var body: some View {
        VStack(alignment: .leading, spacing: 4) {
            NavigationLink(city, destination: Text(city))
            NavigationLink(state, destination: Text(state))
        }
    }
}

Pushの遷移を行う場合は NavigationLink 使用するので、素直に実装すると、このようになるかと思います。
これをビルドすると以下のような挙動になりました。

Oct-01-2019 23-29-42.gif

1回のタップで遷移を2回おこなっていて、意図した挙動になっていません。(また表示自体も disclosureIndicator が2つ表示されてしまっています)

Button を2つ配置して NavigationLink.init(destination:isActive:label:) を使用した場合

NavigationLink のインターフェイスを調べているとNavigationLink.init(destination:isActive:label:) というメソッドが定義されています。
ドキュメントには定義以上の情報がないですが、利用できそうです。


import SwiftUI

struct ContentView: View {
    var body: some View {
        NavigationView {
            List {
                Row(city: "Twentynine Palms", state: "California")
                Row(city: "Port Alsworth", state: "Alaska")
                Row(city: "Skagway", state: "Alaska")
            }
        }
    }
}

struct Row: View {
    var city: String
    var state: String
    @State var buttonDidTap = (false, "")
    var body: some View {
        VStack(alignment: .leading, spacing: 4) {
            Button(city) {
                self.buttonDidTap = (true, self.city)
            }
            Button(state) {
                self.buttonDidTap = (true, self.state)
            }
            NavigationLink(destination: Text(buttonDidTap.1), isActive: $buttonDidTap.0) {
                EmptyView()
            }
        }
    }
}

実装は上記のように各ボタンのアクションを NavigationLink にバインドしています。

Oct-02-2019 00-08-27.gif

一見上手く遷移できているようですが、どのボタンをタップしても2番目にあるボタンの遷移先になってしまっています。
各ボタンのアクションにブレイクポイントを貼って、もう少し挙動を確認してみます。

Oct-02-2019 00-16-05.gif

ボタンを1つだけタップしているはずですが、各ボタンのアクションが実行されてしまっています。
そのため最後のボタンイベントが優先されてしまい、このような挙動になってしまっているようです。

onTapGesture(count:perform:) を使用した場合

View.onTapGesture(count:perform:) という、これまでに試した方法よりも抽象度の低いメソッドがあるようです。これを使用して試してみます。


import SwiftUI

struct ContentView: View {
    var body: some View {
        NavigationView {
            List {
                Row(city: "Twentynine Palms", state: "California")
                Row(city: "Port Alsworth", state: "Alaska")
                Row(city: "Skagway", state: "Alaska")
            }
        }
    }
}

struct Row: View {
    var city: String
    var state: String
    @State var buttonDidTap = (false, "")
    var body: some View {
        VStack(alignment: .leading, spacing: 4) {
            Text(city)
                .onTapGesture {
                    self.buttonDidTap = (true, self.city)
                }
            Text(state)
                .onTapGesture {
                    self.buttonDidTap = (true, self.state)
                }
            NavigationLink.init(destination: Text(buttonDidTap.1), isActive: $buttonDidTap.0) {
                EmptyView()
            }
        }
    }
}

Oct-08-2019 23-17-13.gif

上記が実行した際のキャプチャです。各 Text をタップした際に別画面に遷移することができています。

最後に

上記のように、onTapGesture(count:perform:) を使用した場合は実現できました。
他にも良い方法はありそうですね。もし知っている方がいれば教えてほしいです。

参考資料

https://qiita.com/noppefoxwolf/items/33a1695ed5daa3230f3b
https://developer.apple.com/documentation/swiftui/navigationlink/3364630-init

16
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
7