Refreshing gives Error that "found nil while unwrapping an Optional value" - ios

I'm moving this getCloudKit function from ViewController.swift to Lay.swift so I can keep everything in a single class.
var objects = [Lay]()
override func viewDidLoad() {
super.viewDidLoad()
self.refreshControl?.addTarget(self, action: "handleRefresh:", forControlEvents: UIControlEvents.ValueChanged)
self.getCloudKit()
}
func handleRefresh(refreshControl: UIRefreshControl) {
self.objects.removeAll()
self.getCloudKit()
}
func getCloudKit() {
let now = NSDate()
let predicate = NSPredicate(format: "TimeDate > %#", now)
let sort = NSSortDescriptor(key: "TimeDate", ascending: true)
let container = CKContainer.defaultContainer()
let publicData = container.publicCloudDatabase
let query = CKQuery(recordType: “lay”, predicate: predicate)
query.sortDescriptors = [sort]
publicData.performQuery(query, inZoneWithID: nil) { results, error in
if error == nil {
for lay in results! {
let newlay = Lay()
newLay.tColor = lay["tColor"] as! String
newLay.timeDate = lay["TimeDate"] as! AnyObject
newLay.matchup = lay["Matchup"] as! String
let applicationDict = ["tColor" : newLay.tColor, "Matchup" : newLay.matchup]
let transfer = WCSession.defaultSession().transferUserInfo(applicationDict)
self.objects.append(newLay)
}
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.refreshControl!.endRefreshing()
self.tableView.reloadData()
})
} else {
print(error)
}
}
}
The problem is when I move it (and the necessary related code):
Error in Lay.swift on TableViewController().refreshControl!.endRefreshing()
saying "fatal error: unexpectedly found nil while unwrapping an
Optional value"
Need to put my WCSession: transferUserInfo code from getCloudKit in my AppDelegate.swift, but keep getting errors when I try
New ViewController.swift:
override func viewDidLoad() {
super.viewDidLoad()
self.refreshControl?.addTarget(self, action: "handleRefresh:", forControlEvents: UIControlEvents.ValueChanged)
Lay().getCloudKit()
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return Lay().objects.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("reuseIdentifier", forIndexPath: indexPath)
let object = Lay().objects[indexPath.row];
if let label = cell.textLabel{
label.text = object.matchup
}
return cell
}
func handleRefresh(refreshControl: UIRefreshControl) {
Lay().objects.removeAll()
Lay().getCloudKit()
}
New Lay.swift:
var objects = [Lay]()
func getCloudKit() {
let now = NSDate()
let predicate = NSPredicate(format: "TimeDate > %#", now)
let sort = NSSortDescriptor(key: "TimeDate", ascending: true)
let container = CKContainer.defaultContainer()
let publicData = container.publicCloudDatabase
let query = CKQuery(recordType: “lay”, predicate: predicate)
query.sortDescriptors = [sort]
publicData.performQuery(query, inZoneWithID: nil) { results, error in
if error == nil {
for lay in results! {
let newlay = Lay()
newLay.tColor = lay["tColor"] as! String
newLay.timeDate = lay["TimeDate"] as! AnyObject
newLay.matchup = lay["Matchup"] as! String
let applicationDict = ["tColor" : newlay.tColor, "Matchup" : newlay.matchup]
let transfer = WCSession.defaultSession().transferUserInfo(applicationDict)
self.objects.append(newlay)
}
dispatch_async(dispatch_get_main_queue(), { () -> Void in
TableViewController().refreshControl!.endRefreshing()
TableViewController().tableView.reloadData()
})
} else {
print(error)
}
}
New AppDelegate:
private func setupWatchConnectivity() {
if WCSession.isSupported() {
let session = WCSession.defaultSession()
session.delegate = self
session.activateSession()
}
}
private func sendUpdatedDataToWatch(notification: NSNotification) {
if WCSession.isSupported() {
let session = WCSession.defaultSession()
if session.watchAppInstalled
{
let applicationDict = ["TColor" : Lay().tColor, "Matchup" : Lay().matchup]
let transfer = WCSession.defaultSession().transferUserInfo(applicationDict)
NSLog("Transfer AppDelegate: %#", transfer)
NSLog("Trans AppDelegate: %#", applicationDict)
session.transferCurrentComplicationUserInfo(applicationDict)
}
}
}

Your code has ViewController() and Lay() throughout. This will create new instances of those objects. Therefore, although refreshControl is non-nil in your actual view controller, it will be nil in a newly created one.
By splitting out the getCloudKit function, you're allowing the view controller to just manage the view, and the new class to just manage Cloud Kit. This is good, so ideally your Cloud Kit controller should not know anything about the view controller. Therefore, getCloudKit shouldn't be calling reloadData. Instead, you could pass a closure into getCloudKit that gets called when the query finishes. Something along the lines of:
func getCloudKit(completion completionHandler: (([Lay]) -> Void)?) {
...
publicData.performQuery(query, inZoneWithID: nil) { results, error in
if error == nil {
...
if let completion = completionHandler {
completion(self.objects)
}
} else {
print(error)
}
}
Then in ViewController:
let layCloudKit = LayCloudKit()
layCloudKit.getCloudKit(completion: { (objects) -> Void in
dispatch_async(dispatch_get_main_queue(), {
self.objects = objects
self.refreshControl!.endRefreshing()
self.tableView.reloadData()
})
})
Note that I've assumed you would put the Lay Cloud Kit controller into a separate Swift file, as the Lay model class shouldn't need to know about Cloud Kit. If you want to put it in the same file as Lay, then you should mark the func as static or class, because you don't want to create a dummy instance of Lay just to call getCloudKit. In that case, you would call it using Lay.getCloudKit (ie. specifying the Lay class, rather than a Lay instance).

Related

How to persist Topics ID, Which marked as completed in TableViewCell

I want to track completed Topics list in TableView
I have set delegate which confirms to tableView topic mark as completed
protocol TopicDetialVCDelegate: class {
func hasBeenCompletedTopic()
}
TableViewVC
func hasBeenCompletedTopic() {
isPerformedDelegate = true
if !completedTopicIdArray.contains(completedTopicId) {
completedTopicIdArray.append(completedTopicId)
}
print("completed Topics \(completedTopicIdArray)")
print("TopicVC: Completed Topics total: \(completedTopicIdArray.count)")
}
This is working, but i want to persist always mark as completed which already marked
Here is code of CellForRowAt
if isPerformedDelegate {
for _ in 0...completedTopicIdArray.count {
if completedTopicIdArray.contains(filteredTopicArray[indexPath.row].id!) {
cell.topicCompletedTickImageView.image = #imageLiteral(resourceName: "Tick")
}
}
}
What i want
There should be a array which get all completed topics idz, and on every run of app checks if cell indexpath contain topicID show Tick image
I can use UserDefaults like this
UserDefaults.standard.set(array, forkey: "abc")
but problem is array will reinitialize on run of app again
like so in ViewWillDisappear
override func viewWillDisappear(_ animated: Bool) {
UserDefaults.standard.set(completedTopicIdArray, forKey: "completedTopics")
UserDefaults.standard.synchronize()
}
accessing in ViewDidLoad
let topics = UserDefaults.standard.array(forKey: "completedTopics")
print(topics as? [Int] ?? [Int]())
I have been solved this Problem Using CoreData, want to share
Declare empty array
var completedTopicsIdPersistArray: [Int] = [Int]()
CoreData Method for Creating
func persistIdIntoCoreData() {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
let managedObjectContext = appDelegate.persistentContainer.viewContext
guard let entity = NSEntityDescription.entity(forEntityName: "ComTopicDB", in: managedObjectContext)
else {
print("Entity Not Found")
return
}
let topicIdz = NSManagedObject(entity: entity, insertInto: managedObjectContext)
topicIdz.setValue(completedTopicId, forKey: "topicId")
do {
try managedObjectContext.save()
}
catch let error as NSError {
print("Unable to save into CoreData: \(error.userInfo)")
}
}
CoreData Method for Retrieving
func retrieveIdFromCoreData() {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
let managedObjectContext = appDelegate.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "ComTopicDB")
do {
let topicIdz = try managedObjectContext.fetch(fetchRequest)
for id in topicIdz {
let unwrapId = id.value(forKey: "topicId") as! Int
if !completedTopicsIdPersistArray.contains(unwrapId) {
completedTopicsIdPersistArray.append(unwrapId)
}
}
}
catch let error as NSError {
print("Found issue during retrieve id from CoreData:\(error.userInfo)")
}
}
Calling inside of delegate method, which confirms topic completed
func hasBeenCompletedTopic() {
isPerformedDelegate = true
if !completedTopicIdArray.contains(completedTopicId) {
completedTopicIdArray.append(completedTopicId)
persistIdIntoCoreData()
}
print("completed Topics \(completedTopicIdArray)")
print("TopicVC: Completed Topics total: \(completedTopicIdArray.count)")
retrieveIdFromCoreData()
}
and finally CellForRowAt Method
if completedTopicsIdPersistArray.contains(filteredTopicArray[indexPath.row].id!) {
cell.topicCompletedTickImageView.image = #imageLiteral(resourceName: "Tick")
}
Finally call inside ViewWillAppear, because it comes from topicDetailVC back
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
tableView.reloadData()
retrieveIdFromCoreData()
}
Now all topics will show Tick icon, which marked as completed, even terminate and relaunch app

