First TableView section title doesn't scroll Swift 4 - swift

I'm building the SalesViewControllerfor my app and it consists of a TableView showing all items found in a date range.
Itemis child of Order and it has category, date, itemId, itemName, priceattributes all String.
I finally succeed in displaying the result of itemFetchResultController properly divided in sections as I had wrong sortDescriptor. In configuring itemFetchResultController I want use category property from fetched Item entities to be the section displayed title in populated TableView. My goal is, dough I'm not sure it would be possible or how to achieve it, to only have 1 row per itemName in its section but know ho many of it have been found in fetch and use it to display sold value. It's the first time I use sections and it's all a bit confusing to me. I'm trying to follow Apple documentation sample code gives me a couple of errors in tableView's data source methods as you can see by commented out code. All other posts I found here on stack overflow are very old and in objective c so I don't find answers to my doubts.
So far TableViewgets populated correctly , but first section title doesn't move when scrolling.
Any Idea of what's causing this ?
As always many thanks.
Here is the code I'm using for itemFetchResultController :
lazy var itemFetchedResultController: NSFetchedResultsController<Item> = {
// first sortDescriptor filters the date range: possibly change date from String to dates in both function and CoreData and use "(date >= %#) AND (date <= %#)" instead of "BEGINSWITH" in predicate
let itemFetchRequest = NSFetchRequest<Item>(entityName: "Item")
itemFetchRequest.sortDescriptors = [NSSortDescriptor(key: "category", ascending: true)]
itemFetchRequest.predicate = NSPredicate(format: "order.user.name == %#", UserDetails.fullName!)
itemFetchRequest.predicate = NSPredicate(format: "date BEGINSWITH %#", dateToFetch)
let itemFetchedResultController = NSFetchedResultsController(fetchRequest: itemFetchRequest, managedObjectContext: context, sectionNameKeyPath: "category", cacheName: nil)
return itemFetchedResultController
}()
TableView data source:
func numberOfSections(in tableView: UITableView) -> Int {
// apple doc : trhrows an error : Initializer for conditional binding must have Optional type, not 'NSFetchedResultsController<Item>'
// if let frc = itemFetchedResultController {
// return frc.sections!.count
// }
// return 0
return itemFetchedResultController.sections!.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
guard let sections = self.itemFetchedResultController.sections else {
print(" Error :no sections in fetchedResultController" )
return 0
}
let sectionInfo = sections[section]
return sectionInfo.numberOfObjects
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "statisticsCell", for: indexPath) as! StatisticsTableViewCell
cell.idInfoLabel.text = itemFetchedResultController.object(at: indexPath).itemId!
cell.nameInfoLabel.text = itemFetchedResultController.object(at: indexPath).itemName!
let item = itemFetchedResultController.object(at: indexPath).itemName!
let productRequest: NSFetchRequest<Product> = Product.fetchRequest()
productRequest.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)]
productRequest.predicate = NSPredicate(format: "name == %#", item)
productRequest.fetchLimit = 1
do {
let fetch = try context.fetch(productRequest)
cell.productImageView.image = UIImage(data: (fetch[0].productImage! as Data))
cell.minimumStockInfoLabel.text = fetch[0].minimumStock
cell.soldQuantityInfoLabel.text = fetch[0].soldQuantity
} catch {
print("Error in fetching product for cell: \(error)")
}
return cell
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
// guard let sections = self.itemFetchedResultController.sections else {
// print(" Error : no sections in itemsFetchedResultController " )
// return "empty"
// }
// let sectionInfo = sections[section]
// return sectionInfo.name
guard let sectionInfo = itemFetchedResultController.sections?[section] else {
return nil
}
return sectionInfo.name
}
func sectionIndexTitles(for tableView: UITableView) -> [String]? {
// if let sectionIndexTitles: FetchedResultController.sectionIndexTitles = self.itemFetchedResultController.sectionIndexTitles {
// print(" Error : no sections in itemsFetchedResultController " )
// return [""]
// }
return itemFetchedResultController.sectionIndexTitles
}
func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String, at index: Int) -> Int {
// apple doc : trhrows an error : Initializer for conditional binding must have Optional type, not 'Int'
// guard let result = itemFetchedResultController.section(forSectionIndexTitle: title, at: index) else {
// fatalError("Unable to locate section for \(title) at index: \(index)")
// }
// return result
let result = itemFetchedResultController.section(forSectionIndexTitle: title, at: index)
return result
}

Set your UITableView style to grouped and your all section will scroll along with cells

Related

Differences in work of saveContext() depending of viewController instantination.CoreData Enigma

