Swift3, CloudKit and UITableView returns blank table - ios

I am having a small mental block here, I am pretty comfortable with Core Data and decided to have a delve into CloudKit for some of my Apps, however whilst the upload side was fairly basic, I am having trouble populating a simple table view.
The CKRecord is Activity and the field I would like to display is name. The print function print(actName) returns 7 times showing all the records have been counted but the table is blank and no errors.
I am sure this is something simple and i can't see the wood for the trees, so I am happy for a point in the right direction please.
Cheers
import UIKit
import CloudKit
class TableViewController: UITableViewController {
var activities = [Activity]()
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("counted records")
return activities.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let active = activities[indexPath.row]
if let actName = active.name {
cell.textLabel?.text = actName
print(actName)
}
return cell
}

I seem to have sorted it out, user3069232, I don't think there was/is a great latency problem as the updating code was instantaneous with CloudKit Desktop. Nirav I think you were right it boiled down to not storing and then reloading. I have modified my original code as I think 'Activity' was causing problems too, the script below works well, thanks for the point in the right direction guys.
import UIKit
import CloudKit
class CoursesVC: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var coursesTable: UITableView!
var coursesArray: Array<CKRecord> = []
var selectedCourseIndex: Int!
override func viewDidLoad() {
super.viewDidLoad()
coursesTable.delegate = self
coursesTable.dataSource = self
fetchCourses()
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return coursesArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "courseCell", for: indexPath)
let courseRecord: CKRecord = coursesArray[indexPath.row]
cell.textLabel?.text = courseRecord.value(forKey: "courseVenue") as? String
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "dd MMMM yyyy"
let CSDate = dateFormatter.string(from: courseRecord.value(forKey: "courseDate") as! Date)
// let CSDetail = courseRecord.value(forKey: "courseDetail") as? String
cell.detailTextLabel?.text = CSDate
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 40.0
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
selectedCourseIndex = indexPath.row
performSegue(withIdentifier: "showMapDetail", sender: self)
}
func fetchCourses() {
let container = CKContainer.default()
let publicDatabase = container.publicCloudDatabase
let predicate = NSPredicate(format: "TRUEPREDICATE")
let query = CKQuery(recordType: "Courses", predicate: predicate)
query.sortDescriptors = [NSSortDescriptor(key: "courseDate", ascending: true)]
publicDatabase.perform(query, inZoneWith: nil) { (results, error) -> Void in
if error != nil {
print("error fetch notes \(error)")
} else {
print("Success")
for result in results! {
self.coursesArray.append(result )
}
OperationQueue.main.addOperation({ () -> Void in
self.coursesTable.reloadData()
self.coursesTable.isHidden = false
})
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}

Related

Using a completion handler in another class/extension

I have a class, LocationViewController, which needs to implement a TableView. I have a function getParsedTestingLocation() which uses a completion handler from another function to get some data.
class LocationViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
}
func getParsedTestingLocations(completion: #escaping ([TestingLocation]?, Error?) -> (Void)) {
getTestingLocationsJSON(completion: { testLocationsJSON, error in
if let testLocationsJSON = testLocationsJSON {
let testLocationsData = Data(testLocationsJSON.utf8)
let decoder = JSONDecoder()
do {
let testLocations = try decoder.decode([TestingLocation].self, from: testLocationsData)
completion(testLocations, nil)
} catch {
print(error)
}
}
})
}
}
I want to use the value testLocations within getParsedTestingLocations() in an external extension in this file. Here are the extensions I have:
extension LocationViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("you tapped me!")
}
}
extension LocationViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = "empty cell"
return cell
}
}
Within all 3 tableView() functions I want to get the values stored in testLocations in the completion handler in these functions. How could I do this?
Actually you don't need a completion handler. Reload the table view inside the completion closure of the API call
class LocationViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
var locationData = [TestingLocation]()
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
getParsedTestingLocations()
}
func getParsedTestingLocations() {
getTestingLocationsJSON(completion: { testLocationsJSON, error in
if let testLocationsJSON = testLocationsJSON {
let testLocationsData = Data(testLocationsJSON.utf8)
let decoder = JSONDecoder()
do {
self.locationData = try decoder.decode([TestingLocation].self, from: testLocationsData)
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch {
print(error)
}
}
})
}
}
extension LocationViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return locationData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
let location = locationData[indexPath.row]
cell.textLabel?.text = location.whatEverStringYouWantToDisplay
return cell
}
}
Replace whatEverStringYouWantToDisplay with the real struct member name.
You don't need a completionHandler in getParsedTestingLocations in this case as the function already calls a function which has completionHandler. Just use a variable
class LocationViewController: UIViewController {
private lazy var locationArr = [TestingLocation]()
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
getParsedTestingLocations()
}
func getParsedTestingLocations() {
getTestingLocationsJSON(completion: { testLocationsJSON, error in
if let testLocationsJSON = testLocationsJSON {
let testLocationsData = Data(testLocationsJSON.utf8)
let decoder = JSONDecoder()
do {
let testLocations = try decoder.decode([TestingLocation].self, from: testLocationsData)
self.locationArr = testLocations
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch let error {
//Show Alert
}
}
})
}
}
extension LocationViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return locationArr.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = locationArr[indexPath.row].variable
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("you tapped me! \(locationArr[indexPath.row])")
}
}