Wait for function to execute first before moving to next line of code

I am using CloudKit in my app and facing problem showing data in table view. In viewDidLoad() I am fetching data from CloudKit database.
Then in table view functions I do CKRecord object count for number of rows.
But count returns 0 to table view and after few seconds returns number of row. Because of this table view does not show the results.
override func viewDidLoad() {
super.viewDidLoad()
loadNewData()
}
func loadNewData() {
self.loadData = [CKRecord]()
let publicData = CKContainer.default().publicCloudDatabase
let qry = CKQuery(recordType: "Transactions", predicate: NSPredicate(format: "TRUEPREDICATE", argumentArray: nil))
qry.sortDescriptors = [NSSortDescriptor(key: "Transaction_ID", ascending: true)]
publicData.perform(qry, inZoneWith: nil) { (results, error) in
if let rcds = results {
self.loadData = rcds
}
if error != nil {
self.showAlert(msg: (error?.localizedDescription)!)
}
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return loadData.count
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell2", for: indexPath) as! ViewAllTransactionsTVCell
let pn = loadData[indexPath.row].value(forKey: "Party_Name") as! String
let amt = loadData[indexPath.row].value(forKey: "Amount") as! String
let nrt = loadData[indexPath.row].value(forKey: "Narattions") as! String
let dt = loadData[indexPath.row].value(forKey: "Trans_Date") as! String
cell.partyNameLabel.text = pn
cell.dateLabel.text = dt
cell.narationLabel.text = nrt
cell.amountLabel.text = amt
return cell
}
You shouldn't wait, but instead trigger the reloading of the data when the perform completion handler is called:
publicData.perform(qry, inZoneWith: nil) { (results, error) in
if let rcds = results {
DispatchQueue.main.async {
self.loadData = rcds
self.tableView.reloadData()
}
}
if error != nil {
self.showAlert(msg: (error?.localizedDescription)!)
}
}
Note, I'm dispatching the reload process to the main queue, because you're not guaranteed to have this run on the main thread. As the documentation says:
Your block must be capable of running on any thread of the app ...
And because UI updates must happen on the main thread (and because you want to synchronize your access to loadData), just dispatch this to the main queue, like above.

Get value inside NSURLSession

My codes doesnt work, do you have an idea why? I want to display some data to my UITable from the requested HTTP
class contactView : UITableViewController{
var values: NSMutableArray!
#IBOutlet var tbview: UITableView!
override func viewDidLoad() {
if(signed != 1){
self.navigationItem.title = ""
}else{
let outbtn = UIBarButtonItem(title: "Sign out", style: .Plain, target: self, action: #selector(contactView.out_action))
navigationItem.leftBarButtonItem = outbtn
let reloadData = UIBarButtonItem(title: "Reload", style: .Plain, target: self, action: #selector(contactView.loadData))
navigationItem.rightBarButtonItem = reloadData
//Check Connection
if(reachability.isConnectedToNetwork() == true) {
loadData()
}else{
let alert = UIAlertController(title: "Error Connection", message: "Not Internet Connection", preferredStyle: .ActionSheet)
let alertAct = UIAlertAction(title: "I'll connect later !", style: .Destructive){
(actions) -> Void in
}
alert.addAction(alertAct)
self.presentViewController(alert, animated: true, completion: nil)
}
}
}
func loadData(){
let url = NSURL(string: url_friends)
let to_post = "user=iam&pin=101218"
let request = NSMutableURLRequest(URL: url!)
request.HTTPMethod = "POST"
request.HTTPBody = to_post.dataUsingEncoding(NSUTF8StringEncoding)
let task = NSURLSession.sharedSession().dataTaskWithRequest(request){
(let data,let response,let error) -> Void in
dispatch_async(dispatch_get_main_queue(),{
do{
self.values = try! NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers) as! NSMutableArray
print(self.values)
}catch{
print(error)
return
}
})
}
task.resume()
}
I want to display the variable "value" data in my table but error keep occuring, saying it is nil when call in my table function cellForRowAtIndexPath
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cellidentity") as UITableViewCell!
let mainData = values[indexpath.row] as! String
let x = cell.viewWithTag(2) as! UILabel
if(signed != 1){
print("No people")
}else{
let x = cell.viewWithTag(2) as! UILabel
x.text = mainData["name"]
}
return cell
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let x : Int
if(reachability.signed() != 1){
x = 1
}else{
x = values.count
}
return x
}
Yes, the first time you load the table, it would be nil. The dataTaskWithRequest completion block has to explicitly call self.tableview.reloadData(), to tell the table to update itself now that the network request has finished. Remember, dataTaskWithRequest runs asynchronously, meaning that it finishes after the table is already presented. So you have to tell the table to reload itself (and therefore call the UITableViewDataSource methods again).
So you probably want something like:
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) { data, response, error in
guard data != nil && error == nil else {
print(error)
return
}
do {
if let values = try NSJSONSerialization.JSONObjectWithData(data!, options: []) as? NSMutableArray {
dispatch_async(dispatch_get_main_queue()) {
self.value = values
print(values)
self.tableview.reloadData()
}
}
} catch let parseError {
print(parseError)
return
}
}
task.resume()
Note, before doing forced unwrapping of data with data!, I first guard to make sure it's not nil. Never use ! unless you've know it cannot possibly be nil.
In terms of why your UITableView methods are failing the first time they're called, it's because they're relying upon reachability.signed() or signed. But the real question is whether values is nil or not.
So, perhaps:
var values: NSMutableArray? // make this a standard optional, not an implicitly unwrapped one
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cellidentity") as UITableViewCell!
let x = cell.viewWithTag(2) as! UILabel // btw, using `UITableViewCell` subclasses are more elegant than using cryptic `tag` numbers
if let mainData = values?[indexPath.row] as? [String: String] {
x.text = mainData["name"]
} else {
print("No people")
x.text = "(retrieving data)" // perhaps you want to tell the user that the request is in progress
}
return cell
}
// if `values` is `nil`, return `1`, otherwise returns `values.count`
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return values?.count ?? 1
}
Your code was configured to return one cell if the data was not available, so I've repeated that here, but generally I return zero rows if there's no data. If you do that, it simplifies cellForRowAtIndexPath even more, as it doesn't have to even worry about the "no data" condition at all. But that's up to you.
Now, I've made some assumptions above (e.g. that mainData was really a dictionary given that you are subscripting it with a string "name"). But less important than these little details is the big picture, namely that one should scrupulously avoid using ! forced unwrapping or using implicitly unwrapped optionals unless you know with absolute certainty that the underlying optional can never be nil.

