15

I want to use a @State variable both for the UI and for computing a value.

For example, let's say I have a TextField bound to @State var userInputURL: String = "https://". How would I take that userInputURL and connect it to a publisher so I can map it into a URL.

Pseudo code:

$userInputURL.publisher()
      .compactMap({ URL(string: $0) })
      .flatMap({ URLSession(configuration: .ephemeral).dataTaskPublisher(for: $0).assertNoFailure() })
      .eraseToAnyPublisher()
marcprux
  • 9,845
  • 3
  • 55
  • 72
Ryan
  • 6,432
  • 7
  • 40
  • 54

4 Answers4

15

You can't convert @state to publisher, but you can use ObservableObject instead.

import SwiftUI

final class SearchStore: ObservableObject {
    @Published var query: String = ""

    func fetch() {
        $query
            .map { URL(string: $0) }
            .flatMap { URLSession.shared.dataTaskPublisher(for: $0) }
            .sink { print($0) }
    }
}

struct ContentView: View {
    @StateObject var store = SearchStore()

    var body: some View {
        VStack {
            TextField("type something...", text: $store.query)
            Button("search") {
                self.store.fetch()
            }
        }
    }
}
malhal
  • 26,330
  • 7
  • 115
  • 133
Mecid
  • 4,491
  • 6
  • 30
  • 30
3

You can also use onChange(of:) to respond to @State changes.

struct MyView: View {

  @State var userInputURL: String = "https://"

  var body: some View {
    VStack {
      TextField("search here", text: $userInputURL)
    }
    .onChange(of: userInputURL) { _ in
      self.fetch()
    }
  }

  func fetch() {
    print("changed", userInputURL)
    // ...
  }
}

Output:

changed https://t
changed https://ts
changed https://tsr
changed https://tsrs
changed https://tsrst
Daniel R
  • 312
  • 1
  • 9
-1

The latest beta has changed how variables are published so I don't think that you even want to try. Making ObservableObject classes is pretty easy but you then want to add a publisher for your own use:

class ObservableString: Combine.ObservableObject, Identifiable {
    let id = UUID()
    let objectWillChange = ObservableObjectPublisher()
    let publisher = PassthroughSubject<String, Never>()
    var string: String {
        willSet { objectWillChange.send() }
        didSet { publisher.send(string) }
    }

    init(_ string: String = "") { self.string = string }
}

Instead of @State variables you use @ObservableObject and remember to access the property string directly rather than use the magic that @State uses.

Michael Salmon
  • 1,056
  • 7
  • 15
-4

After iOS 14.0, you can access to Publisher.

struct MyView: View {
    @State var text: String?
    
    var body: some View {
        Text(text ?? "")
            .onReceive($text.wrappedValue.publisher) { _ in
                let publisher1: Optional<String>.Publisher = $text.wrappedValue.publisher
                
                // ... or
                
                let publisher2: Optional<String>.Publisher = _text.wrappedValue.publisher
            }
    }
}
Jinwoo Kim
  • 440
  • 4
  • 7