Populate UITableView from dictionary

I'm trying to make a table from the dictionary I have, but got an error. Since I just learning Swift I don't understand how to fix it. Could you help me please? Thank you for advance.
import UIKit
class TableViewController: UITableViewController {
var DictionaryList = dictionary() {
didSet {
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
GIDSignIn.sharedInstance()?.presentingViewController = self
GIDSignIn.sharedInstance()?.restorePreviousSignIn()
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return DictionaryList.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let entry = DictionaryList[indexPath.row]
var key = Array<String>(self.dictionary.keys)[indexPath.row]
var value = Array<String>(self.dictionary.values)[indexPath.row]
cell.textLabel?.text = key
cell.detailTextLabel?.text = value
return cell
}
var dictionary: Dictionary = [
"line1:" : "description",
"line2:" : "description",
"line3:" : "description",
]
}
You can try like this:
class TableViewController: UITableViewController {
var DictionaryList: Dictionary<String, String> = [:] {
didSet {
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
self.DictionaryList = [
"line1:":"description",
"line2:":"description",
"line3:":"description",
]
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return DictionaryList.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell")
var key = Array<String>(self.DictionaryList.keys)[indexPath.row]
var value = Array<String>(self.DictionaryList.values)[indexPath.row]
cell?.textLabel?.text = key
cell?.detailTextLabel?.text = value
return cell!
}
}
This is never going to work. Dictionaries are unordered - there is no guarantee by a dictionary that keys is going to preserve the ordering each time it is called. There isn't even a guarantee that values is going to return the values in the same order that it returns the keys.
You will need to re-design your model to turn the dictionary into an ordered collection of some kind.

Grouping Data in TableView from Core Data in Swift 4

I am having trouble finding out how to group my sections by a property in my Core Data database. This is what my DB looks like here. I am trying to group my tableView by the dueDate property. I have loaded up my Attributes in an array and that is how they are displayed. I plan on customizing the headers as well, so I would like to use the standard tableView methods. Here is the code from my ViewController.
import UIKit
import CoreData
class MainTableViewController: UITableViewController {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
var taskArray = [Task]()
override func viewDidAppear(_ animated: Bool) {
loadData()
}
// MARK: - Table view functions
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return taskArray.count
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return "Date"
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 65.00
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "taskCell", for: indexPath) as! TaskCell
cell.nameLabel.text = taskArray[indexPath.row].name ?? "Add Items"
if taskArray[indexPath.row].dueTime == nil {
cell.timeLabel.text = ""
} else {
let timeFormatter = DateFormatter()
timeFormatter.timeStyle = .short
cell.timeLabel.text = timeFormatter.string(from: taskArray[indexPath.row].dueTime!)
}
return cell
}
// MARK: Add New Task
#IBAction func addButtonPressed(_ sender: Any) {
performSegue(withIdentifier: "newTaskSegue", sender: self)
}
// MARK: Save & Load Data
func saveData() {
do {
try context.save()
} catch {
print("Error saving context \(error)")
}
tableView.reloadData()
}
func loadData() {
let request : NSFetchRequest<Task> = Task.fetchRequest()
let sort = NSSortDescriptor(key: "dueDate", ascending: false)
let sort2 = NSSortDescriptor(key: "dueTime", ascending: false)
request.sortDescriptors = [sort, sort2]
do {
taskArray = try context.fetch(request)
} catch {
print("Error loading data \(error)")
}
tableView.reloadData()
}
}
Any help would be much appreciated. Thanks!
You can easily group your data using NSFetchedResultsController. One parameter in the instantiation of NSFetchedResultsController specifically allows you to group your results into sections by passing the keyPath of an attribute that constitutes the predicate for section grouping.
Apple's documentation explains this pretty clearly, with example code:
override func numberOfSections(in tableView: UITableView) -> Int {
if let frc = <#Fetched results controller#> {
return frc.sections!.count
}
return 0
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
guard let sections = self.<#Fetched results controller#>?.sections else {
fatalError("No sections in fetchedResultsController")
}
let sectionInfo = sections[section]
return sectionInfo.numberOfObjects
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = <#Get the cell#>
guard let object = self.<#Fetched results controller#>?.object(at: indexPath) else {
fatalError("Attempt to configure cell without a managed object")
}
// Configure the cell with data from the managed object.
return cell
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
guard let sectionInfo = <#Fetched results controller#>?.sections?[section] else {
return nil
}
return sectionInfo.name
}
override func sectionIndexTitles(for tableView: UITableView) -> [String]? {
return <#Fetched results controller#>?.sectionIndexTitles
}
override func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String, at index: Int) -> Int {
guard let result = <#Fetched results controller#>?.section(forSectionIndexTitle: title, at: index) else {
fatalError("Unable to locate section for \(title) at index: \(index)")
}
return result
}
It is generally a Good Idea(tm) to use an NSFetchedResultsController when dealing with CoreData and UITableView or UICollectionView as you get handy notifications (through a NSFetchedResultsControllerDelegate) when your data changes that allow you to insert or remove cells from your displayed view.

