How to load NSUserDefaults data on a Today Extension in Swift? - swift

I've already searched a lot but any answer fixed the problem.
I've created an app group, selected it on both app and today extension, they're linked to the same group now. I'm trying to load data from the app and display it on a table view on the today extension, the thing is that I'm getting a crash saying that my array is nil, and it cannot be nil. I don't know why tho, cause I'm putting the data into the array. Here is my code:
func handleData() {
let defaults = NSUserDefaults(suiteName: "MY APP GROUP HERE")
if let descriptionsArr = defaults?.valueForKey("descriptions") as? [String] {
descriptions = descriptionsArr
}
defaults?.synchronize()
}
So, I'm pretty sure the app group is spelled right, and the value key. What could it be that is making my array nil? Really need help!
Thanks.

Related

UserDefaults/NSUserDefaults removeObject(forKey:) mysteriously failing in app and between apps

I am working on an "uninstaller" for an macOS app we've had for several years now. The purpose for the uninstaller is to allow us to put a given system into a nascent state as if the original app had never been installed so that we can more reliably test the install process.
The original app has an extensive array of preferences stored in UserDefaults. In the original app there is a resetToDefaults() method which works just fine resetting all the defaults however for the uninstaller we'd wanted to remove the values completely. It looks to be straight-forward and this is what I came up with...
func flushPreferences() {
let defaults = getDefaultPreferences()
for preferenceName in defaults.keys.sorted() {
UserDefaults.standard.removeObject(forKey: preferenceName)
}
UserDefaults.standard.synchronize()
}
... which does not work at all.
I read in the documentation
Removing a default has no effect on the value returned by the objectForKey: method if the same key exists in a domain that precedes the standard application domain in the search list.
and I don't really understand what "domain" relates to and thought it might be app so tried the code as a test in the original app and that does nothing either.
Someone else suggested this, which also does nothing
let appDomain = Bundle.main.bundleIdentifier!
UserDefaults.standard.removePersistentDomain(forName: appDomain)
I also found some test code which works absolutely fine... which looks to be nearly identical to what I'm doing. I even tried using it with hard-coding one of our pref keys and that fails as well.
func testRemoveObject() {
let myKey:String = "myKey"
UserDefaults.standard.set(true, forKey: myKey)
let beforeVal = UserDefaults.standard.value(forKey: myKey)
print("before: \(beforeVal ?? "nil")")
UserDefaults.standard.removeObject(forKey: myKey) // Note: This is the only line needed, others are debugging
let afterVal = UserDefaults.standard.value(forKey: myKey)
print("after: \(afterVal ?? "nil")")
}
What am I missing? It looks like this one (based on what I've been able to find on the web) can be somewhat mysterious but I'm thinking it must be something obvious that I'm not seeing.
Well, thanks to red_menace's suggestion I found one article that led to another that suggested that the following command will reset the user preferences cache:
killall -u #USER cfprefsd
which seemed to work (yay) but upon further investigation it appears that simply closing the app is what updates the actual preference in the .plist and that changing it in the app will not show up until you exit.
This makes sense as it explains why you can create a preference on the fly save it, confirm it saved, delete it and confirm it deleted but cannot delete a previously saved preference — as similar to the persistent prefs perhaps the new preference is not added to the cache until the application exits.
This could also explain the various odd behaviors that other posters were finding (only worked every other time, had to do it asynchronously, etc.). As for NSUserDefaults.synchronize() has been depreciated and developer.apple.com indicates that it is unneeded and does nothing.
So one problem solved...
As it turns out my initial instinct was accurate as well and you cannot access prefs from one app in another using the removeObject(forKey: preferenceName)
// Will not work cross-application, though will work locally (inter-app)
UserDefaults.standard.removeObject(forKey: preferenceName)
To get it to work cross applications you have to use CFPreferencesSetAppValue(_ key:, value:, applicationID:) which is part of the "Preferences Utilities" section of the Core Library which requires that you know the appDomain of the initial app. So, the final solution is:
In the source app:
let appDomain = Bundle.main.bundleIdentifier! // Note, needed by uninstaller
will give you the domain for the stored preference in the source app.
And in the app doing the changing — the final working code:
func flushPreferences() {
let defaults = getDefaultPreferences()
let sourceAppDomain = "{THE_BUNDLE_ID_FROM_SOURCE_APP}"
for preferenceName in defaults.keys {
print("Preference name: \(preferenceName)")
CFPreferencesSetAppValue(preferenceName as CFString,
nil,
sourceAppDomain as CFString)
}
}
Hope this helps someone save some time at some point - thanks to everyone who contributed. This one was a BEAR!

Save text entered from textfield to use it in other file [Swift]

I have in two views that are connected to the menu, in one view I have a text field to enter your e-mail and I want to take that textfield text (that is the email address) to the other view, but I think I need to save that data to the phone so later when the user start the app again he/she will never have to type that address again, and then use that email address in the other view that will send an email to that address. Here is the code that I have:
let configCorreo = ConfigurarCorreo()
let mail = configCorreo.textField.text!
And then I set the recipient using that mail the user typed in the other viewController:
let mc: MFMailComposeViewController = MFMailComposeViewController()
mc.mailComposeDelegate = self
mc.setToRecipients(["\(mail)"])
But I get an error saying: "fatal error: unexpectedly found nil while unwrapping an Optional value"
PrepareForSegue, NSUserDefault and Singleton
You have a few possible options to pass your data to other views depending how you want that data to be handled, I will explain each for you and you can choose which one best fit your need.
prepareForSegue: Method
I recommend this method if you want to hold your data for 1 segue transition, it's a good cause to pass this again to another view afterward you need to create another prepareForSegue within the new view. here is an example on how to do this:
First, you create 2 variables in both views, 1 to send (currentViewController.swift) and 1 to receive (toViewyourGoingController.swift).
currentViewController.swift var dataToSend: AnyObject?
ViewYourGoingController.swift var dataToReceive: AnyObject?
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
//Check your segue, this way you can transfer different data to different view. also make sure the identifier match your segue.
if segue.identifier == "toViewYourGoing" {
//Initial your second view data control
let ExchangeViewData = segue.destinationViewController as! toViewyourGoingController
//Send your data with segue
ExchangeViewData.dataToReceive = dataToSend
}
}
NSUserDefault
Now this method is good if you want to keep your data live as long as the app is installed, once the app is removed this will reset automatically. You also have the option to update the value of the key if you wish, here is how you do NSUserDefault:
I always like to register my NSUserDeafult to default setting, a lot of people just continue with the second step without registering.
Register NSUserDefault in AppDelgate.swift
NSUserDefaults.standardUserDefaults().registerDefaults(["valueName": AnyObject])
Set Value to your NSUserDefault, this depends on what type of data you're storing, should match the one with your registration if you did register. (Example of Boolean data type below)
NSUserDefaults.standardUserDefaults().setBool(true, forKey: "valueName") //Bool Data Type
Available types:
Make sure you synchronize once you set the value to the NSUserDefault, this way it will update instantly, otherwise it will update when it get a chance.
NSUserDefaults.standardUserDefaults().synchronize()
Receive Value: this will receive boolean value since we set boolean and register boolean.
let Variable: Bool! = NSUserDefaults.standardUserDefaults().boolForKey("valueName")
Singleton
Now singleton is basically a global variable that you can use them in any views, but some developers experience some bugs and difficulties, use it at your own risk, I recommend this method when you're definite that you will use that data a lot (STILL RISKY), but this method is like goddess of data handling :).
Create a NSObject subclass and call it DataManager.swift (I call it data manager cause it handle data.) as following:
import UIKit
class DataManager: NSObject {
//Store Data Globally
static var someData: Boo! //This Boolean, you can choose whatever you want.
}
the static is what keep your data live.
Now you can store and receive someData from anywhere like you handle any data type like this.
//Store
DataManager.someData = true
//Receive
print(DataManager.someData)
Challenges:
You can also use
Keychain
Sergey Kargopolov will walk you through how to use a third party to use swift keychain. Otherwise, you can take even harder challenge and create one yourself :P .
Key-Value Data in iCloud
Use CoreData to save it in a database, then you can perform a fetch request to get it back out of the database.
Very simple look it up here:
https://www.youtube.com/watch?v=3IDfgATVqHw

