Home > front end >  Creating multiple new Table View Cells based upon a FOR-Each Loop in Swift
Creating multiple new Table View Cells based upon a FOR-Each Loop in Swift

Time:08-18

I am working on an app that populates flight data into a table view of arrivals into a specific airport the user inputs. That then fetches JSON data from the Flight Aware API and will populate it. Below is the generic template of how it will appear (Forgive my constraint issues - low priority right now) Table View Page and Search

Now the idea is that when the below code is executed, through each iteration a new table view cell will populate and allow for interaction (IE if that cell is tapped a new view controller opens with all the flight data - Once again that will have to get passed from the original loop. The question is... which way is easiest to do this? Below is my current code printing to console.


func parseJSON(_ flightDataPassedIn: Data) -> FlightModel? {

        let decoder = JSONDecoder()
        do {

           // let decodedData = try? decoder.decode(FlightDataArray.self, from: flightDataPassedIn)
            let decodedData = try? decoder.decode(FlightData.self, from: flightDataPassedIn)
            if let arrivals = decodedData?.scheduled_arrivals {
                arrivals.forEach {
                    print("Flight #  \(forEachCount)")
                    print("flight_number: \($0.ident ?? "")")
                    flightID = ($0.ident ?? "")
                    print("status: \($0.status ?? "")")
                    print("origin: \($0.origin?.code ?? "")")
                    departureID = ($0.origin?.code ?? "")
                    print("destination: \($0.destination?.code ?? "")")
                    arrivalID = ($0.destination?.code ?? "")
                    print("")
                    forEachCount  = 1
                }
            }

            let builtFlight = FlightModel(departureString: departureID, arrivalString: arrivalID, registrationString: flightID)
            return builtFlight
            
        } catch {
            print("Catch Block")
            delegate?.didFailWithError(error: error)
            return nil
        }
    }

You can see below how the console prints the data - I created a variable just to keep track of the flight # it iterated through...

Console Response

The FlightData is a structure I designed to match the JSON response that FlightAware Provides. Now as you can see I am passing the data I received back through a flight model - Which is below. This only takes a few fields, compared to the FlightData that pulls all the data - which i'll need later on when a user taps the cell for more information. The question is - Is this redundant - Can I just use my main struct and pull the constants I need.. Below is the flight model, followed by the FlightData

struct FlightModel {
    let departureString: String
    let arrivalString: String
    let registrationString: String
}

FlightData Struct is Here

struct FlightData: Codable {
    let scheduled_arrivals: [Scheduled_Arrivals]?
}

struct Scheduled_Arrivals: Codable {
    let ident: String?
    let ident_icao: String?
    let ident_iata: String?
    let fa_flight_id: String
    let `operator`: String?
    let operator_icao: String?
    let flight_number: String?
    let registration: String?
    let atc_ident: String?
    let codeshares: [String]?
    let codeshares_iata: [String]?
    let blocked: Bool?
    let diverted: Bool?
    let cancelled: Bool?
    let position_only: Bool?
    let origin: Origin?
    let destination: Destination?
    let departure_delay: Int?
    let arrival_delay: Int?
    let filed_ete: Int?
    let scheduled_out: String?
    let estimated_out: String?
    let actual_out: String?
    let scheduled_off: String?
    let estimated_off: String?
    let actual_off: String?
    let scheduled_on: String?
    let estimated_on: String?
    let actual_on: String?
    let scheduled_in: String?
    let estimated_in: String?
    let actual_in: String?
    let progess_percent: Int?
    let status: String?
    let aircraft_type: String?
    let route_distance: Int?
    let filed_airspeed: Int?
    let filed_altitude: Int?
    let route: String?
    let baggage_claim: String?
    let seats_cabin_business: Int?
    let seats_cabin_coach: Int?
    let seats_cabin_first: Int?
    let gate_origin: String?
    let gate_destination: String?
    let terminal_origin: String?
    let terminal_destination: String?
    let type: String?
}

struct Origin: Codable {
    let code: String?
    let code_icao: String?
    let code_iata: String?
    let code_lid: String?
    let airport_info_url: String?
    
}

struct Destination: Codable {
    let code: String?
    let code_icao: String?
    let code_iata: String?
    let code_lid: String?
    let airport_info_url: String?
    
}

The purpose is that this FlightModel is called within the FlightDataDelegate method of 'DidUpdateFlight' which lives in the ViewController:

extension TrackingViewController: UITableViewDataSource, FlightDataDelegate {
    
    func didUpdateFlight(_ flightDataManager: FlightDataManager, flight: FlightModel) {
        DispatchQueue.main.async {
            print("Got to Dispatch")
            
            self.departurePassed = flight.departureString
            self.arrivalPassed = flight.arrivalString
            self.flightIDPassed = flight.registrationString
            
            
            
            
            print(flight.registrationString)
            print(flight.departureString)
            print(flight.arrivalString)
        }
    }
    
    func didFailWithError(error: Error) {
        print(error)
    }
    
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 1
    }
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        //let message = messages[indexPath.row]
        //Using as! casts the cell to conform to the properties of the FlightCell xib
        let cell = tableView.dequeueReusableCell(withIdentifier: "ReusableCell", for: indexPath) as! FlightCell
        
        return cell
        
    }

Thank you all so much for the help - I am not sure how easy or hard it is to do what I am looking for but I have to learn somehow so my as well ask those who are much more skilled in swift than myself.

CodePudding user response:

Looks like you're pretty close! The issue you're running into is with how UITableView and its data source work -- it can be pretty confusing until you get used to it.

It's worth spending some time with the docs here: https://developer.apple.com/documentation/uikit/uitableview https://developer.apple.com/documentation/uikit/uitableviewdatasource

But that can be pretty overwhelming. Here's the basic approach you want.

When you get data back, keep it around in a var on your TrackingViewController, maybe something like:

var arrivals: [FlightModel] = []

You tell your table view how many rows it should have through the data source. You'll want a row for each arrival:

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
   return arrivals.count
}

The table view will ask you to configure cells as it needs them, so you might do something like this:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    // Get the data for the row the table view is asking about:
    let flight = arrivals[indexPath.row]

    let cell = tableView.dequeueReusableCell(withIdentifier: "ReusableCell", for: indexPath) as! FlightCell
    
    // Do whatever you need to set up this cell for the row, e.g.:
    cell.titleLabel.text = "\(flight.arrivalString)"
    
    return cell
}

There's lots more to UITableView, but that will get you a list of all your flights! To respond when a cell is selected, you'll want to implement tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath), part of UITableViewDelegate.

It's important to know that the table view doesn't automatically know about your data changing, so you need to tell it. The simplest way to do that is to reload your data by doing this (when you set arrivals above):

tableView.reloadData()

Finally, you asked if you should make a model for your data, or if you can just use the format you're decoding the JSON into. That's up to you, but usually it's nice to convert from whatever JSON you got to a model that just has the bits you want to use, has been cleaned up, etc.

So if you do that, in your parseJSON func, you'll want to return an array of models: [FlightModel] that you can use with your viewController. In the function, you could make the conversion using map with something like:

func parseJSON(_ flightDataPassedIn: Data) -> [FlightModel] {
    let decodedData = try? JSONDecoder().decode(FlightData.self, from: flightDataPassedIn)
    if let arrivals = decodedData?.scheduled_arrivals {
        return arrivals.map {
            // (do whatever you need to do to make departureID, etc. here)
            let departureID = "..."
            let arrivalString = "..."
            let registrationString = "..."

            return FlightModel(departureString: departureID, arrivalString: arrivalID, registrationString: flightID)
        }
    } else {
        return []
    }
}
  • Related