Swift 3 Writing Firebase Data to TableView

I am fetching all user id's from my Firebase database. When I execute the program I can see snapshot of all user id's on console via code in line 26. But the code is not writing to table cells. I done this with tutorial. Everything is same with video. But it does not work for me Where is the problem ?
class ChatInfo: UITableViewController {
let cellId = "cellId"
var users = [User] ()
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Geri", style: .plain, target:self, action: #selector(handleCancel))
fetchUser()
}
func handleCancel() {
dismiss(animated: true, completion: nil)
}
func fetchUser() {
Database.database().reference().child("locations").observe(.childAdded, with: {(snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
let user = User()
user.userId = dictionary["userId"] as! String
print(user.userId) // IT PRINTS ALL USERS TO CONSOLE
self.users.append(user)
DispatchQueue.main.async(execute: {
self.tableView.reloadData()
})
}
} , withCancel: nil)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func numberOfSections(in tableView: UITableView) -> Int {
return users.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .subtitle, reuseIdentifier: cellId)
let user = users[indexPath.row]
cell.detailTextLabel?.text = user.userId
return cell
}
}
You are overriding the wrong method
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return users.count
}
And design the cell style in Interface Builder and use this method in cellForRowAt
let cell = tableView.dequeueReusableCell(withCellIdentifier: cellId, for: indexPath)
As per your requirement actual way for fetching records of tableview cell is this:
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return users.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withCellIdentifier: cellId, for: indexPath)
let user = users[indexPath.row]
cell.detailTextLabel?.text = user.userId
return cell
}

Core data query returns correct values but table cell is empty

I am fetching data from Core Data, the returned data is correct when printed in the console. But the tableview always returns empty cells. The object exists, numberOfRowsinSection is returned with the proper count as well.
Been looking for hours, I hope its not a typo. Any help is appreciated, the code is below. I tried both valueForKey and valueForKeypath with no success
Thanks!
import UIKit
import CoreData
class HistoryViewController: UITableViewController{
#IBOutlet var historyTableView: UITableView!
var activitiesHistory: [NSManagedObject] = []
override func viewDidLoad() {
super.viewDidLoad()
title = "Past Workouts"
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "historyCell")
let entity = NSEntityDescription.entity(forEntityName: "Activity", in: CoreDataStack.context)
let fetchRequest: NSFetchRequest<Activity> = Activity.fetchRequest()
fetchRequest.entity = entity
//let sortDescriptor = NSSortDescriptor(key: #keyPath(Activity.timestamp), ascending: true)
//fetchRequest.sortDescriptors = [sortDescriptor]
do {
activitiesHistory = try CoreDataStack.context.fetch(fetchRequest)
//print(activitiesHistory)
} catch let error{
//handle error
print(error)
}
}
override func tableView(_ tableView: UITableView,
numberOfRowsInSection section: Int) -> Int {
return activitiesHistory.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let workout = activitiesHistory[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "historyCell", for: indexPath)
print(workout.value(forKey: "timestamp"))
cell.textLabel?.text = workout.value(forKey: "timestamp")as? String
//cell.textLabel?.text = "test"
return cell
}
}
This is worked for me.
var activities: [Activity] = []
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
addData()
let app = UIApplication.shared.delegate as! AppDelegate
let context = app.persistentContainer.viewContext
do {
activities = try context.fetch(Activity.fetchRequest())
}
catch {
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.activities.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
cell.textLabel?.text = self.activities[indexPath.row].timestamp
return cell
}
Result
Thanks for all your help! I found the solution:
The problem was that the data returned from the DB as of type "Date". The cell would simply show blank space and Xcode would show a warning or error. When I used DateFormatter() to convert the type to a formatted string, the cells displayed the data. Code below:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let workout = activities[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "historyCell", for: indexPath)
let time = workout.value(forKey: "timestamp")
let formatter = DateFormatter()
formatter.dateFormat = "dd-MM"
let formatteddate = formatter.string(from: time as! Date)
cell.textLabel?.text = formatteddate
return cell
}

Resources