Use core data in swift 2 without using tables - ios

I'm learning to be an iOS app developer and I want to make an app which stores core data. I know how to do it using tables but is there a way I can store data without using tables? Like I'm trying to make an app which saves about a 100 different variables but m not using tables in it. Can someone please direct me to a full tutorial of how it's done? I came across one tutorial on Ray weindervich but it was done on swift 1.2 and it didn't work for me. Thanks

Core data depends on entities, now something that might help to share (probably you knew this already) is that Core data entity is not a table it represents a thing that can be identify and quantify for example a fruit regardless what your back end is; with that been said, now I have a question, when you say table do do you mean the entities or an actual database table? If you mean entity, with Core data you can say use SQLite as backend or xml file as back end but regardless how you store the data you will need to create at least one entity.
What was suggested in the comments still using entities. So my suggestion would be just create one entity using one of this options:
1. Entity
variable1 datatype
variable2 datatype
...
...
variable n datatype
or
2. Entity
key String
Value object
With option one you will have to know all the possible variables that your application will use and one good advantage is that you won't have to do down casting neither unwrapping.
With option two you don't need to know all the possible variables and also your data can grow without changing the app, the only downside is you will have to wrap and unwrap the data from each record.
These are my suggestions for you.
Hope this help
UPDATE :
So here are the steps that I think you need to follow to achieve your request (important: this a simple sample):
Make sure your project has enable since creation Core Data.
In the Model add the Entity as the picture shows:
Then add the attributes as the picture shows:
Add the add the subclass; this part is not mandatory but makes it easy to handle each entity and its properties.
Then you should have something similar to the following code for the entity:
import Foundation
import CoreData
class Generic: NSManagedObject {
#NSManaged var key: String?
#NSManaged var value: NSObject?
}
And your view controller should have something like this in order to read and save the data:
import UIKit
import CoreData
class ViewController: UIViewController {
#IBOutlet var txtVariable: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let delegate = UIApplication.sharedApplication().delegate as! AppDelegate
let context = delegate.managedObjectContext
let request = NSFetchRequest(entityName: "Generic")
let filter = NSPredicate(format: "key = %#", "xyz")
request.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [filter])
do {
let records = try context.executeFetchRequest(request) as! [Generic]
if (records.count>0)
{
txtVariable.text = (records[0].value as! String)
}
}
catch let error as NSError{
NSLog(error.localizedDescription)
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func btnSave_Click(sender: AnyObject) {
let delegate = UIApplication.sharedApplication().delegate as! AppDelegate
let context = delegate.managedObjectContext
let record = NSEntityDescription.insertNewObjectForEntityForName("Generic", inManagedObjectContext: context) as? Generic
record?.key = "xyz"
record?.value = txtVariable.text
do {
try context.save()
}
catch let error as NSError{
NSLog(error.localizedDescription)
}
}}

Related

Beginner in Realm Swift - How to instantiate a new Realm in SwiftUI

