1
votes

Swift 5: Afficher les données récupérées en vue de fermeture en vue tableau

J'ai réussi à obtenir des données de mon API dans une fermeture au sein d'une fonction. Je dois afficher les données dans la table de mon storyboard.

Je sais que la raison pour laquelle elles ne s'affichent pas est que les données sont enregistrées dans la fermeture et que je ne peux pas y accéder de l'extérieur à moins d'avoir un gestionnaire de complétion. J'ai lu un tas d'autres questions ici sur Stack Overflow mais je ne peux pas vraiment le comprendre. J'ai également essayé de recharger le tableau, mais il renvoie simplement une erreur "Trouvé de manière inattendue nil".

Propriétés de la revendication

struct Claims: Decodable {
    let id: Int
    let submission_date: String
    let status: String
    init(json: [String:Any]) {
        id = json["id"] as? Int ?? -1
        submission_date = json["submission_date"] as? String ?? ""
        status = json["status"] as? String ?? ""
    }
}

class DashboardController: UIViewController, GIDSignInUIDelegate {

    @IBOutlet weak var tableView: UITableView!
    var tempArray: [ClaimProperties] = []

    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.delegate = self
        tableView.dataSource = self
    }

    // getTokenFromAPI() gets called form AppDelegate.swift once the user logs in via Google API

    func getTokenFromAPI(usersAppToken:String) {
        guard let urlString = URL(string: "https://claim.ademo.work/claims/") else { return }
        var requestAPI = URLRequest(url: urlString)

        requestAPI.httpMethod = "GET"
        requestAPI.addValue("application/json", forHTTPHeaderField: "Content-Type")
        requestAPI.addValue("application/json", forHTTPHeaderField: "Accept")
        requestAPI.setValue("Bearer \(usersAppToken)", forHTTPHeaderField: "Authorization")

        URLSession.shared.dataTask(with: requestAPI) {
            (data, response, error) in
            if let data = data {
                do {

                    let json = try JSONDecoder().decode([Claims].self, from: data)

                    for n in 0..<json.count {
                        self.tempArray.append(ClaimProperties(id: json[n].id, date: json[n].submission_date, status: json[n].status))
                    }

                    // This is the data I'm trying to display -> tempArray

                } catch let error {
                    print("Localized Error: \(error.localizedDescription)")
                    print("Error: \(error)")
                }
            }
        }.resume()
    }
}

extension DashboardController: UITableViewDataSource, UITableViewDelegate {

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

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "ClaimCell", for: indexPath) as! AllMyClaimsTable
        cell.idField.text = String(tempArray[indexPath.section].id)
        cell.dateField.text = tempArray[indexPath.section].date
        cell.statusField.text = tempArray[indexPath.section].status
        return cell
    }
}

DashboardController p>

class ClaimProperties {
    var id: Int
    var date: String
    var status: String

    init(id: Int, date: String, status: String) {
        self.id = id
        self.date = date
        self.status = status
    }
}

En résumé, ce que j'essaie de faire est simplement d'afficher les données API dans ma vue de table.


4 commentaires

Peut-être que cela devrait être indexPath.row au lieu de indexPath.section, à partir d'un examen rapide (dans votre implémentation cellForRow).


Maintenant section est 0 donc si tempArray a des valeurs, l'implémentation comme tempArray [indexPath.section] doit renvoyer la valeur dans le premier (0) index. Mais OP a déclaré qu'une erreur Nil trouvé de manière inattendue s'est produite.


Je suis d'accord avec vous @emrcftci, mais pour que les données s'affichent correctement, l'implémentation dans cellForRow doit également être corrigée.


@Wattholm oui vous avez raison, devrait être fait avec tempArray [indexPath.row] au lieu de tempArray [indexPath.section]


3 Réponses :


0
votes

Déplacez votre ligne reload sous pour pour la boucle

URLSession.shared.dataTask(with: requestAPI) { [weak self] (data, response, error) in
    if let data = data {
        do {

            let json = try JSONDecoder().decode([Claims].self, from: data)

            for n in 0..<json.count {
                self?.tempArray.append(ClaimProperties(id: json[n].id, date: json[n].submission_date, status: json[n].status))
            }
            self?.tableView.reloadData()

        } catch let error {
            print("Localized Error: \(error.localizedDescription)")
            print("Error: \(error)")
        }
    }
}.resume()

Vous devez recharger une fois la boucle terminée, après que tempArray soit complètement rempli.


