Error: Thread 1: "executeFetchRequest:error: A fetch request must have an entity." - swiftui

I'm trying to use CoreData in my Xcode-project (SwiftUI). I've created a Player entity and wanna use it in my View called "YouView". But when I'm trying to fetch the data, I get the error from Title. My app is called Dart Tools. My Application Language is German, so don't worry if you don't understand everything of the ui :).
Thanks for helping!
I already tried the .shared variant - same error
This is my .xcdatamodeld file (called DataModel.xcdatamodeld)
This is the Code of my DataController file:
import Foundation
import CoreData
class DataController: ObservableObject {
let container = NSPersistentContainer(name: "DataModel")
init() {
container.loadPersistentStores { desc, error in
if let error = error {
print("Daten wurden nicht geladen: \(error.localizedDescription)")
}
}
}
func save(context: NSManagedObjectContext) {
do {
try context.save()
print("Daten wurden gespiechert!")
} catch {
print("Daten konnten nicht gespeichert werden.")
}
}
func addPlayer(name: String, isUser: Bool, context: NSManagedObjectContext) {
let player = Player(context: context)
player.id = UUID()
player.name = name
player.isUser = isUser
save(context: context)
}
func editPlayerName(player: Player, name: String, context: NSManagedObjectContext) {
player.name = name
save(context: context)
}
}
Here is my DartToolsApp.swift file:
import SwiftUI
//The error is here:
#main
struct DartToolsApp: App {
#StateObject private var userDefaults = UserDefaults()
#StateObject private var dataController = DataController()
var body: some Scene {
WindowGroup {
ContentView()
.environment(\.managedObjectContext, DataController().container.viewContext)
.environmentObject(UserDefaults())
}
}
}
This is the file where I want to use the Data:
Btw, this view is a piece of a TabView.
import SwiftUI
import CoreData
struct YouView: View {
#EnvironmentObject var userDefaults: UserDefaults
// I guess the error is because of this line:
#FetchRequest(sortDescriptors: []) var players: FetchedResults<Player>
#Environment(\.managedObjectContext) var managedObjectContext
var body: some View {
NavigationStack {
Form {
}
.navigationTitle("\(findUser()) (Du)")
}
}
func findUser() -> String {
if let index = players.firstIndex(where: { $0.isUser }) {
let output = players[index].name!
return output
}
else {
return ""
}
}
}
This is the code to create the user:
DataController().addPlayer(name: nameText, isUser: true, context: managedObjectContext)
Wow, that was a lot of code. I hope you can help me!

You are creating new instances of DataController everywhere which is a problem in itself but what think causes your particular error is when you assign the \.managedObjectContext environment variable.
So instead of creating a new instance you should use your #StateObject instance instead.
So change this in the App code
var body: some Scene {
WindowGroup {
ContentView()
.environment(\.managedObjectContext, dataController.container.viewContext)
}
}

Related

Updating SwiftUI from HealthKit Query