The project has a minimalistic CoreData Stack.
I have one UIViewController subclass "AddOrChangeEntityVC" to create NSManagedObject, change its properties and write data in DB.
The listed functions work fine if "AddOrChangeEntityViewController" was created from
MyFirstVC, MySecondVC, ... and MyNinthVC any other viewcontroller exept one BadVC.
In this case an attempt is made to write an Entity into DB with all properties = nil.
And i see this error.
Error Domain=NSCocoaErrorDomain Code=1560 "Multiple validation errors occurred."
And for every required property something like
"Error Domain=NSCocoaErrorDomain Code=1570 \"firstProperty is a required value.\
UserInfo={NSValidationErrorObject=<APP.Entity: 0x600006917e80> (entity: Entity; id: 0x600000f152a0 <x-coredata:///Entity/t72AEC6D7-A854-40A3-BA9D-5830DFADC8AF2>; data: {\n firstProperty = nil;\n secondProperty = nil;\n}), NSValidationErrorKey = firstValue, NSLocalizedDescription=firstValue is a required value"
"Error Domain=NSCocoaErrorDomain Code=1570 \"secondProperty is a required value.\"
It should be noted that before saveContext() call all properties are validated.
AddOrChangeEntityVC: UIViewController {
var entity: Entity?
func saveEntity() -> Bool {
//validating data
if !validateData() {
return false
}
//creating passenger
if entity == nil {
entity = Entity()
}
//saving entity
if let entity = entity {
//saving
entity.firstProperty = firstPropertyTextfield.text!
entity.secondProperty = secondPropertyTextfield.text!
...
CoreDataStack.shared.saveContext()
return true
}
}
Entity: NSManaged
class Entity: NSManagedObject {
#nonobjc public class func fetchRequest() -> NSFetchRequest<Entity> {
return NSFetchRequest<Entity>(entityName: "Entity")
}
#NSManaged public var firstProperty: String
#NSManaged public var secondProperty: String
convenience init() {
let entity = NSEntityDescription.entity(forEntityName: "Entity",
in: CoreDataStack.shared.persistentContainer.viewContext)!
self.init(entity: entity, insertInto: CoreDataStack.shared.persistentContainer.viewContext)
}
}
CoreData Manager
class CoreDataStack {
static var shared = CoreDataStack()
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "APP")
container.loadPersistentStores { storeDesription, error in
guard error == nil else {
fatalError("Unresolved error \(error!)")
}
}
// Merge the changes from other contexts automatically. useŠ² for Another Entity
enter code here
container.viewContext.automaticallyMergesChangesFromParent = true
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
container.viewContext.shouldDeleteInaccessibleFaults = true
return container
}()
func saveContext() {
let context = persistentContainer.viewContext
guard context.hasChanges else { return }
do {
try context.save()
} catch let error as NSError{
print("Error: \(error), \(error.userInfo)")
}
}
}
Transition from anyVC to AddOrChangeEntityVC
guard let entityVC = self.storyboard?.instantiateViewController(withIdentifier: "addEntityVC") as? AddOrChangeEntityVC else {return}
self.navigationController?.pushViewController(entityVC, animated: true)
```
Well, Core Data is misterious and even obscurant.
I made this simple steps.
1) in AddOrChangeEntityVC i added
var context: NSManagedObjectContext = CoreDataStack.shared.persistentContainer.newBackGroundContext()
2) in initialization of Entity: NSManaged (if we need to create it)
if entity == nil {
entity = Entity(context: context)
}
3) unexpectedly facing that after some saving in NSFetchingResultController results added new misterious entity with all properties == nil i filtered results in tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int method and tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell by removing from fetchedResultsController.fetchedObjects? elements with some properties == nil.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return fetchedResultsController.fetchedObjects?.filter({!$0.firstProperty.isEmpty}).count ?? 0
}
and
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if fetchedResultsController.object(at: indexPath).isValid {
let entity = fetchedResultsController.object(at: indexPath)
let cell = UITableViewCell()update(with: entity)
....
}

Updating table view cells swift

BACKGROUND
I have a simple bill splitting app that allows the users to assign a meal item to multiple people or users. When the meal is assigned to multiple people, the price is divided accordingly. The meal (which contains an item name and a price are the rows and the users are the sections.
When I delete a row, I want to delete the row, and update or alter certain other values (I basically want to reassign the price to one less person when an item is deleted from a user). My data model is a multidimensional array. Here it is:
struct UserModel {
var name: String
var itemModels = [ItemModel]()
init(name: String, itemModels: [ItemModel]? = nil) {
self.name = name
if let itemModels = itemModels {
self.itemModels = itemModels
}
}
}
struct ItemModel {
var itemName: String
var price: Double
init(itemName: String, price: Double) {
self.itemName = itemName
self.price = price
}
}
class Data {
static var userModels = [UserModel]()
static var itemModels = [ItemModel]()
}
For example, in trailingSwipeActionsConfigurationForRowAt
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let delete = UIContextualAction(style: .destructive, title: "Delete") { (contextualAction, view, actionPerformed: #escaping (Bool) -> ()) in
let user = Data.userModels[indexPath.section].name
let item = Data.userModels[indexPath.section].itemModels[indexPath.row].itemName
let price = Data.userModels[indexPath.section].itemModels[indexPath.row].price
ItemModelFunctions.removeFromUser(from: indexPath.section, remove: indexPath.row)
var duplicate = Data.userModels.filter({$0.itemModels.contains(where: {$0.itemName == item})}).filter({$0.name != user})
for i in 0..<duplicate.count {
???
}
tableView.reloadData()
actionPerformed(true)
}
return UISwipeActionsConfiguration(actions: [delete])
}
The variable var duplicate returns an array of the other users who have the same item at the indexPath.row, but not the user(indexPath.section) who has the item. I know it sounds really confusing, but I can provide more code or print statements if needed.
Also in the for loop, I want to do something like this:
for i in 0..<duplicate.count {
let oldPrice = duplicate[i].price
let newPrice = oldPrice * duplicate.count
duplicate[i].price = newPrice
}
But I can't access the price. I believe need an indexPath.section and indexPath.row.
If you made it this far, thank you for taking the time. I feel like I need a nested loop, but I'm not sure how exactly to implement that. If there are any other easier ways to achieve this I'm open to any suggestions.
Thank you so much!
EDIT:
The marked answer worked! In case anyone else was having a similar issue this is what my final code looks like in the trailingSwipeActionsConfigurationForRowAt:
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let delete = UIContextualAction(style: .destructive, title: "Delete") { (contextualAction, view, actionPerformed: #escaping (Bool) -> ()) in
let item = Data.userModels[indexPath.section].itemModels[indexPath.row]
ItemModelFunctions.removeFromUser(from: indexPath.section, remove: indexPath.row)
let duplicate = Data.userModels.filter({$0.itemModels.contains(item)})
for i in 0..<Data.userModels.count {
if let idx = Data.userModels[i].itemModels.firstIndex(of: item) {
let oldPrice = Data.userModels[i].itemModels[idx].price
let newPrice = oldPrice * Double(duplicate.count+1)
Data.userModels[i].itemModels[idx].price = newPrice / Double(duplicate.count)
}
}
tableView.reloadData()
actionPerformed(true)
}
return UISwipeActionsConfiguration(actions: [delete])
}
Perhaps you can try this. Best of luck, please comment if it doesn't work. I am new to Swift :)
let actualItem = Data.userModels[indexPath.section].itemModels[indexPath.row]
for i in 0..<duplicate.count {
if let idx = duplicate[i].itemModels.firstIndex(of: actualItem) {
let oldPrice = duplicate[i].itemModels[idx].price
duplicate[i].itemModels[idx].price = oldPrice * duplicate.count
}
}

Using type select on only one column with NSTableView in Swift

I'm working in XCode 9.3, Swift developing an application for MacOS.
I've created a table that currently has two columns. I want to use type select that only acts on one column so that when the user types the first few letters, it only selects within the first column and not in the 2nd column.
The Apple documentation has an example in Objective-C here: https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/TableView/RowSelection/RowSelection.html#//apple_ref/doc/uid/10000026i-CH6-SW1
but I can't seem to translate that into Swift. I've included the table code below. Any help at all would be greatly appreciated.
extension ViewController: NSTableViewDataSource {
func numberOfRows(in tableView: NSTableView) -> Int {
return directoryItems?.count ?? 0
}
// Sorting.
func tableView(_ tableView: NSTableView, sortDescriptorsDidChange oldDescriptors: [NSSortDescriptor]) {
guard let sortDescriptor = tableView.sortDescriptors.first else {
return
}
if let order = Directory.FileOrder(rawValue: sortDescriptor.key!) {
sortOrder = order
sortAscending = sortDescriptor.ascending
reloadFileList()
}
}
}
extension ViewController: NSTableViewDelegate {
fileprivate enum CellIdentifiers {
static let NameCell = "NameCellID"
static let TimeCell = "TimeCellID"
static let SizeCell = "SizeCellID"
}
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
var image: NSImage?
var text: String = ""
var cellIdentifier: String = ""
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .long
dateFormatter.timeStyle = .long
guard let item = directoryItems?[row] else {
return nil
}
if tableColumn == tableView.tableColumns[0] {
image = item.icon
text = item.name
cellIdentifier = CellIdentifiers.NameCell
} else if tableColumn == tableView.tableColumns[1] {
let asset = AVAsset(url: item.url as URL)
let audioTime = asset.duration
text = audioTime.durationText
cellIdentifier = CellIdentifiers.TimeCell
} else if tableColumn == tableView.tableColumns[2] {
text = item.isFolder ? "--" : sizeFormatter.string(fromByteCount: item.size)
cellIdentifier = CellIdentifiers.SizeCell
}
if let cell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: cellIdentifier), owner: nil) as? NSTableCellView {
cell.textField?.stringValue = text
cell.imageView?.image = image ?? nil
return cell
}
return nil
}
}
Your problem is twofold:
First, you need to set an Identifier for each of your table columns. You can do this by selecting the column in Interface Builder, going to the Identity Inspector, and setting it to some string value:
Once you've done that, create some static properties to refer to these identifiers (this isn't strictly necessary, since you could of course just use rawValue to do a plain string comparison the Objective-C way, but the new Swift 4 identifiers are type-safe, and thus preferred). Do that like this:
extension ViewController: NSTableViewDelegate {
private struct TableColumns {
static let foo = NSUserInterfaceItemIdentifier("Foo")
static let bar = NSUserInterfaceItemIdentifier("Bar")
}
...
}
Now, you can use these identifiers to refer to your columns, using tableColumn(withIdentifier:) (or column(withIdentifier:) if you just want the column number). I recommend doing this everywhere you refer to them, including in your tableView(:viewFor:row:) method, since the way you're doing it now with tableView.tableColumns[0] and so forth depends on the order of the columns, and if the user reorders them, it may cause unexpected behavior. Using the identifier will make sure you're always looking at the column you think you're looking at.
Anyway, once you've got your identifiers set up, you can address the second problem: You're using the wrong delegate method. Instead of tableView(:shouldTypeSelectFor:searchString:), which is meant for catching these things at the event level (i.e., the user just pressed a key. Should I invoke the type select system at all?), use tableView(:typeSelectStringFor:row:). This method lets you return the string given to the type selection mechanism for each row/column combination in your table. For the ones you want type-select to ignore, just return nil; otherwise, return the string that you'd expect to have to type to select that particular row. So, something like:
func tableView(_ tableView: NSTableView, typeSelectStringFor tableColumn: NSTableColumn?, row: Int) -> String? {
if tableColumn?.identifier == TableColumns.foo {
return directoryItems?[row].name
} else {
return nil
}
}

FetchedResultsController Swift 3 API Misuse: Attempt to serialize store access on non-owning coordinator

I'm attempting to use a fetchedResultsController to handle the results in my UITable.
It works initially when the program starts up. Then when I switch back to the inventory tab where my table is (for the viewToAppear again), this is when it crashes.
I'm getting a runtime crash error in my viewWillAppear() method of the window which has the table.
In particular it crashes on the Inventory+CoredataProperties.swift file on this line let characters = name!.characters.map { String($0) }, but I suspect the error is somewhere else as this works initially so why not now on the 2nd reload?
Here is the function.
override func viewWillAppear(_ animated: Bool) {
print("view appearing")
//When the view appears its important that the table is updated.
//Trigger Event on SearchBar in case returning from BarCode Scanner
// self.searchBar:SearchBar textDidChange:recentlySearchedWord;
//searchBar.performSelector(":textDidChange")
//Perform another fetch again to get correct data~
do {
//fetchedResultsController. //this will force setter code to run again.
print("attempting fetch again, reset to use lazy init")
fetchedResultsController = setFetchedResultsController() //sets it again so its correct.
try fetchedResultsController.performFetch()
} catch {
print("An error occurred")
}
inventoryTable.reloadData()//this is important to update correctly for changes that might have been made
}
The error occurs on the try fetchedResultsController.performFetch() statement. I'm getting a lot of errors before the actual crash occurs saying "API Misuse: Attempt to serialize store access on non-owning coordinator (PSC = 0x170265300, store PSC = 0x0). I've been refactoring my code to work with the new swift 3 standards I have a feeling I did something wrong or maybe something changed with how the fetched results controller works.
Any help is appreciated as to what could be the cause?
If you think I'm missing a file you need to see, just let me know and I'll add it to the relevant source code below.
POSSIBLE RELEVANT SOURCE CODE BELOW:
InventoryController.swift (Entire File)
import UIKit
import CoreData
import Foundation
class InventoryController: UIViewController, UISearchBarDelegate, UITableViewDataSource, UITableViewDelegate, NSFetchedResultsControllerDelegate {
#available(iOS 2.0, *)
//Create fetchedResultsController to handle Inventory Core Data Operations
lazy var fetchedResultsController: NSFetchedResultsController<Inventory> = {
return self.setFetchedResultsController()
}()
//Reference to search text for filtering
var m_searchText = ""
func setFetchedResultsController() -> NSFetchedResultsController<Inventory>{
let moc = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let inventoryFetchRequest : NSFetchRequest<Inventory> = Inventory.fetchRequest()
var primarySortDescriptor = NSSortDescriptor(key: "name", ascending: true)//by default assume name.
print("primarySortDescriptor...")
if(g_appSettings[0].indextype=="numberfront"){
primarySortDescriptor = NSSortDescriptor(key: "barcode", ascending: true)
}else if(g_appSettings[0].indextype=="numberback"){
primarySortDescriptor = NSSortDescriptor(key: "barcodeReverse", ascending: true)
}else if(g_appSettings[0].indextype=="numberfourth"){
primarySortDescriptor = NSSortDescriptor(key: "barcodeFourth", ascending: true, selector: #selector(NSString.localizedCompare(_:)))
}
print("set primarySortDescriptor")
//let secondarySortDescriptor = NSSortDescriptor(key: "barcode", ascending: true)
inventoryFetchRequest.sortDescriptors = [primarySortDescriptor]
print("set sort descriptors to fetch request")
var storefilter : Store? = nil
var predicate: NSPredicate
//Store should never be set to nil, the first store should always be selected by default. For fringe cases just in case ... support added so doesn't break
if(g_appSettings[0].selectedStore != nil){
storefilter = g_appSettings[0].selectedStore
predicate = NSPredicate(format: "store = %#", storefilter!) //default predicate assuming store is selected
//However if search text is present then modify predicate
if(m_searchText != ""){
predicate = NSPredicate(format: "store = %# AND name contains[cd] %# OR store = %# AND barcode contains[cd] %#", storefilter!,m_searchText,storefilter!,m_searchText)
}
//This will ensure correct data relating to store is showing (and if any filters present, them as well)
inventoryFetchRequest.predicate = predicate
}else{
if(m_searchText != ""){
predicate = NSPredicate(format: "name contains[cd] %# OR barcode contains[cd] %#",m_searchText,m_searchText)
inventoryFetchRequest.predicate = predicate
//This will ensure correct data relating to store is showing
}
}
//default assume letter section
var frc = NSFetchedResultsController(
fetchRequest: inventoryFetchRequest,
managedObjectContext: moc,
sectionNameKeyPath: "lettersection",
cacheName: nil)
if(g_appSettings[0].indextype=="numberfront"){
frc = NSFetchedResultsController(
fetchRequest: inventoryFetchRequest,
managedObjectContext: moc,
sectionNameKeyPath: "numbersection",
cacheName: nil)
}else if(g_appSettings[0].indextype=="numberback"){
frc = NSFetchedResultsController(
fetchRequest: inventoryFetchRequest,
managedObjectContext: moc,
sectionNameKeyPath: "numberendsection",
cacheName: nil)
}else if(g_appSettings[0].indextype=="numberfourth"){
frc = NSFetchedResultsController(
fetchRequest: inventoryFetchRequest,
managedObjectContext: moc,
sectionNameKeyPath: "numberfourthsection",
cacheName: nil)
}
print("set the frc")
frc.delegate = self
return frc
}
#IBOutlet weak var searchBar: UISearchBar!
#IBOutlet weak var inventoryTable: UITableView!
// Start DEMO Related Code
var numberIndex = ["0","1","2","3","4","5","6","7","8","9"]
var letterIndex = ["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"]
var previousNumber = -1 //used so we show A,A, B,B, C,C etc for proper testing of sections
func createInventoryDummyData(number: Int) -> Inventory{
let moc = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let tempInventory = NSEntityDescription.insertNewObject(forEntityName: "Inventory", into: moc) as! Inventory
if(number-1 == previousNumber){
tempInventory.name = "\(letterIndex[number-2])-Test Item # \(number)"
previousNumber = -1//reset it again
}else{
tempInventory.name = "\(letterIndex[number-1])-Test Item # \(number)"
previousNumber = number //set previous letter accordingly
}
tempInventory.barcode = "00000\(number+1)00\(number)"
//special exception to demo barcode reader
if(number==5){
tempInventory.barcode = "0051111407592"
}
if(number==6){
tempInventory.barcode = "0036000291452"
}
tempInventory.barcodeReverse = String(tempInventory.barcode!.characters.reversed())
//Convert barcode into array of characters and take note if its size for indexing
let bcArraySize = Int(tempInventory.barcode!.characters.count) - 1//for correct indexing
var bcArray = tempInventory.barcode!.characters.map { String($0) }
print(bcArray)
print(bcArraySize)
//Take the digits from the 4th one at a time and convert to strings concatenating as you go.
let fourth = "\(bcArray[bcArraySize-3])"+"\(bcArray[bcArraySize-2])"+"\(bcArray[bcArraySize-1])"+"\(bcArray[bcArraySize])"
print(fourth)
//Finally convert that into a number again and set to barcodeFourth
tempInventory.barcodeFourth = fourth
print(tempInventory.barcodeFourth!)
//tempInventory.barcodeFourth =
//print(tempInventory.barcodeReverse)
tempInventory.currentCount = 0
tempInventory.id = number as NSNumber?
tempInventory.imageLargePath = "http://distribution.tech//uploads/inventory/7d3fe5bfad38a3545e80c73c1453e380.png"
tempInventory.imageSmallPath = "http://distribution.tech//uploads/inventory/7d3fe5bfad38a3545e80c73c1453e380.png"
tempInventory.addCount = 0
tempInventory.negativeCount = 0
tempInventory.newCount = 0
tempInventory.store_id = 1 //belongs to same store for now
//Select a random store to belong to 0 through 2 since array starts at 0
let lo = 0;
let hi = 2;
let aRandomInt = Int.random(range:lo...hi)
tempInventory.setValue(g_storeList[aRandomInt], forKey: "store") //assigns inventory to one of the stores we created.
return tempInventory
}
func createStoreDummyData(number:Int) -> Store{
let moc = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let tempStore = NSEntityDescription.insertNewObject(forEntityName: "Store", into: moc) as! Store
tempStore.address = "100\(number) lane, Miami, FL"
tempStore.email = "store\(number)#centraltire.com"
tempStore.id = number as NSNumber?
tempStore.lat = 1.00000007
tempStore.lng = 1.00000008
tempStore.name = "Store #\(number)"
tempStore.phone = "123000000\(number)"
return tempStore
}
// End DEMO Related Code
override func viewDidLoad() {
super.viewDidLoad()
let moc = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
print("InventoryController -> ViewDidLoad -> ... starting inits")
// // Do any additional setup after loading the view, typically from a nib.
// print("InventoryController -> ViewDidLoad -> ... starting inits")
//
//First check to see if we have entities already. There MUST be entities, even if its DEMO data.
let inventoryFetchRequest = NSFetchRequest<Inventory>(entityName: "Inventory")
//let storeFetchRequest = NSFetchRequest(entityName: "Store")
do {
let inventoryRecords = try moc.fetch(inventoryFetchRequest)
//Maybe sort descriptor here? But how to organize into sectioned array?
if(inventoryRecords.count<=0){
g_demoMode = true
print("No entities found for inventory. Demo mode = True. Creating default entities & store...")
//Reset the Stores
g_storeList = [Store]()
var store : Store //define variable as Store type
for index in 1...3 {
store = createStoreDummyData(number: index)
g_storeList.append(store)
}
//save changes for inventory we added
do {
try moc.save()
print("saved to entity")
}catch{
fatalError("Failure to save context: \(error)")
}
var entity : Inventory //define variable as Inventory type
for index in 1...52 {
let indexFloat = Float(index/2)+1
let realIndex = Int(round(indexFloat))
entity = createInventoryDummyData(number: realIndex)
g_inventoryItems.append(entity)
}
//Save the changes
(UIApplication.shared.delegate as! AppDelegate).saveContext()
print("finished creating entities")
}
}catch{
fatalError("bad things happened \(error)")
}
// //perform fetch we need to do.
// do {
// try fetchedResultsController.performFetch()
// } catch {
// print("An error occurred")
// }
print("InventoryController -> viewDidload -> ... finished inits!")
}
override func viewWillAppear(_ animated: Bool) {
print("view appearing")
//When the view appears its important that the table is updated.
//Trigger Event on SearchBar in case returning from BarCode Scanner
// self.searchBar:SearchBar textDidChange:recentlySearchedWord;
//searchBar.performSelector(":textDidChange")
//Perform another fetch again to get correct data~
do {
//fetchedResultsController. //this will force setter code to run again.
print("attempting fetch again, reset to use lazy init")
fetchedResultsController = setFetchedResultsController() //sets it again so its correct.
try fetchedResultsController.performFetch()
} catch {
print("An error occurred")
}
inventoryTable.reloadData()//this is important to update correctly for changes that might have been made
}
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
print("inventoryItemControllerPrepareForSegueCalled")
if segue.identifier == "inventoryInfoSegue" {
let vc = segue.destination as! InventoryItemController
vc.hidesBottomBarWhenPushed = true //hide the tab bar. This prevents crashing error from being on this page then syncing & returning.
if let cell = sender as? InventoryTableViewCell{
vc.inventoryItem = cell.inventoryItem //sets the inventory item accordingly, passing its reference along.
}else{
print("sender was something else")
}
}
}
// func tableView(tableView: UITableView, sectionForSectionIndexTitle title: String, atIndex index: Int) -> Int {
// //This scrolls to correct section based on title of what was pressed.
// return letterIndex.indexOf(title)!
// }
func sectionIndexTitles(for tableView: UITableView) -> [String]? {
//This is smart and takes the first letter of known sections to create the Index Titles
return self.fetchedResultsController.sectionIndexTitles
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let sections = fetchedResultsController.sections {
let currentSection = sections[section]
return currentSection.numberOfObjects
}
return 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "InventoryTableCell", for: indexPath as IndexPath) as! InventoryTableViewCell
print("IndexPath=")
print(indexPath)
let inventory : Inventory = fetchedResultsController.object(at: indexPath as IndexPath)
cell.inventoryItem = inventory
cell.drawCell() //uses passed inventoryItem to draw it's self accordingly.
return cell
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if let sections = fetchedResultsController.sections {
let currentSection = sections[section]
return currentSection.name
}
return nil
}
func numberOfSections(in tableView: UITableView) -> Int {
if let sections = fetchedResultsController.sections {
return sections.count
}
return 0
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
//dispatch_async(dispatch_get_main_queue()) {
//[unowned self] in
print("didSelectRowAtIndexPath")//does not recognize first time pressed item for some reason?
let selectedCell = self.tableView(tableView, cellForRowAt: indexPath) as? InventoryTableViewCell
self.performSegue(withIdentifier: "inventoryInfoSegue", sender: selectedCell)
//}
}
#IBAction func BarcodeScanBarItemAction(sender: UIBarButtonItem) {
print("test of baritem")
}
#IBAction func SetStoreBarItemAction(sender: UIBarButtonItem) {
print("change store interface")
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
self.barcodeTextDidChange(searchText: searchText)
}
func barcodeTextDidChange(searchText: String){
print("text is changing")
//Code to change NSFetchRequest Here~ & Reload Table
m_searchText = searchText //sets the local variable to this class so the setFetchedResultsController() will update accordingly
//Perform another fetch again to get correct data~
do {
//fetchedResultsController. //this will force setter code to run again.
print("attempting fetch again, reset to use lazy init")
fetchedResultsController = setFetchedResultsController() //sets it again so its correct.
try fetchedResultsController.performFetch()
} catch {
print("An error occurred")
}
inventoryTable.reloadData()//refreshes the data~
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
print("ended by cancel")
searchBar.text = ""
m_searchText = "" //set the search text accordingly back to nothing.
//Perform another fetch again to get correct data~
do {
//fetchedResultsController. //this will force setter code to run again.
print("attempting fetch again, reset to use lazy init")
fetchedResultsController = setFetchedResultsController() //sets it again so its correct.
try fetchedResultsController.performFetch()
} catch {
print("An error occurred")
}
inventoryTable.reloadData()//refreshes the data~
searchBar.resignFirstResponder()
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
print("ended by search")
searchBar.resignFirstResponder()
}
func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {
print("ended by end editing")
searchBar.resignFirstResponder()
}
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
print("DidBeginEditing")
//searchBar.keyboardType = UIKeyboardType.NamePhonePad
}
#IBAction func unwindBackToInventory(segue: UIStoryboardSegue) {
print("unwind attempt")
let barcode = (segue.source as? ScannerViewController)?.barcode
searchBar.text = barcode!
barcodeTextDidChange(searchText: searchBar.text!)//force it to re-run function manually.
print("barcode="+barcode!)
inventoryTable.reloadData()//reload the data to be safe.
}
}
//Extention to INT to create random number in range.
extension Int
{
static func random(range: ClosedRange<Int> ) -> Int
{
var offset = 0
if range.lowerBound < 0 // allow negative ranges
{
offset = abs(range.lowerBound)
}
let mini = UInt32(range.lowerBound + offset)
let maxi = UInt32(range.upperBound + offset)
return Int(mini + arc4random_uniform(maxi - mini)) - offset
}
}
globals.swift
import Foundation
import CoreData
//Array of Inventory & Store Core Data Managed Objects
var g_inventoryItems = [Inventory]()
var g_storeList = [Store]()
var g_appSettings = [AppSettings]()
var g_demoMode = false
Inventory+CoreDataProperties.swift
import Foundation
import CoreData
extension Inventory {
#nonobjc public class func fetchRequest() -> NSFetchRequest<Inventory> {
return NSFetchRequest<Inventory>(entityName: "Inventory");
}
#NSManaged var addCount: NSNumber?
#NSManaged var barcode: String?
#NSManaged var barcodeReverse: String?
#NSManaged var barcodeFourth: String?
#NSManaged var currentCount: NSNumber?
#NSManaged var id: NSNumber?
#NSManaged var imageLargePath: String?
#NSManaged var imageSmallPath: String?
#NSManaged var name: String?
#NSManaged var negativeCount: NSNumber?
#NSManaged var newCount: NSNumber?
#NSManaged var store_id: NSNumber?
#NSManaged var store: Store?
//This is used for A,B,C ordering...
var lettersection: String {
let characters = name!.characters.map { String($0) }
return (characters.first?.uppercased())!
}
//This is used for 1,2,3 ordering... (using front of barcode and using barcodeReverse)
var numbersection: String {
let characters = barcode!.characters.map { String($0) }
return (characters.first?.uppercased())!
}
//This is used for 0000000123 ordering...(uses back number of barcode)
var numberendsection: String {
let characters = barcodeReverse!.characters.map { String($0) }
return (characters.first?.uppercased())!
}
//This is used for 0000000 -> 0123 ordering...(uses back 4th number of barcode)
var numberfourthsection: String {
let characters = barcodeFourth!.characters.map { String($0) }
//print("characters")
//print(characters)
return (characters.first?.uppercased())!
}
}
Inventory.Swift
import Foundation
import CoreData
class Inventory: NSManagedObject {
// Insert code here to add functionality to your managed object subclass
}
Screenshots of Errors
I have reviewed your all comments and contents posted here.
You have not shared one file here, but the problem is occurring you are creating invalid managed objects in the context.
And then whenever you call viewWillAppear() function in InventoryViewController, it saves the context.
Finally, it synced empty records into your database.
During parsing those invalid objects, it tried to parse nil value, so crashed.
Please never set default value for managed objects you are defining as properties.
I hope this will clarify your issue.
I was running into similar issue and i moved to the new CoreData api introduced in ios10.
This uses the NSPersistentContainer class to create the stack and create associated contexts.
This eliminates the need to manually call save or order the creation of fetch results controller.
Good blog post to read: https://useyourloaf.com/blog/easier-core-data-setup-with-persistent-containers/
My setup is a follows
create a store NSPersistentContainer
let persistentContainer = NSPersistentContainer(name: "ModelFileName");
configure settings
let url = NSPersistentContainer.defaultDirectoryURL()
let path = url.appendingPathComponent(persistentContainer.name);
description.shouldAddStoreAsynchronously = true; //write to disk should happen on background thread
self.persistentContainer.persistentStoreDescriptions = [description];
load the store
persistentContainer.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error {
fatalError("Unresolved error \(error), \(error.localizedDescription)")
}
//configure context for main view to automatically merge changes
persistentContainer.viewContext.automaticallyMergesChangesFromParent = true;
});
in the view controller you can access the view context by calling
persistentContainer.viewContext
if you need to make changes you can call
persistentContainer.performBackgroundTask({ (context) in ... });
or you can get a background context
let context = persistentContainer.newBackgroundContext()
context.perform({ ... })
In case this helps anyone else who gets the "API Misuse: Attempt to serialize store access on non-owning coordinator" error - I was getting the error because I accessed an object in a singleton that had not been destroyed and was still using the old NSManagedObjectContext after I reset the NSPersistentStore and NSManagedObjectContext.

Can't access array values outside of function - Swift

I'm trying to query my Parse database in order to search for a username and show all posts related to the username. The posts are split into two fields and I "paste" them back together inside the function.
func searchDataBase() -> ([String]) {
if let username = PFUser.currentUser()?.username {
userHeading.text = username + "'s Profile"
var query = PFQuery(className:"Wish")
query.whereKey("user", equalTo:username)
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
// The find succeeded.
println("Successfully retrieved \(objects!.count) wishes.")
// Do something with the found objects
if let objects = objects as? [PFObject] {
for object in objects {
//println(object.objectId)
var parseID = object.objectId!
var query = PFQuery(className:"Wish")
query.getObjectInBackgroundWithId(parseID) {
(detailedWish: PFObject?, error: NSError?) -> Void in
if error == nil {
let partOne = detailedWish!["wishFieldOne"] as! String
let partTwo = detailedWish!["wishFieldTwo"] as! String
let fullWish = "I wish I " + partOne + " when I " + partTwo
self.wishes.append(fullWish)
} else {
println(error)
}
}
}
}
} else {
// Log details of the failure
println("Error: \(error!) \(error!.userInfo!)")
}
}
} else {
println("No user logged in")
}
return wishes
}
After the function above runs, I want to place the user's posts inside a UITableView:
//TABLE VIEW FUNCTIONS
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
searchDataBase()
return wishes.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = recentPosts.dequeueReusableCellWithIdentifier(textCellIdentifier, forIndexPath: indexPath) as! UITableViewCell
searchDataBase()
let row = indexPath.row
cell.textLabel?.text = wishes[row]
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
recentPosts.deselectRowAtIndexPath(indexPath, animated: true)
searchDataBase()
let row = indexPath.row
println(wishes[row])
}
When I build the app the UITableView doesn't show the posts I queried in the searchDataBase function. If I println(wishes) immediately after the array is appended, it works, but when I print the array outside of the function it is empty. How can I make the contents of the wishes array accessible outside the function - after the query is successful?
Thanks for your help!
This is an odd approach. Your calling searchDataBase() on every tableView method. You surely dont want to make a call to parse every time? Also your searchDataBase() method doesnt need to return an array. You can simply just append the results to this array and reload your table when complete. Im assuming this would be a much better approach for you.
So, my solution to you would be to remove your searchDatabase() function call from all your tableView methods and to place it inside your viewDidLoad(). Also you should change you searchDataBase() to look like so
func searchDataBase(){
if let username = PFUser.currentUser()?.username {
userHeading.text = username + "'s Profile"
var query = PFQuery(className:"Wish")
query.whereKey("user", equalTo:username)
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
// The find succeeded.
println("Successfully retrieved \(objects!.count) wishes.")
// Do something with the found objects
if let objects = objects as? [PFObject] {
for object in objects {
//println(object.objectId)
var parseID = object.objectId!
var query = PFQuery(className:"Wish")
query.getObjectInBackgroundWithId(parseID) {
(detailedWish: PFObject?, error: NSError?) -> Void in
if error == nil {
let partOne = detailedWish!["wishFieldOne"] as! String
let partTwo = detailedWish!["wishFieldTwo"] as! String
let fullWish = "I wish I " + partOne + " when I " + partTwo
self.wishes.append(fullWish)
} else {
println(error)
}
}
}
}
self.tableView.reloadData() // <- This will populate your tableView with your wishes
} else {
// Log details of the failure
println("Error: \(error!) \(error!.userInfo!)")
}
}
} else {
println("No user logged in")
}
}
You can't do what you are trying to do that way.
What you need to do is to rewrite your searchDataBase method to take a completion closure as a parameter. Then in the completion block to your call to query.getObjectInBackgroundWithId, build your result array and pass to the searchDataBase completion closure.
searchDataBase() performs the returns the array. Where's the variable wishes that you're accessing?
Instead:
let wishes = searchDataBase()
and then you'll have the array for your table view delegate functions.