Text and table view data storage in swift

Could anyone tell me what the way of storing a long text in a swift app is. Let's suppose I have an app that has a table view and when I chose a row I go to a new scene where I have a big page filled by text.
The question is where do I have to store the data of the table's row and the whole text? And how?
Do I have to make a model? Is it just one for both the table and the text? Or more than one. Is there any tutorial that explains this exact situation or close to it?
You could use Parse.com framework to retrieve data from a database. There is a lot of documentation on this.
In this link you can find a tutorial that will explain you how to load data from Parse and show it in your UITableView.
Storing Local (in-memory store):
For storing without a internet connection you could use Core Data Stack with NSInMemoryStoreType as storeType. This tutorial will give you a nice idea on how it works.
You can declare a model like it follows:
struct CoreDataModel {
let name: String
let bundle: NSBundle
init(name: String, bundle: NSBundle)
// other properties & methods
}
And then manage it with:
let model = CoreDataModel(name: "MyModel", bundle: myBundle)
let stack = CoreDataStack(model: model,
storeType: NSInMemoryStoreType,
concurrencyType: .MainQueueConcurrencyType)
// Use context
stack.managedObjectContext

Swift bad access error on empty value even with check

I am in the process of transitioning over to Swift from Obj-C and I think I have a fairly good understanding of the ? ! concepts. I am however experiencing a bad access crash on a field that is nil.
I am using
var cell :ContactIInfoTableViewCell!
cell = tableView.dequeueReusableCellWithIdentifier("notes", forIndexPath: indexPath) as! ContactIInfoTableViewCell
if let clinic = receivedVisitDetails?.clinicNotes {
cell.textLabel?.text = clinic
}
Which produces the error if empty. My understanding was that the IF statement would stop this issue but it hasn't.
What am I doing wrong here?
UPDATE
This is a detail view for the core data fetch.
NSManagedObject is set as
var receivedVisitDetails: VisitDetails! = nil
The prepareForSeague on the previous view
let visits:VisitDetails = fetchedResultsController.objectAtIndexPath(indexPath!) as! VisitDetails
taskController.receivedVisitDetails = visits
The crash only happens when the clinicNotes is null.
After all edits and comments, the problem is in:
#NSManaged var clinicNotes: String
It's optional in your Core Data model, but not in your class. Should be changed to:
#NSManaged var clinicNotes: String?
Here's similar problem where Imanou suggests mogenerator, which can handle it for you. It automatically generates two classes for each managed object. Human part is suitable for editing (not touched by mogenerator when updating your model) and machine is not suitable for editing (rewritten every time you do update your model). Human part inherits from machine part.
Whenever I'm working with Core Data I do use it. I strongly suggest it as well. You can also add it to build phase, so, it automatically update your machine classes when you're building your project.
When using this construction, you don't have to use ? as it is unwrapped for you.
try
if let clinic = receivedVisitDetails.clinicNotes {
cell.textLabel?.text = clinic
}

