I have a User model declared like this:
struct User: Codable {
let nickname : String
let fullName: String
let profilePicUrl: URL
}
Then I save the followed user like this
let followedUser = User(nickname: username, fullName: fullName, profilePicUrl: url)
UserDefaults.standard.set(try! PropertyListEncoder().encode(followedUser), forKey: "user")
Next, in the ViewController which should display the followed users count I retrieve the UserDefaults data like this
var followedUsers = [User]()
if let storedObject: Data = UserDefaults.standard.object(forKey: "user") as? Data {
do {
let storedUser: User = try PropertyListDecoder().decode(User.self, from: storedObject)
followedUsers.append(storedUser)
} catch {
print(error)
}
}
Now, my followedUsers array is being updated with the last stored user each time. How can I append my followedUsers array in a proper way?
If you use Codable , then it make sense to use JSONEncoder & JSONDecoder and store the users as an array
do {
// How to save multiple records
var followers = [User]() // Your array of users
let data = try JSONEncoder().encode(followers) //Convert to Data
UserDefaults.standard.set(data, forKey: "data") // Save to User Default
// How to fetch multiple records
// Check If data available in user default
if let da = UserDefaults.standard.data(forKey: "data") {
// Convert Data back to your cod-able struct
let stored = try JSONDecoder().decode([User].self, from: da)
}
}
catch {
print(error)
}
struct User: Codable {
let nickname , fullName: String
let profilePicUrl: URL
}
Don't encourage using userDefault , you can go with CoreData / Realm
As #Sateesh and #Sanjukta said, storing the users in an array and saving that to UserDefaults is one solution.
The reason why your followedUsers array is updated with only one user, is that when you create a new user, you update the same key in UserDefaults each time.
It should be noted that UserDefaults is mainly used to store small amounts of data. Depending on how many User objects you plan to store, UserDefaults might not be the best solution.
The two solutions for data storage that come to mind are Core Data and Realm. Core Data is an Apple framework with which you can storage data on device. You can read more about it here: Core Data
The second option is to use Realm. Realm is is an open source database management system and can be used for on-device data storage too.
If you are interested in the differences between Realm and Core Data, for starters I can recommend this article.
Personally I use Realm in my own projects because I find it easier to implement and faster than Core Data. Please note that this is just my personal preference and others might disagree. Make sure you listen to other opinions and experiment with both before picking sides.
So let's suppose you want to store the User in Realm, you would need to do the following:
Install Realm for your project.
In your podfile add the following: pod 'RealmSwift'.
Run pod install in terminal and use the newly created .xcworkspace from now on.
In Xcode, in AppDelegate import RealmSwift and insert the following code to the didFinishLaunchingWithOptions method:
do {
_ = try Realm()
} catch {
print("Error initialising new Realm \(error)")
}
This initializes a new default Realm, where your User data will be saved.
Create a new User.swift file. In that, import RealmSwift and insert the following:
class User: Object {
#objc dynamic var nickName: String = ""
#objc dynamic var fullName: String = ""
#objc dynamic var profilePicURL: URL?
}
This creates a Realm object, which will contain the data for your users.
Save the followed user like this:
In the view controller where you want to save the user data import RealmSwift, and under the class declaration create a new instance of realm by let realm = try! Realm()
When you have the user data, save to Realm with the following code:
:
let followedUser = User()
do{
try realm.write {
followedUser.nickName = username
followedUser.fullName = fullName
followedUser.profilePicURL = url
realm.add(followedUser)
}
} catch {
print("Error saving to persistent container! \(error)")
}
In the view controller where you need the user data, create an instance of Realm just like before with let realm = try! Realm()
, retrieve users from Realm with the following code: let followedUsers = realm.objects(User.self)
This retrieves all Users from the default realm.
If you need to count the followedUsers you can do so by: followedUsers.count
Hopes this approach helps you to achieve what you wanted in the first place.
Please make the array of object then store the array in userDefaults.
After that you can easily retrive it.
It may help you. Thank you.
Related
The task is this. I have data that comes from the server. I want to initialize the default CoreData model with this data, for example:
#objc(UserEntity)
public class UserEntity: NSManagedObject {
required convenience init() {
let coredataStack = CoreDataStack(modelName: "UserModel")
let context = coredataStack.context
self.init(context: context)
}
required convenience init(json: JSONData) {
self.init()
self.id = Int16(json.id)
self.nickname = json.nickname
self.firstName = json.firstName
self.lastName = json.lastName
}
}
Further, using this default model, I will fill in the data for example in the profile. But I also need to make sure that I can save this model in CoreData, read it, overwrite it. While it turns out to get them and fill out a profile:
self.currentUser = UserEntity(json: JSONData()).
The question is how to do the CRUD operation with it now. I want the data to be saved in CoreData and the next time the user gets it from it.
I wanted to add a description to my Data instances so I know what they're meant to be used for.
Eg: "This data is for (user.name)'s profile picture" or "This data is a encoded User instance" etc.
I was going through the properties available to Data instances and saw there was a description and debugDescription property. I tried to set the values for those properties but it seems like I can't since they are get-only properties. Is there any other way to add a description to a Data instance?
Edit:
Wrapping a Data instance as recommended below is a great solution but if there's a way to achieve the same without using a wrapper please let me know.
You can create a light-weight wrapper like following -
import Foundation
struct DescriptiveData: CustomStringConvertible, CustomDebugStringConvertible {
let data: Data
let customDescription: String?
init(data: Data, customDescription: String? = nil) {
self.data = data
self.customDescription = customDescription
}
var description: String { customDescription ?? data.description }
var debugDescription: String { description }
}
Usage
let data = DescriptiveData(data: Data(), customDescription: "data for profile picture")
print(data)
// data for profile picture
So far I can create my own Data Models in a swift file. Something like:
User.swift:
class User {
var name: String
var age: Int
init?(name: String, age: Int) {
self.name = name
self.age = age
}
}
When I create a Core Data model, ie. a UserData entity, (1) do I have to add the same number of attributes as in my own data model, so in this case two - the name and age?
Or (2) can it has just one attribute eg. name (and not age)?
My core data model:
UserData
name
age
The second problem I have is that when I start the fetch request I get a strange error in Xcode. This is how I start the fetchRequest (AppDelegate is set up like it is suggested in the documentation):
var users = [User]()
var managedObjectContext: NSManagedObjectContext!
...
func loadUserData() {
let dataRequest: NSFetchRequest<UserData> = UserData.fetchRequest()
do {
users = try managedObjectContext.fetch(dataRequest)
....
} catch {
// do something here
}
}
The error I get is "Cannot assign values of type '[UserData] to type [User].
What does this error mean? In the official documentation are some of the errors described, but not this particularly one.
If you are designing a user model in core data you don't have to write the class yourself. In fact by default Xcode will generate subclasses of NSManagedObject automatically once you create them in your project's, but you can also manually generate them if you would like to add additional functionality:
Then you can go to Editor and manually generate the classes
Doing this will give you User+CoreDataClass.swift and User+CoreDataProperties.swift. I see in your question you are asking about how the core data model compares to your 'own' model, but if you're using core data then that IS the model. The generated User class, which subclasses NSManagedObject, is all you need.
Then you might fetch the users like this:
let userFetch = NSFetchRequest(entityName: "User")
do {
users = try managedObjectContext.executeFetchRequest(userFetch) as! [User]
} catch {
fatalError("Failed to fetch users: \(error)")
}
You cannot use custom (simple) classes as Core Data model.
Classes used as Core Data model must be a subclass of NSManagedObject.
If your entity is named UserData you have to declare the array
var users = [UserData]()
The User class is useless.
I have been trying to find a way to programmatically create the database entity/table at runtime in Swift 3 for iOS platform.
Please explain the process in detail. I have followed other links but nothing worked for me and I was unable to understand the process properly so please explain the whole process with working code instead of providing other existing links.
There is one more table which has been added from XCode and I can perform the database related operations (insert row, delete, update) on that table but I am unable to find a way to perform following operations on the table which will be created programmatically at run time.
There are following operations that need to be performed.
Add Entity - From interface, user can add entity name, number of columns, column names then on tapping a button the entity should be created into the database using CoreData. User can add any number of tables from the interface.
Insert Values - Then user will have interface to insert values into the entity. The name of the entity will be known and the values for a row in the entity will be entered by user. On tapping a button the row should be inserted into the entity/database-table.
Fetch Values - User will have an option to fetch all the saved values form the entity. To use them anytime when needed.
Update value - User will be able to update any value present in the table which was created programmatically.
Delete Entity - There will be an option to delete the database entity as well. On tapping a button, the name of the entity will be passed to the function and that entity/database-table will be deleted from the database.
I have tried the following code -:
let model = NSManagedObjectModel()
let entityName = "TestEntity"
let entity = NSEntityDescription()
entity.name = entityName
entity.managedObjectClassName = entityName
// Creating a attribute
let remoteURLAttribute = NSAttributeDescription()
remoteURLAttribute.name = "columnOne"
remoteURLAttribute.attributeType = .stringAttributeType
remoteURLAttribute.isOptional = true
remoteURLAttribute.isIndexed = true
var properties = Array<NSAttributeDescription>()
properties.append(remoteURLAttribute)
// Adding the attribute to the entity
entity.properties = properties
// Adding the entity into the model
model.entities = [entity]
do {
try CoreDataManager.managedObjectContext.save()
} catch {
print(error)
}
// Inserting the data into the entity
let testEntity = NSManagedObject(entity: entity, insertInto: CoreDataManager.managedObjectContext)
testEntity.setValue("WHY SO SERIOUS?", forKey: "columnOne")
do {
try CoreDataManager.managedObjectContext.save()
} catch {
print(error)
}
// Fetching the data from the entity
let request = NSFetchRequest<NSFetchRequestResult>()
request.entity = entity
request.propertiesToFetch = ["columnOne"]
request.returnsDistinctResults = false
do {
let results = try CoreDataManager.managedObjectContext.fetch(request)
print(results)
} catch {
}
I am getting the following value in the results -:
▿ 1 element
- 0 : (entity: TestEntity; id: 0x600000422f80
;
data: {
columnOne = "WHY SO SERIOUS?"; })
But I don't have any way to get the values from this entity anywhere else and insert the values into the entity from other places as well by using entity name which is TestEntity in this case.
Some of the steps you describe are simply not possible with Core Data. Or at least, not without making things extremely complex and difficult for you. Asking for complete working code is not reasonable because you have an extremely unusual and complex set of requirements for Core Data.
It's true that the NSManagedModel and all of the entities are mutable and can be changed in code. Except, all of these changes must be made before you use that model to load any data. You cannot make changes after you call loadPersistentStores(_:) (if you're using NSPersistentContainer) or addPersistentStore(ofType:configurationName:at:options:) (if you're not using NSPersistentContainer). Doing so is a run-time error that will crash your app.
Because of this, the idea of providing a user interface to dynamically create and delete Core Data entities is not really an option. You can create and delete instances of entities but not the entity descriptions.
If you're willing to do a lot of hard work, you might be able to get something close. The closest you could get with Core Data would be to create an entirely new managed object model any time you need a change. Then copy all of your data to a new persistent store file using this model (including whatever custom code you need to handle non-lightweight migration). Then delete the old model and persistent store file and all references to all objects that you previously fetched with them.
This will be very difficult to get right and may have unforeseen difficulties that I haven't thought of. I would strongly urge not attempting to do this with Core Data, because it was not designed to be used in this way.
It can be done quit simply with the following code:
import Foundation
import CoreData
class CoreData {
static let shared = CoreData()
let dataBase = NSManagedObjectModel()
let dbTable:NSEntityDescription = {
let tableEntity = NSEntityDescription()
tableEntity.name = "NEntity"
tableEntity.managedObjectClassName = "NEntity"
return tableEntity
}()
// The attributes each item in the table will have. e.g. id, name, password, date
let dbTableAttribute:NSAttributeDescription = {
let tblAttr = NSAttributeDescription()
tblAttr.name = "id"
tblAttr.attributeType = .integer64AttributeType
tblAttr.isOptional = true
return tblAttr
}()
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "APICall", managedObjectModel: dataBase)
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
private init(){
dataBase.entities.append(dbTable)
dbTable.properties.append(dbTableAttribute)
}
func saveContext () {
let context = persistentContainer.viewContext
if context.hasChanges {
do {
try context.save()
} catch {
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
}
// MARK: - Defining the class for the table, you can put this in a seperate file if you want but for ease of copying I put them together
#objc(NEntity)
public class NEntity: NSManagedObject {
// Variables must be NSManaged to be saved to the database
#NSManaged var id:Int64
// Call this function to save a new item to the table
static func addEntity(_ id:Int) {
let entityName = NSStringFromClass(self)
let moc = CoreData.shared.persistentContainer.viewContext
guard let entity = NSEntityDescription.insertNewObject(forEntityName:entityName, into: moc)
as? NEntity
else { print("Could not find entity: \(entityName) in database: \(CoreData.shared.persistentContainer.name)"); return }
entity.id = Int64(id)
// Update database
CoreData.shared.saveContext()
}
//Call this function to search the database for items matching the id, see removeEntityWithAttribute() for example
func fetchEntityByAttribute(id:Int, completion:#escaping ([NEntity]?) -> ()){
let moc = CoreData.shared.persistentContainer.viewContext
let fetchRequest = NEntity.fetchRequest()
fetchRequest.predicate = NSPredicate(format: "id == %d", id)
moc.perform {
guard let result = try? fetchRequest.execute() else {
return
}
if result.count > 0 {
completion((result as! [NEntity]))
}else {
completion(nil)
}
}
}
//Call this function to delete entities from the database with the matching id
func removeEntityWithAttribute(id:Int) {
let moc = CoreData.shared.persistentContainer.viewContext
fetchEntityByAttribute(id:id) { matchedEntities in
if let matchingEntities = matchedEntities {
matchingEntities.forEach(){
moc.delete($0)
CoreData.shared.saveContext()
}
}
}
}
}
Just call NEntity.addEntity(5) anywhere in your code to save an item in the database with an id of 5. Be sure to give them unique ids so you can edit and delete them.
Sources:
https://tigi44.github.io/ios/iOS,-Swift-Core-Data-Model-in-a-Swift-Package/
https://dmytro-anokhin.medium.com/core-data-and-swift-package-manager-6ed9ff70921a
I'm trying to create a bundle realm for my application. I thought it should be quite simple. Since I already have all needed records in Parse, I just have to:
create realm models and objects
load parse records to realm objects
save the realm
So, I created two realm models:
class RealmContainer : Object {
dynamic var rContainerId: String! //should be equal objectId from Parse
dynamic var rContainerName: String! //should be equal "name" field from Parse
...
var messages = List<RealmMessage>()
override static func primaryKey() -> String? {
return "rContainerId"
}
}
and
class RealmMessage : Object {
dynamic var rMessageId: String!
...
dynamic var rParentContainer: RealmContainer!
}
Getting results from Parse seems to be working. Also my realm objects are also good
var allUserContainers: [RealmContainer] = []
I was able to populate this array with values from Parse. But when I'm trying to save this realm, I'm getting a) nothing or b) error message
My code (this one'll get nothing):
let realm = try! Realm()
try! realm.write {
realm.add(self.allUserContainers[0])
print(Realm().path)
print(realm.path)
}
My code (this one'll get nothing too):
let realm = try! Realm()
try! realm.write {
realm.create(RealmContainer.self, value: self.allUserContainers[0], update: true)
print(Realm().path)
print(realm.path)
}
My code 3 (this will get me an error message "Terminating app due to uncaught exception 'RLMException', reason: 'Illegal recursive call of +[RLMSchema sharedSchema]. Note: Properties of Swift Object classes must not be prepopulated with queried results from a Realm"):
//from firstViewController, realm is a global variable
let realm = try! Realm()
//another swift module
try! realm.write {
realm.create(RealmContainer.self, value: self.allUserContainers[0], update: true)
print(Realm().path)
print(realm.path)
}
Obviously I don't properly understand how it should work, but I tried several swift/realm tutorials and they were actually straightforward. So, what did I do wrong?
Update
So, I updated my code to make it as much simple/readable as possible. I have a Dog class, and I am trying to get Dogs from Parse and put them to Realm.
AppDelegate.swift
let realm = try! Realm() //global
Dog.swift
class Dog : Object {
dynamic var name = ""
dynamic var age = 0
}
User.swift (getDogs and putDogs functions)
class User {
var newDogs:[Dog] = []
...
func getDogs() {
self.newDogs = []
let dogsQuery = PFQuery(className: "Dogs")
dogsQuery.limit = 100
dogsQuery.findObjectsInBackgroundWithBlock { (currentModes, error) -> Void in
if error == nil {
let tempModes:[PFObject] = currentModes as [PFObject]!
for var i = 0; i < tempModes.count; i++ {
let temp = Dog()
temp.name = currentModes![i]["dogName"] as! String
self.newDogs.append(temp)
}
} else {
print("something happened")
}
}
}
...
func putDogs() {
print(self.newDogs.count)
try! realm.write({ () -> Void in
for var i = 0; i < newDogs.count; i++ {
realm.add(newDogs[i])
}
})
try! realm.commitWrite() //doesn't change anything
}
error message still the same:
Terminating app due to uncaught exception 'RLMException', reason:
'Illegal recursive call of +[RLMSchema sharedSchema]. Note: Properties
of Swift Object classes must not be prepopulated with queried
results from a Realm
I believe I just have some global misunderstanding about how Realm is working because it is extremely simple configuration.
About your RealmSwift code : You have implemented it right.
When you declare a class of realm in swift, it's a subclass of Object class. Similarly for the parse it's subclass of PFObject.
You custom class have to have only one base class. So can't use functionalities of both the libraries Parse as well as Realm.
If you have to have use both Parse and Realm, you need to declare two different classes like RealmMessage and ParseMessage.
Retrieve data for ParseMessage from parse and copy properties to RealmMessage.
Why you want to use both Parse and Realm ?
parse also provides local data store by just writing Parse.enableLocalDatastore() in appDelegate before
Parse.setApplicationId("key",
clientKey: "key")
Your Realm Swift code seems fine. Realm is very flexible when creating objects using (_:value:update:), in being able to take in any type of object that supports subscripts. So you could even directly insert PFObject if the schemas matched.
The problem seems to be how you're populating the allUserContainers array. As the error says, you cannot fetch a Realm Object from Realm and then try and re-add it that way. If you're trying to update an object already in Realm, as long as the primary key properties match, you don't need to supply the whole object back again.
Can you please revisit the logic of how your allUserContainers variable is being populated, and if you can't fix it, post the code into your question?
Sidenote: You probably don't need to define your Realm properties as implicitly unwrapped as well. This is the recommended pattern:
class RealmContainer : Object {
dynamic var rContainerId = ""
dynamic var rContainerName = ""
}
Actually I found what was wrong: as i suspected it was a stupid mistake, some property in my class was UIImage type, and Realm just can't work with this type (even if I'm not trying to put objects of this class into realm). So, my bad. I am slightly embarassed by it, but will not delete my question because error messages from Realm were not very understandable in this case