I am new to Xcode and swift and learning. I am trying to build an app with SQLite database that is saved within the app. The database has five fields viz; id, cdcommand, cdtable, cdshortcut, cddescription; My tableview is loading completely fine. Now I am trying to add sections to tableview. I want to divide data into sections from database field named cdtable. I am struggling to get it right.
numberofrowsinSection is broken totally and so is the UITableView, cellForRowAt indexPath: IndexPath). Both shows weird results.
The only thing I managed to get it right somehow is numberOfSections & viewForHeaderInSection.
Can anyone please guide how to get this working? Below is the code I am using...
My code is shared below from 2 files. Thanks in advance.
File1.swift
class Shortcuts {
var id: Int
var cdcommand: String?
var cdtable: String?
var cdshortcut: String?
var cddescription: String?
init(id: Int, cdcommand: String?, cdtable: String?, cdshortcut: String?, cddescription: String?){
self.id = id
self.cdcommand = cdcommand
self.cdtable = cdtable
self.cdshortcut = cdshortcut
self.cddescription = cddescription
}
}
class tableNames {
var cdtable: String?
init(cdtable: String?){
self.cdtable = cdtable
}
}
File2.swift
import Foundation
import UIKit
import SQLite3
class shortcutCustomCell: UITableViewCell {
#IBOutlet weak var commandText: UILabel!
#IBOutlet weak var tableText: UILabel!
#IBOutlet weak var shortcutText: UILabel!
#IBOutlet weak var descText: UITextView!
}
class shortcutsController: UIViewController, UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate {
var db: OpaquePointer?
var shortcutList = [Shortcuts]()
var tablenameList = [tableNames]()
#IBOutlet weak var tableViewShortcuts: UITableView!
#IBOutlet weak var searchBar: UISearchBar!
#IBAction func unwindToPreviousViewController(_ sender: UIBarButtonItem) {
self.navigationController?.popViewController(animated: true)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let appearance = UINavigationBarAppearance()
appearance.titleTextAttributes = [.foregroundColor: UIColor.white]
appearance.largeTitleTextAttributes = [.foregroundColor: UIColor.white]
appearance.backgroundColor = UIColor(red:0.79, green:0.37, blue:0.31, alpha:1.00)
navigationItem.standardAppearance = appearance
navigationItem.scrollEdgeAppearance = appearance
self.navigationController?.interactivePopGestureRecognizer?.isEnabled = true
self.navigationController?.interactivePopGestureRecognizer?.delegate = nil
//Search Begin
searchBar.delegate = self
searchBar.searchTextField.backgroundColor = .white
searchBar.barTintColor = UIColor.darkGray//(red: 0.79, green: 0.37, blue: 0.31, alpha: 1.00)
searchBar.searchTextField.textColor = .darkGray
//Search End
//SQLite Connection Begin
let fileMgr = FileManager.default
let dbPath = URL(fileURLWithPath: Bundle.main.resourcePath ?? "").appendingPathComponent("database.db")
let pathString = dbPath.path
let success = fileMgr.fileExists(atPath: pathString)
if !success {
print("Cannot locate database file '\(dbPath)'.")
}
if !(sqlite3_open(dbPath.absoluteString, &db) == SQLITE_OK) {
print("An error has occured.")
}
readValues()
queryAlltableNames()
//SQLite Connection End
}
func readValues(){
shortcutList.removeAll()
let queryString = "SELECT * FROM main ORDER BY cdtable, cdcommand ASC"
var stmt:OpaquePointer?
if sqlite3_prepare(db, queryString, -1, &stmt, nil) != SQLITE_OK{
let errmsg = String(cString: sqlite3_errmsg(db)!)
print("error preparing insert: \(errmsg)")
return
}
while(sqlite3_step(stmt) == SQLITE_ROW){
let id = sqlite3_column_int(stmt, 0)
let cdcommand = String(cString: sqlite3_column_text(stmt, 1))
let cdtable = String(cString: sqlite3_column_text(stmt, 2))
let cdshortcut = String(cString: sqlite3_column_text(stmt, 3))
let cddescription = String(cString: sqlite3_column_text(stmt, 4))
shortcutList.append(Shortcuts(id: Int(id), cdcommand: String(describing: cdcommand), cdtable: String(describing: cdtable), cdshortcut: String(describing: cdshortcut), cddescription: String(describing: cddescription)))
}
self.tableViewShortcuts.reloadData()
}
func queryAlltableNames() {
let queryTableNames = "SELECT DISTINCT cdtable FROM main ORDER BY cdtable ASC"
var stmt:OpaquePointer?
if sqlite3_prepare(db, queryTableNames, -1, &stmt, nil) != SQLITE_OK{
let errmsg = String(cString: sqlite3_errmsg(db)!)
print("error preparing insert: \(errmsg)")
return
}
while(sqlite3_step(stmt) == SQLITE_ROW){
let cdtable = String(cString: sqlite3_column_text(stmt, 0))
tablenameList.append(tableNames(cdtable: String(describing: cdtable)))
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return shortcutList[section].cdtable!.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellShortcuts", for: indexPath) as! shortcutCustomCell
let shortcut: Shortcuts
shortcut = shortcutList[indexPath.row]
cell.commandText.text = shortcut.cdcommand
cell.shortcutText.text = shortcut.cdshortcut
cell.descText.text = shortcut.cddescription
return cell
}
func numberOfSections(in tableView: UITableView) -> Int {
return tablenameList.count
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let view = UIView(frame: CGRect(x: 0, y: 0, width: tableView.frame.width, height: 30))
view.backgroundColor = UIColor.black
let lbl = UILabel(frame: CGRect(x: 15, y: 0, width: view.frame.width - 15, height: 30))
lbl.font = UIFont.boldSystemFont(ofSize: 16)
lbl.textColor = UIColor.white
lbl.text = tablenameList[section].cdtable
view.addSubview(lbl)
return view
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 30
}
}
I’d suggest a model structure to capture the sections of the table view. For example:
class Section<Key, Element> {
let name: Key?
let elements: [Element]
init(name: Key?, elements: [Element]) {
self.name = name
self.elements = elements
}
}
Then you can write a routine to iterate through your sorted array and grouping it:
extension Sequence {
/// Group sorted array.
///
/// This assumes the array is already sorted by whatever you will be grouping it.
///
/// let input = [Foo(bar: "a", baz: "x"), Foo(bar: "a", baz: "y"), Foo(bar: "b", baz: "z")]
/// let result = input.grouped { $0.bar }
/// // [
/// // ("a", [Foo(bar: "a", baz: "x"), Foo(bar: "a", baz: "y")]),
/// // ("b", [Foo(bar: "b", baz: "z")])
/// // ]
///
/// - Parameter groupedBy: Closure to dictate how it will be grouped.
///
/// - Returns: An array of tuples, one entry for every new occurenc of the `groupedBy` result.
func grouped<Key: Equatable>(by groupedBy: (Element) -> Key) -> [(Key, [Element])] {
var results: [(Key, [Element])] = []
var previousKey: Key?
var previousElements: [Element] = []
for element in self {
let key = groupedBy(element)
if key != previousKey {
if let previousKey = previousKey {
results.append((previousKey, previousElements))
}
previousKey = key
previousElements = []
}
previousElements.append(element)
}
if let previousKey = previousKey {
results.append((previousKey, previousElements))
}
return results
}
}
Personally, just because your table is using cryptic column names doesn’t mean your Shortcuts should use them, too. I’d also remove the s from the end of Shortcuts because each object represents a single shortcut:
class Shortcut {
let id: Int
let command: String?
let table: String?
let shortcut: String?
let description: String?
let image: UIImage?
init(id: Int, command: String?, table: String?, shortcut: String?, description: String?, image: UIImage?) {
self.id = id
self.command = command
self.table = table
self.shortcut = shortcut
self.description = description
self.image = image
}
}
Then, your reading routine can read the results in, and call this grouped(by:) routine to populate the model structure for your grouped table. Thus, given:
var sections: [Section<String, Shortcut>] = []
You can populate it with:
sections = shortcuts.grouped { $0.table }
.map { Section(name: $0.0, elements: $0.1) }
And then your data source methods would look like:
func numberOfSections(in tableView: UITableView) -> Int {
return sections.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sections[section].elements.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let shortcut = sections[indexPath.section].elements[indexPath.row]
// configure your cell however you want
cell.textLabel?.text = shortcut.command
cell.detailTextLabel?.text = shortcut.shortcut
return cell
}
And get the title for the section like so:
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return sections[section].name
}
Then a database like so ...
... will yield a table like so:
So readValues might look like:
var sections: [Section<String, Shortcut>] = []
var shortcuts: [Shortcut] = []
func readValues() {
let queryString = "SELECT * FROM main ORDER BY cdtable, cdcommand"
var statement: OpaquePointer?
guard sqlite3_prepare(db, queryString, -1, &statement, nil) == SQLITE_OK else {
print("error preparing select: \(errorMessage())")
return
}
defer { sqlite3_finalize(statement) }
shortcuts = []
while sqlite3_step(statement) == SQLITE_ROW {
let id = Int(sqlite3_column_int(statement, 0))
let command = sqlite3_column_text(statement, 1).flatMap({ String(cString: $0) })
let table = sqlite3_column_text(statement, 2).flatMap({ String(cString: $0) })
let shortcut = sqlite3_column_text(statement, 3).flatMap({ String(cString: $0) })
let description = sqlite3_column_text(statement, 4).flatMap({ String(cString: $0) })
let count = Int(sqlite3_column_bytes(statement, 5))
var image: UIImage?
if count > 0, let bytes = sqlite3_column_blob(statement, 5) {
let data = Data(bytes: bytes, count: count)
image = UIImage(data: data)
}
shortcuts.append(Shortcut(id: id, command: command, table: table, shortcut: shortcut, description: description, image: image))
}
sections = shortcuts.grouped { $0.table }
.map { Section(name: $0.0, elements: $0.1) }
}
func errorMessage() -> String {
return sqlite3_errmsg(db)
.flatMap { String(cString: $0) } ?? "Unknown error"
}
A few other observations:
Make sure to sqlite3_finalize every successfully prepared statement.
You’re opening the database from the bundle. Generally you wouldn’t do that. But if you were, I might suggest opening it like so:
func openDatabase() -> Bool {
guard let dbPath = Bundle.main.url(forResource: "database", withExtension: "db") else {
print("Cannot locate database file.")
return false
}
guard sqlite3_open_v2(dbPath.path, &db, SQLITE_OPEN_READONLY, nil) == SQLITE_OK else {
print("An error has occurred.", errorMessage())
sqlite3_close(db)
return false
}
return true
}
The Bundle.main.url(forResource:withExtension:) will check for existence for you.
NB: I used the SQLITE_OPEN_READONLY as documents in the bundle are read-only.
Frankly, we don’t generally open from the bundle, as that’s read-only. We’d generally open from somewhere like the application support directory, and copy from the bundle if we couldn’t find it:
func openDatabase() -> Bool {
let applicationSupportURL = try! FileManager.default
.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
.appendingPathComponent("database.db")
if sqlite3_open_v2(applicationSupportURL.path, &db, SQLITE_OPEN_READWRITE, nil) == SQLITE_OK {
return true
}
// if we got here, we were unable to open database, so we'll copy it from the bundle
sqlite3_close(db)
guard let bundleURL = Bundle.main.url(forResource: "database", withExtension: "db") else {
print("Cannot locate database file.")
return false
}
do {
try FileManager.default.copyItem(at: bundleURL, to: applicationSupportURL)
} catch {
print(error)
return false
}
guard sqlite3_open_v2(applicationSupportURL.path, &db, SQLITE_OPEN_READWRITE, nil) == SQLITE_OK else {
print("An error has occurred.", errorMessage())
sqlite3_close(db)
return false
}
return true
}
Note in both of these alternatives, always close the database if the open failed. I know it seems strange, but see the sqlite3_open documentation, which is specific on this point.
I’d be wary about using * in SQL statements. The correctness of your code should not be contingent on order of the columns in the table. So, rather than:
let queryString = "SELECT * FROM main ORDER BY cdtable, cdcommand"
I’d instead recommend being explicit about the order of the columns:
let queryString = "SELECT id, cdcommand, cdtable, cdshortcut, cddescription FROM main ORDER BY cdtable, cdcommand"
Do I infer from the cd prefixes in your table that you’re dealing with a CoreData database? Having spent all this time talking about SQLite, if you’re using CoreData, I’d suggest staying within (especially if you plan on updating it later). Now, if you’ve given up on CoreData, that’s fine. But then I’d lose the cd prefixes.
I'm clueless as to what is wrong. My console doesn't give me any errors, my code seems fine but nothing is showing up. Could someone check my code, see why it doesn't want to work? My tableView is connected with its delegates and source. Not sure what is the problem.
Here is my code:
private let cellIdentifier = "cell"
private let apiURL = "api link"
class TableView: UITableViewController {
//TableView Outlet
#IBOutlet weak var LegTableView: UITableView!
//API Array
var legislatorArray = [congressClass]()
func getLegislators (fromSession session: NSURLSession) {
//Calling url
if let jsonData = NSURL(string: apiURL) {
// Requesting url
let task = session.dataTaskWithURL(jsonData) {(data, response, error) -> Void in
//Check for errors
if let error = error {print(error)
} else {
if let http = response as? NSHTTPURLResponse {
if http.statusCode == 200 {
//Getting data
if let data = data {
do {
let legislatorData = try NSJSONSerialization.JSONObjectWithData(data, options: .MutableContainers)
//Get API data
if let getData = legislatorData as? [NSObject:AnyObject],
findObject = getData["results"] as? [AnyObject]{
//Return data
for cellFound in findObject{
if let nextCell = cellFound["results"] as? [NSObject:AnyObject],
name = nextCell["first_name"] as? String,
lastName = nextCell["last_name"] as? String,
title = nextCell["title"] as? String,
partyRep = nextCell["party"] as? String,
position = nextCell ["position"] as? String,
id = nextCell ["bioguide_id"] as? String
{
//Add data to array
let addData = congressClass(name: name, lastName: lastName, title: title, party: partyRep, position: position, bioID: id)
self.legislatorArray.append(addData)
}
}//end cellFound
//Adding data to table
dispatch_async(dispatch_get_main_queue()) { () -> Void in
self.tableView.reloadData()
}
}
}
//end do
catch {print(error)}
}//end data
}//end statusCode
}//end http
}//else
}//end task
//Run code
task.resume()
}//end jsonData
}
override func viewDidLoad() {
super.viewDidLoad()
let sessionConfig = NSURLSessionConfiguration.defaultSessionConfiguration()
let urlSession = NSURLSession(configuration: sessionConfig)
getLegislators(fromSession: urlSession)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
//TableView Rows
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return legislatorArray.count
//return 5
}
//Cell Configuration
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! CellTableView
cell.lesName?.text = legislatorArray[indexPath.row].name + " " + legislatorArray[indexPath.row].lastName
cell.lesTitle?.text = legislatorArray[indexPath.row].title
cell.lesParty?.text = legislatorArray[indexPath.row].party
//These tests worked fine.. the tableView is working. But the data doesn't seem to pass.
//cell.lesName.text = "Name" + " " + "lastName"
//cell.lesTitle.text = "Title goes here"
//cell.lesParty.text = "D"
return cell
}
}
You're not reloading the tableView
The problem is in this piece of code
//-----------------------------
//New empty array for api data
var indexPath:[NSIndexPath] = []
//Adding data to new array
for i in 0..<self.legislatorArray.count{
let secondIndexPath = NSIndexPath(forRow: i, inSection: 0)
indexPath.append(secondIndexPath)
}
//Adding data to table
dispatch_async(dispatch_get_main_queue()) { () -> Void in
self.tableView.insertRowsAtIndexPaths(indexPath, withRowAnimation: .Left)
}
You don't need any of that. You can just reload the tableView as follows:
//Adding data to table
dispatch_async(dispatch_get_main_queue()) { () -> Void in
//You only need to reload it and that should do the trick
self.tableView.reloadData()
}
I know you said your tableView is connected to the delegate and dataSource but it's not showing in your code.
You conformed the ViewController to the correct protocols but you need something like this in your viewDidLoad.
self.tableView.deletage = self
self.tableView.dataSource = self
//I don't know if this was a typo but in your cellForRowAtIndexPath you are using CellTableView
let nibName = UINib(nibName: "CellTableView", bundle:nil)
self.tableView.registerNib(nibName, forCellReuseIdentifier: cellIdentifier)
I created an example of a better design for your implementation
This is for the WebService and your Custom Class
https://github.com/phantomon/Stackoverflow/blob/master/SO1/MyTableView/MyTableView/Models/WebServiceManager.swift
This is for the ViewController with your tableView
https://github.com/phantomon/Stackoverflow/blob/master/SO1/MyTableView/MyTableView/ViewController.swift
You just need to modify the UITableViewCell with your custom one.
And of course review your custom class data.
Tried so many times to find out what causes the fatal error. But, still can't figure it out. The first table (result table) causes this error when I try to refresh the table with pull. The second table (favoriteProductTableView) works perfect, so I didn't put any code about the second one. Wondering why. Thank you for your help.
var followArray = [String]()
var resultsNameArray = [String]()
var resultsImageFiles = [PFFile?]()
var resultsDetailsArray = [String]()
var resultsDetailsImageFiles = [PFFile?]()
var resultsObjectID = [String]()
var resultsTitle = [String]()
var personPriceArray = [String]()
var personQuantityArray = [String]()
var personOrderTypeArray = [String]()
var refresher:UIRefreshControl!
override func viewDidLoad() {
super.viewDidLoad()
favoriteProductTableView.hidden = true
refresher = UIRefreshControl()
refresher.tintColor = UIColor.blackColor()
refresher.addTarget(self, action: "refresh", forControlEvents: UIControlEvents.ValueChanged)
self.resultsTable.addSubview(refresher)
}
override func viewDidAppear(animated: Bool) {
refreshResults()
}
func refresh(){
refreshResults()
}
func refreshResults(){
switch(segmentedControl.selectedSegmentIndex){
case 0:
followArray.removeAll(keepCapacity: false)
resultsNameArray.removeAll(keepCapacity: false)
resultsImageFiles.removeAll(keepCapacity: false)
resultsDetailsArray.removeAll(keepCapacity: false)
resultsDetailsImageFiles.removeAll(keepCapacity: false)
resultsObjectID.removeAll(keepCapacity: false)
resultsTitle.removeAll(keepCapacity: false)
personPriceArray.removeAll(keepCapacity: false)
personQuantityArray.removeAll(keepCapacity: false)
personOrderTypeArray.removeAll(keepCapacity: false)
let followQuery = PFQuery(className: "follow")
followQuery.whereKey("user", equalTo: (PFUser.currentUser()!.username)!)
followQuery.whereKey("userToFollow", notEqualTo: (PFUser.currentUser()!.username)!)
followQuery.findObjectsInBackgroundWithBlock { (objects:[PFObject]?, error: NSError?) -> Void in
if error != nil {
}
for object in objects! {
self.followArray.append(object.objectForKey("userToFollow") as! String)
}
let query = PFQuery(className: "products")
query.whereKey("userName", containedIn: self.followArray)
query.findObjectsInBackgroundWithBlock { (catchobjects:[PFObject]?, error:NSError?) -> Void in
if error != nil {
}
for catchobject in catchobjects! {
if catchobject.objectForKey("selling_price") != nil {
self.personPriceArray.append(catchobject.objectForKey("selling_price") as! String)
self.personOrderTypeArray.append("Selling")
} else {
self.personPriceArray.append(catchobject.objectForKey("buying_price") as! String)
self.personOrderTypeArray.append("Buying")
}
self.personQuantityArray.append(catchobject.objectForKey("quantity") as! String)
self.resultsNameArray.append(catchobject.objectForKey("unique_username") as! String)
self.resultsImageFiles.append(catchobject.objectForKey("profile_picture") as? PFFile)
self.resultsDetailsArray.append(catchobject.objectForKey("details") as! String)
self.resultsDetailsImageFiles.append(catchobject.objectForKey("detailsImage") as? PFFile)
self.resultsTitle.append(catchobject.objectForKey("title") as! String)
self.resultsObjectID.append(catchobject.objectId!)
}
dispatch_async(dispatch_get_main_queue()) {
self.resultsTable.reloadData()
}
self.loadEmptyLabel(self.resultsTable)
}
self.refresher.endRefreshing()
}
break
case 1:
...
break
default:
break
}
}
func loadEmptyLabel(tableView: UITableView) {
let emptyLabel = UILabel(frame: CGRectMake(0, 0, UIScreen.mainScreen().bounds.width, UIScreen.mainScreen().bounds.height))
emptyLabel.textAlignment = NSTextAlignment.Center
emptyLabel.textColor = UIColor.blackColor()
emptyLabel.text = "No matched result found."
tableView.backgroundView = emptyLabel
tableView.separatorStyle = UITableViewCellSeparatorStyle.None
var resultCount = Int()
if tableView == resultsTable {
resultCount = resultsNameArray.count
} else {
resultCount = resultsTitleArray.count
}
if resultCount == 0 {
tableView.reloadData()
emptyLabel.hidden = false
} else {
emptyLabel.hidden = true
}
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
var numRow: Int = 0
switch(segmentedControl.selectedSegmentIndex){
case 0:
numRow = resultsNameArray.count
break
case 1:
numRow = resultsTitleArray.count
break
default:
break
}
return numRow
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if tableView == resultsTable {
let cell:favoritedTableViewCell = resultsTable.dequeueReusableCellWithIdentifier("Cell") as! favoritedTableViewCell
cell.profileLbl.text = self.resultsNameArray[indexPath.row]
cell.messageTxt.text = self.resultsDetailsArray[indexPath.row]
cell.priceLabel.text = "\(self.personOrderTypeArray[indexPath.row]) \(self.personQuantityArray[indexPath.row]) for $\(self.personPriceArray[indexPath.row])"
cell.titleLabel.text = self.resultsTitle[indexPath.row]
if resultsImageFiles[indexPath.row] != nil {
resultsImageFiles[indexPath.row]!.getDataInBackgroundWithBlock { (imageData:NSData?, error:NSError?) -> Void in
if error == nil{
let image = UIImage(data: imageData!)
cell.imgView.image = image
}
}
} else {
cell.imgView.image = UIImage(named: "Profile Picture")
}
if resultsDetailsImageFiles[indexPath.row] != nil{
resultsDetailsImageFiles[indexPath.row]?.getDataInBackgroundWithBlock({ (imageData:NSData?, error:NSError?) -> Void in
if error == nil{
let image = UIImage(data: imageData!)
cell.detailsImg.image = image
}
})
} else {
cell.detailsImg.image = UIImage(named: "Profile Picture")
}
return cell
} else {
....
}
}
Your numberOfRowsInSection function returns one of two array lengths based on segmentedControl.selectedSegmentIndex, whereas cellForRowAtIndexPath indexes the arrays based on the tableView being displayed. This doesn't look right, especially given your referencing `` which doesn't appear to be populated anywhere - should it just be resultsTitle?.
Also, you're calling self.resultsTable.reloadData() from a background thread. This is bad - it must be called from the main thread using:
dispatch_async(dispatch_get_main_queue()) {
self.resultsTable.reloadData()
}
Nevertheless, it's not clear why you've got this inside the loop either.
What I am trying to do is to have data from Parse be retrieved from columns by object order. All labels are connected to their respective outlets and all of the outputs retrieve their correct data.
When I run it and open a cell in the tableview it crashes and gives me Thread 1: EXC_BAD_INSTRUCTION (code=EXC>I386_INVOP, subcode=0x0) on this line: self.navBar.topItem?.title = output1 if I select the first cell, and then on this line: self.navBar.topItem?.title = output1b if I select the second cell.
Here is the full function:
firstObject is grabbing the first object in the "eventsdetail" column
secondObject is grabbing the second object in the "eventsdetail" column
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
var query = PFQuery(className: "eventsdetail")
let runkey = query.orderByAscending("ID")
runkey.findObjectsInBackgroundWithBlock {
(objects: [PFObject]?, error : NSError?) -> Void in
if error == nil {
if let objects = objects as [PFObject]! {
for object in objects {
var firstObject = objects[0]
var secondObject = objects[1]
let output1 = firstObject.objectForKey("navTitle") as! String!
let output2 = firstObject.objectForKey("articleTitle") as! String!
let output3 = firstObject.objectForKey("written") as! String!
let output4 = firstObject.objectForKey("date") as! String!
let output5 = firstObject.objectForKey("article") as! String!
let output1b = secondObject.objectForKey("navTitle") as! String!
let output2b = secondObject.objectForKey("articleTitle") as! String!
let output3b = secondObject.objectForKey("written") as! String!
let output4b = secondObject.objectForKey("date") as! String!
let output5b = secondObject.objectForKey("article") as! String!
if indexPath.row == 0 {
self.performSegueWithIdentifier("0a", sender: nil)
self.tableview.deselectRowAtIndexPath(indexPath, animated: true)
self.navBar.topItem?.title = output1
self.articleTitle.text = output2
self.writtenBy.text = output3
self.date.text = output4
self.article.text = output5
} else if indexPath.row == 1 {
self.performSegueWithIdentifier("0a", sender: nil)
self.tableview.deselectRowAtIndexPath(indexPath, animated: true)
self.navBar.topItem?.title = output1b
self.articleTitle.text = output2b
self.writtenBy.text = output3b
self.date.text = output4b
self.article.text = output5b
}
}
}
}
}
}
If there is an easier way of doing this, please mention it, if not try to just solve this method's problem. I know it isn't the cleanest way of doing things.
I am not sure of how you Parse your data but if it can help you, here's how I would do:
//
// Test.swift
// Test
//
// Created by Charles-Olivier Demers on 16-01-04.
//
import UIKit
//import Parse
class EventViewController: UIViewController, UITableViewDelegate {
private var _info = [EventDetails]()
override func viewDidLoad() {
fetchData()
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
self.performSegueWithIdentifier("0a", sender: nil)
self.tableview.deselectRowAtIndexPath(indexPath, animated: true)
self.navBar.topItem?.title.text = _info[indexPath.row].navTitle()
self.articleTitle.text = _info[indexPath.row].articleTitle()
self.writtenBy.text = _info[indexPath.row].writtenBy()
self.date.text = _info[indexPath.row].date()
self.article.text = _info[indexPath.row].article()
}
func fetchData() {
let query = PFQuery(className: "eventsDetails")
query.orderByAscending("ID")
query.findObjectsInBackgroundWithBlock { (objects: [PFObject]?, error: NSError?) -> Void in
if error == nil {
if let objects = objects {
for object in objects {
let navTitleObject = object["navTitle"] as! String
let articleTitleObject = object["articleTitle"] as! String
let writtenByObject = object["writtenByObject"] as! String
let dateObject = object["dateObject"] as! String
let articleObject = object["articleObject"] as! String
_info.append(EventDetails(navTitle: navTitleObject, articleTitle: articleTitleObject, writtenBy: writtenByObject, date: dateObject, article: articleObject))
}
}
}
else {
print("Error #\(error!.code)")
}
}
}
}
class EventDetails {
private var _navTitle: String!
private var _articleTitle: String!
private var _writtenBy: String!
private var _date: String!
private var _article: String!
init(navTitle: String, articleTitle: String, writtenBy: String, date: String, article: String) {
self._navTitle = navTitle
self._article = articleTitle
self._writtenBy = writtenBy
self._date = date
self._article = article
}
func navTitle() -> String {
return _navTitle
}
func articleTitle() -> String {
return _articleTitle
}
func writtenBy() -> String {
return _writtenBy
}
func date() -> String {
return _date
}
func article() -> String {
return _article
}
}
First of all, I would create a class named EventDetails. This class will take all the property of EventsDetails class on Parse. So when you fetch your data, you append the data you fetch in an array of EventDetails class in Swift.
After that in your
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
you take the value in your EventDetails array with the indexPath.row and you fill your Table View.
That is how I would do.
Good day! I'm using Parse in my swift project, Now my problem is loading and saving the query or objects to the localDataStore, I tried this method
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath, object: PFObject?) -> PFTableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("Cell") as CustomPFTableViewCell!
if cell == nil {
cell = CustomPFTableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "Cell")
}
// Extract values from the PFObject to display in the table cell
if let placeName = object?["placeName"] as? String {
cell.cellName.text = placeName
}
if let station = object?["station"] as? String {
cell.cellDetail.text = station
}
if let placeImg = object?["placeImg"] as? String {
let decodedData = NSData(base64EncodedString: placeImg, options: NSDataBase64DecodingOptions(rawValue: 0))
// var decodedimage = UIImage(data: decodedData!)
var finImage = UIImage(data: decodedData!)
cell.cellBgImg.image = finImage
}
PFObject.pinAllInBackground(self.objects, block: { (succeeded, error) -> Void in
if (error == nil) {
}else {
println(error!.userInfo)
}
})
return cell
}
now in my queryForTable method i have this
override func queryForTable() -> PFQuery {
// Start the query object
var query = PFQuery(className: "Places")
// Add a where clause if there is a search criteria
if searchBar.text != "" {
query.whereKey("filterKeyword", containsString: searchBar.text.lowercaseString)
}
// Order the results
query.orderByAscending("placeName")
var cReturn = PFQuery()
if (IJReachability.isConnectedToNetwork()) {
cReturn = query
} else {
cReturn = query.fromLocalDatastore()
}
return cReturn
}
As you can see, I'm using Reachability to check if the device is connected to the internet. If not, The query will return query.fromLocalDataStore and if the device is connected it will return the normal query to get the latest data.
Now, my problem is when I'm turning off the internet to test it, it gives me an error 'Method requires Pinning enabled.' which i already did in tableView method
PFObject.pinAllInBackground(self.objects, block: { (succeeded, error) -> Void in
if (error == nil) {
}else {
println(error!.userInfo)
}
})
What do you think I did wrong? Thanks!
I think you should put the method where you pin the objects inside your objectsDidLoad() method and not in your cellForRowAtindexPath() method.