Search code examples
classcore-dataswiftui

SwiftUI Using Core Data for a calculation in a Class


In my project I have a Core Data entity called "Course" which contains the following attributes:-

name (String)

tee (String)

hole1no (Int16)

hole1index (Int16)

hole1par (Int16)

I use a fetch request and select the record required which is then used in variable calculations and text fields in my views.

I then have set up a "class" view to create ObsevableObject using @Published to enable certain data to be used across multiple views.

The issue I have is in my class setup I need to use a combination of data from the Core data and data entered in the views to calculate variables which are needed in multiple views.

Below is a copy of my code, in the ScoreManager class code I get the error message "Class ScoreManager has no initializers" which disappears if I remove any reference to the Course data so I presume that is what it is referring to in the error.

Any advice would be greatly appreciated, this is my first complex project so I may well be missing something in my knowledge. If possible could you update my code so I can see where the changes are to assist my learning.

Thanks in advance.

This is the code that fetches the data from Core Data

import SwiftUI
import CoreData

struct Score4PickCourse: View {
    
    @Environment(\.managedObjectContext) var moc
    @FetchRequest(entity: Course.entity(), sortDescriptors:[NSSortDescriptor(keyPath: \Course.name, ascending: false)]) var courses: FetchedResults<Course>
    
    @State private var showingAddScreen = false
    
    
    
    
    
    var body: some View {
        
        
            List {
                ForEach(courses, id: \.self) { course in NavigationLink(destination: Score4SelectPlayers(course: course)) {
                    
                    
                    VStack(alignment: .leading) {
                        Text(course.name ?? "Unknown Course")
                        
                        Text(course.tee ?? "Unknown Tee")
                        
                    }
                }
                    
                }
                
            }
                .navigationBarTitle("Courses")
                .navigationBarItems(trailing: Button(action: {
                    self.showingAddScreen.toggle()
                }) {
                    Image(systemName: "plus")
                }
            )
                .sheet(isPresented: $showingAddScreen) {
                    CourseAddNew().environment(\.managedObjectContext, self.moc)
                }
        
        
    }



}

This is the code where you enter basic information (player name, handicap etc.

import SwiftUI
import CoreData

struct Score4SelectPlayers: View {
    
    @Environment(\.managedObjectContext) var moc
    @Environment(\.presentationMode) var presentationMode
    
    
    
    @ObservedObject var scoreManager = ScoreManager()
    
    @State private var date = Date()
    
    let course: Course
    
    var body: some View {
        
        
        
        Form {
        
        Section {
            
            Text("Course Selected - \(course.name ?? "No Course Selected")")
            
            Text("Course Hole No - \(course.hole1no)")
            
            Text("Tee Category Selected - \(course.tee ?? "No Tee Selected")")
            
            DatePicker("Date of Round", selection: $date, displayedComponents: .date)
                .foregroundColor(Color.blue)
                .font(.system(size: 17, weight: .bold, design: .rounded))
            
            TextField("Player 1 Name", text: $scoreManager.player1)
            
            Picker("Player 1 Handicap", selection: $scoreManager.p1handicap) {
                ForEach(Array(stride(from: 0, to: 55, by: 1)), id: \.self) { index in Text("\(index)")
            }
        }       .frame(width: 300, height: 20, alignment: .center)
            .foregroundColor(Color.blue)
                .font(.system(size: 17, weight: .bold, design: .rounded))
        }
        }
    
        NavigationLink(destination: Score4Hole1(scoreManager: scoreManager, course: course)) {
                Text("Go To Hole 1")
            
            }
        
    }
        
    }


struct Score4SelectPlayers_Previews: PreviewProvider {
    static let moc = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
    
    static var previews: some View {
        let course = Course(context: moc)
        course.name = "Great Barr"
        course.tee = "White Tee"
        course.hole1no = 1
        course.hole1par = 5
        course.hole1index = 10
        
        return NavigationView {
            CourseDetailView(course: course)
        }
    }
}

This is the class code where I need to be able to include data from Core Data in the variable calculations

import SwiftUI
import CoreData

class ScoreManager : ObservableObject {
   
    let course: Course
 
    @Published var player1 = ""
    @Published var p1handicap = 0
    
    var p1h1shots: Int16 {
        
        let p1h1hand = Int16(p1handicap)
        let index = Int16(course.hole1index)
        let p1h1shot = p1h1hand - index
        
        if p1h1shot < 0 {
            return 0
        }
        if p1h1shot >= 0 && p1h1shot < 18 {
            return 1
        }
        if p1h1shot >= 18 && p1h1shot < 36 {
            return 2
        }
        if p1h1shot >= 36 && p1h1shot < 54 {
            return 3
        }
        return 0
        }
    
    
}

Solution

  • I can't test the code without a Minimal Reproducible Example but these changes could work. I don't know where you will be using it

    1st: CoreData objects are ObservableObjects so in Score4SelectPlayers change your course to @ObservedObject var course: Course

    2nd: In your ScoreManager change p1h1shots to a func

    func p1h1shots(hole1index: Int16) -> Int16 {
        let p1h1hand = Int16(p1handicap)
        let p1h1shot = p1h1hand - hole1index
        
        if p1h1shot < 0 {
            return 0
        }
        if p1h1shot >= 0 && p1h1shot < 18 {
            return 1
        }
        if p1h1shot >= 18 && p1h1shot < 36 {
            return 2
        }
        if p1h1shot >= 36 && p1h1shot < 54 {
            return 3
        }
        return 0
    }
    

    and then you can use it in any View with scoreManager.p1h1shots(hole1index: course.hole1index)

    3rd: You can always just put it in your model

    extension Course{
        func p1h1shots(p1handicap: Int16) -> Int16 {
            let index = Int16(hole1index)
            let p1h1shot = p1handicap - index
            
            if p1h1shot < 0 {
                return 0
            }
            if p1h1shot >= 0 && p1h1shot < 18 {
                return 1
            }
            if p1h1shot >= 18 && p1h1shot < 36 {
                return 2
            }
            if p1h1shot >= 36 && p1h1shot < 54 {
                return 3
            }
            return 0
        }
    }
    

    and then use it course.p1h1shots(p1handicap: Int16) in your View