Saving an custom object array that is appended constantly - arrays

I'm relatively new to Swift and coding in general. I'm trying to hone my skills at the moment but putting together a simple reminder app. I'm trying to get the back end working before I put together the story board but I have the essential story board elements to test if my system will work.
Basically I'm trying to save a array that contains a custom object, but this array is appended to each reminder addition done by the user. This is so that every time the app opens, the array will contain the reminders from last time.
Here is the code I have so far to create and append the list;
func createReminder() {
let reminderAdd = Reminder(chosenReminderDescription: textRetrieve.text!, chosenReminderLength: 1)
reminderList.append(reminderAdd)
dump(reminderList)
}
Here is the object code;
class Reminder {
var reminderDescription = "Require initalisation."
var reminderLength = 1 // in days
init (chosenReminderDescription: String, chosenReminderLength: Int) {
reminderDescription = chosenReminderDescription
reminderLength = chosenReminderLength
}
}
How would I go about saving the array?
EDIT:
This is what i've added so far.
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let reminderAdd = Reminder(chosenReminderDescription: "Placeholder test", chosenReminderLength: 1)
reminderList.append(reminderAdd)
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
let entity = NSEntityDescription.entity(forEntityName: "Tasks", in: context)
let newTask = NSManagedObject(entity: entity!, insertInto: context)
newTask.setValue(reminderList, forKey: "taskName")
do {
try context.save()
} catch {
print("Failed saving")
}
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Tasks")
//request.predicate = NSPredicate(format: "age = %#", "12")
request.returnsObjectsAsFaults = false
do {
let result = try context.fetch(request)
for data in result as! [NSManagedObject] {
print(data.value(forKey: "taskName"))
}
} catch {
print("Failed")
}
I'm getting crashes and I can't seem to debug it as of yet. I believe this line is causing the crash as when I remove it the app launches fine.
let reminderAdd = Reminder(chosenReminderDescription: "Placeholder test", chosenReminderLength: 1)
reminderList.append(reminderAdd)
Any ideas?
EDIT 2:
datamodel
That is the data model, I'm not entirely sure what you mean to make the object into a codable. Thanks again.
EDIT 3:
ViewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
let entity = NSEntityDescription.entity(forEntityName: "Tasks", in: context)
let newTask = Tasks(entity: entity!, insertInto: context)
newTask.setValue(reminderList, forKey: "taskName")
do {
try context.save()
} catch {
print("Failed saving")
}
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Tasks")
//request.predicate = NSPredicate(format: "age = %#", "12")
request.returnsObjectsAsFaults = false
do {
let result = try context.fetch(request)
for data in result as! [Tasks] {
print(data.value(forKey: "taskName"))
}
} catch {
print("Failed")
}
dump(reminderList)
}