NSFetchedResultsController Sort Descriptor for Last Character Of String

How do you set a NSSortDescriptor which will sort by an attribute (but the last character of it?)
For example, if I have the following barcodes...
0000000005353
0000000000224
0000000433355
It should sort using last character, in asc or desc order. So like 3,4,5 in this example. Which would create section headers 3,4,5.
The current code I have gives me an error, sayings the "fetched object at index 7 has an out of order section name '9'. Objects must be sorted by section name. Which tells me I messed up the sort. To understand more please look at the code as I'm using transient properties on the core data model.
The idea is that "numberendsection", should sort from the end of the number as I described previously.
The other two sorts I describe work perfectly right now.
Inventory+CoreDataProperties.swift
import Foundation
import CoreData
extension Inventory {
#NSManaged var addCount: NSNumber?
#NSManaged var barcode: 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?.uppercaseString
}
//This is used for 1,2,3 ordering... (using front of barcode)
var numbersection: String? {
let characters = barcode!.characters.map { String($0) }
return characters.first?.uppercaseString
}
//This is used for 0000000123 ordering...(uses back number of barcode)
var numberendsection: String? {
let characters = barcode!.characters.map { String($0) }
return characters.last?.uppercaseString
}
}
InventoryController.swift - (showing only relevant part)
import UIKit
import CoreData
import Foundation
class InventoryController: UIViewController, UISearchBarDelegate, UITableViewDataSource, UITableViewDelegate, NSFetchedResultsControllerDelegate {
//Create fetchedResultsController to handle Inventory Core Data Operations
lazy var fetchedResultsController: NSFetchedResultsController = {
return self.setFetchedResultsController()
}()
func setFetchedResultsController() -> NSFetchedResultsController{
let inventoryFetchRequest = NSFetchRequest(entityName: "Inventory")
var primarySortDescriptor = NSSortDescriptor(key: "name", ascending: true)//by default assume name.
if(g_appSettings[0].indextype=="numberfront"){
primarySortDescriptor = NSSortDescriptor(key: "barcode", ascending: true)
}else if(g_appSettings[0].indextype=="numberback"){
primarySortDescriptor = NSSortDescriptor(key: "barcode", ascending: true)
}
//let secondarySortDescriptor = NSSortDescriptor(key: "barcode", ascending: true)
inventoryFetchRequest.sortDescriptors = [primarySortDescriptor]
let storefilter = g_appSettings[0].selectedStore!
let predicate = NSPredicate(format: "store = %#", storefilter) //This will ensure correct data relating to store is showing
inventoryFetchRequest.predicate = predicate
//default assume letter section
var frc = NSFetchedResultsController(
fetchRequest: inventoryFetchRequest,
managedObjectContext: self.moc,
sectionNameKeyPath: "lettersection",
cacheName: nil)
if(g_appSettings[0].indextype=="numberfront"){
frc = NSFetchedResultsController(
fetchRequest: inventoryFetchRequest,
managedObjectContext: self.moc,
sectionNameKeyPath: "numbersection",
cacheName: nil)
}else if(g_appSettings[0].indextype=="numberback"){
frc = NSFetchedResultsController(
fetchRequest: inventoryFetchRequest,
managedObjectContext: self.moc,
sectionNameKeyPath: "numberendsection",
cacheName: nil)
}
frc.delegate = self
return frc
}
Entity Diagram
Entity + Core Data Screenshot
Screenshot of Error and Code where it occurs
Inventory.swift
** Inventory.swift Entire File **
import UIKit
import CoreData
import Foundation
class InventoryController: UIViewController, UISearchBarDelegate, UITableViewDataSource, UITableViewDelegate, NSFetchedResultsControllerDelegate {
//Create fetchedResultsController to handle Inventory Core Data Operations
lazy var fetchedResultsController: NSFetchedResultsController = {
return self.setFetchedResultsController()
}()
func setFetchedResultsController() -> NSFetchedResultsController{
let inventoryFetchRequest = NSFetchRequest(entityName: "Inventory")
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: "barcode", ascending: true)
}
print("set primarySortDescriptor")
//let secondarySortDescriptor = NSSortDescriptor(key: "barcode", ascending: true)
inventoryFetchRequest.sortDescriptors = [primarySortDescriptor]
print("set sort descriptors to fetch request")
var storefilter : Store? = nil
if(g_appSettings[0].selectedStore != nil){
storefilter = g_appSettings[0].selectedStore
let predicate = NSPredicate(format: "store = %#", storefilter!) //This will ensure correct data relating to store is showing
inventoryFetchRequest.predicate = predicate
}
//default assume letter section
var frc = NSFetchedResultsController(
fetchRequest: inventoryFetchRequest,
managedObjectContext: self.moc,
sectionNameKeyPath: "lettersection",
cacheName: nil)
if(g_appSettings[0].indextype=="numberfront"){
frc = NSFetchedResultsController(
fetchRequest: inventoryFetchRequest,
managedObjectContext: self.moc,
sectionNameKeyPath: "numbersection",
cacheName: nil)
}else if(g_appSettings[0].indextype=="numberback"){
frc = NSFetchedResultsController(
fetchRequest: inventoryFetchRequest,
managedObjectContext: self.moc,
sectionNameKeyPath: "numbersection",
cacheName: nil)
}
print("set the frc")
frc.delegate = self
return frc
}
#IBOutlet weak var searchBar: UISearchBar!
#IBOutlet weak var inventoryTable: UITableView!
var moc = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext //convinience variable to access managed object context
// 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 tempInventory = NSEntityDescription.insertNewObjectForEntityForName("Inventory", inManagedObjectContext: 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 = "\(number)00000000\(number)"
tempInventory.currentCount = 0
tempInventory.id = number
tempInventory.imageLargePath = "http://website.tech//uploads/inventory/7d3fe5bfad38a3545e80c73c1453e380.png"
tempInventory.imageSmallPath = "http://website.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 aRandomInt = Int.random(0...2)
tempInventory.setValue(g_storeList[aRandomInt], forKey: "store") //assigns inventory to one of the stores we created.
return tempInventory
}
func createStoreDummyData(number:Int) -> Store{
let tempStore = NSEntityDescription.insertNewObjectForEntityForName("Store", inManagedObjectContext: moc) as! Store
tempStore.address = "100\(number) lane, Miami, FL"
tempStore.email = "store\(number)#centraltire.com"
tempStore.id = number
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()
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(entityName: "Inventory")
let storeFetchRequest = NSFetchRequest(entityName: "Store")
do {
let storeRecords = try moc.executeFetchRequest(storeFetchRequest) as? [Store]
//Maybe sort descriptor here? But how to organize into sectioned array?
if(storeRecords!.count<=0){
g_demoMode = true
print("No store entities found. Demo mode = True. Creating default store entities...")
var store : Store //define variable as Store type
for index in 1...3 {
store = createStoreDummyData(index)
g_storeList.append(store)
}
//save changes for the stores we added
do {
try moc.save()
print("saved to entity")
}catch{
fatalError("Failure to save context: \(error)")
}
}
let inventoryRecords = try moc.executeFetchRequest(inventoryFetchRequest) as? [Inventory]
//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...")
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(realIndex)
g_inventoryItems.append(entity)
}
//save changes for inventory we added
do {
try moc.save()
print("saved to entity")
}catch{
fatalError("Failure to save context: \(error)")
}
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.
//Look at the selected Store & Use the LIST of Inventory Under it.
//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 prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// 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.destinationViewController as! InventoryItemController
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 sectionIndexTitlesForTableView(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, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("InventoryTableCell", forIndexPath: indexPath) as! InventoryTableViewCell
let inventory = fetchedResultsController.objectAtIndexPath(indexPath) as! Inventory
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 numberOfSectionsInTableView(tableView: UITableView) -> Int {
if let sections = fetchedResultsController.sections {
return sections.count
}
return 0
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
//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, cellForRowAtIndexPath: indexPath) as? InventoryTableViewCell
self.performSegueWithIdentifier("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) {
print("text is changing")
}
func searchBarCancelButtonClicked(searchBar: UISearchBar) {
print("ended by cancel")
searchBar.text = ""
searchBar.resignFirstResponder()
}
func searchBarSearchButtonClicked(searchBar: UISearchBar) {
print("ended by search")
searchBar.resignFirstResponder()
}
func searchBarTextDidEndEditing(searchBar: UISearchBar) {
print("ended by end editing")
searchBar.resignFirstResponder()
}
#IBAction func unwindBackToInventory(segue: UIStoryboardSegue) {
print("unwind attempt")
let barcode = (segue.sourceViewController as? ScannerViewController)?.barcode
searchBar.text = barcode!
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: Range<Int> ) -> Int
{
var offset = 0
if range.startIndex < 0 // allow negative ranges
{
offset = abs(range.startIndex)
}
let mini = UInt32(range.startIndex + offset)
let maxi = UInt32(range.endIndex + offset)
return Int(mini + arc4random_uniform(maxi - mini)) - offset
}
}
NOTE::
I've cleared phone database also, just in case it was old database by deleting the app (holding down till it wiggles and deleting).
When your persistent store for Core Data is stored in SQLite (which I am assuming here otherwise the other answers would have worked already) you can't use computed properties or transient properties.
However, you can alter your data model so that you are storing the last digit of that bar code in its own property (known as denormalizing) and then sort on that new property. That is the right answer.
You can also do a secondary sort after you have done a fetch. However that means that you are holding a sorted array outside of the NSFetchedResultsController and you will then need to maintain the order of that array as you receive delegate callbacks from the NSFetchedResultsController. This is the second best answer.
If you can change the data model, then add a sort property. Otherwise your view controller code will be more complex because of the second sort.
You can add a comparator to your NSSortDescriptor
example
NSSortDescriptor *sortStates = [NSSortDescriptor sortDescriptorWithKey:#"barcode"
ascending:NO
comparator:^(id obj1, id obj2) {
[obj1 substringFromIndex:[obj1 length] - 1];
[obj2 substringFromIndex:[obj2 length] - 1];
return [obj1 compare: obj2])
}];
I think that you can use transient property in order to achieve what you want:
In order for it to work properly you have to provide implementation of this property in Inventory class.
var lastCharacter: String? {
let characters = barcode!.characters.map { String($0) }
return characters.last?.uppercaseString
}
Having 'lastCharacter' property set up correctly you can create sort descriptor that will allow you to achieve what you want:
NSSortDescriptor(key: "lastCharacter", ascending: true)
So it turns out that my method of trying to sort on a transient property does not work with NSSortDescriptors, the value has to be a real persisted one in the database.
Therefore, my solution was to create a new variable called barcodeReverse in the entity and at the time I enter data into the database for the barcode I also enter a reversed version using this code.
String(tempInventory.barcode!.characters.reverse())
tempInventory is an instance of my coreData class, and barcode a property on it. Simply just use characters.reverse() on the string.
Then you simply do the following:
primarySortDescriptor = NSSortDescriptor(key: "barcodeReverse", ascending: true)
and set frc like so...
frc = NSFetchedResultsController(
fetchRequest: inventoryFetchRequest,
managedObjectContext: self.moc,
sectionNameKeyPath: "numberendsection",
cacheName: nil)
and finally the inventory extension should look like this.
//This is used for 0000000123 ordering...(uses back number of barcode)
var numberendsection: String? {
let characters = barcodeReverse!.characters.map { String($0) }
return characters.first?.uppercaseString
}
This will create the sections and order correctly using the last digit of the barcode.

