.reloadData() and .setNeedsDisplay() are being ignored - ios

I created a pagination tool from a toolbar that hits this method :
func nextPage(sender: UIBarButtonItem) {
let currentChapter = page.valueForKey("chapter") as! Int
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext
let fetchRequest = NSFetchRequest(entityName: "Page")
fetchRequest.predicate = NSPredicate(format: "(chapter = %d)", currentChapter + 1)
do {
let result = try managedContext.executeFetchRequest(fetchRequest)
// It is here, I can clearly see we have the old object.
self.page = result[0] as! NSManagedObject
// And here I can clearly see that a new object was set.
self.tableView.reloadData()
self.view.setNeedsDisplay()
} catch {
let fetchError = error as NSError
print(fetchError)
}
}
This method is located in my UIViewController that is set up like so :
import UIKit
import CoreData
class PageViewController: UIViewController, UITableViewDelegate, UINavigationBarDelegate, UITableViewDataSource {
// Mark: Properties
var page: NSManagedObject!
var tableView = UITableView()
var toolBar = UIToolbar()
override func viewDidLoad() {
super.viewDidLoad()
tableView.frame = CGRectMake(0, 0, view.frame.width, view.frame.height - 50)
tableView.estimatedRowHeight = 200
tableView.rowHeight = UITableViewAutomaticDimension
tableView.scrollEnabled = true
tableView.userInteractionEnabled = true
tableView.delegate = self
tableView.dataSource = self
tableView.tableHeaderView = containerView
self.view.addSubview(tableView)
Any ideas why my tableView is not reloading its new data?
Update
As recommended by #Koder and #Simon, I updated my code as so.. but the UI still did not update :
func nextPage(sender: UIBarButtonItem) {
let currentChapter = page.valueForKey("chapter") as! Int
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext
let fetchRequest = NSFetchRequest(entityName: "Page")
fetchRequest.predicate = NSPredicate(format: "(chapter = %d)", currentChapter + 1)
do {
let result = try managedContext.executeFetchRequest(fetchRequest)
self.page = result[0] as! NSManagedObject
dispatch_async(dispatch_get_main_queue()) {
self.tableView.reloadData()
self.view.setNeedsDisplay()
}
} catch {
let fetchError = error as NSError
print(fetchError)
}
}
Per LucaD's recommendation, I'll also include my numberOfRows and numberOfSections :
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.total
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}

You should fire all the UI refreshing code on the main thread.
For updating tableView, try firing the below from the background thread:
dispatch_async(dispatch_get_main_queue())
{
self.tableView.reloadData()
}
this code block will asynchronously get executed on the main thread.

I suspect you need to move that code into the main thread: UI changes performed in background threads won't update the screen. Try this:
dispatch_async(dispatch_get_main_queue())
{
self.tableView.reloadData()
self.view.setNeedsDisplay()
}
Simon

Related

How can i Solve "Thread 1: Fatal error: Index out of range" error

Hey i got "Thread 1: Fatal error: Index out of range" (in this line selectedId = idArray[indexPath.row] ) error every time i clicked any cell at table view. How i can solve this problem. I think i have problem with my arrays but i cant figure that out. I was do same thing at my last app but i couldnt get any error.
import UIKit
import CoreData
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
var nameArray = [String]()
var idArray = [UUID?]()
var selectedName = ""
var selectedId : UUID?
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
navigationController?.navigationBar.topItem?.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.add, target: self, action: #selector(addNewPatient))
getData()
}
override func viewWillAppear(_ animated: Bool) {
NotificationCenter.default.addObserver(self, selector: #selector(getData) , name: NSNotification.Name(rawValue: "newData"), object: nil)
}
#objc func getData() {
nameArray.removeAll(keepingCapacity: false)
idArray.removeAll(keepingCapacity: false)
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "ToName")
fetchRequest.returnsObjectsAsFaults = false
do {
let results = try context.fetch(fetchRequest)
if results.count > 0 {
for result in results as! [NSManagedObject] {
if let name = result.value(forKey: "name") as? String {
self.nameArray.append(name)
}
if let id = result.value(forKey: "id") as? UUID {
self.idArray.append(id)
}
}
}
} catch {
print("error")
}
tableView.reloadData()
}
#objc func addNewPatient() {
selectedName = ""
performSegue(withIdentifier: "toNameVC", sender: nil)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return nameArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
var content = cell.defaultContentConfiguration()
content.text = nameArray[indexPath.row]
// content.secondaryText = "secondary test"
cell.contentConfiguration = content
return cell
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "toNameVC" {
let destinationVC = segue.destination as! ToNameViewController
destinationVC.choosenName = selectedName
destinationVC.choosenId = selectedId
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
selectedName = nameArray[indexPath.row]
selectedId = idArray[indexPath.row]
performSegue(withIdentifier: "toNameVC", sender: nil)
}
}
This is my toNameViewController.swift page
ToNameViewController.swift
Patient Record App
import UIKit
import CoreData
class ToNameViewController: UIViewController {
#IBOutlet weak var nameLabelText: UITextField!
#IBOutlet weak var tcLabelText: UITextField!
#IBOutlet weak var birthDateLabelText: UITextField!
var choosenName = ""
var choosenId : UUID?
override func viewDidLoad() {
super.viewDidLoad()
if choosenName != "" {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "ToName")
let idString = choosenId?.uuidString
fetchRequest.predicate = NSPredicate(format: "id = %#", idString!)
fetchRequest.returnsObjectsAsFaults = false
do {
let results = try context.fetch(fetchRequest)
if results.count > 0 {
for result in results as! [NSManagedObject] {
if let name = result.value(forKey: "name") as? String {
nameLabelText.text = name
}
if let tc = result.value(forKey: "tc") as? Int {
tcLabelText.text = String(tc)
}
if let birth = result.value(forKey: "birth") as? Int {
birthDateLabelText.text = String(birth)
}
}
}
} catch {
print("error")
}
}else {
}
let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(hiddenKeyboard))
view?.addGestureRecognizer(gestureRecognizer)
}
#objc func hiddenKeyboard() {
view?.endEditing(true)
}
#IBAction func saveButton(_ sender: Any) {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
let newPatient = NSEntityDescription.insertNewObject(forEntityName: "ToName", into: context)
newPatient.setValue(nameLabelText.text, forKey: "name")
if let tc = Int(tcLabelText.text!) {
newPatient.setValue(tc, forKey: "tc")
}
if let birth = Int(birthDateLabelText.text!) {
newPatient.setValue(birth, forKey: "birth")
}
do {
try context.save()
print("saved")
} catch {
print("error")
}
NotificationCenter.default.post(name: NSNotification.Name("newData"), object: nil)
self.navigationController?.popViewController(animated: true)
}
}
This is a common mistake: Multiple arrays for the data source is extremely error-prone if both arrays are populated with optionals.
Declare a custom struct
struct Item {
let name: String
let id: UUID?
}
Declare the data source
var items = [Item]()
Populate the array (valueForKey syntax is outdated)
let fetchRequest = NSFetchRequest<ToName>(entityName: "ToName")
fetchRequest.returnsObjectsAsFaults = false
do {
let results = try context.fetch(fetchRequest)
for result in results {
if let name = result.name {
self.items.append(Item(name: name, id: result.id))
}
}
tableView.reloadData()
} catch {
print(error)
}
In numberOfRowsInSection
return items.count
In cellForRowAt
content.text = items[indexPath.row].name
And in didSelectRowAt
let item = items[indexPath.row]
selectedName = item.name
selectedId = item.id
You can even pass the Item instance to the second view controller rather than the two selected... properties.
But why not even
var toNames = [ToName]()
This avoids any out of range crash
Inside your getData function u append values to idArray. But result.value(forKey: "id") is optional and values get appended unless it is nil. So there may be a difference between the count between nameArray and idArray. So if the result.value(forKey: "id") is nil append a default value to idArray.
#objc func getData() {
nameArray.removeAll(keepingCapacity: false)
idArray.removeAll(keepingCapacity: false)
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "ToName")
fetchRequest.returnsObjectsAsFaults = false
do {
let results = try context.fetch(fetchRequest)
if results.count > 0 {
for result in results as! [NSManagedObject] {
if let name = result.value(forKey: "name") as? String {
self.nameArray.append(name)
//change here like this
if let id = result.value(forKey: "id") as? UUID {
self.idArray.append(id)
}else{
self.idArray.append("your_default_value")//default value or nil
}
}
}
} catch {
print("error")
}
tableView.reloadData()
}
Add an extension to the Array:
extension Array {
func object(at index: Int) -> Element? {
if index < count {
return self[index]
} else {
return nil
}
}
}
Now use this method whenever you access the element from the array.

