モノトーンの伝説日記

Apex Legends, Splatoon, Programming, and so on...

<mini> `View.onReceive(_:perform:) on watchOS 8 isn't working.` / watchOS 8 で `View.onReceive(_:perform:)` が正常に動作しない

 実験してたらまさかの watchOS 7 では正しく動くという。

onAppearonDisappear が呼ばれないケースはみなさんご存知

 特定のケースで onAppearonDisappear のは多分だけど仕様だと思う。

 それと同じように、watchOS 8 では onReceive までもがその条件に当てはまる可能性が高い。

 onAppearonDisappearLazyView を作ることでいくらでも対処可能(基本的に子の初期化遅延して、init で View の binding 用データ作成)。

import SwiftUI

struct LazyView<Content: View>: View {
    private let build: () -> Content
    
    init(_ build: @autoclosure @escaping () -> Content) {
        self.build = build
    }
    
    var body: Content {
        build()
    }
}

どうやって対処しようか……

 結構深刻だと思います。

 UI が struct なので、Combine 側から assign して使うのもできないですしね……

 とりあえず速報ということで。

 Apple Watch Series 7 の記事は今夜書く予定です(予定は未定)。

追記: 一連の問題発生に関して

 検索用: watchOS で TabViewNavigationView を併用する場合の注意点

 これの問題の解決方法は、Root に NavigationView 配置すること。iOS のように Tab -> Nav みたいな Controller のイメージで配置をすると、ドツボにハマる。

 ちなみに、watchOS 7 では onAppearonDisappear、watchOS 8 では onAppearonDisappearonReceive が効かなくなり、色々と動作もおかしくなる(例えば、NaviigationView に対する設定が 2 ページ目以降、つまり NavigationLink で遷移した画面で設定している内容が反映されないなど)。

 ということで一件落着。TabView を Root に配置できないので、いつでもタブの繊維ができなくなる不便さがある。

さらに追記: 解決編

 Nav -> Tab 構造の場合はレベル 2 のページで sheet を非表示にした際、watchOS 8 のみ (watchOS 7 は発生しない) レベル 1 の画面に戻る不具合が発生した。そのため、上であげた方法は使えない。

 解決方法としては、

import SwiftUI

struct RootView: View {
    private(set) var content: RootViewModel
    
    @State
    private var selectedIndex: String = "home"
    
    var body: some View {
        TabView(selection: $selectedIndex) {
            ForEach(content.collection) { item in
                NavigationView {
                    TimelineView(content: item)
                }
                .tag(item.id)
            }
        }
    }
}

のように、selection を受け取るようにすること。たったこれだけで全てがうまくいく。上で書いていた不具合も全部消えます。

 モックの部分解決できたし、今から Apple イベントなんでちょうどいい!