I have a class DataEntry that I want to store instances of in a Realm database, but i'm having issue instantiating a Realm. Here is my DataEntry class:
class DataEntry: Object {
#objc dynamic var id = 0
#objc dynamic var exercise = ""
#objc dynamic var weightLBS = 0
#objc dynamic var averageBarSpeed = 0
#objc dynamic var topBarSpeed = 0
}
Some context as to what I'll be using it for:
I'd like to have functions to write and delete instances of a DataEntry, but the documentation on that seems fairly simple.
Adding new dataEntry would be done by a user inputing data into a form
Deleting dataEntrys will simply be a button
Planning on reading the data to create graphs to track performance over time
The issue I'm having is instantiating a new Realm, and using the appropriate error handling. I've found a few simpler examples, but they all throw errors so i'm assuming there's something i'm missing. I know you're not supposed to use try!, so I'm wondering, what is a simple way to instantiate a new Realm, so I can then read/write/delete DataEntry's to the Realm Database.
This one gives me multiple "Expected Declaration" errors in lines 1 and 3.
do {
let realm = try Realm()
} catch let error as NSError {
// handle error
}
This one gives me an "Expected Declaration" error on line 1
try {
let realm = try Realm()
} catch {
print(error.localizedDescription)
}
Any additional pointers on how to best set up this would be amazing, wether or not I should have a RealmManager class that would aid in error handling. I've seen in some cases people create extensions of Realm, but this is a little too advanced for me right now.
Background in CS, but brand new to both Swift and Realm for context.
Edit/Update:
Quick clarification, I'm having issues with instantiating a Realm. Not a Realm object. I've updated above to be more clear.
To clarify my question, I'm getting errors for what appears to be good code above, so I assume I have it in the wrong place. I get errors when its inside the DataEntry class, when its inside of a view, and at the top level of a SwiftUI file. Any advice on where I should include the c ode for the instantiation would be great!
I am not sure if this will help but knowing where to put things within a project can sometimes help understand the flow.
So I created a realm project in XCode that writes a person object with the name of Jay to realm when button 0 is clicked. Then when button 1 is clicked, it retrieves that data and prints to console.
There's no error checking here and I am force unwrapping an optional so don't do that in a real app.
import Cocoa
import RealmSwift
class PersonClass: Object {
#Persisted var name = ""
}
class ViewController: NSViewController {
#IBAction func button0Action(_ sender: Any) {
let realm = try! Realm()
let jay = PersonClass()
jay.name = "Jay"
try! realm.write {
realm.add(jay)
}
}
#IBAction func button1Action(_ sender: Any) {
let realm = try! Realm()
let jay = realm.objects(PersonClass.self).where { $0.name == "Jay" }.first!
print(jay.name) //outputs "Jay" to console
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
The project starts with importing RealmSwift and then the Realm object(s) are defined outside of any other classes, so they are at a high level.
Then we have the ViewController with two button actions - as you can see, within each action function, we access realm with
let realm = try! Realm()
and then interact with realm. When that function exits, the realm object is deallocated so it's a safe way to work with Realm and not leave it 'connected'.
Some developers create a singleton or a RealmService class to interact with Realm but singletons can be tricky. I suggest initially going with the pattern shown above (and in the docs).
there has a better way to update and append a new record to Realm in the latest version.
#ObservedResults(Group.self) var groups
$groups.append(Group())
TextField("New name", text: $item.name)
The documentation is here, please take some time to read it.
If it is hard for you, I suggest at least finishing this video by code along with her. LINK

iOS: Core Data fails to save when attribute is a double

I thought I'd ask here first before I sent my laptop flying out of the window. I am very new to iOS development and have been toying around with Core Data trying to create an expense manager app. I have a single entity named "Account" which has two non-optional attributes (name and initBalance) as follows:
I have also created an NSManagedObject subclass as follows:
//Account.swift
class Account: NSManagedObject {
override func awakeFromInsert() {
super.awakeFromInsert()
initBalance = NSNumber(double: 0.0)
name = ""
}
}
//Account+CoreDataProperties.swift
extension Account {
#NSManaged var initBalance: NSNumber
#NSManaged var name: String
}
The Account entities are presented in the AccountsViewController (a subclass of UITableViewController conforming to the NSFetchedResultsControllerDelegate protocol) using an NSFetchedResultsController. To add a new Account I use a segue from AccountsViewController to direct me to the AccountInfoViewController (shown below) which has two textFields, one for the name and one for the balance of the account. When the latter is about to disappear a new Account is inserted in the context with the name and balance derived from the textFields. Once the parent controller appears (AccountsViewController) it tries to save the changes and updates the tableView.
Now, if I insert a new Account for which the balance is an integer number life is good; the context is able to save the changes, tableView updates its rows by showing the newly inserted account, and I am happy. When I try to insert an Account for which the balance is a decimal number, the app crashes at the point where the context tries to save the changes. I get no useful error at all as to why it happens. Here is the code for the controller managing the textFields:
class AccountInfoViewController: UIViewController {
#IBOutlet weak var nameField: UITextField!
#IBOutlet weak var balanceField: UITextField!
var store: DataStore! // set by the parent viewController everytime this controller appears
let numberFormatter = MyFormatter()
// insert the new account into the context before the controller disappears
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
store.insertAccount(nameField.text!, balance: numberFormatter.numberFromString(balanceField.text!)!)
}
}
The MyFormatter is this:
func MyFormatter() -> NSNumberFormatter {
let formatter = NSNumberFormatter()
formatter.numberStyle = .DecimalStyle
formatter.minimumFractionDigits = 2
formatter.maximumFractionDigits = 2
return formatter
}
and the DataStore shown above contains a simple Core Data Stack and is responsible for inserting into the context:
// DataStore.swift
class DataStore {
let coreDataStack = CoreDataStack(modelName: "SmartMoney")
// inserts a new account into the context
func insertAccount(name: String, balance: NSNumber) -> Account {
let account = NSEntityDescription.insertNewObjectForEntityForName("Account",
inManagedObjectContext: coreDataStack.mainQueueContext) as! Account
account.name = name
account.initBalance = balance
return account
}
}
Is there any reason why the context fails to save the changes when the balanceField contains a non-integer number? Thank you for reading and please let me know if you would like me to post any other parts of the code.
I was finally able to figure out what's going on. Changing my attribute name from initBalance to startingBalance makes everything work again. In fact, changing the attribute's name to anything that does not start with new or init works just fine. I got the idea from this post.
It seems that when using ARC, your property's name should not start with the word new. It turns out that initBalance (or newBalance for that matter) produces the same issue. Hope it helps the next poor soul running in a similar problem.

