I'm new to iOS dev, so sorry if it's an obvious question. But I can't figure out how to update the data in SwiftUI List. I'm fetching the data from API and using @ObservedObject to pass it to the ContentView. It works fine when I'm launching my app, but after I change my API request (by typing a keyword in the SearchBar) and fetch it again, it doesn't seem to update the List, even though the data was changed.
ContentView.swift
struct ContentView: View {
@ObservedObject var networkManager = NetworkManager()
@State var searchText: String = ""
var body: some View {
NavigationView{
VStack {
SearchBar(text: $searchText, placeholder: "Enter a keyword")
List(networkManager.posts) { post in
NavigationLink(destination: DetailView(url: post.url)) {
HStack {
Text(post.title)
}
}
}.gesture(DragGesture().onChanged { _ in UIApplication.shared.endEditing() })
}.navigationBarTitle("News")
}
.onAppear {
self.networkManager.fetchData(self.searchText)
}
}
}
NetworkManager.swift
class NetworkManager: ObservableObject {
@Published var posts = [Post]()
func fetchData(_ keyword: String?){
var urlString = "https://newsapi.org/v2/top-headlines?country=us&apiKey=5dcef32f4c69413e8fe128cc5c7ba4cf"
if keyword != nil {
urlString = "https://newsapi.org/v2/top-headlines?country=us&apiKey=5dcef32f4c69413e8fe128cc5c7ba4cf&q=\(keyword!)"
}
print(urlString)
if let url = URL(string: urlString){
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { (data, response, error) in
if error == nil{
let decoder = JSONDecoder()
if let safeData = data{
do{
let results = try decoder.decode(News.self, from: safeData)
DispatchQueue.main.async {
self.posts = results.articles
print(self.posts)
}
} catch{
print(error)
}
}
}
}
task.resume()
}
}
}
SearchBar.swift (I fetch data again inside searchBarSearchButtonClicked)
struct SearchBar: UIViewRepresentable {
@Binding var text: String
var placeholder: String
class Coordinator: NSObject, UISearchBarDelegate {
@ObservedObject var networkManager = NetworkManager()
@Binding var text: String
init(text: Binding<String>) {
_text = text
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
text = searchText
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
print(text)
DispatchQueue.main.async {
self.networkManager.fetchData(self.text)
}
UIApplication.shared.endEditing()
}
}
func makeCoordinator() -> SearchBar.Coordinator {
return Coordinator(text: $text)
}
func makeUIView(context: UIViewRepresentableContext<SearchBar>) -> UISearchBar {
let searchBar = UISearchBar(frame: .zero)
searchBar.delegate = context.coordinator
searchBar.placeholder = placeholder
searchBar.searchBarStyle = .minimal
searchBar.autocapitalizationType = .none
return searchBar
}
func updateUIView(_ uiView: UISearchBar, context: UIViewRepresentableContext<SearchBar>) {
uiView.text = text
}
}
extension UIApplication {
func endEditing() {
sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
}
News.swift
struct News: Decodable {
let articles: [Post]
}
struct Post: Decodable, Identifiable {
var id: String{
return url!
}
let title: String
let url: String?
}
objectWillChange.send()after decoding. Off-topic but I think you can remove theDispatchQueue.main.asyncpart and assignself.postdirectlyobjectWillChange.send()afterself.posts = results.articlesand it didn't work. And about DispatchQueue, when I remove it Xcode says: "Publishing changes from background threads is not allowed"