today I read and created around 10 projects related to Core Data. I and finally accomplished what I need but I'm not sure that this approach is very good
I want to change the book title base on the user device lang, I will use
let locale = NSLocale.preferredLanguages()[0] as! String
but first to test the Core Data implementation
Core Data Setup
Source Code
// Get the data from Core Data and change the book title base on the lang
func printBooks(){
let request = NSFetchRequest(entityName: "Langs")
let predicate: NSPredicate = NSPredicate(format: "lang==%#", "fr");
request.returnsObjectsAsFaults = false;
request.predicate = predicate;
let result:NSArray = context.executeFetchRequest(request, error: nil)!;
var frTitle: String!
if(result.count > 0){
for res in result {
let resTmp = res as! NSManagedObject
frTitle = resTmp.valueForKey("langTitle") as! String
}
} else {
println("empty");
}
let requestTwo = NSFetchRequest(entityName: "Book")
requestTwo.returnsObjectsAsFaults = false;
let resultTwo:NSArray = context.executeFetchRequest(requestTwo, error: nil)!;
if(resultTwo.count > 0){
for res in resultTwo {
let resTmpTwo = res as! NSManagedObject
// rename the title
resTmpTwo.setValue(frTitle, forKey: "title");
}
// add to NSArray
entriesArray = resultTwo;
} else {
println("empty two");
}
}
// Adds the new book with the fr version
func insertBook(){
let appDel:AppDelegate = (UIApplication.sharedApplication().delegate as! AppDelegate)
let context: NSManagedObjectContext = appDel.managedObjectContext!
let newBook = NSEntityDescription.insertNewObjectForEntityForName("Book", inManagedObjectContext: context) as! DB.Book
newBook.setValue("Sir Arthur Conan Doyle", forKey: "author")
newBook.setValue("Sherlock Holmes", forKey: "title")
let newLang = NSEntityDescription.insertNewObjectForEntityForName("Langs", inManagedObjectContext: context) as! DB.Langs
newLang.setValue("fr", forKey: "lang")
newLang.setValue("Sherlock Holmes fr - test", forKey: "langTitle")
newBook.setValue(newLang, forKey: "lanBook")
context.save(nil)
println("Saved");
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.entriesArray.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCellWithIdentifier("cell_one", forIndexPath: indexPath) as! UITableViewCell
let db = entriesArray[indexPath.row] as! NSManagedObject
// result in the UITableView is: Sherlock Holmes fr - test
cell.textLabel?.text = db.valueForKey("title") as? String
return cell
}
The code does change the book title, but it can be done much better. I'm not sure how this will be done with large numbers of books.
Anyone who knows how it must be done, pleace be so kind to take a moment of your time and answer with some code :)
Also excuse my bad english..
You should think of implementing a transient property say 'langSpecificTitle' on your NSObjectModel, that would compute this for you from your actual'title' property. Ideally to access localised title, you should access this property and it should get a language specific title by reading the actual 'title' property.
This tutorial explains 'transient' properties well.
Also see this from Apple documentation
Hope this helps.
Related
I am pretty sure the error lies in the set line. I thought .key should have made the writing dynamic, but instead the child photopost gets overwritten each time. What do I need to change in order for every upload to be stored vs overwritten.
class post{
var imageDownloadURL: String?
var image: UIImage!
var caption: String!
init(image: UIImage, caption: String) {
self.image = image
self.caption = caption
}
func save() {
let uid = Auth.auth().currentUser!.uid
let newPostRef = Database.database().reference().child("people").child(uid).child("PhotoPosts")
let newPostRef1 = Database.database().reference().child("people").child(uid).child("PhotoPosts1")
let newPostKey = newPostRef.key
if let imageData = image.jpegData(compressionQuality: 0.6){
let imageStorageRef = Storage.storage().reference().child("images")
let newImageRef = imageStorageRef.child(newPostKey)
let newImageRef1 = imageStorageRef.child(newPostKey)
newImageRef.putData(imageData).observe(.success, handler: {(snapshot) in
self.imageDownloadURL =
snapshot.metadata?.downloadURL()?.absoluteString
newPostRef.setValue(self.imageDownloadURL as Any)
})
newImageRef1.putData(imageData).observe(.success, handler: {(snapshot) in
self.imageDownloadURL = snapshot.metadata?.downloadURL()?.absoluteString
let keyToPost = Database.database().reference().child("people").child(uid).childByAutoId().key
let f1: [String: Any] = [(keyToPost) : self.imageDownloadURL as Any]
newPostRef1.updateChildValues(f1)
})
}
}
}
Here is the database rules:
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow read, write: if request.auth != null;
}
}
}
Note: I know newimageref1 is referring to the same thing as newimageref, but I don't see how that could be logically causing the overwrite. I am pretty sure that is not what is causing the overwrite.
Update after correct answer:
Fetching the image.
public override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! ViewControllerTableViewCell
let immy = cell.viewWithTag(1) as! UIImageView
let person: Userx = people[indexPath.row]
cell.postID = self.people[indexPath.row].postID
if let PhotoPosts = person.PhotoPosts {
let url = URL(string: PhotoPosts)
immy.sd_setImage(with: url)
}
return cell
........
for people in snapshot.children.allObjects as! [DataSnapshot] {
if people.key != thisUsersUid {
print("peoplekey",people.key)
let peoplePhotoPosts = peopleObject?["PhotoPosts"] as? String
let peopl = Userx(PhotoPosts: peoplePhotoPosts, imageDownloadURL: peopleimageDownloadURL, postID: peoplepostID, ...)
self.people.append(peopl)
self.people.append(peopl)
Since you only have one newPostKey, it can only have one value at a time. So in these two lines, you end up referencing the same location in Cloud Storage:
let newImageRef = imageStorageRef.child(newPostKey)
let newImageRef1 = imageStorageRef.child(newPostKey)
And since newImageRef and newImageRef1 point to the same location, the last write overwrites whatever is at that location already.
I think you want to write to two distinct locations based on the lowest-level database key, in which case you'll need to make sure you set up the storage references based on the individual keys.
For example:
let newImageRef = imageStorageRef.child(newPostRef.key)
let newImageRef1 = imageStorageRef.child(newPostRef1.key)
I use self.like.alpha = 0.5 to grey out the like button next to the user who was liked. Scrolling causes the highlight to sometimes disappear and appear next to other users.
I've used self.like.alpha = 0.5 last various places in the code but it changes nothing.
#IBAction func likePressed(_ sender: Any) {
self.like.alpha = 0.5
let ref = Database.database().reference()
let keyToPost = ref.child("likes").childByAutoId().key
ref.child("humans").child(self.postID).observeSingleEvent(of: .value, with: {(snapshot) in
if let humans = snapshot.value as? [String: AnyObject] {
let updateLikes: [String: Any] = ["humansWhoLike/\(keyToPost)" : Auth.auth().currentUser!.uid]
ref.child("humans").child(self.postID).updateChildValues(updateLikes, withCompletionBlock: { (error, reff) in
if error == nil {
ref.child("humans").child(self.postID).observeSingleEvent(of: .value, with: { (snap) in
if let properties = snap.value as?[String: AnyObject]{
if let likes = properties["humansWhoLike"] as? [String : AnyObject] {
let count = likes.count
let update = ["likes" : count]
ref.child("humans").child(self.postID).updateChildValues(update)
}
}
})
}
})
}
})
ref.removeAllObservers()
}
What I need is for the like button that is clicked to be greyed out. It has to stay greyed out and the greying out should not jump to another user's like button.
/Updated code after 1st answer
public override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! ViewControllerTableViewCell
let like = cell.viewWithTag(3) as! UIButton
let immy = cell.viewWithTag(1) as! UIImageView
let person: Userx = humans[indexPath.row]
cell.lblName.text = person.Education
cell.postID = self.humans[indexPath.row].postID
if let PhotoPosts = person.PhotoPosts {
let url = URL(string: PhotoPosts)
immy.sd_setImage(with: url)
}
return cell
}
Remember that tableView cells are reusable. When you dequeue one, you cannot assume anything about the existing values. If you mark a cell liked (with button formatting), when that cell is reused, the formatting is still there.
When you dequeue a cell in your cellForRowAt function, you need to reset all the values according to your data store.
I am having a little trouble understanding your database design/usage, but based on the code you added to the post:
let currUser = Auth.auth().currentUser!.uid // better to add this as a VC level variable as you will do this lookup a lot.
let likeArray = person.humansWhoLike ?? []
let likeStatus = likeArray.contains(currentUser)
//from your code, 'like' is the button to be formatted
like.alpha = likeStatus ? 0.5 : 1.0
[enter image description here][1]I'm trying to retrieve specific data from Database. I'd like to read all data in product_list
My Database
This is what i try but when i print listName. It show like this listName = nil
let refList = Database.database().reference().child("Users/Sellers")
refList.observe(DataEventType.value, with:{(snapshot) in
if snapshot.childrenCount>0{
self.listProduct.removeAll()
for lists in snapshot.children.allObjects as! [DataSnapshot]{
let userList = lists.value as? [String: AnyObject]
let listName = userList?["name"]
let listDetail = userList?["detail"]
let listPrice = userList?["price"]
print("key = \(listName)")
let list = ListModel(name: listName as! String?, detail: listDetail as! String?, price: listPrice as! String?)
self.listProduct.append(list)
}
self.tableList.reloadData()
}
})
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! ViewControllerTableViewCell
let list: ListModel
list = listProduct[indexPath.row]
cell.lblName.text = list.name
cell.lblDetail.text = list.detail
cell.lblPrice.text = list.price
return cell
}
It have 4 things i want to retrieve
(details, name, price and product_image_url)
Right now your code is looping over the child nodes of /Users/Sellers, which is the nodes of each individual user. What you seem to want to do is include all nodes under the product_list property of each user, which requires an extra nested loop:
let refList = Database.database().reference().child("Users/Sellers")
refList.observe(DataEventType.value, with:{(snapshot) in
if snapshot.childrenCount>0{
self.listProduct.removeAll()
for user in snapshot.children.allObjects as! [DataSnapshot]{
let productList = user.childSnapshot(forPath: "product_list")
for product in productList.children.allObjects as! [DataSnapshot]{
let userList = product.value as? [String: AnyObject]
let listName = userList?["name"]
let listDetail = userList?["details"] // NOTE: also fixed a typo here
let listPrice = userList?["price"]
print("key = \(listName)")
let list = ListModel(name: listName as! String?, detail: listDetail as! String?, price: listPrice as! String?)
self.listProduct.append(list)
}
}
self.tableList.reloadData()
}
})
I'm building reminder app, when I swap to the left I can delete and update the reminder; when I press on edit button as shown below, it suppose to load data from data base, so I can edit and update the data, but instead it just works like create a new reminder.
and how I can resize the row actions.
ReminderTable.swift
override func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [UITableViewRowAction]? {
let deleteAction = UITableViewRowAction(style: UITableViewRowActionStyle.Default, title: "Delete",handler: { (action, indexPath) -> Void in
if let managedObjectContext = (UIApplication.sharedApplication().delegate as? AppDelegate)?.managedObjectContext {
let reminderToDelete = self.fetchResultController.objectAtIndexPath(indexPath) as! ReminderData
managedObjectContext.deleteObject(reminderToDelete)
do {
try managedObjectContext.save()
} catch {
print(error)
}
}
})
let editAction = UITableViewRowAction(style: UITableViewRowActionStyle.Default, title: "Edit",handler: { (action, indexPath) -> Void in
let st:UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let vc :UINavigationController = st.instantiateViewControllerWithIdentifier("Reminder") as! UINavigationController
self.presentViewController(vc, animated: true, completion: nil)
})
deleteAction.backgroundColor = UIColor.redColor()
editAction.backgroundColor = UIColor.blueColor()
return [deleteAction,editAction]
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "Reminder" {
let task = segue.destinationViewController as! Reminder
let indexpath = self.tableView.indexPathForSelectedRow
let row = indexpath?.row
task.reminders = reminders[row!]
}
}
Reminder.swift
override func viewDidLoad() {
super.viewDidLoad()
dateTextField.delegate = self
nameTextField.delegate = self
if let reminderContent = reminders
{
nameTextField.text = reminderContent.name
dateTextField.text = reminderContent.stringForDate()
timePick.date = reminderContent.time!
}
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "MM-dd-yyyy"
dateFormatter.stringFromDate(reminderContent.date!)
let timeFormatter = NSDateFormatter()
timeFormatter.dateFormat = "hh:mma"
timeFormatter.stringFromDate(reminderContent.time!)
}
func inserte(){
let storingName = nameTextField.text
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "MM-dd-yyyy"
let date = dateFormatter.dateFromString(dateTextField.text!)
if reminders == nil {
if let managedObjectContext = (UIApplication.sharedApplication().delegate as? AppDelegate)?.managedObjectContext {
reminders = NSEntityDescription.insertNewObjectForEntityForName("Reminder", inManagedObjectContext: managedObjectContext) as? ReminderData
reminders!.name = storingName!
reminders.date = date
reminders.time = timePick.date
do {
try managedObjectContext.save()
} catch {
print(error)
return
}
}
}
}
note that I'm still kinda newbie in iOS development
thanks in advance
enter image description here
You have not shown us an important piece of the code, what are you doing in Reminder View Controller?
From what I can see you are setting a reminders property on the Reminder VC to the selected row, is this code running as expected?
Once in the Reminder View Controller, I'm guessing you have a form, are you setting the properties in viewDidLoad/viewDidAppear?
I would expect to see something like:
func viewDidLoad(animated:Bool) {
Super.viewDidLoad(animated:animated)
DescriptionTextField.text = reminder.description
LocationTextField.text = reminder.location
// ... And so on, filling out each property on your model
}
This functionality isn't built in, so you need to build the form and handle the validation of the data and saving into the managed object context (make sure you do this on the same thread)
After looking over the code on github I've updated it and managed to get the reminder to update the changes and show this on the table view.
The main change needed was:
// near the top of the file..
var reminder:ReminderData?
// then further down
func update(){
let storingName = nameTextField.text
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "MM-dd-yyyy"
let date = dateFormatter.dateFromString(dateTextField.text!)
let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
if let reminder = reminder {
reminder.name = storingName
if let categoryStorage = storingCategory {
reminder.category = categoryStorage
}
reminder.date = date
reminder.time = timePick.date
do {
try managedObjectContext.save()
} catch let error as NSError {
print("ERROR in update: \(error)")
}
}
self.navigationController?.popViewControllerAnimated(true)
}
The main change here is not trying to fetch the reminder again, you have passed the managedObject(Reminder) over to the editVC so dont need to fetch it again. so you just update the values and then save.
Also when you was fetching you was fetching all records. If you want to do it this way you would likely pass over the reminder ID to the Reminder VC and then load it by ID, modify and save. in order to do this you'd need to add an ID field and update your fetch to include a predicate, but for now you dont need it.
I don't really know what I have to explain or not, don't hesitate to ask me more code or explanations if needed..
I'm trying to use a CoreData to stock datas gotten from an http POST request and then print them on an UITableView.
I successfully get datas from the JSON and send them to the database. The problem is when I try to send the datas from the database to the UITableView.
It's my first time with the Core Data, so to understand how it works, I have followed this tutorial I adapted to my situation: https://www.youtube.com/watch?v=UniafUWsvLg
This is the Entity in which I'm working:
import Foundation
import CoreData
class Task: NSManagedObject {
#NSManaged var summary: String
#NSManaged var status: String
#NSManaged var responsable: String
#NSManaged var id: String
#NSManaged var detail: String
#NSManaged var date: String
#NSManaged var context: String
}
This is a part of the code preparing the work on the CoreData, I have some comments on it:
//Preparing variables used to get and send datas from DB
let context = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
var nTask: Task? = nil
var frc : NSFetchedResultsController = NSFetchedResultsController()
func getFetchedResultsController() -> NSFetchedResultsController{
frc = NSFetchedResultsController(fetchRequest: taskFetchRequest(), managedObjectContext: context!, sectionNameKeyPath: nil, cacheName: nil)
return frc
}
func taskFetchRequest() -> NSFetchRequest {
//On which Entity are we working?
let fetchRequest = NSFetchRequest(entityName: "Task")
//Which attribute get the Order by. There summary as Ascending
let sortDescriptor = NSSortDescriptor(key: "summary", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
return fetchRequest
}
Now I have declared this, I set on the viewDidLoad the delegate of the getFetchedResultsController to self:
override func viewDidLoad() {
super.viewDidLoad()
frc = getFetchedResultsController()
frc.delegate = self
frc.performFetch(nil)
}
This is how I create the link to the database to get datas from:
//Link creation to SQLite DB
let context = self.context
let ent = NSEntityDescription.entityForName("Task", inManagedObjectContext: context!)
let nTask = Task(entity: ent!, insertIntoManagedObjectContext: context)
then I populate my nTask with String extracted from the JSON, I save the context and I reload the DataBase:
for dict in json2 {
var apps = [String]()
if let summary = dict["summary"] as? String{
nTask.summary = summary
}
if let description = dict["description"] as? String{
nTask.detail = description
}
if let context = dict["context"] as? String{
nTask.context = context
}
if let due = dict["due"] as? String {
nTask.date = due
}
if let status = dict["status"] as? String{
nTask.status = status
}
if let responsible = dict["responsible"] as? String{
nTask.responsable = responsible
}
if let id = dict["id"] as? String{
nTask.id = id
}
}
context?.save(nil)
println(nTask)
self.tableView.reloadData()
When we use a TableView, we have to declare cellForRowAtIndexPath and numberOfRowsInSection, these are them:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell: UITableViewCell = self.tableView.dequeueReusableCellWithIdentifier("customTableViewCell") as! UITableViewCell
let task = frc.objectAtIndexPath(indexPath) as! Task
cell.textLabel?.text = task.summary
var detail = task.detail
var context = task.context
var due = task.date
var status = task.status
var responsible = task.responsable
cell.detailTextLabel?.text = "Contexte: \(context), Detail: \(detail), Status: \(status), Ending date: \(due)"
return cell
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let numberOfRowsInSection = frc.sections?[section].numberOfObjects
return numberOfRowsInSection!
}
The error is line let task = frc.objectAtIndexPath(indexPath) as! Task on my cellForRowAtIndexPath.
The complete error is: Could not cast value of type 'NSManagedObject_Task_' (0x79ebd190) to 'TaskManager.Task' (0xa1f08).
I search for more than half day and no results. I really don't understand what's happening to me...
I'm sorry to give so much code but I haven't any idea of where or why I have this error, so I have to give as informations as possible..
Thanks you so much for having read to the end, thank you for your help.
Regards.
Edit:
I have finally solved my problem by doing several things. I don't really know which one solved... I added the annotation #objc(Task) on my Task class,on my DataModel I changed the class to Task, checked my NSManagedObjectModel was let modelURL = NSBundle.mainBundle().URLForResource("TaskManager", withExtension: "momd")! and the url let url = self.applicationDocumentsDirectory.URLByAppendingPathComponent("TaskManager.sqlite") on the AppDelegate..
Thank you for your help.
I experienced a similar issue, and in my case what worked was to add this #objc(NameOfClass) above my core data class definition. Thank you!
Try:
let task = frc.objectAtIndexPath(indexPath) as NSManagedObject
Perhaps the real problem you have is not the extraction in its "cellForRowAtIndexPath" is in its "FOR":
for dict in json2 {
...
if let summary = json2["summary"] as? String{
nTask.summary = summary
}
...
You it is seeking "summary" of "dict" when you should get it from "json2"