I’m trying to get this table view algorithm to work for this art app project for school. I’ve got most of the code working. However, for some reason the func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
and the func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
are not working correctly.
I’m 90% sure that the code for retrieving and storing database is correct. However, within the table view cell the Art Name:, Artist:, Date:, Location: the image in the cell, and the numberOfRowsInSection do not update like they are supposed to. Every time I test the app the cell does not update.
I’ve tried multiple things such as adding new variables, converting the "Art Date" Int array to a String array, but nothing works. I think a potential fix can be found either in the override func viewDidLoad()
, func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
, func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
, or somewhere else.
Current View Controller:
What is currently being displayed when the app is open:
Below is the current code in the artSearchViewController.
import UIKit
import SQLite3
class artSearchViewController: UIViewController,UITableViewDelegate, UITableViewDataSource{
@IBOutlet weak var txtArtNameField: UITextField!
@IBOutlet weak var txtArtistNameField: UITextField!
@IBOutlet weak var txtLocationField: UITextField!
@IBOutlet weak var txtYearField: UITextField!
// Location of the database on the computer.
var dbPath = "/Users/williamfletcher/Documents/School Files/DIS/Assignment Work/Year 12 Term 2/Database File/art_finder_app.db"
// Variable that points to the location of a database, nor created until first used.
lazy var db = openDatabase()
// Variable to hold an SQL query.
var queryStatementString = ""
// Arrays that will store the data collected from a search using SQL.
var artName:[String] = [""]
var artist:[String] = [""]
var artLocation:[String] = [""]
var artYear:[Int] = [0]
var artYearString:[String] = [""]
// Art Info Var
var artInfo: [artworkDetails] = []
// Currently not working 100% correctly.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// Remove 'return' if code does not work.
return artInfo.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Display cell items on outlets that exist in the class.
let cell = tableView.dequeueReusableCell(withIdentifier: "artInfoCell", for: indexPath) as! searchArtDescriptionTableViewCell
// For each cell update with the correct image, name and information. Check outlet names are correct.
// Interestingly, only the text "Artist:, "Date:", and "Location appears.
cell.lblArtName.text = self.artInfo[indexPath .row].artName
cell.lblArtistName.text = "Artist: " + self.artInfo[indexPath .row].artistName
cell.lblArtDate.text = "Date: " + self.artInfo[indexPath .row].artDate
cell.lblArtLocation.text = "Location: " + self.artInfo[indexPath .row].artLocation
cell.imgArtPhoto.image = UIImage (named: artInfo[indexPath .row].artImage)
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 132
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let destinationVC = self.storyboard?.instantiateViewController(withIdentifier: "artInfoView") as! artInfoViewController
savedArtName = artName[indexPath.row]
self.present(destinationVC, animated: true, completion: nil)
}
// This fuctionion will open up the database if it can find it, and return a pointer to the database location. Otherwise an error message is printed. (ONLY USEFUL FOR ADDING INFORMATION TO THE DATABASE)
func openDatabase() -> OpaquePointer? {
var db: OpaquePointer? = nil
if sqlite3_open(dbPath, &db) == SQLITE_OK {
print("Successfully opened connection to database as (dbPath)")
return db
} else {
print("Unable to locate database")
return nil
}
}
// Process SQL Quaries
func queryArtInfomationList() {
// Pointer that keeps track of where we are up to - records
var queryStatement: OpaquePointer?
// If the query can be run in the database; proceed.
if sqlite3_prepare_v2(db, queryStatementString, -1, &queryStatement, nil) == SQLITE_OK {
// Results will be collected one row/record at a time.
// The order of results depends on order in the selected statement.
while sqlite3_step(queryStatement) == SQLITE_ROW {
// HANDLES SQL TEXT
// Return data from records: art name[0]
guard let qrArtName = sqlite3_column_text(queryStatement, 0)
else {
print("Query result: art name is nil")
return
}
// Return data from record: artist[1]
guard let qrArtist = sqlite3_column_text(queryStatement, 1)
else {
print("Query result: artist name is nil")
return
}
// Return data from record: location[2]
guard let qrArtLocation = sqlite3_column_text(queryStatement, 2)
else {
print("Query result: art location is nil")
return
}
// Return data from record: artYear[4]
let qrArtYear = sqlite3_column_int(queryStatement, 4)
// CONVERT int32 to Int (Swift)
let artYearResult = Int("(qrArtYear)")
// Convert data collected from the database that are stored as C values to Swift.
// CONVERT cString to String (Swift)
let artNameResult = String(cString: qrArtName)
let artistResult = String(cString: qrArtist)
let artLocationResult = String(cString: qrArtLocation)
// Integers append
artYear.append(artYearResult!)
// STORE Swift version of results in the arrays.
// Strings - if arrays are empty initialise with a value first
if artName != nil {
artName.append(artNameResult)
} else {
artName = [artNameResult]
}
if artist != nil {
artist.append(artistResult)
} else {
artist = [artistResult]
}
if artLocation != nil {
artLocation.append(artLocationResult)
} else {
artLocation = [artLocationResult]
}
// trace for debugging purposes
print("Query Result:")
print("(artName) (artYear) (artist) (artLocation)")
}
} else {
print("Error with SQL query/command")
}
sqlite3_finalize(queryStatement)
}
@IBOutlet weak var tblSearchArtList: UITableView!
@IBAction func btnArtSearch(_ sender: Any) {
var typedArtName:String = String(txtArtNameField.text!)
var typedArtist:String = String(txtArtistNameField.text!)
var typedLocaiton:String = String(txtLocationField.text!)
var typedYear:String = String(txtYearField.text!)
artName.removeAll()
artist.removeAll()
artLocation.removeAll()
artYear.removeAll()
// SQL query used with this search term that is stored in the variable queryStatementString
queryStatementString = "SELECT * FROM art_data WHERE art_name LIKE '%(typedArtName)%' AND artist LIKE '%(typedArtist)%' AND location LIKE '%(typedLocaiton)%' AND installation_year LIKE '%(typedYear)%' ORDER BY art_name ASC;"
print("(queryStatementString)")
// Function that processes the query and stores the results
queryArtInfomationList()
print ("(artName.count)")
}
@IBAction func btnHome(_ sender: Any) {
let destinationVC = self.storyboard?.instantiateViewController(withIdentifier: "homeView") as! homeViewController
self.present(destinationVC, animated:true, completion: nil)
}
@IBAction func btnSavedArt(_ sender: Any) {
let destinationVC = self.storyboard?.instantiateViewController(withIdentifier: "savedArtView") as! savedArtViewController
self.present(destinationVC, animated:true, completion: nil)
}
@IBAction func btnMap(_ sender: Any) {
let destinationVC = self.storyboard?.instantiateViewController(withIdentifier: "artMapView") as! artMapViewController
self.present(destinationVC, animated:true, completion: nil)
}
override func viewDidLoad() {
// CODE NEEDED
artYearString = artYear.map { String($0) }
for i in 0 ... artName.count - 1 {
artInfo.append(artworkDetails(artName: artName[i], artistName: artist[i], artLocation: artLocation[i], artDate: artYearString[i], artImage: artName[i]))
}
super.viewDidLoad()
// CODE NEEDED
}
Somethings that I’ve tried but have not worked include adding artWork strut.
import Foundation
struct artworkDetails {
let artName: String
let artistName: String
let artLocation: String
let artDate: String
let artImage: String
}
As well as
tblSearchArtList.reloadData()
within the override func.
2
Answers
Your idea of using a struct to hold all the info about an art item is good. Maintaining multiple arrays is fragile. If you end up with different numbers of elements in any of the arrays your code could crash with an "Array index out of range" error.
That’s not the cause of your problem though. The problem is that your code doesn’t load your data until after the table view has already displayed.
Once you determine that your SQL load is finished, you should call your table view’s
reloadData()
method on the main thread. That will cause the table view to re-ask the data source for the number of sections, and the number of rows in each section, and then ask for cells to display.I haven’t used SQLite very much, and not in a long time. I don’t think I’ve ever used the SQLite3 Swift library. It would take some research to figure out how it handles queries and tell you how to tell when it is done loading rows. I think you would just add a call to
tableView.reloadData()
after your call toqueryArtInfomationList()
.Probably the dataSource and delegate of the table view are not set in its storyboard. To do so, follow these steps: