【watchOS】navigationTitle(_:)をBindingすると表示されない
WristCounterというApple Watch向けのアプリをリリースしました。
しばらくの間、この開発の中で得たwatchOS開発の知見を流していきます。
前提
- watchOS 9.0
- Xcode 14.3
SwiftUIで全プラットフォームで動くコード←本当か?
SwiftUI使って開発しました。
SwiftUIで書いたUIは、iOS/iPadOS/MacOS/watchOS/tvOSそれぞれのプラットフォームで最適なスタイルとなって動作します。
基本的には、動作します。
今回僕の開発したカウンターアプリは、watchOSのみで動くアプリなので、別にOSで分岐させることは考えずにすみました。
もし本気で対応するとなると、SwiftUIのデフォルト動作に任せつつ、問題が出たところは、
#if os(watchOS)
// …
#else
// …
#endif
のようにOSごとに分岐させることになるかと思います。
しかしどうにもならなかった点があって、それがnavigationTitleでした。
navigationTitle
watchOSの↓の部分です。
Apple Watchは画面がめちゃくちゃ狭いので、文字のラベルを一行入れるだけでめちゃくちゃ画面を圧迫します。
端末にもよりますが、標準の文字サイズで、画面の縦1/4ぐらいが必要となります。
なので、なるべくnavigationTitleは活用したくなります。
要件
タイトルが固定文字列のときは問題ありません。
しかし状況に応じて変えたい場合、どうすればいいでしょう?
今回の要件としては、カウンター画面を左スワイプすると設定画面に行ける、というものでした。
複数カウンターが設定できるので、設定画面には今選択しているカウンターの名前が出ます。
左右スワイプは、TabViewを使うと実現できます。
// 一部コードを簡略化しています
@State private var counters = [Counters()]
@State private var selection: Tab = .counter
@State private var selectionCounter: Int = 0
enum Tab {
case setting, counter
}
var body: some View {
TabView(selection: $selection) {
SettingView()
.tag(Tab.setting)
TabView(selection: $selectionCounter) {
ForEach(counters) { counter in
CounterView(counter: counter)
.tag(counter.tag)
}
}
.tabViewStyle(.carousel)
.tag(Tab.counter)
}
}
このSettingViewに、Counter名をnavigationTitleとして渡したい、というのが今回の要件です。
navigationTitleのBinding
iOS/iPadOSなら、↓このようにBidingでNavigationTitleを設定することが可能です。
import SwiftUI
struct ContentView: View {
@State var title = "Title"
var body: some View {
NavigationStack {
VStack {
TextField("change title", text: $title)
}
.padding()
.navigationTitle($title)
}
}
}
これをwatchOSで動かすと、ビルドは通るんですが、文字列が表示されません。
ダメだったサンプル
なので、watchOSアプリでこんな感じで実装すると、タイトルが表示されません。
struct SettingView: View {
@Binding var counter: Counter {
var body: some View {
NavigationStack {
ScrollView { … }
.navigationTitle($counter.name)
.navigationBarTitleDisplayMode(.inline)
}
}
}
Bindingにしないで渡すと、更新タイミングが意図通りにならなかったと思います。
今試して大丈夫そうだったサンプル
「これNavigationStack、親Viewで指定したらイケそうじゃない?」とふと思って、こんな風に書いてみました。
TabView(selection: $selection) {
NavigationStack {
SettingView()
.tag(Tab.setting)
.navigationTitle(counters[selectionCounter].name)
.navigationBarTitleDisplayMode(.inline)
}
// …
}
このタイトルの渡し方ならBindingっぽい更新がされました……
final class Counter: ObservableObject, Identifiable, Equatable {
var id = UUID()
var tag: Int
@Published var name = ""
Counter
の定義はこんな感じです。
「watchOSだとnavigationTitle(_:)のBindigが効かない」だと思って記事書き始めたんですが、なんか別の問題なのかもしれません。
正解ご存知の方いたら教えてください。
(了)
Discussion