5 commentaires

self? .tableView.reloadData () donne une erreur de: "Impossible d'utiliser le chaînage optionnel sur une valeur non optionnelle de type 'DashboardController'" "J'ai donc supprimé le point d'interrogation. Après cela, j'ai eu le message d'erreur "Trouvé nul de manière inattendue lors du déballage implicite d'une valeur facultative"


Veuillez ajouter [self faible] avant (données, réponse, erreur) , veuillez revérifier ma réponse :) Pouvez-vous me dire à quelle ligne vous faites face avec l'erreur? @ShaniceC.


Oops! Désolé, j'ai manqué cette partie. J'ai essayé, montre toujours la même erreur. Capture d'écran ici pour votre référence: flic.kr/p/2iAq97Y


Je pense que votre tableView est nil veuillez vérifier la connexion de votre tableView .


Pouvez-vous me dire quelle propriété est nulle ? self et tableView peuvent être nil dans ce cas. Vous pouvez le trouver facilement avec le côté gauche de la console.



0
votes

Ce sont les lignes de la table qui représentent les données dans tempArray donc l'indice doit être indexPath.row.

print("In cellForRowAt...")
print(tempArray)

Edit: Aussi , ajoutez un observateur de propriété didSet qui rechargera automatiquement la vue de la table au fur et à mesure que votre tableau change. Bien que cela ne devrait être qu'une solution de contournement temporaire pour illustrer un point et pour déboguer votre code plus facilement. Appeler tableView.reloadData () pour chaque insertion ou suppression est très inefficace.

var tempArray: [ClaimProperties] = [] {
    didSet {
        print("didSet...")
        print(tempArray)
        tableView.reloadData()
    }
}

Et si vous ajoutez maintenant un journal d'impression dans cellForRowAt cela doit prouver que les valeurs de tempArray sont définies et enregistrées, comme vous le souhaitez.

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "ClaimCell", for: indexPath) as! AllMyClaimsTable
        cell.idField.text = String(tempArray[indexPath.row].id)
        cell.dateField.text = tempArray[indexPath.row].date
        cell.statusField.text = tempArray[indexPath.row].status
        return cell
    }

Explication:

La raison pour laquelle votre print (tempArray) n'imprime les valeurs mises à jour nulle part ailleurs est que les méthodes dataSource de tableView ne sont appelées qu'une seule fois, lorsque la tableView apparaît pour la première fois. Ils ne sont rappelés que lorsque vous appelez spécifiquement la méthode reloadData . Si vous ajoutez le code didSet , il devrait se recharger à chaque fois que vous ajoutez à votre tableau, puis cellForRowAt sera appelé à nouveau à ce moment-là lorsque votre fermeture a fini de s'exécuter, ce qui imprimera le tempArray mis à jour (non vide) .

Si le tableau ne s'affiche toujours pas après les correctifs ci-dessus, je pense que je peux dire avec confiance que le problème est ailleurs.


5 commentaires

Je l'ai changé en ligne, et il n'y a aucune erreur lorsque je l'exécute, mais il ne montre rien non plus. Je suppose que c'est parce que mon ajout de tempArray dans getTokenFromAPI () n'a pas été `` enregistré '' en dehors de la fonction. Il reste donc encore vide .. mais merci de le signaler sur l'indice!


En plus de ce correctif, faites ce que @emrcftci suggère dans sa réponse, car la tableView doit être rechargée lorsque les données changent. Et cela se produit lors de la fermeture. De plus, votre tempArray est une propriété de DashboardViewController , donc les modifications sont enregistrées. Il vous suffit de mettre à jour votre tableau au bon moment pour refléter ces changements.


La raison pour laquelle je soupçonne qu'il ne sera peut-être pas sauvegardé est que j'ai essayé d'imprimer tempArray partout ailleurs mais fais {} et il est vide. Cependant, si je l'imprime dans do {} bien sûr, les données sont toutes là. Je suis encore nouveau dans ce domaine, merci beaucoup pour l'explication.


Que diriez-vous d'ajouter un modificateur didSet pour votre tempArray, où vous appelez tableView.reloadData () . De cette façon, vous verrez que les valeurs de tempArray persistent après avoir été définies dans la fermeture, et cela peut déclencher la mise à jour de l'affichage correctement. Je modifierai ma réponse.


J'ai réussi à résoudre le problème et à afficher toutes les données dans ma vue de table. J'ai publié ma réponse si vous êtes intéressé. Merci beaucoup pour l'explication, cela m'a aidé à mieux comprendre!



