0

I am trying to create a list of text objects out of a function that returns an array of params. Everything seems to be working fine, getting the data, console shows the correct results, except the list itself which remains empty.

The function call:

import UIKit
import SwiftUI

struct SubdomainsList: View {

    @State var SubDomains = VTData().funky(XDOMAIN: "giphy.com")
    
    var body: some View {
            VStack {
                List{
                    Text("Subdomains")
                    ForEach(SubDomains, id: \.self) { SuDo in
                        Text(SuDo)
                    }
                }
            }
        }
}

struct SubdomainsList_Previews: PreviewProvider {
    static var previews: some View {
        SubdomainsList()
    }
}

The Json handlers:

struct VTResponse: Decodable {
    let data: [VT]
}

struct VT: Decodable {
    var id: String
}

The class:

class VTData {
    func funky (XDOMAIN: String) -> Array<String>{
        var arr = [""]
        getDATA(XDOMAIN: "\(XDOMAIN)", userCompletionHandler: { (SubDomain) in
            print(SubDomain)
            arr.append(SubDomain)
            return SubDomain
        })
        return arr
    }
    
    
    func getDATA(XDOMAIN: String, userCompletionHandler: @escaping ((String) -> String))  {
        let token = "<TOKEN>"
        guard let url = URL(string: "https://www.lalalla.com/subdomains") else {fatalError("Invalid URL")}
        var request = URLRequest(url: url)
        request.httpMethod = "GET"
        request.setValue("\(token)", forHTTPHeaderField: "x-apikey")
        
        let task = URLSession.shared.dataTask(with: request, completionHandler: { data, response, error in
            guard let data = data else { return }
            let decoder = JSONDecoder()
            let result = try? decoder.decode(VTResponse.self, from: data)
            if let result = result {
                for SubDo in result.data {
                    let SubDomain = SubDo.id
                    userCompletionHandler(SubDomain)
                }
            }
            else {
                fatalError("Could not decode")
            }
        })
            task.resume()
    }
    
}

I'm getting no errors whatsoever, and the console output shows the correct results:

support.giphy.com
cookies.giphy.com
media3.giphy.com
i.giphy.com
api.giphy.com
developers.giphy.com
media.giphy.com
x-qa.giphy.com
media2.giphy.com
media0.giphy.com

It is also worth mentioning that when I add print(type(of: SubDomain)) to the code I'm getting a String rather than an array.

The preview: preview

Any ideas?

jnpdx
  • 45,847
  • 6
  • 64
  • 94
Saisuk
  • 5
  • 2
  • Your `funky` is `**synchronous**` method and `getDATA` is `**asynchronous**` method so `return arr` from `funky` will call before your closure called – Nirav D Oct 04 '22 at 04:47
  • I've been trying to handle this by using the `userCompletionHandler `, am I doing it wrong? – Saisuk Oct 04 '22 at 04:51
  • Yes you need a completion handler in funky as well also for each domain you are calling completion handler instead of that you need to prepare array of string and then pass that as completion handler. – Nirav D Oct 04 '22 at 04:52
  • Could you please demonstrate? I tried everything I could thing of and it didn't work. I am new to Swift so this concept is still blurred for me – Saisuk Oct 04 '22 at 04:55
  • I showed you how to use an async function in your last question at: https://stackoverflow.com/questions/73931507/swiftui-urlsession-jsondecoder-returning-error-when-trying-to-parse-nested-json – workingdog support Ukraine Oct 04 '22 at 05:05

1 Answers1

0

try this approach, again, to extract the list of subdomain from your API, and display them in a List using the asynchronous function getDATA(...):

class VTData {
    
    // `func funky` is completely useless, remove it
    
    func getDATA(XDOMAIN: String, completion: @escaping ([String]) -> Void) { // <-- here
        let token = "<TOKEN>"
        guard let url = URL(string: "https://www.virustotal.com/api/v3/domains/\(XDOMAIN)/subdomains") else {
            print("Invalid URL")
            return
        }
        var request = URLRequest(url: url)
        request.httpMethod = "GET"
        request.setValue("\(token)", forHTTPHeaderField: "x-apikey")
        
        URLSession.shared.dataTask(with: request) { data, response, error in
            guard let data = data else { return } // todo return some error msg
            do {
                let results = try JSONDecoder().decode(VTResponse.self, from: data)
                return completion(results.data.map{ $0.id }) // <-- here
            } catch {
                print(error) // <-- here important
            }
        }.resume()
    }
    
}

struct SubdomainsList: View {
    @State var subDomains: [String] = []  // <--- here
    
    var body: some View {
        VStack {
            List{
                Text("Subdomains")
                ForEach(subDomains, id: \.self) { SuDo in
                    Text(SuDo)
                }
            }
        }
        .onAppear {
            // async function
            VTData().getDATA(XDOMAIN: "giphy.com") { subs in  // <--- here
                subDomains = subs
            }
        }
    }
}
  • This is it, thanks again. Could you please provide additional information about the solution? How `([String]) -> Void` returns an array? When I tried to return from `URLSession` I got a type error. – Saisuk Oct 04 '22 at 06:25
  • if my answers helped, please mark them as accepted, using the tick mark, it turns green. The `results.data.map{ $0.id }` transforms the array `data` into an array of strings, since `id` is a String. And this is what is returned in the completion – workingdog support Ukraine Oct 04 '22 at 06:30
  • do you get an error with my code? If so, what exactly is the error message and on what line? – workingdog support Ukraine Oct 04 '22 at 06:41
  • No, just asking for a description of the changes that've been made. – Saisuk Oct 04 '22 at 06:58