you could create an instance using CoreData and store it like an internal database.
These are some good tutorial to start with that:
https://medium.com/xcblog/core-data-with-swift-4-for-beginners-1fc067cca707
https://www.raywenderlich.com/7569-getting-started-with-core-data-tutorial
EDIT 2
As you can see in this image,
https://ibb.co/f1axcA
my list in coreData is of type [Notifica], so is an array of object Notifica, to implement codable you should do something like this
public class Notifica: NSObject, NSCoding {
public required init?(coder aDecoder: NSCoder) {
self.id = aDecoder.decodeObject(forKey: "id") as? Double
self.type = aDecoder.decodeObject(forKey: "type") as? String
self.idEvent = aDecoder.decodeObject(forKey: "idEvent") as? Int
self.contactPerson = aDecoder.decodeObject(forKey: "contactPerson") as? People
self.title = aDecoder.decodeObject(forKey: "title") as? String
self.date = aDecoder.decodeObject(forKey: "date") as? String
}
public func encode(with aCoder: NSCoder) {
aCoder.encode(id, forKey: "id")
aCoder.encode(type, forKey: "type")
aCoder.encode(idEvent, forKey: "idEvent")
aCoder.encode(contactPerson, forKey: "contactPerson")
aCoder.encode(title, forKey: "title")
aCoder.encode(date, forKey: "date")
}
ecc..
Another thing is to not call NSManagedObject and pass the entity, but you should name that Tasks as you called in dataModel, if you type Tasks on xcode it will fin for you the NSManagedObject created and then you can set the value for taskName
EDIT 3
"<Simple_Reminders.Reminder: 0x60400046da40>" means that a Reminder object exist! So you saved it! Reminder has two variable:
-reminderDescription and
-reminderLength, so change your code
do {
let result = try context.fetch(request)
for data in result as! [Tasks] {
print(data.value(forKey: "taskName"))
}
} catch {
print("Failed")
}
with this
do {
let result = try context.fetch(request)
for data in result as! [Tasks] {
print(data.value(forKey: "taskName"))
if let reminders = data.value(forKey: "taskName") as? [Reminder] {
for reminder in reminders {
// Now you have your single object Reminder and you can print his variables
print("Your reminder description is \(reminder. reminderDescription), and his length is \(reminder. reminderLength))"
}
}
}
} catch {
print("Failed")
}

Related

use textFieldDidEndEditing textfield delegate to find 3 element in array

My swift code below uses UITextFieldDelegate in textfield var enterT. nameString converts a core data entity to the array of strings, like [bob,bryan,jessica]. In textFieldDidEndEditing, I want the user to be able to enter any number, e.g 3 and third element from the array to be printed and then sort the array.
import UIKit
import CoreData
class ViewController: UIViewController,UITextFieldDelegate {
#IBOutlet var labelName : UILabel!
#IBOutlet var enterT : UITextField!
let appDelegate = UIApplication.shared.delegate as! AppDelegate //Singlton instance
var context:NSManagedObjectContext!
override func viewDidLoad() {
super.viewDidLoad()
openDatabse()
}
func textFieldDidEndEditing(_ textField: UITextField) {
guard let index = Int(textField.text!) else {
// display an alert about invalid text
return
}
joke(at: index)
}
func joke(at index : Int) {
let fetchRequest = NSFetchRequest<Users>(entityName: "Users")
fetchRequest.predicate = NSPredicate(format: "idx == %d", Int32(index))
do {
if let user = try context.fetch(fetchRequest).first {
print(user.username)
}
} catch {
print("Could not fetch \(error) ")
}
}
func openDatabse()
{
context = appDelegate.persistentContainer.viewContext
let entity = NSEntityDescription.entity(forEntityName: "Users", in: context)
let newUser = NSManagedObject(entity: entity!, insertInto: context)
let newUser2 = NSManagedObject(entity: entity!, insertInto: context)
let newUser3 = NSManagedObject(entity: entity!, insertInto: context)
saveData(UserDBObj: newUser, UserDBObj2: newUser2, UserDBObj3: newUser3)
}
func saveData(UserDBObj:NSManagedObject,UserDBObj2:NSManagedObject,UserDBObj3:NSManagedObject)
{
UserDBObj.setValue("kim kardashian", forKey: "username")
UserDBObj2.setValue("jessica biel", forKey: "username")
UserDBObj3.setValue("Hailey Rienhart", forKey: "username")
print("Storing Data..")
do {
try context.save()
} catch {
print("Storing data Failed")
}
fetchData()
}
func fetchData()
{
print("Fetching Data..")
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Users")
request.returnsObjectsAsFaults = false
do {
let result = try context.fetch(request)
for data in result as! [NSManagedObject] {
let userName = data.value(forKey: "username") as! String
print("User Name is : "+userName)
}
} catch {
print("Fetching data Failed")
}
}}
Convert the entered number to Int. If this succeeds pass the integer to joke and fetch the record matching the idx attribute.
Consider that indexes start with zero. If you want to enter numbers starting with one you have to decrement the index (joke(at: index - 1))
#IBOutlet var enterT : UITextField!
func textFieldDidEndEditing(_ textField: UITextField) {
guard let index = Int(textField.text!) else {
// display an alert about invalid text
return
}
joke(at: index)
}
func joke(at index : Int) {
let fetchRequest = NSFetchRequest<Users>(entityName: "Users")
fetchRequest.predicate = NSPredicate(format: "idx == %d", Int32(index))
do {
if let user = try context.fetch(fetchRequest).first {
print(user.username)
}
} catch {
print("Could not fetch \(error) ")
}
}
Note: I recommend to define idx and username as non-optional in the Core Data model. And make sure that all idx values are unique.

I'm getting this error in Swift 'NSInvalidArgumentException'

'NSInvalidArgumentException', reason: 'Unacceptable type of value for attribute: property = "save1D"; desired type = NSData; given type = Swift.__SwiftDeferredNSArray;
I've been stuck on this a few days now. I'm trying to save to and load from core data. I'm trying to save arrays from a collection view into a tableview then reload them back into a collection view. I've not been able to find a solution that fits what I need. I'm sure I'm missing something obvious but I can't se it. Can anyone help me with this?
#IBAction func saveData(_ sender: Any) {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let entity = NSEntityDescription.entity(forEntityName: "TextSave", in: context)
let textEntity = NSManagedObject(entity: entity!, insertInto: context)
textEntity.setValue(v1, forKey: "save1D")
textEntity.setValue(i1, forKey: "save2D")
textEntity.setValue(ImageView, forKey: "picCock")
do {
try context.save()
print("saved")
} catch {
print("failed save")
}
}
func getData() {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "TextSave")
request.returnsObjectsAsFaults = false
do {
let result = try context.fetch(request)
for data in result as! [NSManagedObject]
{
v1 = data.value(forKey: "save1D") as! [String]
i1 = data.value(forKey: "save2D") as! [String]
ImageView = (data.value(forKey: "picCock") as! UIImageView)
}
} catch {
print("failed")
}
}
Problem is here
textEntity.setValue(v1, forKey: "save1D")
v1 is of type NSData while it should be an array

