I am trying to create a Today Extension Widget which displays stored data in the widget.
Here's what I have done;
Added New Today Widget Target
Set up UIViewController for widget
Enable App Groups for both the app and extension
Now I have hit a road block, I am unsure the best way to retrieve and display a simple array of fetched data.
There is very little Swift tutorials and they often do not use core data.
In the main app project I fetch the array.
let moc = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
let request = NSFetchRequest(entityName: "Objects")
do {
try
self.objectsArray = moc.executeFetchRequest(request) as! [Objects]
} catch {
}
How can I use NSUserDefaults to store the objects array and then use in the today widget extension ? Or Even an array of string values.
1) Get url to your database
var containerPath: String = NSFileManager.defaultManager().containerURLForSecurityApplicationGroupIdentifier‌​(YOUR_SECURITY_APP_GROUP).path
var sqlitePath: String = "(containerPath)/("database.sqlite")"
2) Create Persistent Store Coordinator as you do in parent app.
3) Set it for your Managed Object context
context = NSManagedObjectContext()
context.persistentStoreCoordinator = coordinator
4) Retrieve objects as you do in your parent app
let moc = context
let request = NSFetchRequest(entityName: "Objects")
do {
try self.objectsArray = moc.executeFetchRequest(request) as! [Objects]
} catch {}
If you set up the App Groups correct and added the same group for both Application and Extension you can use NSUserDefaults. Just for instance, to write something from the app, you need:
static func setSharedScoreNumber(score: Int) {
let defaults = UserDefaults(suiteName: "group.bubblewrapSharedDefaults") // this is the name of the group we added in "App Groups"
defaults?.set(String(score), forKey: "score")
defaults?.synchronize()
}
And to read from Todays Extension:
private func getScore() -> String {
let defaults = UserDefaults(suiteName: "group.bubblewrapSharedDefaults")
defaults?.synchronize()
return String(describing: defaults!.object(forKey: "score") ?? "0")
}
Here's the complete guide how to do so.
Related
I'm trying to synch the core data between more than one ViewController. I have an attribute called Score, which is in my Core Data model. In the first ViewController:
// Defining context
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
The following code is inside this first ViewController:
// Creating a new username & Saving it to Core Data
let newUser = UserInfo(context: self.context)
newUser.name = usersName.text
newUser.birthday = bdPicker.date
newUser.gender = genderTextField.text!
newUser.prevTested = yesOrNoTextField.text!
newUser.score = 0
// Core Data calling method
saveInfo()
func saveInfo(){
do {
try self.context.save()
} catch {
print("Error saving context\(error)")
}
}
In the remaining ViewControllers, I want to access the Score attribute and modify it. Now, how is it possible to access all Score attribute from all the remaining controllers in my app.
I have a button and below it is the table view. Table view cell has some random data.On button click I am calling the the api(function name is : api.urlRequest(userID: 80, businessUnitID: 2) ) .I have an API that has 35,0000 entries. What I want is to save that data in Realm database. The problem is that, when I am calling the save function, my UI freezes. I am appending the JSON data to Model and then saving it to database. I can get the start index and end index of the the JSON data.
What I tried was to call the API on background thread and when saving function is called, I am calling it on main thread. But this didn't worked.
class ViewController: UIViewController,getAdhocJSONDelegate{
let realm = try! Realm()
#IBOutlet weak var tableViewRef: UITableView!
var array = [NSDictionary]()
var adhocData : [AdhocModel] = []//for appending the JSON data to the model
var adhocDB : Results<AdhocDB>?// for accessing the database
let api = AdhocAPIParamteres()
var adhocJSONDatafromAPI : NSDictionary!
override func viewDidLoad() {
super.viewDidLoad()
adhocDB = realm.objects(AdhocDB.self)
}
#IBAction func buttonPressed(_ sender: Any) {
print("BUtton Tapped")
api.urlRequest(userID: 80, businessUnitID: 2)
api.delegate = self
}
func appTutorialData(json: NSDictionary) {
adhocJSONDatafromAPI = json
let apiData = adhocJSONDatafromAPI.value(forKey: "data") as! [NSDictionary]
print("Start Index of the data : ",apiData.startIndex)
print("End Index of the data : ",apiData.endIndex)
apiData.forEach { (abc) in
let model = AdhocModel()
model.site_id = abc.value(forKey: "site_id") as! Int
model.atm_id = abc.value(forKey: "atm_id") as! String
model.site_address = abc.value(forKey: "site_address") as! String
adhocData.append(model)
print("data appended")
DispatchQueue.main.async {
self.saveToDb(data:model)
}
}
func saveToDb(data: AdhocModel) {
let adhoc = AdhocDB()
try! realm.write {
adhoc.SiteId = data.site_id
adhoc.AtmId = data.atm_id
adhoc.SiteAdress = data.site_address
realm.add(adhoc)
}
}
}
I want to save data in such a way that my UI doesn't freeze.
There are a few issues with the code and writing data to Realm on a background thread is covered in the documentation so I won't address that. Following that design pattern will correct the UI lockup.
This is another issue
func saveToDb(data: AdhocModel) {
**let adhoc = AdhocDB()**
You want to write your populated model to realm, but AdhocDB is a Results object, not a Realm model object. Additionally the realm object created in appTutorialData which is model, is passed to saveToDb, then another object is created and then populated with data from the first object. There's no reason to do that (in this code)
Assuming AdHocModel is a Realm object, this is much cleaner
func appTutorialData(json: NSDictionary) {
adhocJSONDatafromAPI = json
let apiData = adhocJSONDatafromAPI.value(forKey: "data") as! [NSDictionary]
print("Start Index of the data : ",apiData.startIndex)
print("End Index of the data : ",apiData.endIndex)
apiData.forEach { (abc) in
let model = AdhocModel()
model.site_id = abc.value(forKey: "site_id") as! Int
model.atm_id = abc.value(forKey: "atm_id") as! String
model.site_address = abc.value(forKey: "site_address") as! String
try! realm.write {
realm.add(model)
}
}
}
You're going to want to wrap that write within a background thread (again, see the documentation) something like this
DispatchQueue(label: "background").async {
autoreleasepool {
.
.
.
try! realm.write {
realm.add(model)
}
}
}
You may ask about populating your array adhocData.append(model). We don't know what you're doing with it but if you're using it as perhaps a dataSource for a table view or some other UI element, you may want to consider using a Results object instead of an Array.
A significant advantage is, if you have 35,000 objects, that's a pretty sizable array and if you have more, it could overwhelm the device as ALL of that data is stored in memory. However, Results objects are lazily loaded so you could have a much larger dataset without overwhelming the device.
Additionally, when Realm objects are stored in an array, they 'Disconnect' from Realm and loose Realm functionality - they will not auto-update nor will changes to the actual object in Realm be reflected in that array nor can you just update the object - it doesn't appear to have a primary key.
However, if you populate a Results object with those models, they will be live updating - so if for example the atm_id changes in Realm, that object will automatically be updated. If you need to change a property you can change it directly on that object within a write transaction.
So the pattern would be to have a class var of Results and load your objects into those results within viewDidLoad. As you add more models, the results object will automatically be updated.
To keep your UI fresh, you would want to add observers (aka Notifications)to those Results so you can be notified when an object is updated so you can reload your tableView for example.
I have 2 ViewControllers, one displays the UI and the 2nd one displays a segmented control used as a settings button. Im using the below code to save the segmented control state:
UserDefaults.standard.set(selectorLabel.selectedSegmentIndex, forKey: "stateSelected")
I then retrieve that usedefault on the viewdidload method:
if let value = UserDefaults.standard.value(forKey: "stateSelected"){
let selectedIndex = value as! Int
selectorLabel.selectedSegmentIndex = selectedIndex
}
So far this works as intended and the state of the segmented controlled is loaded properly each app load.
The segmented control has two text titles - one is "LBs & INs" and the second is "KGs & CMs".
How would I save those two segmented control text titles as UserDefaults and then call them on the first ViewController to set two labels on the viewdidload?
Define a model to represent data you want to store and restore:
struct SegmentedControlState: Codable {
let selectedIndex: Int
let titles: [String]
}
Initialize a model, encode and store it somewhere (like user default):
func saveState(of segmentedControl: UISegmentedControl) {
let state = SegmentedControlState(
selectedIndex: segmentedControl.selectedSegmentIndex,
titles: (0..<segmentedControl.numberOfSegments).map { segmentedControl.titleForSegment(at: $0) ?? ""})
let plist = try! PropertyListEncoder().encode(state)
UserDefaults.standard.set(plist, forKey: "SegmentedControlState")
//UserDefaults.standard.synchronize() //if targeting older iOS
}
for restoring, you should reverse the order like this:
func loadState(on segmentedControl: UISegmentedControl) {
guard let plist = UserDefaults.standard.value(forKey: "SegmentedControlState") as? Data else { return }
let state = try! PropertyListDecoder().decode(SegmentedControlState.self, from: plist)
for element in state.titles.enumerated() {
segmentedControl.setTitle(element.element, forSegmentAt: element.offset)
}
segmentedControl.selectedSegmentIndex = state.selectedIndex
}
usage:
// store `selectorLabel` data
saveState(of: selectorLabel)
// restore `selectorLabel` data
loadState(on: selectorLabel)
Note that it is not a good idea to store data like this to userdefaults at all. If you want to access some data from anywhere in code, you should follow singleton pattern and define your own singleton instance instead of standard userdefault.
Just store the value as a string, instead of an integer index.
UserDefaults.standard.set(selectorLabel.titleForSegment(at: selectorLabel.selectedSegmentIndex), forKey: "stateSelected")
And then to retrieve:
UserDefaults.standard.string(forKey: "stateSelected")
EDIT: #rmaddy is correct above - you should ideally be storing an index value like you're already doing, and then using an array to determine which title the index refers to (cleaner than just using a title as a reference). You could make this array global so you can access from anywhere, if you must.
segmentedControlTitles: [String] = ["LBs & INs", "KGs & CMs"]
And then call by
let index = UserDefaults.standard.integer(forKey: "stateSelected")
let title = segmentedControlTitles[index]
I have entities Application and Process, one application can have many processes, but one process can only have one application. I get one specific application entity:
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext
let appfetchRequest = NSFetchRequest(entityName: "Application")
//appfetchRequest.returnsObjectsAsFaults = false
do{
let results = try managedContext.executeFetchRequest(appfetchRequest)
applications.addObjectsFromArray(results as! [NSMutableArray])
}catch let error as NSError{
print("Jakis blad z applications entity: \(error)")
}
let predicate:NSPredicate = NSPredicate(format: "name == %#", appTitle)
let application:NSArray = applications.filteredArrayUsingPredicate(predicate)
and in this I have relationship (named applicationprocesses).
I try to get an array with these entities in many ways, but no one work.
Actually, I have:
let processes = application.valueForKey("applicationprocesses").allObjects.first
print(processes?.valueForKey("applicationprocesses"))
And this give me:
Optional({(
<NSManagedObject: 0x7f945871b7a0> (entity: Application; id: 0xd000000000080002 <x-coredata://00C2FE4A-143B-436E-B39B-A0A32C300B68/Application/p2> ; data: {
appcolor = "#3F3F3F";
appicon = bed;
applabel = Proces;
applabelplural = Procesy;
applicationprocesses = (
"0xd000000000140004 <x-coredata://00C2FE4A-143B-436E-B39B-A0A32C300B68/Process/p5>",
"0xd000000000100004 <x-coredata://00C2FE4A-143B-436E-B39B-A0A32C300B68/Process/p4>",
"0xd000000000180004 <x-coredata://00C2FE4A-143B-436E-B39B-A0A32C300B68/Process/p6>",
"0xd0000000001c0004 <x-coredata://00C2FE4A-143B-436E-B39B-A0A32C300B68/Process/p7>",
"0xd0000000000c0004 <x-coredata://00C2FE4A-143B-436E-B39B-A0A32C300B68/Process/p3>"
);
companyid = 392;
id = 1261;
name = "aplikacja 1";
processescount = 5;
})
)})
I need to display these processes in a UITablewView, so I need an array.
I will be grateful for any help.
The problem you are having is not the result of a bad line of code somewhere. It is actually working as it is supposed to. But you can make it a lot easier to work with NSManagedObject
Any fetch from your database results in [AnyObject]. If you leave it like it is, you are forced to use key-value coding which is a pain and very easy to mess up.
However it is very simple to create Classes from CD Entities and downcast the fetch result. This is an awesome feature of CoreData that unfortunately is not stressed enough.
link to gist
Your related entities might look like this:
Go to Menu -> Editor -> Create....
Select the entities you want to create a subclass for.
New files will show up in your project :
Now you can use code like this :
Insert objects
Notice the .... as? Company this is the downcast.
It allows you to access the attributes from the CD Entity like you would access any attributes from a Struct or Class.
func createACompany() {
// no appDel here. appDel and managedContext are best declared in the class scope. See gist for entire ViewController
guard let moc = managedContext else { return }
guard let company = NSEntityDescription.insertNewObjectForEntityForName("Company", inManagedObjectContext: moc) as? Company else {
return // guard is handy to "abort"
}
company.name = "Apple"
guard let bob = NSEntityDescription.insertNewObjectForEntityForName("Employee", inManagedObjectContext: moc) as? Employee else {
return
}
bob.name = "Bob"
bob.company = company
do {
try moc.save()
} catch {
print("core data save error : \(error)")
}
moc.refreshAllObjects()
}
Fetch objects
func fetchCompanies() -> [Company] {
guard let moc = managedContext else { return [] }
let request = NSFetchRequest(entityName: "Company")
request.returnsObjectsAsFaults = true
do {
let results = try moc.executeFetchRequest(request)
guard let companies = results as? [Company] else {
return []
}
return companies
}catch let error as NSError{
print("core data fetch error: \(error)")
return []
}
}
Get related objects
Look closely at the guard statement.
let employeeSet = company.employees -> unwrap of optional NSSet
let employees = employeeSet.allObjects -> get all objects in the NSSet
as? [Employee] -> downcast the result of allObjects to an Array
of Employee
func getEmployees(forCompany company : Company) -> [Employee] {
guard let employeeSet = company.employees, let employees = employeeSet.allObjects as? [Employee] else {
return []
}
return employees
}
Side note:
If you change your mind about the naming of the class and you change it everywhere. Don't forget the update it here too :
The class field has to be updated.
Similar to the InterfaceBuilder. If you change the class name it will not find it anymore.
I believe that you want to get all the managed objects (process) relate to the application object you already got. We usually use the "entity" word for same things as table in database.
I'm sorry I didn't have swift version for this line of code. I'm not sure about my swift skill
NSArray *processes = [[application fristObject] objectIDsForRelationshipNamed:#"applicationprocesses"]
Then use API to convert objectID to ManagedObject
(__kindof NSManagedObject *)objectWithID:(NSManagedObjectID *)objectID;
The only problem is this API, (NSArray *)objectIDsForRelationshipNamed:(NSString *)key , only supported from version 8.3
I am programming an iOS app using Swift, following a tutorial on Youtube. The app will have the same function as a to-do-list-app, but another use. However, when I expect the data to be saved (and printed in the debugger) nothing happens. Have I done something wrong?
#IBAction func saveTapped(sender: AnyObject) {
println("SaveTapped")
// Reference to our app delegate
let appDel: AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
// Reference moc
let contxt: NSManagedObjectContext = appDel.managedObjectContext!
let en = NSEntityDescription.entityForName("Tankningslista", inManagedObjectContext: contxt)
// Create instance of our data model and initialize
var nyTankning = Model(entity: en!, insertIntoManagedObjectContext: contxt)
// Map our properties
nyTankning.liter = (textFieldLiter.text as NSString).floatValue
nyTankning.kronor = (textFieldKronor.text as NSString).floatValue
nyTankning.literpris = (textFieldLiterpris.text as NSString).floatValue
//nyTankning.datum = datePickerDatum.date
// Save our context
contxt.save(nil)
println(nyTankning) //HERE I ESPECT THE DATA TO BE PRINTED IN THE DEBUG WINDOW
// navigate back to root vc
self.navigationController?.popToRootViewControllerAnimated(true)
}
You need to fetch the data first in order to display. Now you only save it. See my additions in your code after saving the data.
#IBAction func saveTapped(sender: AnyObject) {
println("SaveTapped")
// Reference to our app delegate
let appDel: AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
// Reference moc
let contxt: NSManagedObjectContext = appDel.managedObjectContext!
let en = NSEntityDescription.entityForName("Tankningslista",inIntoManagedObjectContext: contxt)
// Create instance of our data model and initialize
// Use your NSManagedModel to initialize not only the Model Keyword!
var nyTankning = Tankningslista(entity: en!, insertIntoManagedObjectContext: contxt)
// Map our properties
nyTankning.liter = (textFieldLiter.text as NSString).floatValue
nyTankning.kronor = (textFieldKronor.text as NSString).floatValue
nyTankning.literpris = (textFieldLiterpris.text as NSString).floatValue
//nyTankning.datum = datePickerDatum.date
// Save our context
if contxt.save(nil) {
// Fetch the Data from Core Data first....
let fetchRequest = NSfetchRequest(entityName: "Tankningslista")
var error:NSError?
var result = contxt.executeFetchRequest(fetchRequest, error: &error) as [Tankningslista]
for res in result {
// Now you can display the data
println(res.liter)
......
......
}
// End of the fetching
} else {
println(error)
}
//println(nyTankning) //HERE I ESPECT THE DATA TO BE PRINTED IN THE DEBUG WINDOW
// navigate back to root vc
self.navigationController?.popToRootViewControllerAnimated(true)
I hope that solution will solve your problem.
Puuh, there is a lot to improve, I really suggest you take a "real" iOS/ swift course on a platform like Udemy/ Bitfountain or Udacity. First, you need to fetch the ManagedObject somewhere either in a function (then store the results in an array) or with NSFetchedResultController (which is mostly used for CoreData with TableViews) then I am not sure what you have in your numberOfRowsInSection function, here you should also have the correct return values. As I said, to fix that here is really too much...