Use Realm with Collection View Data Source Best Practise

I'll make it short as possible.
I have an API request that I fetch data from (i.e. Parse).
When I'm getting the results I'm writing it to Realm and then adding them to a UICollectionView's data source.
There are requests that take a bit more time, which run asynchronous. I'm getting the needed results after the data source and collection view was already reloaded.
I'm writing the needed update from the results to my Realm database.
I have read that it's possible to use Realm's Results. But I honestly didn't understood it. I guess there is a dynamic and safe way working with collection views and Realm. Here is my approach for now.
This is how I populate the collection view's data source at the moment:
Declaration
var dataSource = [Realm_item]()
where Realm_item is a Realm Object type.
Looping and Writing
override func viewDidLoad() {
super.viewDidLoad()
for nowResult in FetchedResultsFromAPI
{
let item = Realm_item()
item.item_Title = nowResult["Title"] as! String
item.item_Price = nowResult["Price"] as! String
// Example - Will write it later after the collectionView Done - Async request
GetFileFromImageAndThanWriteRealm(x.image)
// Example - Will write it later after the collectionView Done - Async request
dataSource.append(item)
}
//After finish running over the results *Before writing the image data*
try! self.realm.write {
self.realm.add(self.dataSource)
}
myCollectionView.reloadData()
}
After I write the image to Realm to an already created "object". Will the same Realm Object (with the same primary key) automatically update over in the data source?
What is the right way to update the object from the data source after I wrote the update to same object from the Realm DB?
Update
Model class
class Realm_item: Object {
dynamic var item_ID : String!
dynamic var item_Title : String!
dynamic var item_Price : String!
dynamic var imgPath : String?
override class func primaryKey() -> String {
return "item_ID"
}
}
First I'm checking whether the "object id" exists in the Realm. If it does, I fetch the object from Realm and append it to the data source. If it doesn't exist, I create a new Realm object, write it and than appending it.
Fetching the data from Parse
This happens in the viewDidLoad method and prepares the data source:
var query = PFQuery(className:"Realm_item")
query.limit = 100
query.findObjectsInBackgroundWithBlock { (respond, error) -> Void in
if error == nil
{
for x in respond!
{
if let FetchedItem = self.realm.objectForPrimaryKey(Realm_item.self, key: x.objectId!)
{
self.dataSource.append(FetchedItem)
}
else
{
let item = Realm_item()
item.item_ID = x.objectId
item.item_Title = x["Title"] as! String
item.item_Price = x["Price"] as! String
let file = x["Images"] as! PFFile
RealmHelper().getAndSaveImageFromPFFile(file, named: x.objectId!)
self.dataSource.append(item)
}
}
try! self.realm.write {
self.realm.add(self.dataSource)
}
self.myCollectionView.reloadData()
print(respond?.count)
}
}
Thank you!
You seem to have a few questions and problems here, so I'll do my best.
I suggest you use the Results type as your data source, something like:
var dataSource: Results<Realm_item>?
Then, in your viewDidLoad():
dataSource = realm.objects(Realm_item).
Be sure to use the relevant error checking before using dataSource. We use an optional Results<Realm_item> because the Realm object you're using it from needs to be initialised first. I.e., you'll get something like "Instance member * cannot be used on type *" if you try declaring the results like let dataSource = realm.objects(Realm_item).
The Realm documentation (a very well-written and useful reference to have when you're using Realm as beginner like myself), has this to say about Results...
Results are live, auto-updating views into the underlying data, which means results never have to be re-fetched. Modifying objects that affect the query will be reflected in the results immediately.
Your mileage may vary depending on how you have everything set up. You could try posting your Realm models and Parse-related code for review and comment.
Your last question:
What is the right way to update the "object" from the Data Source after i wrote the update to same object from the Realm DB?
I gather you're asking the best way to update your UI (CollectionView) when the underlying data has been updated? If so...
You can subscribe to Realm notifications to know when Realm data is updated, indicating when your app’s UI should be refreshed for example, without having to re-fetch your Results.

Swift2.0 CoreData issue on NSFetchRequest

This is a code snippet of me trying to fetch data from coredata. I tried to fetch data and checked using breakpoint and there was NO data fethed! The code is in swift2. Any suggestions would be appreciated. I defined a struct constants where I defined all the constants. gate is a string type defined in nsmanagedobject class. The snippet is a part of viewDidLoad() method.
let context: NSManagedObjectContext? = (UIApplication.sharedApplication().delegate as? AppDelegate)?.managedObjectContext!
let TPTodayFetchRequest = NSFetchRequest(entityName: Constants.CoreDataEntities.TPTodayCoreDataEntity)
do {
let patroDaily = try context!.executeFetchRequest(TPTodayFetchRequest) as! [TPToday]
for patroEntity in patroDaily {
print(patroEntity.gate)
}
}
catch { print("error") }
Except for cumbersome variable naming, and unfortunate indentation, your code is fine and should work.
You will have to investigate if you have indeed saved the data you are expecting to find. For example, you could have the program log the documents directory URL after which you could examine the sqlite file with the sqlite3 command line tool.

How to use Core Data synchronously?

I'm trying to make an iOS 8 App with Swift and i need to download data from JSON and save it , but i don't understand Core Data mechanism. (I'm coming from Android with ORM Lite and Windows Phone with sqlite-net).
I'm trying to make two tasks, "GetAllNewsTask" returning all News from database , and "UpdateAllNewsTask" downloading JSON and parsing it, save to database and return all News.
The function getEntitiesFromJson transform parsed JSON string to entity object
class func getEntitiesFromJson(json: JSONValue) -> [NewsEntity]?{
var rList : [NewsEntity] = []
var array = json.array
var countItr = array?.count ?? 0
if(array == nil){
return nil
}
if(countItr > 0){
for index in 0...countItr-1{
var news = NewsEntity()
var jsonVal = array?[index]
news.id = jsonVal?["id"].integer ?? 0
........
rList.append(news)
}
}
return rList
}
GetAllNewsTask (newsDao.findAll() currently return an harcoded empty array, i didn't found how to select all NewsEntity synchronously)
class GetAllNewsTask:NSOperation {
var result : Array<News>?
override func main() -> (){
result = executeSync()
}
func executeSync() -> Array<News>? {
let newsDao = NewsDAO()
let entities = newsDao.findAll()
return NewsModel.getVOsFromEntities(entities)
}
UpdateAllNewsTask
class UpdateAllNewsTask:NSOperation {
var result : Array<News>?
override func main() -> (){
result = executeSync()
}
func executeSync() -> Array<News>? {
let response = JsonServices.getAllNews()
var managedObjectContext = (UIApplication.sharedApplication().delegate as AppDelegate).managedObjectContext!
var entityDescription = NSEntityDescription.entityForName("NewsEntity", inManagedObjectContext: managedObjectContext)
var entities = NewsModel.getEntitiesFromJson(response)
//TODO insert new, update existing and remove old
return GetAllNewsTask().executeSync()
}
I'm trying to add or update all NewsEntity and delete old, in Java i used List.removeAll(Collection<T>) but i can't found how to do this in Swift.
I got an exception when i override equals and hashcode in NewsEntity class.
Before continuing, is it the correct way to do this ?
If yes there is any good tutorial which demonstrate how to do this?
If no what is the correct way ?
Typically Core Data transactions should always be performed on the object's Managed Object Context thread. For this reason you will see the performBlock and performBlockAndWait calls in NSManagedObjectContext.
Since you are using the main thread you are technically synchronous assuming you are making those update calls on the main thread. If you are not then I would suggest wrapping your synch call into a performBlockAndWait call.
That being said, you should leverage Apple's Documentation on the subject as they explain how you can implement multithreaded core data. You should always perform your server related updates on a background thread.
If you want to implement a removeAll feature you will need to manually fetch all the objects you want to remove and call context.deleteObject(managedObject). Alternatively if you want something more powerful that should enforce cascade deletion, you can set this in your model editor when you select the relationship. The following Delete Rules are available:
Nullify
Cascade
No Action
Deny
Finally, you might find this post useful in explaining some of the commonly used Core Data stack setups and the various performance of each.
Welcome to iOS and good luck:)
EDIT
As an aside you might find Ray Wenderlich provides some great Core Data Tutorials

Resources