Implementing comments on feed post in swift using firebase

I am trying to implement a comment section on each feed post in my app using swift and firebase, but am having trouble with the code that will get the comments. In my function it is returning a empty array of messageComments but I do not know what I am doing wrong. If I want my firebase database structure to look like that in the picture how can I implement the code that will download those comments in an array?
func getFeedMessages(handler: #escaping (_ feedMessages:[FeedMessages]) -> ()){
var feedMessagesArray = [FeedMessages]()
var commentArray = [messageComments]()
REF_FEEDMESSAGES.observeSingleEvent(of: .value) { (feedMessagesSnapshot) in
guard let feedMessagesSnapshot = feedMessagesSnapshot.children.allObjects as? [DataSnapshot] else {return}
for messages in feedMessagesSnapshot {
let content = messages.childSnapshot(forPath: "content").value as? String ?? "Joe Flacco is an elite QB"
let icon = messages.childSnapshot(forPath: "icon").value as? String ?? "none"
let color = messages.childSnapshot(forPath: "color").value as? String ?? "bop"
self.REF_FEEDCOMMENTS.observeSingleEvent(of: .value, with: { (feedCommentsSnapshot) in
guard let feedCommentsSnapshot = feedCommentsSnapshot.children.allObjects as? [DataSnapshot] else {return}
for comments in feedCommentsSnapshot {
commentArray.append((comments.childSnapshot(forPath: "comments").value as? messageComments!)!)
}
})
print(" comment: ")
print(commentArray)
let messages = FeedMessages(content: content, color: color, icon: icon, comments: commentArray)
feedMessagesArray.append(messages)
}
handler(feedMessagesArray)
}
}
If you also have the same data structure, there would be no need for another request for the comments since they are nested in the feed messages. This will only require some simple parsing, which can be made easier to read and understand with a few extensions.
extension DataSnapshot {
var string: String? {
return value as? String
}
var childSnapshots: [DataSnapshot] {
return children.allObjects as? [DataSnapshot] ?? []
}
func child(_ path: String) -> DataSnapshot {
return childSnapshot(forPath: path)
}
}
These two extensions take care of the snapshot operations needed to initialize the objects.
extension MessageComments {
convenience init(snapshot: DataSnapshot) {
self.comments = snapshot.childSnapshots.map { $0.string }
}
}
extension FeedMessages {
convenience init(snapshot: DataSnapshot) {
self.color = snapshot.child("color").string ?? "bop",
self.comments = MessageComments(snapshot: snapshot.child("comments"))
self.content = snapshot.child("content").string ?? "Joe Flacco is an elite QB",
self.icon = snapshot.child("icon").string ?? "none",
}
}
Just map the children snapshots to initialize each of them as a FeedMessages object.
func getFeedMessages(handler: #escaping (_ feedMessages: [FeedMessages]) -> ()) {
REF_FEEDMESSAGES.observeSingleEvent(of: .value) {
handler($0.childSnapshots.map { FeedMessages(snapshot: $0) })
}
}

