HC77
@HC77

Are you sure you want to delete the question?

If your question is resolved, you may close it.

Leaving a resolved question undeleted may help others!

We hope you find it useful!

[Swift・CoreData]プレビューでPersistenceControllrのインスタンスが何故か使えません。

プレビューでPersistenceControllrのインスタンスが何故か使えません。

Swift初学者です。以下のソースコードで、プレビューを表示させようと試みたのですがうまくいきません。

プレビューに「Instance member 'persistenceController' cannot be used on type 'ContentView_Previews'」とエラーが出ます。
構造体ContentView_PreviewsのメンバのpersistenceControllerがここでは使えないとのことですが、具体的な解決策が提示されていません。

ちなみにデータベース名?は「PetShop.xcdatamodeld」、エンティティ名は「Cat」、属性は「name(String)」のみとなっています。

import SwiftUI
import CoreData

struct ContentView: View {

    @Environment(\.managedObjectContext) var viewContext
    @FetchRequest(sortDescriptors: [])
    var Results: FetchedResults<Cat>

    var body: some View {
        VStack {
            ForEach(Results) { result in
                Text(result.name!).padding()
            }
        }
    }
}

struct PersistenceController {

    static var preview = PersistenceController()

    let container: NSPersistentContainer

    init() {
        container = NSPersistentContainer(name: "PetShop")

        container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")

        container.loadPersistentStores(completionHandler: {(description, nsError) in
            if let _ = nsError as NSError? {
                fatalError()
            }
        })
        let viewContext = container.viewContext

        for num in 1...3 {
            let newCat = Cat(context: viewContext)
            newCat.name = "ネコ\(num)"
        }
        do {
            try viewContext.save()
        } catch {
            fatalError()
        }
    }
}



struct ContentView_Previews: PreviewProvider {

    let persistenceController = PersistenceController()

    static var previews: some View {
        // この部分がエラーになります。
        ContentView().environment(\.managedObjectContext, persistenceController.container.viewContext)
    }
}

しかし、PreviewProviderのコードを以下のように書き換えるとうまくいきます。

struct ContentView_Previews: PreviewProvider {

    static var previews: some View {
        ContentView().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
    }
}

私がソースコードを見る限り、一つ目はPersistenceControllerのインスタンスをContentView_Previewsで作成し、使用している。二つ目は、引数で環境変数の設定?で引数として使用されるときに、即時関数としてインスタンスを作成し、使用している。

このように認識していて、一つ目も二つ目も実際は同じことをやっているようにしか思えないのですが、なぜ一つ目のコードではうまくプレビューが表示されないのでしょうか?

0

5Answer

一つ目の
let persistenceController = PersistenceController()
static var persistenceController = PersistenceController() にすれば、
二つ目と同じになると思いますが、環境が無いので、実際に確かめることができません。

もしくは、一つ目のlet persistenceController = PersistenceController()previewsの内側に移動して、ローカルにインスタンス生成する。

コンパイルエラーが示す通り、インスタンスが生成されていないのでインスタンスメンバーは参照できないと思います。

1Like

@HC77さん、以下の説明でいかがでしょうか?

(Swift的に正しい言葉使いではないと思いますが、)「インスタンスが生成されていないのでインスタンス変数は(存在しないから)参照できない」ことが原因です。

そこで、対応策は、下記2つのアプローチのどちらかになると思います。

なぜlet persistenceController = PersistenceController()をstaticプロパティとすることで、プレビュー表示できるのか?

→スタティック変数にすることで、インスタンスが生成されてなくても参照できるようにした。

なぜローカルにlet persistenceController = PersistenceController()を作成することで、プレビュー表示できるのか?

→インスタンス変数を参照できようにインスタンスを生成した。

追加で、

そのため、上記のコードのようにContentView_Previewsも書けばうまく行くと思っていました。

→このExperimentApp型には@mainディレクティブ(attribute)が付いているので、app起動時にシステムが自動的にインスタンスを生成していると思います。(よって、インスタンス変数を参照できた)

1Like

Swiftにおいて、staticなメンバー(プロパティやメソッド)からinstanceのメンバーを参照することはできません。

struct Foo {
    static let staticMember: Int = 42
    let instanceMember: Int = 42
    
    static var staticProperty: Int {
        // OK
        return staticMember
        // NG: Instance member 'instanceMember' cannot be used on type 'Foo'
        return instanceMember
    }
}

2つの方法がそれぞれ有効なのはpersistenceControllerがinstanceのメンバーではなくなるからで、"うまく行く"コードがうまく行くのはExperimentAppbodyContentView_Previewspreviewsとは違いstaticではないからです。

1Like

Comments

  1. @HC77

    Questioner

    woxtu様、
    staticのメンバーからはinstanceのメンバーを参照することができないのですね。
    わざわざご丁寧にコードまで書いていただき助かります。この度はありがとうございました!

確かに@woxtuさんの回答がそのものズバリですね。
特に三つ目の@mainは直接関係なかったです(staticでないところを見逃していました)。
失礼しました。

1Like

Comments

  1. @HC77

    Questioner

    nak435様、
    一度ならず二度三度とご回答いただき本当にありがたく思います。ご丁寧に回答いただきありがとうございました!

nsk435様、ご回答ありがとうございます。

ご指摘を頂きました二つの方法を試してみましたが、両方ともうまくプレビューを表示することができました。本当に助かりました。

差し支えなければ、

なぜlet persistenceController = PersistenceController()をstaticプロパティとすることで、プレビュー表示できるのか?

なぜローカルにlet persistenceController = PersistenceController()を作成することで、プレビュー表示できるのか?

ということも教えていただけませんか?

import SwiftUI

@main
struct ExperimentApp: App {

let persistenceController = PersistenceController()

    var body: some Scene {
        WindowGroup {
            ContentView().environment(\.managedObjectContext, persistenceController.container.viewContext)
        }
    }
}

(私はこのコードを書く前に、プレビュー表示をさせずにシミュレータでアプリを実行してうまくできていました。そのため、上記のコードのようにContentView_Previewsも書けばうまく行くと思っていました。)

0Like

Your answer might help someone💌