skip to Main Content

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:

Current View Controller

What is currently being displayed when the app is open:

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


  1. 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 to queryArtInfomationList().

    Login or Signup to reply.
  2. Probably the dataSource and delegate of the table view are not set in its storyboard. To do so, follow these steps:

    1. Select the Table View object and open the Connections Inspector. Under "Outlets," connect the "dataSource" property to the View Controller by dragging from the circle next to "dataSource" to the View Controller icon.
    2. Repeat step 1 for the "delegate" property.
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search