【SwiftUI】NavigationPathのRestoreの不具合(iOS16.0/Xcode14.0.1)

SwiftUI

NavigationPathのRestoreを行うとクラッシュする場合があります。
iOSおよびXcodeの更新を待ちましょう。

確認したバージョンはOSはiOS16.0のシミュレータで、
Xcodeは14.0.0と14.0.1です。

追記
Xcode14.1 beta2とiOS16.1 betaの組み合わせでは動作しました。
Xcode14.1 beta2とiOS16.0ではダメだったのでiOS側のアップデートが必要なようです。

不具合の出るコードについて

//NavigationPath(NavigationPath.CodableRepresentation)
NavigationPath(representation)

要はイニシャライザに復元用データを渡すと不具合が出ます。

以下のサンプルコードにNavigationStackとデータの保存部分を追加して実行してください。
データ保存はUserDefaultsとかで良いです。

NavigationPath | Apple Developer Documentation
A type-erased list of data representing the content of a navigation stack.

私が使ったテストコードは記事の最下部に置いておきます。
面倒な方がこちらをコピペして使ってください。

画面遷移後にアプリを終了させ、再度起動したら遷移後の画面からルートに戻してください。
以下のエラーが出てクラッシュします。

SwiftUI/NavigationPath.swift:214: Fatal error: attempting to remove 1 items from path with 1 items

意味としては「1つのアイテムを持つパスから1つのアイテムを削除しようとしています。」
ルートの場合pathのcountは0なので間違った事は何もしていません。

イニシャライザ内の処理の問題なので手の付けようがありません。
修正を待ちましょう。

対処法

現状はどうしようもないのでNavigationPath外でデータを保存しましょう。
単純に別途保存して起動時にNavigationPathにappendすれば問題ないです。

余談

原因について

恐らくJSONと変換する時に問題が起きています。
NavigationPath.codableからNavigationPathを初期化しても問題が起きないからです。

JSONへのEncodeまたはDecodeの何処かにミスがあるのではないかと思います。

Xcodeのバージョンについて

どうやらXcode14 beta4まででも発生しており、フォーラムにもこの問題がありました。
beta5で解決したとのコメントがありましたが、最近また発生したとのコメントもありました。
どうやらデグレしたようですね・・・

テストコード

import SwiftUI

struct NavigationStackView: View {
    
    @Environment(\.scenePhase) var scenePhase    
    @StateObject private var pathState = MyModelObject()
    
    var body: some View {
        VStack {
            NavigationStack(path:$pathState.path){
                IntListView()
                    .navigationDestination(for: Int.self) { item in
                        StringListView()
                    }
                    .navigationDestination(for: String.self) { item in
                        DetailView()
                    }
                
            }.onChange(of: scenePhase) { phase in
                if phase == .background {
                    pathState.save()
                }
            }
            Text("\(pathState.path.count)")
        }
    }

}

class MyModelObject: ObservableObject {
    @Published var path: NavigationPath

    static func readSerializedData() -> Data? {
        // Read data representing the path from app's persistent storage.
        let data = UserDefaults.standard.data(forKey: "path")
        if data != nil {
            print(String(data: data!, encoding: .utf8))
        }
        return data
    }

    static func writeSerializedData(_ data: Data) {
        // Write data representing the path to app's persistent storage.
        UserDefaults.standard.set(data, forKey: "path")
        print(String(data: data, encoding: .utf8))
    }

    init() {
        if let data = Self.readSerializedData() {
            do {
                let representation = try JSONDecoder().decode(
                    NavigationPath.CodableRepresentation.self,
                    from: data)
                self.path = NavigationPath(representation)
            } catch {
                self.path = NavigationPath()
            }
        } else {
            self.path = NavigationPath()
        }
    }
    
    func save() {
        guard let representation = path.codable else { return }
        do {
            let encoder = JSONEncoder()
            let data = try encoder.encode(representation)
            Self.writeSerializedData(data)
        } catch {
            // Handle error.
        }
    }
}

struct IntListView: View {
    
    let items = 1..<4
    
    var body: some View {
        List(items ,id:\.self) { item in
            NavigationLink("\(item)", value: item)
        }.navigationTitle("IntList")
    }
}

struct StringListView: View {
    
    let items = ["Item1","Item2","Item3"]
    
    var body: some View {
        List(items, id:\.self) { item in
            NavigationLink("\(item)", value: item)
        }.navigationTitle("StringList")
    }
}

コメント

  1. […] […]

タイトルとURLをコピーしました