I want to output the variable 'healthStore.valueTest' via ContentView in SwiftUI.
The class healtStore is structured as follows:
class HealthStore {
var healthStore: HKHealthStore?
var query: HKStatisticsQuery?
var valueTest: HKQuantity?
init() {
if HKHealthStore.isHealthDataAvailable() {
healthStore = HKHealthStore()
}
}
func calculateBloodPressureSystolic() {
guard let bloodPressureSystolic = HKObjectType.quantityType(forIdentifier: .bloodPressureSystolic) else {
// This should never fail when using a defined constant.
fatalError("*** Unable to get the bloodPressure count ***")
}
// let startDate = Calendar.current.date(byAdding: .day, value: -7, to: Date())
// let anchorDate = Date.mondayAt12AM()
// let daily = DateComponents(day: 1)
// let predicate = HKQuery.predicateForSamples(withStart: startDate, end: Date(), options: .strictStartDate)
query = HKStatisticsQuery(quantityType: bloodPressureSystolic,
quantitySamplePredicate: nil,
options: .discreteAverage) {
query, statistics, error in
DispatchQueue.main.async{
self.valueTest = statistics?.averageQuantity()
}
}
healthStore!.execute(query!)
}
}
ContentView is built as follows:
import SwiftUI
import HealthKit
struct ContentView: View {
private var healthStore: HealthStore?
init() {
healthStore = HealthStore()
}
var body: some View {
Text("Hello, world!")
.padding().onAppear(){
if let healthStore = healthStore {
healthStore.requestAuthorization { success in
if success {
healthStore.calculateBloodPressureSystolic()
print(healthStore.query)
print(healthStore.valueTest)
}
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
The value for the variable self.valueTest is assigned in the process DispatchQueue.main.async. Nevertheless, I get only a nil back when querying via ContentView.
You could set up your HealthStore class and use it as an EnvironmentObject. Assuming your app uses the SwiftUI lifecycle you can inject HealthStore into the environment in the #main entry point of your app.
import SwiftUI
#main
struct NameOfYourHeathApp: App {
let healthStore = HealthStore()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(healthStore)
}
}
}
Change your HealthStore class to this. (I removed your commented out code in my sample below)
import HealthKit
class HealthStore: ObservableObject {
var healthStore: HKHealthStore?
var query: HKStatisticsQuery?
var valueTest: HKQuantity?
init() {
if HKHealthStore.isHealthDataAvailable() {
healthStore = HKHealthStore()
}
}
// I moved the HealthStore conditional check out of your View logic
// and placed it here instead.
func setUpHealthStore() {
let typesToRead: Set = [
HKQuantityType.quantityType(forIdentifier: .bloodPressureSystolic)!
]
// I left the `toShare` as nil as I did not dig into adding bloodpressure reading to HealthKit.
healthStore?.requestAuthorization(toShare: nil, read: typesToRead, completion: { success, error in
if success {
self.calculateBloodPressureSystolic()
}
})
}
func calculateBloodPressureSystolic() {
guard let bloodPressureSystolic = HKObjectType.quantityType(forIdentifier: .bloodPressureSystolic) else {
// This should never fail when using a defined constant.
fatalError("*** Unable to get the bloodPressure count ***")
}
query = HKStatisticsQuery(quantityType: bloodPressureSystolic,
quantitySamplePredicate: nil,
options: .discreteAverage) {
query, statistics, error in
DispatchQueue.main.async{
self.valueTest = statistics?.averageQuantity()
}
}
healthStore!.execute(query!)
}
}
Then use it in your ContentView like this.
import SwiftUI
struct ContentView: View {
#EnvironmentObject var healthStore: HealthStore
var body: some View {
Text("Hello, world!")
.onAppear {
healthStore.setUpHealthStore()
}
}
}
I didn't go through the trouble of setting up the proper permissions in the .plist file, but you'll also need to set up the Health Share Usage Description as well as Health Update Usage Description. I assume you have already done this but I just wanted to mention it.

swiftui list doens't appear but array isn't empty

I am working on a Swiftui file that loads data from Firebase.
It did work but when I added things it suddenly stopt working...
I tried to strip it back down but I can't get it working again.
Does anyone know what I do wrong?
import SwiftUI
import Firebase
struct Fav: View {
#StateObject var loader = Loader()
var body: some View {
ScrollView {
if loader.userfav.count != 0 {
List (loader.userfav, id: \.id) { fav in
Text(fav.name.capitalized)
}
}
else
{
Text("You haven't added favorits yet...")
}
}
.onAppear{
loader.loadfav(loadfavorits: "asd")
}
.navigationBarTitle("")
.navigationBarHidden(true)
.navigationBarBackButtonHidden(true)
}
func deletefav (docid: String) {
print(docid)
}
}
struct Fav_Previews: PreviewProvider {
static var previews: some View {
Fav()
}
}
and the loader file
import Foundation
import Firebase
import FirebaseFirestore
class Loader : ObservableObject {
private var db = Firestore.firestore()
#Published var userfav = [fav]()
func loadfav (loadfavorits: String) {
userfav = [fav]()
db.collection("favo").whereField("user", isEqualTo: loadfavorits).getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting favorits: \(err.localizedDescription)")
}
else
{
for fav in querySnapshot!.documents {
let brand = fav.get("brand") as! String
let store = fav.get("store") as! String
let name = fav.get("name") as! String
let type = fav.get("type") as! String
let docid = fav.get("docid") as! String
self.userfav.append(fav(brand: brand, store: store, name: name, type: type, docid: docid))
}
}
}
}
}
It doesn't show the Text("You haven't added favorits yet...")
So that means dat loader.userfav.count is not empty
Having a List embedded in a ScrollView (which also scrolls) can lead to layout problems. Remove the outer ScrollView and the issue will be solved.

How should I get and set a value of UserDefaults?

I'm currently developing an application using SwiftUI.
I want to use a UserDefaults value in this app.
So I made a code below.
But in this case, when I reboot the app(the 4'th process in the process below), I can't get value from UserDefaults...
Build and Run this project.
Pless the home button and the app goes to the background.
Double-tap the home button and remove the app screen.
press the app icon and reboot the app. Then I want to get value from UserDefaults.
to resolve this problem how should I set and get a value in UserDefaults?
Here is the code:
import SwiftUI
struct ContentView: View {
#State var text = "initialText"
var body: some View {
VStack {
Text(text)
TextField( "", text: $text)
}.onAppear(){
if let text = UserDefaults.standard.object(forKey: "text" ){
self.text = text as! String
}
}
.onDisappear(){
UserDefaults.standard.set(self.text, forKey: "text")
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
ADD
When I add this class following the first answer, that code has a couple of errors like this, is it usual?
Xcode: Version 11.7
Swift: Swift 5
Set in a class like this your values: Bool, String(see example), Int, etc...
#if os(iOS)
import UIKit
#else
import AppKit
#endif
import Combine
#propertyWrapper struct UserDefault<T> {
let key: String
let defaultValue: T
init(_ key: String, defaultValue: T) {
self.key = key
self.defaultValue = defaultValue
}
var wrappedValue: T {
get {
return UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
}
set {
UserDefaults.standard.set(newValue, forKey: key)
}
}
}
final class UserSettings: ObservableObject {
let objectWillChange = PassthroughSubject<Void, Never>()
#UserDefault("myText", defaultValue: "initialText")
var myText: String {
willSet { objectWillChange.send() }
}
}
this to read:
let settings = UserSettings()
let count = settings.countSentence // default countsentence 1
this to update:
let settings = UserSettings()
settings.countSentence = 3 // default countsentence 3
Based on your code:
struct ContentView: View {
let UserDef = UserSettings()
#State var text = ""
var body: some View {
VStack {
Text(UserDef.myText)
TextField("placeholder", text: $text, onCommit: { self.UserDef.myText = self.text})
}.onAppear() {
self.text = self.UserDef.myText
}
}
}

How to make a SwiftUI DocumentGroup app without starting on the file picker?

If a user uses the Document App template in Xcode to create a SwiftUI app, macOS starts them off with a new document. This is good. I can work with that to present onboarding UI within a new document.
However, with that same app running on iOS, the user is instead greeted by the stock document view controller to create or pick a document.
This is not helpful in that I don't have a way to present onboarding or any other custom information.
I did notice that if you add a WindowGroup to the Scene, the app will display that window group. But then I don't know how to get the user to the picker UI.
Has anyone figured out how to do things like present onboarding on top of this DocumentGroup-based app?
Here is a full document app
import SwiftUI
import UniformTypeIdentifiers
#main
struct DocumentTestApp: App {
var body: some Scene {
DocumentGroup(newDocument: DocumentTestDocument()) { file in
ContentView(document: file.$document)
}
}
}
struct ContentView: View {
#Binding var document: DocumentTestDocument
var body: some View {
TextEditor(text: $document.text)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView(document: .constant(DocumentTestDocument()))
}
}
extension UTType {
static var exampleText: UTType {
UTType(importedAs: "com.example.plain-text")
}
}
struct DocumentTestDocument: FileDocument {
var text: String
init(text: String = "Hello, world!") {
self.text = text
}
static var readableContentTypes: [UTType] { [.exampleText] }
init(configuration: ReadConfiguration) throws {
guard let data = configuration.file.regularFileContents,
let string = String(data: data, encoding: .utf8)
else {
throw CocoaError(.fileReadCorruptFile)
}
text = string
}
func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
let data = text.data(using: .utf8)!
return .init(regularFileWithContents: data)
}
}
App shows first window scene by default, so place first on-boarding window scene and afterwards a DocumentGroup. Somewhere at the end of on boarding process (success path) call document controller to create new document (DocumentGroup is based on same NSDocumentController engine inside).
Update: below is for macOS
*just recognised that original question is for iOS
So a possible approach is
#main
struct DocumentTestApp: App {
var body: some Scene {
WindowGroup("On-Boarding") {
// ContentView()
// In some action at the end of this scene flow
// just close current window and open new document
Button("Demo") {
NSApp.sendAction(#selector(NSWindow.performClose(_:)), to: nil, from: nil)
NSDocumentController.shared.newDocument(nil)
}
}
DocumentGroup(newDocument: DocumentTestDocument()) { file in
ContentView(document: file.$document)
}
}
}
Alright friends, here is a nice and hacky way to get things going, reaching into the key windows, and setting up onboarding/paywall/whatever you want!
import SwiftUI
#main
struct ExampleApp: App {
#StateObject var captain = Captain()
var body: some Scene {
DocumentGroup(newDocument: ExampleOfDocumentGroupAndOnboardingPaywallDocument()) { file in
ContentView(document: file.$document)
}
}
}
class Captain: ObservableObject {
var onboardingSheet: UIViewController?
#objc func loadData() {
onboardingSheet = try? OnboardingOrPaywall(dismissHandler: dismissSheet).presentFromDocumentGroup()
}
func dismissSheet() {
onboardingSheet?.dismiss(animated: true)
}
init() {
NotificationCenter.default.addObserver(self,
selector: #selector(loadData),
name: UIApplication.didBecomeActiveNotification,
object: nil)
}
}
public protocol DocumentGroupSheet: View {}
struct OnboardingOrPaywall: DocumentGroupSheet {
var dismissHandler: () -> Void
var body: some View {
Button("Done") {
dismissHandler()
}
Text("Let me introduce you to this delicious app!")
}
}
public enum DocumentGroupSheetError: Error {
case noParentWindow
}
public extension DocumentGroupSheet {
func presentFromDocumentGroup() throws -> UIViewController {
let window = UIApplication.shared.activeKeyWindows.first
let parent = window?.rootViewController
guard let parent = parent else { throw DocumentGroupSheetError.noParentWindow }
let sheet = UIHostingController(rootView: body)
sheet.modalPresentationStyle = .fullScreen
parent.present(sheet, animated: false, completion: nil)
return sheet
}
}
public extension UIApplication {
var activeWindowScenes: [UIWindowScene] {
connectedScenes
.filter { $0.activationState == .foregroundActive }
.compactMap { $0 as? UIWindowScene }
}
var activeWindows: [UIWindow] {
activeWindowScenes
.flatMap { $0.windows }
}
var activeKeyWindows: [UIWindow] {
activeWindows
.filter { $0.isKeyWindow }
}
}

SwiftUI: Execute functions in Preview Provider

I think I'm going about this SwiftUI thing all wrong. It's clear that we're just defining the layout as a structs and there can be limited conventional programming embroiled in the layout. I'm having difficulties thinking like this. What is the best way of doing this?
Take the example below. Project is an NSManagedObject. All I want to do is pass in example record so the SwiftUI will render. Nothing I try works.
struct ProjectView: View
{
#State var project: Project //NSManagedObject
var body: some View
{
TextField("", text: Binding<String>($project.projectName)!)
}
}
struct ProjectView_Previews: PreviewProvider
{
static var previews: some View
{
var p:Project
p = getFirstProject() //returns a Project
return ProjectView(project: p)
}
}
If I try returning the struct it says it cannot preview in the file.
If I don't return the struct I get a Function declares an opaque return type, but has no return statements in its body from which to infer an underlying type error.
UPDATE:
var app = UIApplication.shared.delegate as! AppDelegate
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
lazy var persistentContainer: NSPersistentCloudKitContainer = {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
lazy var managedObjectContext: NSManagedObjectContext =
{
return persistentContainer.viewContext
}()
}
And the rest of the code:
func allRecords<T: NSManagedObject>(_ type : T.Type, sort: NSSortDescriptor? = nil) -> [T]
{
let context = app.managedObjectContext
let request = T.fetchRequest()
if let sortDescriptor = sort
{
request.sortDescriptors = [sortDescriptor]
}
do
{
let results = try context.fetch(request)
return results as! [T]
}
catch
{
print("Error with request: \(error)")
return []
}
}
func getCount() -> String
{
let r = allRecords(Project.self)
return String(r.count)
}
struct ProjectView: View
{
// #ObservedObject var project: Project
var body: some View
{
Text(getCount())
// TextField("", text: Binding<String>($project.projectName)!)
}
}
struct ProjectView_Previews: PreviewProvider
{
static var previews: some View
{
ProjectView()
}
}
r.count is returning 0, but in the main application thread it is returning 8. Has app.managedObjectContext not been defined properly? I think this has just got too complicated too quickly.
Assuming getFirstProject works correctly the following should work
struct ProjectView_Previews: PreviewProvider
{
static var previews: some View
{
ProjectView(project: getFirstProject())
}
}
However there are concerns about the following...
struct ProjectView: View
{
#State var project: Project //NSManagedObject
because #State is designed to be internal view state-only thing, but Project in your case is a model, so the recommended scenario for this is to use ObservableObject view model either by conforming Project or as standalone clue class holding Project instance(s).