1
votes

Après avoir cherché et modifié mon code, voici la solution de travail:

Contrôleur de tableau de bord

DispatchQueue.main.async {
    self!.dashboardMyClaim.reloadData()
}

Je pense que l'ajout de ceci a aidé un peu:

import UIKit

struct Claims: Decodable {
    let id: Int
    let submission_date: String
    let status: String
    
    init(json: [String:Any]) {
        id = json["id"] as? Int ?? -1
        submission_date = json["submission_date"] as? String ?? ""
        status = json["status"] as? String ?? ""
    }
}

class DashboardController: UIViewController {
    
    @IBOutlet weak var dashboardMyClaim: UITableView!
    var myClaimArray: [PropertyExistingClaim] = []
    var idToken = ""
    var email = ""
    var appToken = ""
    
    override func viewDidLoad() {
        super.viewDidLoad()
        idToken = AppDelegate.originalAppDelegate.userIdToken
        email = AppDelegate.originalAppDelegate.userEmail
        tableSettings()
        getAppToken()
    }
    
    func tableSettings() {
        dashboardMyClaim.delegate = self
        dashboardMyClaim.dataSource = self
    }
    
    // Get user's App Token from Google API

    func getAppToken() {
        guard let urlString = URL(string: "https://claim.ademo.work/sessions/") else { return }
        var requestAPI = URLRequest(url: urlString)

        let parameters: [String: Any] = ["email":email, "platform":"web", "id_token":idToken]
        requestAPI.httpMethod = "POST"
        requestAPI.setValue("Application/json", forHTTPHeaderField: "Content-Type")
        guard let httpBody = try? JSONSerialization.data(withJSONObject: parameters, options: []) else {return}
        requestAPI.httpBody = httpBody
        requestAPI.timeoutInterval = 20
        URLSession.shared.dataTask(with: requestAPI) { (data, response, error) in
            if let data = data {
                do {
                    let jsonResult = try JSONDecoder().decode(SignIn.self, from: data)
                    self.getClaimFromAPI(usersAppToken: jsonResult.app_token)
                } catch {
                    print(error)
                }
            }
        }.resume()
    }
    
    // Use the App Token to GET claims from the database / API

    func getClaimFromAPI(usersAppToken:String) {
        guard let urlString = URL(string: "https://claim.ademo.work/claims/") else { return }
        var requestAPI = URLRequest(url: urlString)

        requestAPI.httpMethod = "GET"
        requestAPI.addValue("application/json", forHTTPHeaderField: "Content-Type")
        requestAPI.addValue("application/json", forHTTPHeaderField: "Accept")
        requestAPI.setValue("Bearer \(usersAppToken)", forHTTPHeaderField: "Authorization")

        URLSession.shared.dataTask(with: requestAPI) { [weak self] (data, response, error) in
            if let data = data {
                do {
                    let json = try JSONDecoder().decode([Claims].self, from: data)

                    for n in 0..<json.count {
                        self!.myClaimArray.append(PropertyExistingClaim(id: json[n].id, date: json[n].submission_date, desc: "-", amount: "-", amountMyr: "-", currency: "-", status: json[n].status))
                    }
                    DispatchQueue.main.async {
                        self!.dashboardMyClaim.reloadData()
                    }
                    
                } catch let error {
                    print("Localized Error: \(error.localizedDescription)")
                    print("Error: \(error)")
                }

            }
        }.resume()
    }
    
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "goToNewClaimDashboard" {
            let destinationVC = segue.destination as? NewClaimDashboardController
        }
    }
}

extension DashboardController: UITableViewDelegate, UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        myClaimArray.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = dashboardMyClaim.dequeueReusableCell(withIdentifier: "DashboardMyClaim", for: indexPath) as! TableDashboardMyClaim
        cell.dateField.text = "Date: \(myClaimArray[indexPath.row].date)"
        cell.descriptionField.text = "Description: \(myClaimArray[indexPath.row].desc)"
        cell.amountMyrField.text = "RM \(myClaimArray[indexPath.row].amountMyr)"
        cell.idField.text = "#\(myClaimArray[indexPath.row].id)"
        cell.statusField.text = "\(myClaimArray[indexPath.row].status)"
        return cell
    }
    
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 120.0
    }
    
}

Un merci spécial à tous ceux qui m'ont aidé sur cette question. J'apprécie vraiment cela! :)


0 commentaires