In a SwiftUI lifecycle app, where exactly should I register a CoreData transformerValue? - core-data

I'm trying to follow this tutorial:
https://www.avanderlee.com/swift/valuetransformer-core-data/
But I'm stumped on where (and how!) exactly to use
UIColorValueTransformer.register()
The author says to do it before setting up the persistent container... and I'm think I'm doing that here:
import SwiftUI
#main
struct ContactApp: App {
let persistenceController = PersistenceController.shared
var body: some Scene {
WindowGroup {
MainTabView()
.environment(\.managedObjectContext, persistenceController.container.viewContext)
}
}
}
But I'm at a loss of what exactly to write, or if this is the right place. Maybe I should be doing it in a different place?

Put it into init, like
class PersistenceController {
static let shared = PersistenceController()
init() {
UIColorValueTransformer.register() // << here !!
// ... other init code
}
// ... other code
}

Related

SwiftUI and FetchRequest : delay the reordering of a list

I have written an application displaying a list of students. I use a List of NavigationLink for that purpose. The students are ordered by one of their properties questionAskedClass which is an integer. All this information is stored within CoreData.
#Environment(\.managedObjectContext) var managedObjectContext
#FetchRequest(entity: Student.entity(),
sortDescriptors: [NSSortDescriptor(keyPath: \Student.questionAskedClass,
ascending: true)])
var students: FetchedResults<Student>
var body: some View {
NavigationView {
List {
ForEach(students) {student in
NavigationLink(destination: StudentView(student: student)) {
HStack {
VStack(alignment: .leading) {
Text("\(student.firstName)").font(.headline).foregroundColor(Color.black)
Text("\(student.lastName)").font(.headline).foregroundColor(Color.gray)
}
}
}
}
}
}
}
When I press the name of the students, I switch to a new view called StudentView where I can get more information about the student and where I can update the property questionAskedClass
struct StudentView: View {
#Environment(\.managedObjectContext) var managedObjectContext
func askQuestion() {
self.managedObjectContext.performAndWait {
self.student.questionAskedClass += 1
try? self.managedObjectContext.save()
}
}
}
Unfortunately, when I change that property, the ordering of the initial list is changed and I am taken away from the StudentView. The framework seems to get the feeling that the list needs to be reordered. But I just want this list to be reordered when I go back to the list. Not immediately when I change the value of questionAskedClass.
What can I do to mitigate this problem?
Thanks for your help.
You can try creating a simple NSFetchRequest<Student> and use the result of this fetch to update your students list.
#State var students: [Student] = []
fun refresh() {
let fetchRequest = NSFetchRequest<Student>(entityName: "Student")
students = try? managedObjectContext.fetch(fetchRequest) ?? []
}
You can trigger this refresh in onAppear so the list will be updated every time the View appears:
NavigationView {
...
}.onAppear {
self.refresh()
}
Alternatively you can save your context in onAppear of the main view instead of StudentView:
struct StudentView: View {
func askQuestion() {
self.student.questionAskedClass += 1
}
}
NavigationView {
...
}.onAppear {
try? self.managedObjectContext.save()
}
If you want your data to persist when the app is terminated, add this function in AppDelegate (assuming your persistentContainer is declared there:
func applicationWillTerminate(_ application: UIApplication) {
try? persistentContainer.viewContext.save()
}

Using SwiftUI, Core Data, and one-to-many relationships, why does the list not update when adding a row on the Many side

I have a SwiftUI project with Core Data. The data model is a simple one-to-many and two primary views which each have a textfield at the top and a button to add a new item to the list view below. The first view is for the One side of the relation and the second for the Many. So, the NavigationLink in the first opens the second and passes the One object. Pretty standard stuff, it would seem. The methodology for creating the One works and the list below gets updated immediately when the managed object context saves the new item. But, the same type of methodology doesn't refresh the list for the Many side when viewing on a device, although it does work fine in the simulator and the preview window. The data is definitely saved because if you navigate back to the One side then re-select it to re-load the Many view, it shows the new item in the list.
I've looked through lots of tutorials, other questions, etc. and haven't found a reason for this. Am I doing something wrong in how I am going to the Many side of the relation, or is there something else I have to do to refresh the view only on the Many side? Thanks!!!
Full project available at https://github.com/fahrsoft/OneToManyTest
From the ContentView, showing the One side (note: OneView is a simple view that takes the object and shows the text. Same for ManyView.):
struct ContentView: View {
#Environment(\.managedObjectContext) var moc
#FetchRequest(entity: One.entity(), sortDescriptors: []) var ones: FetchedResults<One>
#State private var newName = ""
#State var isNavTitleHidden = true
var body: some View {
NavigationView {
VStack {
HStack {
TextField("New One", text: self.$newName)
Spacer()
Button(action: {
let newOne = One(context: self.moc)
newOne.name = self.newName
self.newName = ""
try? self.moc.save()
}) {
Image(systemName: "plus.circle.fill")
.foregroundColor(.green)
.frame(width: 32, height: 32, alignment: .center)
}
}
.padding(.top)
.padding(.horizontal)
List {
Section(header: Text("Ones")) {
ForEach(self.ones, id:\.self) { (one:One) in
NavigationLink(destination: OneDetailView(one: one, isNavTitleHidden: self.$isNavTitleHidden).environment(\.managedObjectContext, self.moc)) {
OneView(one: one).environment(\.managedObjectContext, self.moc)
}
}
.onDelete { indexSet in
let deleteOne = self.ones[indexSet.first!]
self.moc.delete(deleteOne)
do {
try self.moc.save()
} catch {
print(error)
}
}
}
}
}
.navigationBarTitle(Text("Ones List"))
.navigationBarHidden(self.isNavTitleHidden)
.onAppear {
self.isNavTitleHidden = true
}
}
}}
From the OneDetailView showing the Many side:
struct OneDetailView: View {
#Environment(\.managedObjectContext) var moc
#ObservedObject var one: One
#State private var newManyAttribute = ""
#Binding var isNavTitleHidden: Bool
var body: some View {
VStack {
HStack {
TextField("New Many", text: self.$newManyAttribute)
Spacer()
Button(action: {
let newMany = Many(context: self.moc)
newMany.attribute = self.newManyAttribute
self.newManyAttribute = ""
self.one.addToMany(newMany)
try? self.moc.save()
}) {
Image(systemName: "plus.circle.fill")
.foregroundColor(.green)
.frame(width: 32, height: 32, alignment: .center)
}
}
.padding(.top)
.padding(.horizontal)
List {
Section(header: Text("Manys")) {
ForEach(self.one.manyArray, id: \.self) { many in
ManyView(many: many).environment(\.managedObjectContext, self.moc)
}
}
}
}
.navigationBarTitle("\(self.one.wrappedName) Details")
.onAppear {
self.isNavTitleHidden = false
}
}}
If you create a separate View for the child objects, and bind them to a FetchRequest inside that view, it will work.
e.g. say you have a list of Restaurant NSManagedObjects, each with a bunch of associated MenuItem NSManaged objects. Assuming the MenuItems have a 1:1 relationship with a Restaurant object, you can do this:
public struct RestaurantList: View {
#FetchRequest private var restaurants: FetchedResults<Restaurant>
#Environment(\.managedObjectContext) private var viewContext
public init() {
_restaurants = FetchRequest(fetchRequest: Restaurant.fetchRequest().then {
$0.predicate = NSPredicate(format: "city = %#", argumentArray: ["Tokyo"])
$0.sortDescriptors = [NSSortDescriptor(keyPath: \Restaurant.title, ascending: false)]
})
}
public var body: some View {
VStack {
Text("All restaurants in Tokyo")
ForEach(restaurants) { restaurant in
MenuItemsView(restaurant)
}
}
}
}
public struct MenuItemsView: View {
#FetchRequest private var items: FetchedResults<MenuItem>
#ObservedObject var restaurant: Restaurant
#Environment(\.managedObjectContext) private var viewContext
public init(restaurant: Restaurant) {
self.restaurant = restaurant
_items = FetchRequest(fetchRequest: MenuItem.fetchRequest().then {
$0.predicate = NSPredicate(format: "restaurant = %#", argumentArray: [restaurant])
$0.sortDescriptors = [NSSortDescriptor(keyPath: \MenuItem.order, ascending: true)]
})
}
public var body: some View {
VStack {
Text("Menu for ")
Text(restaurant.title)
ForEach(items) { menuItem in
MenuItemDetailView(menuItem)
}
}
}
}
public struct MenuItemDetailView: View {
#ObservedObject var menuItem: MenuItem
#Environment(\.managedObjectContext) private var viewContext
public init(_ menuItem: MenuItem) {
self.menuItem = menuItem
}
public var body: some View {
Text("info about your menuItem here")
}
}
Now whenever a MenuItem changes, the main screen (RestaurantList) will automatically update.
Many-to-many relationships
If the relationship between Restaurants and MenuItems is n:n, all you have to do is change the predicate from
$0.predicate = NSPredicate(format: "restaurant = %#", argumentArray: [restaurant])
to
$0.predicate = NSPredicate(format: "ANY restaurant = %#", argumentArray: [restaurant])
Identifiable
If your NSManagedObjects have a unique string ID, use that for Identifiable. If you don't want to add a unique ID right away, objectID.debugDescription is a good placeholder.
#objc(Restaurant)
public class Restaurant: NSManagedObject, Identifiable {
public var id: String {
get {
return your_unique_field_from_core_data_here
// or this in a pinch
// return objectID.debugDescription
}
}
}
The only thing I could come up with as a way to make it work decently well was to create a new FetchRequest for the Many items using the selected One in a predicate. Adding the FetchRequest and an init to the beginning of the OneDetailView allows for the list to update.
struct OneDetailView: View {
#Environment(\.managedObjectContext) var moc
#ObservedObject var one: One
#State private var newManyAttribute = ""
#Binding var isNavTitleHidden: Bool
#FetchRequest var manys: FetchedResults<Many>
init(one: One, isNavTitleHidden: Binding<Bool>) {
self.one = one
self._isNavTitleHidden = isNavTitleHidden
var predicate: NSPredicate?
predicate = NSPredicate(format: "one = %#", one)
self._manys = FetchRequest(
entity: Many.entity(),
sortDescriptors: [],
predicate: predicate
)
}
var body: some View {
VStack {
HStack {
TextField("New Many", text: self.$newManyAttribute)
Spacer()
Button(action: {
let newMany = Many(context: self.moc)
newMany.attribute = self.newManyAttribute
self.newManyAttribute = ""
self.one.addToMany(newMany)
try? self.moc.save()
}) {
Image(systemName: "plus.circle.fill")
.foregroundColor(.green)
.frame(width: 32, height: 32, alignment: .center)
}
}
.padding(.top)
.padding(.horizontal)
List {
Section(header: Text("Manys")) {
ForEach(self.manys, id: \.self) { many in
ManyView(many: many).environment(\.managedObjectContext, self.moc)
}
}
}
}
.navigationBarTitle("\(self.one.wrappedName) Details")
.onAppear {
self.isNavTitleHidden = false
}
}}
After more research, ObservableObject has a built-in publisher by default that can notify any views that the object will change. Simply call
objectWillChange.send()
on an ObservableObject before changes occur to have any UI refresh that is observing that object.
For example, to fix my issue where changes to Core Data relationships weren't updating the UI, I've added this call before saving the context.
if workContext.hasChanges {
objectWillChange.send()
do {
try self.workContext.save()
} catch {
fatalError(error.localizedDescription)
}
}
No need to implement a custom #Published property or other workaround.
I've found a fix/workaround that's literally just a few lines of code and seems to work great.
What's happening as you've seen is CoreData isn't announcing anything has changed when a relationship changes (or for that matter a relation to a relation). So your view struct isn't getting reinstantiated and it's not querying those computed properties on your core data object. I've been learning SwiftUI and trying to rewrite a UI to use a Model that uses a few different relationships, some nested.
My initial thought was to use subviews with #FetchRequests and pass in parameters to those views. But I've got a lot of subviews that need to make use of relationships - that's a ton of code, and for me could potentially be tens if not hundreds of fetchrequests for some layouts. I'm no expert, but that way lies madness!
Instead I've found a way that seems hackish, but uses very little code and feels kind of elegant for a cheat.
I have a ModelController class that handles all Core Data code on a background context and I use that context to 'kick' the ui to tell it to refresh itself when it saves (any time something changes). To do the kicking, I added a #Published kicker property to the class which any views can use to be notified when they need to be torn down and rebuilt. Any time the background context saves, the kicker toggles and that kick is pushed out into the environment.
Here's the ModelController:
public class ModelController: ObservableObject {
// MARK: - Properties
let stack: ModelStack
public let viewContext: NSManagedObjectContext
public let workContext: NSManagedObjectContext
#Published public var uiKicker = true
// MARK: - Public init
public init(stack: ModelStack) {
self.stack = stack
viewContext = stack.persistentContainer.viewContext
viewContext.automaticallyMergesChangesFromParent = true
workContext = stack.persistentContainer.newBackgroundContext()
workContext.automaticallyMergesChangesFromParent = true
}
// Logic logic...
public func save() {
workContext.performAndWait {
if workContext.hasChanges {
do {
try self.workContext.save()
} catch {
fatalError(error.localizedDescription)
}
}
}
uiKicker.toggle()
}
}
I currently instantiate ModelController in #main and inject it into the environment to do my bidding:
#main
struct MyApp: App {
let modelController = ModelController(stack: ModelStack())
var body: some Scene {
WindowGroup {
MainView()
.environment(\.managedObjectContext, modelController.viewContext)
.environmentObject(modelController)
}
}
}
Now take a view that isn't responding... Here's one now! We can use the uiKicker property to force the stubborn view to refresh. To do that you need to actually use the kicker's value somewhere in your view. It doesn't apparently need to actually change something, just be used - so for example in this view you'll see at the very end I'm setting the opacity of the view based on the uiKicker. It just happens the opacity is set to the same value whether it's true or false so this isn't a noticeable change for the user, other than the fact that the 'sticky' value (in this case list.openItemsCount) gets refreshed.
You can use the kicker anywhere in the UI and it should work (I've got it on the enclosing VStack but it could be anywhere in there).
struct CardView: View {
#ObservedObject var list: Model.List
#EnvironmentObject var modelController: ModelController
var body: some View {
VStack {
HStack {
Image(systemName: "gear")
Spacer()
Label(String(list.openItemsCount), systemImage: "cart")
}
Spacer()
Text(list.name ?? "Well crap I don't know.")
Spacer()
HStack {
Image(systemName: "trash")
.onTapGesture {
modelController.delete(list.objectID)
}
Spacer()
Label("2", systemImage: "person.crop.circle.badge.plus")
}
}
.padding()
.background(Color.gray)
.cornerRadius(30)
.opacity(modelController.uiKicker ? 100 : 100)
}
}
And there you have it. Use the uiKicker anywhere things aren't refreshing properly. Literally a few lines of code and stale relationships are a thing of the past!
As I learn more about SwiftUI I have to say I'm loving it!
EDIT TO ADD:
Poking around some more I've found that this only works if the observed object is injected using .environmentObject, it doesn't work if you use custom environment keys and inject using .environment(\.modelController). I have no idea why but it's true as of iOS 14.3/XCode 12.3.

Using WebKit in SwiftUI hides keyboard when view updates

I've created a minimum reproducible example of this problem I'm facing.
First, I created a WKWebView housed in a UIViewRepresentable to be used with SwiftUI. Then, I set up a WKUserContentController and a WKWebViewConfiguration so the WKWebView can send messages to native code. In this case, I have a <textarea> that sends over its value on input.
The value sent over through WebKit is assigned to a #State variable which causes the view to update. Unfortunately, the WKWebView is deselected whenever the view updates. How can I work around this? The WKWebView needs to stay selected until the user deliberately chooses to hide the keyboard.
This is a minimal example of what's happening:
import SwiftUI
import WebKit
struct WebView: UIViewRepresentable {
let html: String
let configuration: WKWebViewConfiguration
func makeUIView(context: Context) -> WKWebView {
.init(frame: .zero, configuration: configuration)
}
func updateUIView(_ webView: WKWebView, context: Context) {
webView.loadHTMLString(html, baseURL: nil)
}
}
struct ContentView: View {
final class Coordinator: NSObject, WKScriptMessageHandler {
#Binding var text: String
init(text: Binding<String>) {
_text = text
}
func userContentController(
_ userContentController: WKUserContentController,
didReceive message: WKScriptMessage
) {
guard let text = message.body as? String else { return }
self.text = text
}
}
#State var text = ""
var body: some View {
WebView(
html: """
<!DOCTYPE html>
<html>
<body>
<textarea>\(text)</textarea>
<script>
const textarea = document.querySelector('textarea')
textarea.oninput = () =>
webkit.messageHandlers.main.postMessage(textarea.value)
</script>
</body>
</html>
""",
configuration: {
let userContentController = WKUserContentController()
userContentController.add(Coordinator(text: $text), name: "main")
let configuration = WKWebViewConfiguration()
configuration.userContentController = userContentController
return configuration
}()
)
}
}
I would recommend (as I see the simplest in this scenario) to change handling event to
textarea.onblur = () =>
webkit.messageHandlers.main.postMessage(textarea.value)
otherwise a complex refactoring is needed to keep away WK* entities from SwiftUI representable wrapper, or changing model to avoid using #State or anything resulting in view rebuild, or both of those.

Saving custom class to array in CoreData SwiftUI after closing app fails

I am getting the error below, when I close my app and then try to save to the core the when reloading the app.
error: Serious application error. Exception was caught during Core Data change processing. This is usually a bug within an observer of NSManagedObjectContextObjectsDidChangeNotification. -[__NSSingleObjectArrayI compare:]: unrecognized selector sent to instance 0x600002291510 with userInfo (null)
When I first run the app fresh after deleting it off the simulator it works fine though once I close the program and try to add to the core again I get the error above.
Here is my code:
import SwiftUI
import CoreData
struct ContentView: View {
#Environment(\.managedObjectContext) var managedObjectContext
#State var objRating:[ObjectToRate]
#FetchRequest(entity: ObjectsForRate.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \ObjectsForRate.objectsClass, ascending: true)])
var classesRetrieved: FetchedResults<ObjectsForRate>
var body: some View {
VStack {
Text(self.objRating[0].name!)
Button(action: {
print("Mark 1")
let obj = ObjectsForRate(context: self.managedObjectContext)
obj.objectsClass = self.objRating
do
{
try self.managedObjectContext.save()
}
catch
{
print("ERROR \(error)")
}
}) {
Text("Insert to Core ")
}
Button(action: {
print("---------")
print(self.classesRetrieved.count)
print(self.classesRetrieved)
}) {
Text("Retrieve")
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView(objRating:data )
}
}
public class ObjectToRate:NSObject, NSCoding
{
public var name:String?
init(name:String) {
self.name = name
}
public func encode(with coder: NSCoder) {
coder.encode(name,forKey: "name")
}
public required init(coder decoder: NSCoder) {
self.name = decoder.decodeObject(forKey: "name") as? String
}
}
var obj: ObjectToRate = {
let o = ObjectToRate(name: "hi")
return o
}()
var data = [
obj
]
I've tried a lot of things but not sure what I'm doing wrong or if it is a bug.
Here is my Coredata model:
Another thing is when I retrieve the data from the core I get this:
<ObjectsForRate: 0x6000038fd400> (entity: ObjectsForRate; id: 0x8b8684e607da61d2 <x-coredata://0CDCAD97-CA46-402F-B638-3F0ACB6E30A7/ObjectsForRate/p5>; data: <fault>
Thank you in advance for your time and help.

save uiimageview to coredata as binary data swift (5)

I am trying to save a imageview as a image to binary data in core data. My code is not working. It has a compile error. In View controller it is not regisitering cdHandler. All i want to do is save the the imaveview as binary data in a core data model.I have 2 classes a app delegate and a view controller.
CLASS VIEW CONTROLLER
import UIKit
import CoreData
class ViewController: UIViewController {
var canVasView = UIImageView()
#objc func hhh() {
let photo = self.canVasView.image
let data = photo!.pngData()
if cdHandler.saveObject(pic: data!){
}
}
}
APP DELEGATE
import UIKit
import CoreData
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
lazy var persistentContainer: NSPersistentContainer = {
/*
The persistent container for the application. This implementation
creates and returns a container, having loaded the store for the
application to it. This property is optional since there are legitimate
error conditions that could cause the creation of the store to fail.
*/
let container = NSPersistentContainer(name: "Model")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
/*
Typical reasons for an error here include:
* The parent directory does not exist, cannot be created, or disallows writing.
* The persistent store is not accessible, due to permissions or data protection when the device is locked.
* The device is out of space.
* The store could not be migrated to the current model version.
Check the error message to determine what the actual problem was.
*/
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
class cdHandler: NSObject {
private class func getContext() -> NSManagedObjectContext {
let appdeleagetzz = UIApplication.shared.delegate as! AppDelegate
return appdeleagetzz.persistentContainer.viewContext
}
class func saveObject(pic: Data, userName: String) -> Bool {
let context = getContext()
let entity = NSEntityDescription.entity(forEntityName: "User", in: context)
let managedObject = NSManagedObject(entity: entity!, insertInto: context)
managedObject.setValue(pic, forKey:"pic")
managedObject.setValue(userName, forKey:"userName")
do {
try context.save()
return true
} catch {
return false
}
}
class func deletObject(user: User) -> Bool {
let context = getContext()
context.delete(user)
do {
try context.save()
return true
} catch {
return false
}
}
class func fetchObject() -> [User]? {
do {
let context = getContext()
return try context.fetch(User.fetchRequest())
} catch {
return [User]()
}
}
}
}
The error message, *Value of type 'AppDelegate' has no member named 'persistentContainer', explains the problem. Indeed, when I look at the code for your AppDelegate class, I can confirm that it has no member named 'persistentContainer'. (If I am reading it correctly, the last two lines in the file are closing curly brackets. The first one closes your cdHandler nested class, and the second one closes your AppDelegate class.)
Do the following exercise. In Xcode, click in the menu: File > New Project and select iOS, Application and Single View App. Name your new project Junk. Switch on the Core Data checkbox. Click button Create. After it is done, look at the AppDelegate.swift which Xcode created, and in the AppDelegate class, you see it contains 8 functions (func). The 7th one is lazy var persistentContainer. Aha! The compiler is telling you that you probably should not have deleted those 8 functions, persistentContainer in particular.
You should copy that persistentContainer func from that Junk project into your AppDelegate class in your real project. Or, to head off future trouble, consider copying most of the other 7 funcs also. As you can see, most of them don't do anything except provide comments with explanations that are useful for beginners. After you are done copying, close the Junk project. (I overwrite my Junk project with a new Junk project several times in a typical week, especially when answering StackOverflow questions.)
That should fix this particular error and answer this question. Onward to the next issue. :)
Response to comment that you still get the error with cdHandler
Having nothing else to go on, I presume that the error that you are referring to is the compiler error still in your screenshot. In other words, you are saying that adding the persistentContainer definition did not make it any better.
Well, it works for me. Please replace all of the code in your AppDelegate.swift class with the following, build and run it…
import UIKit
import CoreData
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
AppDelegate.cdHandler.testGetContext()
return true
}
lazy var persistentContainer: NSPersistentContainer = {
/*
The persistent container for the application. This implementation
creates and returns a container, having loaded the store for the
application to it. This property is optional since there are legitimate
error conditions that could cause the creation of the store to fail.
*/
let container = NSPersistentContainer(name: "Junk")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
/*
Typical reasons for an error here include:
* The parent directory does not exist, cannot be created, or disallows writing.
* The persistent store is not accessible, due to permissions or data protection when the device is locked.
* The device is out of space.
* The store could not be migrated to the current model version.
Check the error message to determine what the actual problem was.
*/
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
class cdHandler: NSObject {
private class func getContext() -> NSManagedObjectContext {
let appdeleagetzz = UIApplication.shared.delegate as! AppDelegate
return appdeleagetzz.persistentContainer.viewContext
}
class func testGetContext() {
let context = getContext()
print("getContext() succeeded, got \(context)")
}
class func saveObject(pic: Data, userName: String) -> Bool {
let context = getContext()
let entity = NSEntityDescription.entity(forEntityName: "User", in: context)
let managedObject = NSManagedObject(entity: entity!, insertInto: context)
managedObject.setValue(pic, forKey:"pic")
managedObject.setValue(userName, forKey:"userName")
do {
try context.save()
return true
} catch {
return false
}
}
class func deletObject(user: NSManagedObject) -> Bool {
let context = getContext()
context.delete(user)
do {
try context.save()
return true
} catch {
return false
}
}
}
}
You see that compiles with no errors. Also, it runs and the AppDelegate.cdhandler.getContext() method works. As you can see, in AppDelegate.application(application:didFinishLaunchingWithOptions:), I have added a call to a new method which I defined later,AppDelegate.cdHandler.testGetContext()`. It works perfectly.
Are you getting a different error now? If so, you need to specify whether it is a Build or Run error. In either case, copy and paste the text of the error into your question, and tell us where it occurs.

Resources