I am using the Photos framework to fetch album list in iOS8 using this code,
how to reorder smartAlbums, so i can show 'Recently Added' on top of all
let smartAlbums : PHFetchResult = PHAssetCollection.fetchAssetCollectionsWithType(PHAssetCollectionType.SmartAlbum, subtype: PHAssetCollectionSubtype.AlbumRegular, options: nil)
in cellForRow method
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let collection = smartAlbums[indexPath.row]
cell.textLabel?.text = collection.localizedTitle
return cell
}
let photofetchOpt:PHFetchOptions? = PHFetchOptions()
photofetchOpt?.sortDescriptors = [NSSortDescriptor(key:"localizedTitle", ascending: false)]
i have tried to use PHFetchOptions while fetching AssetCollections, but there is no effect on order of smartAlbums.
PHAssetCollection class has a property name startDate which indicates the earliest creation date among all assets in the asset collection. Link.
Use sortDescriptor in the PHFetchOptions to fetch albums buy latest image order.
Objective-C
PHFetchOptions *options = [PHFetchOptions new];
options.sortDescriptors = #[[NSSortDescriptor sortDescriptorWithKey:#"startDate" ascending:NO]];
PHFetchResult<PHAssetCollection*> *allCollections = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeSmartAlbum subtype:PHAssetCollectionSubtypeAny options:nil];
Swift
let options = PHFetchOptions()
options.sortDescriptors = [NSSortDescriptor(key: "startDate", ascending: false)]
let result = PHAssetCollection.fetchAssetCollections(with: .smartAlbum, subtype: .any, options: options)
I got a solution for reordering PHCollectionList, I am posting this answer for whom who are struggling to reorder the list
Basically i am using NSMutableArray sortedSmartAlbumsCollectionsFetchResults
if(smartAlbums.count > 0) {
for i in 0...smartAlbums.count-1 {
let assetCollection:PHAssetCollection = smartAlbums[i] as! PHAssetCollection
if assetCollection.assetCollectionSubtype == PHAssetCollectionSubtype.SmartAlbumRecentlyAdded {
sortedSmartAlbumsCollectionsFetchResults.insertObject(assetCollection, atIndex: 0)
}
else {
sortedSmartAlbumsCollectionsFetchResults.addObject(assetCollection)
}
}
}
as shown in above code, I have fetch each PHAssetCollection from smartAlbums and check if it's subtype is SmartAlbumRecentlyAdded if YES then insert it at 0 index else continue adding in NSMutableArray
see result
Related
The sample app has a table view that is powered by UITableViewDiffableDataSource that gets data from NSFetchedResultsController. You can add letters of the alphabet to the table view by pressing the plus button. To implement data source, I used this article. The issue is that when I add new item to Core Data NSFetchedResultsController feeds temporary IDs to the cell provider. And when I scroll down and cell provider has to reuse cells, it fails to fetch managed object with temporary ID. It does not, however, happen when the item is added to the area of the table view that is on the screen.
lazy var fetchedResultsController: NSFetchedResultsController<Item> = {
let fetchRequest: NSFetchRequest<Item> = Item.fetchRequest()
let sort = NSSortDescriptor(key: #keyPath(Item.name), ascending: true)
fetchRequest.sortDescriptors = [sort]
let controller = NSFetchedResultsController(
fetchRequest: fetchRequest,
managedObjectContext: moc,
sectionNameKeyPath: nil,
cacheName: nil
)
controller.delegate = self
return controller
}()
func configureDiffableDataSource() {
let diffableDataSource = UITableViewDiffableDataSource<Int, NSManagedObjectID>(tableView: tableView) { (tableView, indexPath, objectID) -> UITableViewCell? in
guard let object = try? self.moc.existingObject(with: objectID) as? Item else {
// Crash happens here.
fatalError("Managed object should be available.")
}
let cell = tableView.dequeueReusableCell(withIdentifier: "cell_id", for: indexPath)
cell.textLabel?.text = object.name
return cell
}
self.diffableDataSource = diffableDataSource
tableView.dataSource = diffableDataSource
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference) {
guard let dataSource = tableView?.dataSource as? UITableViewDiffableDataSource<Int, NSManagedObjectID> else {
assertionFailure("The data source has not implemented snapshot support while it should.")
return
}
var snapshot = snapshot as NSDiffableDataSourceSnapshot<Int, NSManagedObjectID>
let currentSnapshot = dataSource.snapshot() as NSDiffableDataSourceSnapshot<Int, NSManagedObjectID>
let reloadIdentifiers: [NSManagedObjectID] = snapshot.itemIdentifiers.compactMap { itemIdentifier in
guard let currentIndex = currentSnapshot.indexOfItem(itemIdentifier), let index = snapshot.indexOfItem(itemIdentifier), index == currentIndex else {
return nil
}
guard let existingObject = try? controller.managedObjectContext.existingObject(with: itemIdentifier), existingObject.isUpdated else { return nil }
return itemIdentifier
}
snapshot.reloadItems(reloadIdentifiers)
let shouldAnimate = tableView?.numberOfSections != 0
dataSource.apply(snapshot as NSDiffableDataSourceSnapshot<Int, NSManagedObjectID>, animatingDifferences: shouldAnimate)
}
Adding try! fetchedResultsController.performFetch() right after saving to Core Data fixes the issue, however, it’s a brute-force solution, which causes double call to controller(_:didChangeContentWith:) delegate method and, sometimes, double animation. Fetching should happen automatically in this case. I wonder, why cell provider fails to fetch data and how to fix this in an efficient way.
#objc func handleAdd() {
// Add item to Core Data.
let context = moc
let entity = Item.entity()
let item = Item(entity: entity, insertInto: context)
item.name = "\(letters[counter])" // Adds letters of the alphabet.
counter += 1
try! context.save()
// Manually fetching right after saving doesn’t seem efficient.
try! fetchedResultsController.performFetch()
}
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 pray to god someone can help! I've been stuck on this for weeks now.
I have a tableview representing coredata objects. At the top of the table view, I have a segmented controller representing different ways to sort the data. On change, the segmented controller updates the arrangement of the table.
I have a problem where, upon adding data, everything looks ok - until I change sorting methods. After leaving "date" and going to "location" (for example), if you return to date, the HEADERS don't match the dates... the dates should appear in descending order with headers that MATCH...
BEFORE CLICKING "LOCATION"
AFTER CLICKING "LOCATION" AND RETURNING TO "DATE"
Here's the pertinent code (at least I think so...)
#IBAction func segmentedControlChange(sender: AnyObject) {
//call custom class for overlay loading indicator
let progressHUD = ProgressHUD(text: "Sorting")
self.view.addSubview(progressHUD)
//put the table load on a different thread for processing
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
self.getFetchResultsController()
self.tableView.reloadData()
//after tableview is loaded, execute this
dispatch_sync(dispatch_get_main_queue(), {
self.view.sendSubviewToBack(progressHUD)
progressHUD.hide()
})
})
}
func getFetchResultsController(){
let fetchRequest = NSFetchRequest(entityName: "Place")
switch self.segmentedControl.selectedSegmentIndex {
case 0:
let sortByDate = NSSortDescriptor(key: "date", ascending: false)
let sortDescriptors = [sortByDate]
fetchRequest.sortDescriptors = sortDescriptors
println("sorted by date")
fetchRequest.fetchBatchSize = 30
aFetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: moc, sectionNameKeyPath: "sectionDateHdr", cacheName: nil)
aFetchedResultsController.delegate = self
case 1:
let sortByState = NSSortDescriptor(key: "state", ascending: true)
let sortByAOI = NSSortDescriptor(key: "aoiData.aoiName", ascending: true)
let sortByDate = NSSortDescriptor(key: "date", ascending: false)
let sortDescriptors = [sortByState, sortByAOI, sortByDate]
fetchRequest.sortDescriptors = sortDescriptors
println("sorted by AOI, then date")
fetchRequest.fetchBatchSize = 30
aFetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: moc, sectionNameKeyPath: "state", cacheName: nil)
aFetchedResultsController.delegate = self
case 2:
let sortByTail = NSSortDescriptor(key: "tailNum", ascending: false)
let sortByDate = NSSortDescriptor(key: "date", ascending: false)
let sortDescriptors = [sortByTail, sortByDate]
fetchRequest.sortDescriptors = sortDescriptors
println("sorted by tail, then date")
fetchRequest.fetchBatchSize = 30
aFetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: moc, sectionNameKeyPath: "tailNum", cacheName: nil)
aFetchedResultsController.delegate = self
default:
println("no coredata object to query in fetchData")
}
var error: NSError? = nil
if !self.aFetchedResultsController.performFetch(&error) {
abort()
}
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return self.aFetchedResultsController.sections?.count ?? 0
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return aFetchedResultsController.sections?[section].numberOfObjects ?? 0
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellIdentifier = "Cell"
var cell : CustomTableViewCell!
cell = self.tableView.dequeueReusableCellWithIdentifier("Cell") as! CustomTableViewCell
var curPlace = aFetchedResultsController.objectAtIndexPath(indexPath) as! AllPlaces
if curPlace.hasImage == true {
image = UIImage(data: curPlace.images.imageData)!
} else {
image = UIImage(named: "Placeholder")!
}
cell.loadItem(curPlace.hasImage, aoi: curPlace.aoiData.aoiName, date: curPlace.date, city: curPlace.city, state: curPlace.state, arrOrDep: curPlace.arrOrDep)
cell.accessoryType = UITableViewCellAccessoryType.DisclosureIndicator
cell.selectionStyle = UITableViewCellSelectionStyle.Blue
if indexPath.row == (aFetchedResultsController.fetchedObjects!.count - 1)
{
self.getFetchResultsController()
}
return cell
}
override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
//self.getFetchResultsController()
let sectionInfo = self.aFetchedResultsController.sections![section] as! NSFetchedResultsSectionInfo
return sectionInfo.name
}
Ok, so in typical fashion as soon as I cave and ask for help I figured it out. Not sure why this worked but here is what I did -
1.) In the "AllPlaces" class, I added a function that just looked at the date and returned the value of MMMM yyyy as a string. I called this sectionID.
func sectionID() -> String {
var aformatter = NSDateFormatter()
aformatter.dateFormat = "MMMM yyyy"
return aformatter.stringFromDate(date)
}
2.) I was previously using a property called sectionDateHdr, which was a string format of the same "date" variable, in the same MMMM yyyy format. For some reason, using sectionID instead of sectionDateHdr caused the table to correctly label the headers.
I really have no clue why.. but I am so relieved it is finally working. I'm guessing it has something to do with when the values are set that get passed through the fetchResultsController.
2 weeks later!!! SOLVED!
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.
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"