1

I've got a SwiftUI View that takes a Hacker News API submission ID, and then fetches the details for that item in fetchStory().

When fetchStory() completed its HTTP call, it updates the @State private var url on the View, however the View never re-renders to show the new value -- it always shows the initial empty value.

Why?

import Foundation
import SwiftUI

struct StoryItem: Decodable {
    let title: String
    let url: String?
}

struct StoryView: View {
    public var _storyId: Int

    @State private var url: String = "";

    init(storyId: Int) {
        self._storyId = storyId
        self.fetchStory()
    }

    var body: some View {
        VStack {
            Text("(Load a webview here for the URL of Story #\(self._storyId))")
            Text("URL is: \(self.url)") // this never changes!
        }
    }

    private func fetchStory() {
        let url = URL(string: "https://hacker-news.firebaseio.com/v0/item/\(self._storyId).json")!

        let task = URLSession.shared.dataTask(with: url) {(data, response, error) in
            guard let data = data else {
                print(String(error.debugDescription))
                return
            }

            do {
                let item: StoryItem = try JSONDecoder().decode(StoryItem.self, from: data)

                if let storyUrl = item.url {
                    self.url = storyUrl
                } else {
                    print("No url")
                }
            } catch {
                print(error)
            }
        }

        task.resume()
    }
}

struct StoryView_Previews: PreviewProvider {
    static var previews: some View {
        StoryView(storyId: 22862053)
    }
}

2 Answers 2

1

something like this:

import SwiftUI

struct StoryItem: Decodable {
let title: String
let url: String?
}

class ObservedStoryId: ObservableObject {
@Published var storyId: String = ""
init(storyId: String) {
    self.storyId = storyId
}
}

struct StoryView: View {

@ObservedObject var storyId: ObservedStoryId
@State var url: String = ""

var body: some View {
    VStack {
        Text("(Load a webview here for the URL of Story #\(self.storyId.storyId))")
        Text("URL is: \(self.url)")
        Text(self.storyId.storyId)
    }.onReceive(storyId.$storyId) { _ in self.fetchStory() }
}

private func fetchStory() {
    let url = URL(string: "https://hacker-news.firebaseio.com/v0/item/\(self.storyId.storyId).json")!
    let task = URLSession.shared.dataTask(with: url) {(data, response, error) in
        guard let data = data else {
            print(String(error.debugDescription))
            return
        }
        do {
            let item: StoryItem = try JSONDecoder().decode(StoryItem.self, from: data)
            if let storyUrl = item.url {
                self.url = storyUrl
            } else {
                print("No url")
            }
        } catch {
            print(error)
        }
    }
    task.resume()
}
}

and call it like this:

struct ContentView: View {

    @ObservedObject var storyId = ObservedStoryId(storyId: "22862053")

    var body: some View {
     StoryView(storyId: storyId)
    }
}
Sign up to request clarification or add additional context in comments.

Comments

1

to fix your problem:

init(storyId: Int) {
    self._storyId = storyId
}

var body: some View {
    VStack {
        Text("(Load a webview here for the URL of Story #\(self._storyId))")
        Text("URL is: \(self.url)") // this now works
    }.onAppear(perform: fetchStory)
}

why does this works and not your code, my guess is this: "self.url" can only be updated/changed within the special SwiftUI View functions, such as onAppear(), elsewhere it does not change a thing.

4 Comments

This is almost perfect, however it only works once. If the parent view changes the storyId prop, then onAppear does not re-trigger (I guess since it's "appeared" once already)
in that case maybe you could use an ObservableObject instead.
I'll give that a go. If you post that recommendation as an answer I'll accept it
The accepted answer (using an ObservableObject) is certainly valid, but for many (simpler) cases this approach will resolve the problem with less complexity.

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.