executeFetchRequest:error: A fetch request must have an entity

I had a project that was working fine. It had "Core Data" checked, and had the data model all set up. I only started adding a couple entities and properties tonight. When I try to run my app now, it won't even load. It just crashes on me.
Here's the error:
'NSInvalidArgumentException', reason: 'executeFetchRequest:error: A fetch request must have an entity.'
I'm really scared because I don't have a backup of this and if I can't get it working I don't know what I'll do. :)
Thanks in advance!
EDIT:
I got fed up with my data, so I just copied a new blank xcdatamodel to my project and I'm going to start fresh. Thanks for the help!
My issue is I didn't use the same name for Entity and Class. Trivial solution to fix it is by giving them the same name.
If you are using MagicalRecored with Swift:
Make sure you use #objc directive in the Swift NSManagedObject subclass to make the class accessible to Objective-C code from the MagicalRecord library
#objc(MyEntity)
class MyEntity: NSManagedObject {
#NSManaged var myAttribute: Int16
}
After searching all over for a solution, what fixed it for me was doing a Clean/Build in Xcode.
Product->Clean, Product->Build, then try running it.
It seemed as if my data got corrupted, so I deleted my data model and the database in the iPhone simulator, and started fresh.
I had the same error.
For me, it is because I have added a new Model Version, but I did not set it as "Current Version". Careless me! To fix, select the xcdatamodel, click Design > Data Model > Set Current Version. The xcdatamodel file will then have a green tick.
Hopes that helps.
Also, make sure that your .xcdatamodeld file is in the "Copy Bundle Resources" phase of your Build Phases.
Here's what fixed it for me:
As I was converting to Swift 3, Xcode was giving me an error when declaring a new NSFetchRequest, saying that it needed a type. After adding the type, I did what anyone else would have assumed; if the request is typed, why specify an entity name? So, I removed it.
It actually was my mistake.
Swift 2.2:
let request = NSFetchRequest(entityName: "MyEntity")
When I first converted to Swift 3:
let request = NSFetchRequest<MyEntity>()
That was giving me an error. I ended up with this:
let request = NSFetchRequest<MyEntity>(entityName: "MyEntity")
And everything works fine. Personally, I'm not sure why it needs to have an entity name specified, if you're typing the request. Maybe they'll update that at some point (I hope)
i found this solution in the apple develper forum and it was exactly my problem!
the solutions is that the context must be defined inside the struct App.
not in the environment parameter
import SwiftUI
#main
struct CoreDataDemoApp: App {
private let context = CoreDataStack.context.
var body: some Scene {
WindowGroup {
ContentView()
.environment(\.managedObjectContext, context)
}
}
}
Check if,
the entity is present in the xcdatamodel file.
entity name used are same.
If you are using Swift 3 and Core Data's new stack syntax:
var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "MyAppModel")
container.loadPersistentStores(completionHandler: {
(storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
} else {
print(storeDescription)
}
})
return container
}()
Then you should be using this fetch syntax:
let request: NSFetchRequest<Client> = Client.fetchRequest()
I had this error on the first fetch after app launches when using different variations:
let request: NSFetchRequest<NSFetchRequestResult> = Client.fetchRequest()
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Client")
I did stumble across the same precise error upon taking my first steps into Core Data (and iOS 11 and Swift 4). I started off a book (sixth edition meant to target Swift 4 but presumably including some legacy stuff).
As suggested in the book my code was:
let fetchRequest = NSFetchRequest<ReminderData>()
let entity = ReminderData.entity()
fetchRequest.entity = entity
do {
let rows = try managedObjectContext.fetch(fetchRequest)
} catch {
fatalError("Unresolved error")
}
It turned out that all I got from ReminderData.entity() is nil. Not sure if I did something wrong when setting up the data model or ... Apple's docs say that NSManagedObject.entity() must not be overwritten?
Long story short, the Codegen file ReminderData+CoreDataProperties.swift did include the solution:
#nonobjc public class func fetchRequest() -> NSFetchRequest<ReminderData> {
return NSFetchRequest<ReminderData>(entityName: "ReminderDB")
}
which was all I had to use to end-up with a proper NSFetchRequest, no fiddling with the NSEntityDescription, problem gone!
let fetchRequest = NSFetchRequest<ReminderData>(entityName: "ReminderDB")
do {
let rows = try managedObjectContext.fetch(fetchRequest)
} catch {
fatalError("Unresolved error")
}
I built clean, and that didn't fix it. Then I deleted the app, and that didn't fix it. Then I built clean and deleted the app AT THE SAME TIME, and that fixed it.
Just add the same problem. I copied all my entities. Deleted the data model, recreated an empty one and pasted the entities back into the new data model. Solved my issue.
First I downloaded the app's data through the Organizer (to see what was happening) and noticed that it offered me to save it under a previous project name. This puzzled me. So I exited XCode 4.6.1, deleted the app (and its data) from my iPhone, and came back.
This time I got an error saying Cannot create an NSPersistentStoreCoordinator with a nil model. So I looked into the AppDelegate.m file and changed the URLForResource in the - (NSPersistentStoreCoordinator *) persistentStoreCoodinator method. It was set to the name of my app, and I changed it to 'Model' so as to match the name of my Model.xcdatamodeld.
It's working now.
This happened to me when I was fetching from the wrong database. My application has 3 sqlite databases, and of course 3 ManagedObjectContext instances. Well I was submitting the wrong ManagedObjectContext to a method asking it to query a table that didn't exist in the ManagedObjectContext I submitted. After using the correct ManagedObjectContext, all was good.
I think the original question/problem, and also the issue that most of these answers fixes (just in different ways) is just a real simple one:
Anytime you modify your core data (like adding an entity as you mention), you either have to delete all existing data (if you haven't published your app yet), or add a new version to your model.
Just thought I would post this answer, even though this is an older question, because this answer seems pretty obvious and so far hasn't been discussed in any of the questions or comments I read above.
You can also use setter method from CoraData ... Just do something like this...
On your CustomCoreDataManager.m
import "ObjectiveRecord.h"
call init method like this
(instancetype)init {
self = [super init];
if (self) {
[[CoreDataManager sharedManager] setModelName:#"YourModelName"];
}
return self; }
Hope this helps to someone...
Maybe you are trying to load a Database from a different/the wrong bundle?
For instance from or within a Framework?
I had this issue and solved it by loading the DB from the bundle of the related Framework. And then it all worked fine!!
Swift 4 + MagicalRecord:
let frameworkBundle = Bundle(for: AClassFromTheFramework.self)
let managedObjectModel = NSManagedObjectModel.mergedModel(from: [frameworkBundle])
MagicalRecord.setShouldAutoCreateManagedObjectModel(false)
NSManagedObjectModel.mr_setDefaultManagedObjectModel(managedObjectModel)
MagicalRecord.setupCoreDataStack(withAutoMigratingSqliteStoreNamed: "db.sqlite")
And voila !
I faced same issue, actually i was calling MyEnty instead of MyEntity so please re-check what names you have given to your entities and call the same and also check whether you are calling same attributes that you have defined like name
In my case, it was because this dropdown was not set to "Current Product Module" in the Data Model Inspector in Xcode (13.4.1):
Once I set that, it stopped crashing.
Hope this helps!