NavigationSplitViewの"Simultaneous accesses to..."クラッシュの回避方法
iOS 16から登場したNavigationSplitViewとSwift 5.9から登場したmacroの@Observableの組み合わせでクラッシュが発生することがあります。
この記事ではその回避方法を説明します。
動作環境
- Xcode 15
- iOS 17
クラッシュが起こる原因
ListにはSelectionValueと呼ばれる選択された保持するパラメーターが定義されています。
このSelectionValueはHashableを準拠したインスタンスを指定します。
@MainActor
struct List<SelectionValue, Content> where SelectionValue : Hashable, Content : View
そして、NavigationSplitViewとListのSelectionValue、そしてNavigationLinkを組み合わせるとエラーが発生する場合があります。
再現コードとして、文字列を表示複数リスト表示するViewを作りましょう。
let items = ["Item1", "Item2", "Item3", "Item4"]
@Observable
final class FolderViewModel {
var selectedItem: String?
}
itemsは単純なStringの配列です。
そしてFolderViewModelはObservableのオブジェクトでどのセルが選択されたかを表すselectedItemプロパティを持っています。
これらを使って、NavigationSplitViewでViewを作成します。
// クラッシュパターン
struct SplitView: View {
@State var viewModel = FolderViewModel()
var body: some View {
NavigationSplitView {
List(items, id: \.self, selection: $viewModel.selectedItem) { item in
NavigationLink(value: item) {
Text(item)
}
}
.navigationTitle("Sidebar")
} detail: {
if let selectedItem = viewModel.selectedItem {
Text(selectedItem)
.navigationTitle(selectedItem)
} else {
Text("Choose an item from the content")
}
}
}
}
NavigationSplitViewの子ViewにListがあり、データとしてitemsを渡しています。
selectionのパラメーターに$viewModel.selectedItemを渡すことで選択アイテムをFolderViewModelでハンドリングしています。
NavigationLinkのvalueイニシャライザーを使ってセルがタップしたら、Listのselectionが更新されるようにします。
NavigationLink(value: item) {
Text(item)
}
NavigationLinkのvalueイニシャライザーは、NavigationSplitView内のListの子Viewに配置される場合、タップされるとvalueに渡した値をListのselectionの値へ更新します。
Viewの見た目はこのようになります。

このコードを実行し、セルをタップすると次のようなエラーを出力し、アプリがクラッシュしてしまいます。
Simultaneous accesses to 0x6000006e6410, but modification requires exclusive access.
Previous access (a modification) started at SwiftUI`OUTLINED_FUNCTION_11 + 3132 (0x105867044).
エラー発生箇所はmacroで生成されたwithMutationメソッドで発生していました。

iOS & iPadOS 17 Release Notes
Appleもこのバグは認識しているようで、iOS 17 & iPadOS 17のリリースノートにも記載がありました。
On iOS, using an Observable object’s property as a selection value of a List inside NavigationSplitView may cause a “Simultaneous accesses to …” error when a list selection is made via tap gesture. (113978783) (FB12981860)
Workaround: There is no current workaround for Observable properties. Alternatives include factoring out the selection value into separate state stored outside the object, or using ObservableObject instead.
筆者訳:iOSではNavigationSplitViewの中でListのselection値としてObservableのプロパティを使い、タップジェスチャーでリストが選択されると「Simultaneous accesses to …」のエラーが発生する場合があります。
修正方法。Observableのプロパティについては修正方法が現状ありません。代替方法として、selection値をObservableオブジェクトの外部で保存される別の状態として扱うか、代わりに ObservableObject を使用します。
このエラーが発生した場合、Observableの利用はあきらめるしか現状なさそうです。
Observableではない別な方法で状態を管理するか、ObservableObjectのプロトコルでオブジェクトを管理するのが良いそうです。
AppStorageで回避する
クラッシュを回避するには、Observable以外で状態を管理する必要があります。
回避方法の一つとしてAppStorageを利用する方法を考えました。
Listのselect値を@Observableで管理することをやめて、AppStorageを利用します。
struct SplitViewWorkaround: View {
// FolderViewModelをAppStorageに変更
@AppStorage public var selectedItem: String?
var body: some View {
NavigationSplitView {
List(items, id: \.self, selection: $selectedItem) { item in
NavigationLink(value: item) {
Text(item)
}
}
.navigationTitle("Sidebar")
} detail: {
if let selectedItem {
Text(selectedItem)
.navigationTitle(selectedItem)
} else {
Text("Choose an item from the content")
}
}
}
}
#Preview {
SplitViewWorkaround(selectedItem: AppStorage("selectedItem"))
}
先ほどクラッシュしたSplitViewとの差分は@Observableを使ったFolderViewModelをやめてAppStorageに差し替えただけです。
@State private var viewModel = FolderViewModel()
これでエラーが発生しなくなりました。
エラーが発生しないパターン
ただし、手元で確認したところ、NavigationLinkを使わない場合はクラッシュが発生しませんでした。
下記のコードはView階層がNavigationSplitView > ListとなっていてNavigationLinkがありません。
// OKパターン
struct SplitViewOK: View {
@State private var viewModel = FolderViewModel()
var body: some View {
NavigationSplitView {
List(items, id: \.self, selection: $viewModel.selectedItem) { item in
Text(item)
}
.navigationTitle("Sidebar")
} detail: {
if let selectedItem = viewModel.selectedItem {
Text(selectedItem)
.navigationTitle("selection")
} else {
Text("hello")
.navigationTitle("detail")
}
}
}
}
このコードでもListのselection値は更新され、詳細画面が表示されました。
NavigationLinkの内部にバグ原因があるのかもしれません。
まとめ
今回はiOS 17における、@ObservableとNavigationSplitViewの組み合わせによるクラッシュとその回避方法を解説しました。
Listのselection値が@Observableのオブジェクトでクラッシュするのは意外でした。
アップデートで早く解決することを願います。
参考
- NavigationSplitView
- NavigationLink: init(_:value:)
- List
- https://developer.apple.com/documentation/ios-ipados-release-notes/ios-ipados-17-release-notes#SwiftUI
宣伝
BOOTHより、同人版「Swift Concurrency入門」発売中です。
Swift Concurrencyを網羅的に学べ、さらに既存アプリへの適応方法も解説しています。
日本語で体系的に学べる解説本は他になかなかありません。
1章、2章が立ち読みできるおためし版もありますので、ぜひチェックしてください!
