SwiftData Fatal error: failed to find a currently active container 📦

SwiftData Fatal error: failed to find a currently active container

まったく動かない。画面が真っ白。再起動でも同じ。

エラーメッセージ。

SwiftData/ModelContainer.swift:159: Fatal error: failed to find a currently active container for Task
Failed to find any currently loaded container for Task)

コンテナを渡す前に初期化すると問題が解決されるようです。


@main
struct MyApp: App {
  let modelContainer: ModelContainer
    
  init() {
    do {
      modelContainer = try ModelContainer(for: Item.self)
    } catch {
      fatalError("Could not initialize ModelContainer")
    }
  }
    
  var body: some Scene {
    WindowGroup {
      ContentView()
    }
    .modelContainer(modelContainer)
  }
}

👉 SwiftData Fatal error: failed to find a currently active container | Apple Developer Forums hatena-bookmark

というかんじで、モデルコンテナの初期化位置を最上位にすると直りました。

モデルデータの変更後の整合性の問題でしょうか。

突然動かなくなるのでびっくりします。

 

📦 どこでモデルコンテナを生成するべきか

利用する View につければ、


struct ContentView: View {
  @State private var container = ModelContainer(...)

  var body: some Scene {
    RecipesList()
      .modelContainer(container)
  }
}

それ以下では @Environmentを使って問題なく利用できると思っていましたが。


struct RecipesList: View {
    @Environment(\.modelContext) private var modelContext

The environment’s modelContext property will be assigned a new context associated with this container. All implicit model context operations in this view, such as Query properties, will use the environment’s context.

Environment ののmodelContext プロパティには、このコンテナに関連付けられた新しいコンテキストが割り当てられます。Query プロパティなど、このビューのすべての暗黙のモデルコンテキスト操作は、Environment のコンテキストを使用します。

👉 modelContainer(_:) | Apple Developer Documentation hatena-bookmark

Modifier や Preview などあちこちで 生成していると、利用できる範囲が分かりづらくなるのは確か。



【SwiftUI】NavigationSplitView と TabView の切り替え判定 🔀

マルチプラットフォームアプリで、

Mac/iPad と iPhone/AppleTV でグループ分けしてそれなりに切り替える。


if prefersTabNavigation { // *
//if UIDevice.current.userInterfaceIdiom == .phone { // NG on macOS
//if UserInterface.prefersTabNavigation {
  TabView(selection: $screen) {
    ForEach(Screen.allCases) { screen in
      screen.destination
        .tag(screen as Screen?)
        .tabItem { screen.label }
    }
  }
} else {
  NavigationSplitView { 
    SidebarList(screen: $screen)
  } detail: {
    DetailColumn(screen: screen)
  }
}

判定の条件 (Bool) をどう書くか。

 

🔀 Apple 公式サンプル

WWDC23 でアナウンスの新機能ですか。

👉 Unleash the UIKit trait system - WWDC23 - Videos - Apple Developer hatena-bookmark

便利な SwiftUI Environment に UIKit UITrait をブリッジするという UITraitBridgedEnvironmentKey を使った方法。


// Create custom Environment
struct PrefersTabNavigationEnvironmentKey: EnvironmentKey {
  static var defaultValue: Bool = false
}

extension EnvironmentValues {
  var prefersTabNavigation: Bool {
    get { self[PrefersTabNavigationEnvironmentKey.self] }
    set { self[PrefersTabNavigationEnvironmentKey.self] = newValue }
  }
}

// Bridge UITrait read only
#if os(iOS)
extension PrefersTabNavigationEnvironmentKey: UITraitBridgedEnvironmentKey {
  static func read(from traitCollection: UITraitCollection) -> Bool {
    return traitCollection.userInterfaceIdiom == .phone || traitCollection.userInterfaceIdiom == .tv
  }