How to Save complex Arrayobjects to device using NSKeyedArchiver swift

I want to save an array of any class (e.g let array = SymptomsModel) type into device using NSKeyedArchiver in swift .
I Know how to save an array if SymptomsModel class contains all variables with primitive data types , but don't know how to save it if also contains an array of any other class as its property
Below I have explained my problem with the help of example , please go through it and provide solution.
I have a class
class SymptomsModel: NSObject, NSCoding ,ResponseJSONObjectSerializable {
var slug:String?
var name:String?
var images:[Sym_images]?
var videos:[Sym_videos]?
struct Keys {
static let Name = "name"
static let Slug = "slug"
static let Images = "images"
static let Videos = "videos"
}
required init(json:SwiftyJSON.JSON) {
self.slug = json["slug"].string
self.name = json["name"].string
self.images = [Sym_images]()
if let imagesJSON = json["images"].array {
for(imagesJSON) in imagesJSON {
if let newImages = Sym_images(json: imagesJSON){
self.images?.append(newImages)
}
}
}
self.videos = [Sym_videos]()
if let videosJSONArray = json["videos"].array {
for(videosJSON) in videosJSONArray {
if let newVideos = Sym_videos(json: videosJSON){
self.videos?.append(newVideos)
}
}
}
}
init(dictionary: [String : AnyObject]) {
self.name = dictionary[Keys.Name] as? String
self.slug = dictionary[Keys.Slug] as? String
self.images = dictionary[Keys.Images] as? [Sym_acc_images_objects]
self.videos = dictionary[Keys.Videos] as? [Sym_acc_videos_objects]
}
func encodeWithCoder(archiver: NSCoder) {
archiver.encodeObject(name, forKey: Keys.Name)
archiver.encodeObject(slug, forKey: Keys.Slug)
archiver.encodeObject(images, forKey: Keys.Images)
archiver.encodeObject(videos, forKey: Keys.Videos)
}
required init(coder unarchiver: NSCoder) {
super.init()
name = unarchiver.decodeObjectForKey(Keys.Name) as? String
slug = unarchiver.decodeObjectForKey(Keys.Slug) as? String
self.images = unarchiver.decodeObjectForKey(Keys.Slug) as? [Sym_acc_images_objects]
self.videos = unarchiver.decodeObjectForKey(Keys.Slug) as? [Sym_acc_videos_objects]
}
and a PersistanceManager class to save the data with NskeyedArchiver as
class PersistenceManager {
class private func documentsDirectory() -> NSString {
let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
let documentDirectory = paths[0] as String
return documentDirectory
}
class func saveNSArray(arrayToSave: NSArray, key: String) {
print(" saveNSArray key : \(key)")
let file = documentsDirectory().stringByAppendingPathComponent(key)
NSKeyedArchiver.archiveRootObject(arrayToSave, toFile: file)
}
class func loadNSArray(path: String) -> NSArray? {
print(" loadNSArray key : \(path)")
let file = documentsDirectory().stringByAppendingPathComponent(path)
let result = NSKeyedUnarchiver.unarchiveObjectWithFile(file)
return result as? NSArray
}
}
And Here is my implimentation of saving and then retreiving the array
class ViewController: UIViewController{
var ArraySymptom = [SymptomsModel]()
override func viewDidLoad() {
super.viewDidLoad()
ArraySymptom = loadArray()
//saving data in device
PersistenceManager.saveNSArray(ArraySymptom, key: "Symptom")
//loading data from device
if let value = PersistenceManager.loadNSArray("Symptom") as? [SymptomsModel] {
let images = value[0].images
print("images : \(images)")
let slug = value[0].slug
print("slug : \(slug)")
}
}
Here am able to get the value of slug but not able to fetch images value.
It might be happening because slug is of String type and Images is of Custom Class type .
Please suggest me the way i can get it done .
Is is possible to save these type of arrays with NSKeyedArchiver , so that i can access images value just by retreiving ArraySymptom from device.
Silly mistake it was
I was getting nil in Images because it was decoded with wrong key , it was copy paste mistake
The Error was in this function..
required init(coder unarchiver: NSCoder) {
super.init()
name = unarchiver.decodeObjectForKey(Keys.Name) as? String
slug = unarchiver.decodeObjectForKey(Keys.Slug) as? String
self.images = unarchiver.decodeObjectForKey(Keys.Slug) as? [Sym_images]
self.videos = unarchiver.decodeObjectForKey(Keys.Slug) as? [Sym_videos]
}
And the correct decoding must be
self.images = unarchiver.decodeObjectForKey(Keys.Images) as?
[Sym_images]
self.videos = unarchiver.decodeObjectForKey(Keys.Videos) as?
[Sym_videos]

How to check equality of object properties in an array of objects. Swift

I have a class called Movie, which as of now, only has a string property called movieTitle.
I have an array of Movie, and using the .contains method returns false even when an object with the same title is in the array. Interestingly enough, .contains works in a playground I made but not in an app setting.
Thanks for the help! I'm fairly new to the programing game so if you and ELI5 things, that would be great!
Here's a snippet of the code I have. What ends up happening, is it just keeps adding the same 10 entries onto the array.
do{
let json = try JSONSerialization.jsonObject(with: data!, options:.allowFragments) as! [String: AnyObject]
if let movieSearch = json["Search"] as? [[String: AnyObject]] {
for movie in movieSearch {
if let title = movie["Title"] as? String {
let newMovie = Movie(movieTitle: title)!
if (!self.movieList.contains(newMovie)) {
self.movieList.append(newMovie)
}
self.tableView.reloadData()
}
}
}
}catch {
print("Error with Json: \(error)")
}
Movie Class
import UIKit
class Movie: NSObject, NSCoding {
// MARK: Properties
struct PropertyKey {
static let movieTitleKey = "title"
}
// MARK: Archiving Paths
static let DocumentsDirectory = FileManager().urls(for: .documentDirectory, in: .userDomainMask).first!
static let ArchiveURL = DocumentsDirectory.appendingPathComponent("Movies")
var movieTitle: String
// MARK: Initialization
init?(movieTitle: String) {
// Initialize stored properties.
self.movieTitle = movieTitle
super.init()
// Initialization should fail if there is no itemName
if movieTitle.isEmpty {
return nil
}
}
// MARK: NSCoding
func encode(with aCoder: NSCoder) {
aCoder.encode(movieTitle, forKey: PropertyKey.movieTitleKey)
}
required convenience init?(coder aDecoder: NSCoder) {
let title = aDecoder.decodeObject(forKey: PropertyKey.movieTitleKey) as! String
//Must call designated initializer.
self.init(movieTitle: title)
}
}
// MARK: Equatable
func ==(lhs: Movie, rhs: Movie) -> Bool { // Implement Equatable
return lhs.movieTitle == rhs.movieTitle
}
What works in playgrounds
class Movie: NSObject {
var movieTitle: String
init?(movieTitle: String) {
// Initialize stored properties.
self.movieTitle = movieTitle
super.init()
// Initialization should fail if there is no itemName
if movieTitle.isEmpty {
return nil
}
}
}
var movieList = [Movie]()
var movie1 = Movie(movieTitle: "Batman")
var movie2 = Movie(movieTitle: "Batman")
var movie3 = Movie(movieTitle: "Superman")
movieList.append(movie1!)
movieList.append(movie2!)
movieList.contains(movie1!) // Returns True
movieList.contains(movie3!) // Returns False
Because your Movie class (why is it a class?) inherits from NSObject (why?), it inherits NSObject's conformance of the Equatable protocol, with the NSObject implementation of ==. By default, this does identity comparison (comparing references), rather than value comparison.
Here's an example:
let movie1 = Movie(movieTitle: "Batman")
let movie2 = Movie(movieTitle: "Batman")
var movieList = [Movie1]
movieList.contains(movie1!) // True, because movie1 was added in
movieList.contains(movie2!) // False, movie2 was never added
Since Movie doesn't override == with an implementation that compares its value(s) (such as movieTitle), it defers to the default implementation, which is comparing the references. Even though movie2 has the same value, it's a distinct object with its own (separate) memory location. Thus, the identity comparison fails, and it's not found.
To solve this implement == to return true iff all the fields of Movie match up. What you're trying to do may be better off being implemented with structs, however.
you should try with this way.
var filtered = [Movie]()
filtered = movieList.filter({$0.movieTitle == "Superman"})
if filtered.count == 1 {
//so,"Superman" movie contained in array..
}
let me know the results... thanks.
Just try this code it works perfectly.
do{
let json = try JSONSerialization.jsonObject(with: data!, options:.allowFragments) as! [String: AnyObject]
if let movieSearch = json["Search"] as? [[String: AnyObject]] {
for movie in movieSearch {
if let title = movie["Title"] as? String {
let newMovie = Movie(movieTitle: title)!
let movieTitles = (self.movieList as NSArray).value(forKeyPath: "movieTitle") as? [String]
if movieTitles == nil || movieTitles!.contains(title) == false {
self.movieList.append(newMovie)
}
self.tableView.reloadData()
}
}
}
}catch {
print("Error with Json: \(error)")
}
Try overriding isEqual method of NSObject since it is already conforming Equatable protocol. You can test the code below in a playground. Hope it helps.
class Movie: NSObject {
var movieTitle: String
init?(movieTitle: String) {
// Initialize stored properties.
self.movieTitle = movieTitle
super.init()
// Initialization should fail if there is no itemName
if movieTitle.isEmpty {
return nil
}
}
override func isEqual(_ object: Any?) -> Bool {
guard let theMovie = (object as? Movie) else { return false }
return movieTitle == theMovie.movieTitle
}
}
var movieList = [Movie]()
func appendToList(newMovie: Movie) {
if (!movieList.contains(newMovie)) {
movieList.append(newMovie)
}
}
var movie1 = Movie(movieTitle: "Batman")
var movie2 = Movie(movieTitle: "Batman")
var movie3 = Movie(movieTitle: "Superman")
appendToList(newMovie: movie1!)
movieList.count // count is 1
appendToList(newMovie: movie2!)
movieList.count // count is still 1 not incremented
movieList.contains(movie1!) // Returns true
movieList.contains(movie2!) // Returns true
movieList.contains(movie3!) // Returns false

Resources