How to get data from CloudKit before loading TableData

I'm writing an app in Swift where the first scene has a TableView, I have it setup to display the title and it works fine, I also have it setup to count occurrences in a CloudKit database(or whatever its called) but it performs the count in async so the table defaults to show 0 in the detail pane.
I need to know how to make the app wait before it sets the value for the right detail until the count is completed or how to change them afterwards.
I have attached the code I used to perform the count etc, if I am doing this wrong or inefficiently please let me know
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.hidesBackButton = true;
self.textArray.addObject("Link 300")
self.textArray.addObject("Link 410")
self.textArray.addObject("Link 510")
let container = CKContainer.defaultContainer()
let publicData = container.publicCloudDatabase
let query = CKQuery(recordType: "Inventory", predicate: NSPredicate(format: "TRUEPREDICATE", argumentArray: nil))
publicData.performQuery(query, inZoneWithID: nil){results, error in
if error == nil {
for res in results {
let record: CKRecord = res as! CKRecord
if(record.objectForKey(("TrackerModel")) as! String == "Link 300"){
self.count300 = self.count300++
}else if(record.objectForKey(("TrackerModel")) as! String == "Link 410"){
self.count410 = self.count410++
}else if(record.objectForKey(("TrackerModel")) as! String == "Link 510"){
self.count510 = self.count510++
}
}
}else{
println(error)
}
}
self.detailArray.addObject(self.count300.description)
self.detailArray.addObject(self.count410.description)
self.detailArray.addObject(self.count510.description)
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.textArray.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) ->UITableViewCell {
var cell: UITableViewCell = self.tableView.dequeueReusableCellWithIdentifier("cell") as! UITableViewCell
cell.textLabel?.text = self.textArray.objectAtIndex(indexPath.row) as? String
cell.detailTextLabel?.text = self.detailArray.objectAtIndex(indexPath.row) as? String
return cell
}
Many thanks - Robbie
The closure associated with the performQuery will complete asynchronously - that is after viewDidLoad has finished. You need to make sure that you reload your table view once the query has completed and you have the data. You also have a problem because you are updating your totals outside the closure - this code will also execute before the data has loaded.
Finally, make sure that any update to the UI (such as reloading the table view) is dispatched on the main queue
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.hidesBackButton = true;
self.textArray.addObject("Link 300")
self.textArray.addObject("Link 410")
self.textArray.addObject("Link 510")
let container = CKContainer.defaultContainer()
let publicData = container.publicCloudDatabase
let query = CKQuery(recordType: "Inventory", predicate: NSPredicate(format: "TRUEPREDICATE", argumentArray: nil))
publicData.performQuery(query, inZoneWithID: nil){results, error in
if error == nil {
for res in results {
let record: CKRecord = res as! CKRecord
if(record.objectForKey(("TrackerModel")) as! String == "Link 300"){
self.count300++
}else if(record.objectForKey(("TrackerModel")) as! String == "Link 410"){
self.count410++
}else if(record.objectForKey(("TrackerModel")) as! String == "Link 510"){
self.count510++
}
}
self.detailArray.addObject(self.count300.description)
self.detailArray.addObject(self.count410.description)
self.detailArray.addObject(self.count510.description)
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.tableView.reloadData()
})
}else{
println(error)
}
}
}

Resources