Cannot save and display data in table view correctly with Core Data and Swift

Goal:
My goal is to pass the current minimum, average and maximum value data to table view and present them to the user.
Error:
When I hit the "Save" button "nan" value is passed with the current minimum, average and maximum value data.
UI screenshot
Here is the table view before pressing the Save button
Here is the table view after pressing the Save button
Here is the current code:
Record.swift
import Foundation
import CoreData
class Record: NSManagedObject {
var minimumValue: Float = .nan
var averageValue: Float = .nan
var maximumValue: Float = .nan
}
RecordTableViewController.swift
class RecordCell: UITableViewCell {
#IBOutlet weak var maximumValueLabel: UILabel!
#IBOutlet weak var averageValueLabel: UILabel!
#IBOutlet weak var minimumValueLabel: UILabel!
}
class RecordTableViewController: UITableViewController, NSFetchedResultsControllerDelegate {
private lazy var fetchedResultsController: NSFetchedResultsController = { () -> NSFetchedResultsController<NSFetchRequestResult> in
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "History")
let sortDescriptor: [NSSortDescriptor] = []
fetchRequest.sortDescriptors = sortDescriptor
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let managedObjectContext = appDelegate.persistentContainer.viewContext
let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: managedObjectContext, sectionNameKeyPath: nil, cacheName: nil)
return fetchedResultsController
}()
let cellIdentifier: String = "cellID"
var recordsArray = [Record]()
override func viewDidLoad() {
super.viewDidLoad()
let swipe = UISwipeGestureRecognizer(target: self, action: #selector(swipeRight(_:)))
swipe.direction = .right
self.view.addGestureRecognizer(swipe)
recordsArray = fetchAllRecords()
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
switch editingStyle {
case .delete:
let removedRecord = fetchedResultsController.object(at: indexPath) as! NSManagedObject
let context = fetchedResultsController.managedObjectContext
context.delete(removedRecord)
do {
try context.save()
self.recordsArray.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
} catch _ {
}
default:
break
}
}
}
extension RecordTableViewController {
public func fetchAllRecords() -> [Record] {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "History")
request.returnsObjectsAsFaults = false
do {
let result = try context.fetch(request)
return result as! [Record]
} catch let error {
fatalError(error.localizedDescription)
}
}
}
MainViewController.swift
import UIKit
import Dispatch
import CoreData
import Accelerate
import Foundation
import UserNotifications
class MainViewController: UIViewController {
var minimum: Float = .nan
var average: Float = .nan
var maximum: Float = .nan
var decibel: Float = .nan
/// MARK: Segue functions
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "saveRecord" {
let recordVC = segue.destination as! RecordTableViewController
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let record = Record.init(entity: NSEntityDescription.entity(forEntityName: "History", in: context)!, insertInto: context)
record.minimumValue = Float(minDbLabel.text!) ?? 0.0
record.averageValue = Float(averageDbLabel.text!) ?? 0.0
record.maximumValue = Float(maximumDbLabel.text!) ?? 0.0
saveNewRecord(record: record)
self.recordsArray.append(record)
recordVC.recordsArray = self.recordsArray
}
}
#IBAction func save(_ sender: UIButton){
self.performSegue(withIdentifier: "saveRecord", sender: nil)
}
// MARK: Core Data functions
private func saveNewRecord(record: Record) {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
var index: Int = 0
let entity = NSEntityDescription.entity(forEntityName: "History", in: context)
let newRecord = [NSManagedObject(entity: entity!, insertInto: context)] as [NSManagedObject]
newRecord[index].setValue(record.minimumValue, forKey: "minimumValue")
newRecord[index].setValue(record.averageValue, forKey: "averageValue")
newRecord[index].setValue(record.maximumValue, forKey: "maximumValue")
index += 1
do {
try context.save()
} catch let error {
print("\(error.localizedDescription)")
}
}
}
Any help is appreciated.
The code you provided is kinda confusing, however, here's how I setup my CoreData applications.
First: I create a CoreDataManager model
This will handle all of my Model functions like fetching viewing and context etc...
struct CoreDataManager {
static let shared = CoreDataManager()
let persistentContainer: NSPersistentContainer = {
let perCon = NSPersistentContainer(name: "EntityName")
perCon.loadPersistentStores { (storeDescription, err) in
if let err = err {
fatalError("\(err)")
}
}
return perCon
}()
func fetchEntitys() -> [Entity] {
let context = persistentContainer.viewContext
let fetchRequest = NSFetchRequest<Entity>(entityName: "EntityName")
do {
let entity = try context.fetch(fetchRequest)
return entity
} catch let err {
print("\(err)")
return []
}
}
func resetEntitys() {
let context = persistentContainer.viewContext
let batchRequest = NSBatchDeleteRequest(fetchRequest: Entity.fetchRequest())
do {
try context.execute(batchRequest)
} catch let err {
print("\(err)")
}
}
}
Second: Setup your secondViewController the viewController in which you create the objects in
You would need to create a delegate for this viewController
protocol CreateControllerDelegate: class {
func didAdd(entity: Entity)
}
In your secondViewController in this case, it's CreateController you should add the following property.
weak var delegate: CreateControllerDelegate?
Implement the function in which you will save objects into your CoreData.
func createEntity() {
let context = CoreDataManager.shared.persistentContainer.viewContext
let entity = NSEntityDescription.insertNewObject(forEntityName: "EntityName", into: context)
entity.setValue(nameTextField.text, forKey: "name")
// set all of your values that you want to be saved.
do {
try context.save()
dismiss(animated: true) {
self.delegate?.didAdd(entity: entity as! Entity)
}
} catch {
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
Finally, setup your firstViewController the one in which you will display all of your Core Data objects.
Add a property to your MainController in which it will hold all of your Entity objects.
var objectsArray = [Entity]()
Inside of your MainController's viewDidLoad function add the following.
override func viewDidLoad() {
super.viewDidLoad()
self.objectsArray = CoreDataManager.shared.fetchEntitys()
}
Confirm and implement the CreateControllerDelegate in your MainController.
extension MainController: CreateControllerDelegate {
func didAdd(entity: Entity) {
objectsArray.append(entity)
let indexPath = IndexPath(row: objectsArray.count - 1, section: 0)
tableView.insertRows(at: [indexPath], with: .automatic)
}
}
Push your secondViewController.
I don't use Segues.
#IBAction func handleAdd(_ sender: Any) {
let createController = storyboard?.instantiateViewController(withIdentifier: "CreateController") as! CreateController
createController.delegate = self
let navController = UINavigationController(rootViewController: createController)
navController.modalPresentationStyle = .fullScreen
present(navController, animated: true)
}
I hope this is helpful and if there's something that's not working let me know!
Good Luck!

SearchBar does not stay in place (table headerview) - swift

I have made a searchbar in my table header view. I actually would not mind having it in my navigation bar, by setting it to: navigationItem.titleView = searchController.searchBar.. The problem is though that when the searchBar is active, the navigation bar is dismissed - including the searchBar, So you cannot see what you are searching for?
How can I change that? Here is my complete searchBar code:
import UIKit
import FirebaseDatabase
import FirebaseAuth
import FBSDKCoreKit
import FirebaseStorage
class SearchTableViewController: UITableViewController, UISearchResultsUpdating {
#IBOutlet var searchUsersTableView: UITableView!
let searchController = UISearchController(searchResultsController: nil)
var usersArray = [NSDictionary?]()
var filteredUsers = [NSDictionary?]()
let databaseRef = FIRDatabase.database().reference()
override func viewDidLoad() {
super.viewDidLoad()
searchController.searchResultsUpdater = self
searchController.dimsBackgroundDuringPresentation = false
definesPresentationContext = true
//tableView.tableHeaderView = searchController.searchBar
navigationItem.titleView = searchController.searchBar
searchController.searchBar.placeholder = "Søg"
searchController.searchBar.setValue("Annuller", forKey:"_cancelButtonText")
let cancelButtonAttributes: NSDictionary = [NSForegroundColorAttributeName: UIColor.whiteColor()]
UIBarButtonItem.appearance().setTitleTextAttributes(cancelButtonAttributes as? [String : AnyObject], forState: UIControlState.Normal)
databaseRef.child("UserInformation").queryOrderedByChild("userName").observeEventType(.ChildAdded, withBlock: { (snapshot) in
self.usersArray.append(snapshot.value as? NSDictionary)
self.searchUsersTableView.insertRowsAtIndexPaths([NSIndexPath(forRow: self.usersArray.count-1, inSection: 0)], withRowAnimation: .Automatic)
}) { (error) in
print(error.localizedDescription)
}
}
override func viewWillAppear(animated: Bool) {
if let user = FIRAuth.auth()?.currentUser {
let userId = user.uid
FIRDatabase.database().reference().child("Users").child(userId).child("NotificationBadge").observeSingleEventOfType(.Value, withBlock: { (snapshot) in
// Get user value
let value = snapshot.value as? NSDictionary
let badgesNumber = value?["numberOfBadges"] as! Int?
if badgesNumber != nil {
self.tabBarController?.tabBar.items?[3].badgeValue = String(badgesNumber!)
} else {
self.tabBarController?.tabBar.items?[3].badgeValue = nil
}
// ...
}) { (error) in
print(error.localizedDescription)
}
}
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showProfile" {
if let nextVC = segue.destinationViewController as? theProfileTableViewController {
nextVC.viaSegue = sender! as! String
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
if searchController.active && searchController.searchBar.text != "" {
return filteredUsers.count
}
return usersArray.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:SearchTableViewCell = tableView.dequeueReusableCellWithIdentifier("searchCell", forIndexPath: indexPath) as! SearchTableViewCell
let user: NSDictionary?
if searchController.active && searchController.searchBar.text != "" {
user = filteredUsers[indexPath.row]
} else {
user = self.usersArray[indexPath.row]
}
cell.nameLabel.text = user?["userName"] as? String
let profileUserIDPic = user?["usersUID"] as? String
let storage = FIRStorage.storage()
// Refer to your own Firebase storage
let storageRef = storage.referenceForURL("gs://bigr-1d864.appspot.com")
let profilePicRef = storageRef.child(profileUserIDPic!+"/profile_pic.jpg")
// Download in memory with a maximum allowed size of 1MB (1 * 1024 * 1024 bytes)
profilePicRef.dataWithMaxSize(1 * 300 * 300) { (data, error) -> Void in
if (error != nil) {
// Uh-oh, an error occurred!
print("Unable to download image")
cell.profilePic.image = UIImage(named: "profile.png")
cell.profilePic.layer.cornerRadius = cell.profilePic.frame.size.width/2
cell.profilePic.clipsToBounds = true
} else {
// Data for "images/island.jpg" is returned
// ... let islandImage: UIImage! = UIImage(data: data!)
if (data != nil){
cell.profilePic.image = UIImage(data: data!)
cell.profilePic.layer.cornerRadius = cell.profilePic.frame.size.width/2
cell.profilePic.clipsToBounds = true
}
}
}
return cell
}
func updateSearchResultsForSearchController(searchController: UISearchController) {
// update something
filterContent(self.searchController.searchBar.text!)
}
func filterContent(searchText: String) {
self.filteredUsers = self.usersArray.filter{ user in
let username = user!["userName"] as? String
return (username?.lowercaseString.containsString(searchText.lowercaseString))!
}
self.tableView.reloadData()
}
}
When going to the search page it looks like this (because I added the insets
When searching for a user it looks like this
When having click a user and returned to search it looks like this

Trouble with multiple-sectioned TableView in Swift

I apologize in advance if this is confusing. I'm new to Swift with no prior experience and learning.
Basically, I have a UITableView that pulls its data form another viewController with four text fields. The data is saved and added to the tableView (only displaying the two of the fields in the cell). When the cell is selected it segues back to the viewController and adds the data in the appropriate text fields. This also utilized CoreData and NSFetchResultsController. All of this is working properly so far, just wanted to give a little back ground on whats going on.
The Problem...
I am trying to get the tableView to display the data in two sections. I want the first section (section 0) to display the data added to the list created by adding the text fields on the viewController. However, the tableView adds first row as the first section (with the header correct) and then adds the second row (and on) as the second section (section 1)(with section header). I want to add all new items from the viewController to section 0, leaving section 1 blank for when I figure out how to cross items off section 0 and move to section 1 by selecting the row (Coming in the future).
I am new to this site as well and could not figure out how to post my code. If anyone needs to look through it you may have to walk me through adding it. Also, I have trouble converting Objective C to Swift please keep that in mind while answering. Thank you!! and I apologize for being difficult.
(tableView Controller "SList")
class ShoppingList: UIViewController, NSFetchedResultsControllerDelegate, UITableViewDataSource, UITableViewDelegate {
let moc = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
var frc : NSFetchedResultsController = NSFetchedResultsController()
func itemFetchRequest() -> NSFetchRequest{
let fetchRequest = NSFetchRequest(entityName: "SList")
let primarySortDescription = NSSortDescriptor(key: "slitem", ascending: true)
let secondarySortDescription = NSSortDescriptor(key: "slcross", ascending: true)
fetchRequest.sortDescriptors = [primarySortDescription, secondarySortDescription]
return fetchRequest
}
func getFetchRequetController() ->NSFetchedResultsController{
frc = NSFetchedResultsController(fetchRequest: itemFetchRequest(), managedObjectContext: moc, sectionNameKeyPath: "slitem", cacheName: nil)
return frc
}
#IBOutlet weak var tableView: UITableView!
#IBAction func AddNew(sender: AnyObject) {
frc = getFetchRequetController()
frc.delegate = self
do {
try frc.performFetch()
} catch _ {
print("Failed to perform inital fetch.")
return
}
self.tableView.reloadData()
}
override func viewDidLoad() {
super.viewDidLoad()
frc = getFetchRequetController()
frc.delegate = self
do {
try frc.performFetch()
} catch _ {
print("Failed to perform inital fetch.")
return
}
self.tableView.reloadData()
//TableView Background Color
self.tableView.backgroundColor = UIColor.clearColor()
tableView.reloadData()
self.navigationItem.leftBarButtonItem = self.editButtonItem()
self.tableView.separatorColor = UIColor.blackColor()
}
override func viewDidDisappear(animated: Bool) {
frc = getFetchRequetController()
frc.delegate = self
do {
try frc.performFetch()
} catch _ {
print("Failed to perform inital fetch.")
return
}
self.tableView.reloadData()
}
//TableView Data
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
let managedObject:NSManagedObject = frc.objectAtIndexPath(indexPath) as! NSManagedObject
moc.deleteObject(managedObject)
do {
try moc.save()
} catch _ {
print("Failed to save.")
return
}
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
let numberOfSections = frc.sections?.count
return numberOfSections!
}
//table section headers
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String?{
let sectionHeader = "Items - #\(frc.sections![section].numberOfObjects)"
let sectionHeader1 = "Crossed Off Items - #\(frc.sections![section].numberOfObjects)"
if (section == 0) {
return sectionHeader
}
if (section == 1){
return sectionHeader1
}else{
return nil
}
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let numberOfRowsInSection = frc.sections?[section].numberOfObjects
return numberOfRowsInSection!
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
let items = frc.objectAtIndexPath(indexPath) as! SList
cell.backgroundColor = UIColor.clearColor()
cell.textLabel?.text = "\(items.slitem!) - Qty: \(items.slqty!)"
cell.textLabel?.font = UIFont.systemFontOfSize(23)
return cell
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func controllerDidChangeContent(controller: NSFetchedResultsController) {
tableView.reloadData()
}
//segue to add/edit
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if segue.identifier == "edit" {
let cell = sender as! UITableViewCell
let indexPath = tableView.indexPathForCell(cell)
let SListController:SLEdit = segue.destinationViewController as! SLEdit
let items:SList = frc.objectAtIndexPath(indexPath!) as! SList
SListController.item = items
}
}
}
(ViewController "SLEdit")
class SLEdit: UIViewController {
let moc = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
#IBOutlet weak var slitem: UITextField!
#IBOutlet weak var sldesc: UITextField!
#IBOutlet weak var slqty: UITextField!
#IBOutlet weak var slprice: UITextField!
var item: SList? = nil
override func viewDidLoad() {
super.viewDidLoad()
if item != nil{
slitem.text = item?.slitem
sldesc.text = item?.sldesc
slqty.text = item?.slqty
slprice.text = item?.slprice
}
// "x" Delete Feature
self.slitem.clearButtonMode = UITextFieldViewMode.WhileEditing
self.sldesc.clearButtonMode = UITextFieldViewMode.WhileEditing
self.slqty.clearButtonMode = UITextFieldViewMode.WhileEditing
self.slprice.clearButtonMode = UITextFieldViewMode.WhileEditing
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func dismissVC() {
navigationController?.popViewControllerAnimated(true)
}
#IBAction func saveButton(sender: AnyObject) {
if item != nil {
edititems()
} else {
createitems()
}
dismissVC()
}
func createitems() {
let entityDescription = NSEntityDescription.entityForName("SList", inManagedObjectContext: moc)
let item = SList(entity: entityDescription!, insertIntoManagedObjectContext: moc)
item.slitem = slitem.text
item.sldesc = sldesc.text
item.slqty = slqty.text
item.slprice = slprice.text
if slitem.text == nil{
createitems()
}else{
edititems()
}
do {
try moc.save()
} catch _ {
return
}
}
func edititems() {
item?.slitem = slitem.text!
item?.sldesc = sldesc.text!
item?.slqty = slqty.text!
item?.slprice = slprice.text!
do {
try moc.save()
} catch {
return
}
}
}
I think the problem lies in your FRC configuration:
frc = NSFetchedResultsController(fetchRequest: itemFetchRequest(), managedObjectContext: moc, sectionNameKeyPath: "slitem", cacheName: nil)
Because sectionNameKeyPath is slitem, the FRC creates a new section for each different value of slitem.
If the slcross attribute is used to indicate that the item has been crossed off the list, specify that as the sectionNameKeyPath:
frc = NSFetchedResultsController(fetchRequest: itemFetchRequest(), managedObjectContext: moc, sectionNameKeyPath: "slcross", cacheName: nil)
You will also need to modify the sorting of the fetch request (it MUST be sorted so that all items in a given section are together):
let primarySortDescription = NSSortDescriptor(key: "slcross", ascending: true)
let secondarySortDescription = NSSortDescriptor(key: "slitem", ascending: true)
fetchRequest.sortDescriptors = [primarySortDescription, secondarySortDescription]

Save and load data to / from Core Data database (iOS 8)

First of all I have to say that I am pretty new to iOS programming. I develop and Core Data App with Swift. I have a UITableViewController, called tanningarTableViewController" where I want the data to be displayed the data is going to be added (saved) in another ViewController called "nyTankning".
I have followed a tutorial on Youtube, but because Swift has changed during since the WWDC I have had to search for other sultans sometimes.
I am not 100% sure The UITableViewController is coded right, but I guess, because it seems like the problem is with the saving? I have read about some NSFetchedController or something like that, should I use that?
Here is the button I expect to save the data:
#IBAction func saveButton(sender: AnyObject) {
// Reference to out app delegate
let appDel: AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
// Reference moc
let contxt: NSManagedObjectContext = appDel.managedObjectContext!
let en = NSEntityDescription.entityForName("Tankningslista", inManagedObjectContext: contxt)
// Create instance of our data model and initialize
var nyTankning = Model(entity: en!, insertIntoManagedObjectContext: contxt)
// Map our properies
nyTankning.liter = (textFieldLiter.text as NSString).floatValue
nyTankning.kronor = (textFieldKronor.text as NSString).floatValue
nyTankning.literpris = (textFieldLiterpris.text as NSString).floatValue
// Save or context
contxt.save(nil)
println(nyTankning.liter)
println(nyTankning.kronor)
println(nyTankning.literpris)
if contxt.save(nil) {
// Fetch the Data from Core Data first....
let fetchRequest = NSFetchRequest(entityName: "Tankningslista")
var error:NSError?
var result: NSArray = contxt.executeFetchRequest(fetchRequest, error: nil)!
for res in result {
// Now you can display the data
println(res.liter)
println(res.kronor)
println(res.literpris)
}
// End of the fetching
} else {
println("error")
}
// navigate back to root vc
self.navigationController?.popToRootViewControllerAnimated(true)
}
And here is the code for the UITableViewController
import UIKit
import CoreData
class tankningarTableViewController: UITableViewController {
var Tankningar: Array<AnyObject> = []
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "Cell")
}
override func viewDidAppear(animated: Bool) {
let appDel:AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
let contxt:NSManagedObjectContext = appDel.managedObjectContext!
let freq = NSFetchRequest(entityName: "Tankningslista")
var error :NSError?
let fetchedResults = contxt.executeFetchRequest(freq, error: &error) as? [NSManagedObject]
if let results = fetchedResults { Tankningar = results
}
else
{
println("Could not fetch \(error)")
}
tableView.reloadData()
println(Tankningar)
println("freq")
println(freq)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Potentially incomplete method implementation.
// Return the number of sections.
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete method implementation.
// Return the number of rows in the section.
return Tankningar.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// Configure the cell...
let CellID: NSString = "Cell"
var cell: UITableViewCell = tableView.dequeueReusableCellWithIdentifier(CellID) as UITableViewCell
let fetchRequest = NSFetchRequest(entityName: "Tankningslista")
var data: NSManagedObject = Tankningar[indexPath.row] as NSManagedObject
var literLabel = data.valueForKeyPath("liter") as? String
var kronorLabel = data.valueForKeyPath("kronor") as? String
var literprisLabel = data.valueForKeyPath("literpris") as? String
cell.textLabel?.text = data.valueForKeyPath("liter") as? String
return cell
}
When I check the debugger it seems like the array that I created in the TableViewController is empty:
[]
freq
<NSFetchRequest: 0x7ffd4ad24830> (entity: Tankningslista; predicate: ((null)); sortDescriptors: ((null)); type: NSManagedObjectResultType; )
34.0
551.0
14.0
39.0
551.0
14.0
[, ]
freq
<NSFetchRequest: 0x7ffd4ae8c330> (entity: Tankningslista; predicate: ((null)); sortDescriptors: ((null)); type: NSManagedObjectResultType; )
What is wrong?

Resources