はじめに
SwiftUIでプッシュ遷移をする際にNavigationLinkを使用するかと思います。
iOS15以前ではNavigationViewで囲み、内部でNavigationLink(_:destination:isActive:)を使用すれば、プッシュ遷移を実装することができました。
ただ、iOS16でNavigationStackが登場し、上記の関数がDeprecatedになり、以降使用すると、ビルド時に警告が表示されます。
案件等の事情により、現在はiOS15がサポート対象に入っている場合でも、
将来的にサポート対象を外した際に、NavigationLinkの警告表示の対応が必要になるかと思います。
今回の記事では、現在はiOS15だが、今後iOS16を見越したNavigationLinkの書き方を提案したいと思います。
想定読者
最低サポートバージョンにiOS15を含みNavigationLinkを実装する必要がある開発者
開発環境
MacBook Pro 13 inch M1
Xcode14.2
iOS15以前のNavigationLinkの書き方
まず、最低サポートバージョンからiOS15を外した際に警告が表示されるNavigationLinkの書き方は以下になります。
struct ContentView: View {
@State var isActive = false
@State var number = 0
var body: some View {
NavigationView {
VStack {
List(0 ..< 10) { number in
Button("\(number)") {
isActive = true
self.number = number
}
}
NavigationLink(isActive: $isActive) { // 🙅 Deprecatedと警告される
SecondView(number: self.number)
} label: { EmptyView() }
}
.navigationTitle(Text("First"))
}
}
}
/// 遷移先のView定義
struct SecondView: View {
let number: Int
var body: some View {
Text("SecondView number:\(number)").font(.title)
}
}
参考:init(_:destination:isActive:) Deplicated
ちなみに、以下のような書き方だと、最低サポートバージョンがiOS16になっても警告はされません。
iOS16以降のDeprecated対応を避ける意味でこちらの遷移方法を使用する選択肢もあるかもしれません。
// iOS15以前のNavigationLink 警告が出ない書き方
struct ContentView: View {
var body: some View {
NavigationView {
List(0 ..< 10) { number in
NavigationLink { // 🙆♂️ iOS16でも警告は表示されない
SecondView(number: number)
} label: {
Text("\(number)")
}
}
.navigationTitle(Text("First"))
}
}
}
今後iOS15のサポートを切ることを想定したNavigationLinkの書き方
遷移フラグ等で画面遷移を制御したい場合に、
提案としては、iOS16から使用可能な.navigationDestination(isPresented:destination:)
に置き換えることを想定して、新たに似たようなExtensionとして.navigationViewDestination(isPresented:destination:)
を作成し、最低サポートバージョン変更による移行をスムーズにします。
struct ContentView: View {
@State var isActive = false
@State var number = 0
var body: some View {
NavigationView {
List(0 ..< 10) { number in
Button("\(number)") {
isActive = true
self.number = number
}
}
.navigationTitle(Text("First"))
// 🙆♂️ iOS16に切り替えた際に```.navigationDestination(isPresented:destination:)```に変えれば良いだけ
.navigationViewDestination(isPresented: $isActive) {
SecondView(number: number)
}
}
}
}
// 新たにExtentionを用意
extension View {
func navigationViewDestination(isPresented: Binding<Bool>, @ViewBuilder destination: @escaping () -> some View) -> some View {
self.background {
NavigationLink(isActive: isPresented, destination: destination) {
EmptyView()
}
}
}
}
【参考】 NavigationStackに置き換えた場合の例
struct ContentView: View {
@State var isActive = false
@State var number = 0
var body: some View {
NavigationStack { // NavigationView→NavigationStack
List(0 ..< 10) { number in
Button("\(number)") {
isActive = true
self.number = number
}
}
.navigationTitle(Text("First"))
.navigationDestination(isPresented: $isActive) { // navigationViewDestination→navigationDestination
SecondView(number: number)
}
}
}
}
参考
APIを差し替えた際にコード修正が最小限で済む方法について参考にさせていただきました。
SwiftUI で「グループスタンプ」というチャット機能を5日間で作った話
NavigtaionStackについて参考にさせていただきました。
iOSDC Japan 2022: SwiftUI Navigation のすべて / アイカワ