0

it's my first message in the forum and i hope to post it right.

I'm trying to create a Dynamic List in SwiftUI that get's updated as soon as the user type something inside a textfield.

The list use the API of viaggiatreno.it which is the service from Italian Train company.

The specific link i'll use returns the list of the train stations that begin with the string provided to a certain URL.

I've created a Station Class as follows:

struct Station: Decodable, Identifiable {
    var iid = UUID()
    var name : String
    var id: String
}

And a StationFetcher class that fetches the API url initialized with a string that is the string the the user will pass from the text field:

import Foundation

public class StationFetcher : Decodable, ObservableObject {

    var stations = [Station]()
    var search = ""

    init(search: String) {
        getJsonData(string: search)
    }

    func getJsonData(string: String) {

        let url = URL(string: "http://www.viaggiatreno.it/viaggiatrenonew/resteasy/viaggiatreno/cercaStazione/" + string) 
//string is the initial string of the station
            let task = URLSession.shared.dataTask(with: url!) { (data, response, error) in
                if error != nil {
                    print(error!)
                } else {
                    if let urlContent = data {
                        do {
                            let jsonResult = try JSONSerialization.jsonObject(with: urlContent , options: JSONSerialization.ReadingOptions.mutableContainers) as AnyObject
                            for i in 0..<jsonResult.count {
                                if let station = jsonResult[i] as AnyObject? {
                                    if let nameStation = station["nomeLungo"] as! String? {
                                        if let idStation = station["id"] as! String? {
                                            let searchItem = Station(name: nameStation, id: idStation)
                                            DispatchQueue.main.async {
                                                self.stations.append(searchItem)
                                            }
                                        }
                                    } else {
                                        print("error catching dictionary value")
                                    }
                                }
                            }
                        } catch {
                            print("JSON Processing failed")

                        }
                    }
                }

            }
            task.resume()
        }
    }

How can i manage this in the main SwiftUI View?

import SwiftUI

struct Departure: View {
    @State public var selectedStation = ""
    @State private var departureDate = Date()
    @ObservedObject var fetcher = StationFetcher(search: "")

    var body: some View {
        NavigationView {
            Form {
                Section(header: Text("Cerca Stazione di partenza:")) {
                    TextField("Da dove parti?...", text: $selectedStation)
                }
                Section(header: Text("Orario:")) {
                    DatePicker(selection: $departureDate) {
                        Text("Partenza")
                    }
                }
                Section(header: Text("Lista stazioni")){
                    List(fetcher.stations) { station in
                        Text(station.name)
                    }
                }
            }
        .navigationBarTitle("Partenza")
        }
    }
}

Thanks to everybody

2
  • Can you explain what you mean "how can I manage this in the main SwiftUI view? What is your current problem? If it doesn't work, best to include the error. If it does work, what do you think should be made better? Commented Feb 8, 2020 at 20:25
  • Dear Bart, i'm actually not getting the list of items loaded in the Departure View, and after that, i'll need to make it loadable every time i write something inside the text field because the input in the text field will have a different result from the JSON Commented Feb 8, 2020 at 22:19

2 Answers 2

1

thanks for the help given. I've partially succeeded to do what i'm looking for, a part for one thing.

By using ObservableObject protocol in StationFetcher, @Published to the variable i wanted to update, i've got the list updated.

struct Departure: View {
    @State public var selectedStation = ""
    @State private var departureDate = Date()
    @ObservedObject var fetcher = StationFetcher(search: "")

    var body: some View {
        NavigationView {
            Form {
                Section(header: Text("Cerca Stazione di partenza:")) {
                    TextField("Da dove parti?...",
                              text: $selectedStation, onEditingChanged: { _ in
                                self.fetcher.getJsonData(string: self.selectedStation)
                    })
                }
                Section(header: Text("Orario:")) {
                    DatePicker(selection: $departureDate) {
                        Text("Partenza")
                    }
                }
                Section(header: Text("Lista stazioni")){
                    List(fetcher.stations) { station in
                        Text(station.name)
                            .autocapitalization(.words)
                    }
                }
            }
        .navigationBarTitle("Partenza")
        }
    }
}

Code of StationFetcher is now:

import Foundation

public class StationFetcher : ObservableObject {

    @Published var stations = [Station]()


    init(search: String) {
        getJsonData(string: search)
    }

    func getJsonData(string: String) {
        stations.removeAll(keepingCapacity: false)
        let url = URL(string: "http://www.viaggiatreno.it/viaggiatrenonew/resteasy/viaggiatreno/cercaStazione/" + string.replacingOccurrences(of: " ", with: "%20"))
//string is the initial string of the station name
            let task = URLSession.shared.dataTask(with: url!) { (data, response, error) in
                if error != nil {
                    print(error!)
                } else {
                    if let urlContent = data {
                        do {
                            let jsonResult = try JSONSerialization.jsonObject(with: urlContent , options: JSONSerialization.ReadingOptions.mutableContainers) as AnyObject
                            for i in 0..<jsonResult.count {
                                if let station = jsonResult[i] as AnyObject? {
                                    if let nameStation = station["nomeLungo"] as! String? {
                                        if let idStation = station["id"] as! String? {
                                            let searchItem = Station(name: nameStation, id: idStation)
                                            DispatchQueue.main.async {
                                                self.stations.append(searchItem)
                                                print(self.stations.count)
                                            }
                                        }
                                    } else {
                                        print("error catching dictionary value")
                                    }
                                }
                            }
                        } catch {
                            print("JSON Processing failed")

                        }
                    }
                }

            }
            task.resume()
        }
    }

The only thing i'm not getting working is the live update of the list as soon as one character is digited. Right now, the list gets updated as soon as i click outside of the textfield or when i press return key.

I'm not sure if onEditingChanged is the correct option.

Does anyone have an idea?

Thanks a lot

Sign up to request clarification or add additional context in comments.

Comments

0

Antonio. Are you saying that you want to filter the list of stations based on the user's search input? I would suggest a couple small changes to make this happen.

Generally in an ObservableObject, you can add @Published to an instance variable to cause views to update automatically when it changes (e.g. @Published var stations = [Station]()). However, because the StationFetcher class also conforms to Decodable, you must implement this publishing yourself, which can be done by calling send() on the object's publisher:

var stations = [Station]() {
    didSet {
        self.objectWillChange.send()
    }
}

So then in your Departure view, you just need to trigger the new search when the user changes the search field. This can be done with the optional onEditingChanged parameter for a TextField, to perform a closure when the user types new text:

TextField("Da dove parti?...",
          text: $selectedStation,
          onEditingChanged: { _ in
            self.fetcher.getJsonData(string: self.selectedStation)
})

Let me know if this works for you!

2 Comments

@Antonio85It Hm, is the function getting called at all? Maybe swap your getJsonData implementation with a dummy (non-network) response to see if that part is working? Another approach could be to add a didSet to selectedStation
Thanks Ben. It seems working but not completely. It updates the array of the "stations" but doesn't reload the List immediately. What i would manage in UIKit with a table.reloadData() here is not working.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.