  static func write(to mutableTraits: inout UIMutableTraits, value: Bool) {
    // Do not write
  }
}
#endif

👉 sample-backyard-birds/Multiplatform/General/PrefersTabNavigationEnvironmentKey.swift at 1843d5655bf884b501e2889ad9862ec58978fdbe · apple/sample-backyard-birds hatena-bookmark


@Environment(\.prefersTabNavigation) private var prefersTabNavigation

👉 sample-backyard-birds/Multiplatform/ContentView.swift at 1843d5655bf884b501e2889ad9862ec58978fdbe · apple/sample-backyard-birds hatena-bookmark

高級な感じで相互読み書き可能な状態にできるのはいいけども。

 

🔀 その他

なんとなく試しながら書いてみました。

Constants な感じで。書き方はいくつかあるようです。

enum を使う。意見はいろいろありそう。


enum UserInterface { // as namespace
  static var prefersTabNavigation: Bool {
    #if os(iOS) //canImport(UIKit)
    let idiom = UIDevice.current.userInterfaceIdiom
    return idiom == .phone || idiom == .tv
    #else
    return false
    #endif
  }
}

フツーに struct で書く。


struct UserInterface {
  static var prefersTabNavigation: Bool {
    #if os(iOS) //canImport(UIKit)
    let idiom = UIDevice.current.userInterfaceIdiom
    return idiom == .phone || idiom == .tv
    #else
    return false
    #endif
  }
}

👉 `static let` in enum vs struct? - Using Swift - Swift Forums hatena-bookmark

呼び出し側。


if UserInterface.prefersTabNavigation {

 

🔀 まとめ


#if os(iOS)

ての、なんとなく

なるべくは使いたくないです。

Apple 公式サンプルは、新機能を強引に Read Only でサンプルコードに利用した感じに見えるけどもー。




【SwiftUI / SwiftData】Using ViewModifier for setting ModelContainer 🔌

ViewModifiers can be used in nested parent-child Views for each respective #Preview, making it convenient. This also enhances clarity even further.

 

🔌 Using as an extension of View

Create a custom ViewModifier.


struct DogDataContainerViewModifier: ViewModifier {
  func body(content: Content) -> some View {
    content
      .modelContainer(try! ModelContainer(for: Dog.self))
  }
}

All ViewModifiers will be turned into extensions.


extension View {
  func dogDataContainer() -> some View {
    modifier(DogDataContainerViewModifier())
  }
}

It is used in the implementation of the parent as well as in the #Preview of the child.


struct DogView: View {
  // ...
}

#Preview {
  DogView()
    .dogDataContainer()
}

 

🔌 Initializing or creating data

Additionally, as there are often data initialization or creation tasks, I'll add those.

This will be in the part with View.onAppear().

We'll use ModelContext to manipulate the data.


struct GenerateDataViewModifier: ViewModifier {
  @Environment(\.modelContext) private var modelContext
    
  func body(content: Content) -> some View {
    content.onAppear {
      DataGeneration.generateAllData(modelContext: modelContext)
    }
  }
}

This will also be made into an extension.


extension View {
  func generateData() -> some View {
    modifier(GenerateDataViewModifier())
  }
}

Let's add this to the initial code.


struct DogDataContainerViewModifier: ViewModifier {
  func body(content: Content) -> some View {
    content
      .generateData()
      .modelContainer(try! ModelContainer(for: Dog.self))
  }
}

 

🔌 Conclusion

I'll summarize it.


struct DogDataContainerViewModifier: ViewModifier {
  func body(content: Content) -> some View {
    content
      .generateData()
      .modelContainer(try! ModelContainer(for: Dog.self))
  }
}

struct GenerateDataViewModifier: ViewModifier {
  @Environment(\.modelContext) private var modelContext
    
  func body(content: Content) -> some View {
    content.onAppear {
      DataGeneration.generateAllData(modelContext: modelContext)
    }
  }
}

extension View {
  func dogDataContainer() -> some View {
    modifier(DogDataContainerViewModifier())
  }
}

fileprivate extension View {
  func generateData() -> some View {
    modifier(GenerateDataViewModifier())
  }
}

When using, only basic public extensions are used.


struct DogView: View {
  // ...
}

#Preview {
  DogView()
    .dogDataContainer()
}

It can also be used on the implementation side.

For reference, below is Apple's official sample code.

👉 sample-backyard-birds/BackyardBirdsData/General/BackyardBirdsDataContainer.swift at 1843d5655bf884b501e2889ad9862ec58978fdbe · apple/sample-backyard-birds hatena-bookmark