One-way data binding from @ObservedObject with SwiftUI

Is it possible to achieve one-way data binding with SwiftUI in situations like below?

import Combine

final class Counter: ObservableObject {
    @Published private(set) var count: Int = 0
    func increment() { count += 1 }
    func reset() { count = 0 }
}
import SwiftUI

struct ContentView: View {
    @ObservedObject private var counter: Counter = .init()
    var body: some View {
        VStack {
            NumberDisplay(number: $counter.count) // ⛔because `count` is read-only
            HStack {
                Button("Reset") { self.counter.reset() }
                Button("Increment") { self.counter.increment() }
            }
        }
    }
}
import SwiftUI

struct NumberDisplay: View {
    @Binding var number: Int
    private let superCoolFont: Font = .system(.largeTitle)
    
    var body: some View {
        Text("\(number)")
            .font(superCoolFont)
    }
}

It seems that SwiftUI does not provide any simple APIs to achieve one-way data binding as far as I know, though I am not familiar with SwiftUI. Could you inform me of the way if you know it?

I think, for example, some APIs like readOnly in the following code can be helpful.

import SwiftUI

struct ContentView: View {
    @ObservedObject private var counter: Counter = .init()
    var body: some View {
        VStack {
            //NumberDisplay(number: $counter.count) // ⛔
            NumberDisplay(number: $counter.readOnly.count) // ✅
            HStack {
                Button("Reset") { self.counter.reset() }
                Button("Increment") { self.counter.increment() }
            }
        }
    }
}
import SwiftUI

extension ObservedObject.Wrapper {
    var readOnly: ReadOnly {
        // FIXME: ⚠️ Replace it with a safe implementation!!
        return ReadOnly(unsafeBitCast(self, to: ObjectType.self))
    }
    
    @dynamicMemberLookup
    struct ReadOnly {
        private let object: ObjectType
        
        init(_ object: ObjectType) {
            self.object = object
        }
        
        subscript<Subject>(dynamicMember keyPath: KeyPath<ObjectType, Subject>) -> Binding<Subject> {
            Binding<Subject>(
                get: { self.object[keyPath: keyPath] },
                set: { _ in assertionFailure("Read-only") }
            )
        }
    }
}

If it's read-only, you should pass it as normal let value:

struct ContentView: View {
    @ObservedObject private var counter: Counter = .init()
    var body: some View {
        VStack {
            NumberDisplay(number: counter.count)
            ...
        }
    }
}

struct NumberDisplay: View {
  let number: Int
  private let superCoolFont: Font = .system(.largeTitle)
    
  ...
}

PS:

Apple's framework, like SwiftUI, should actually be on Apple Developer Forums

3 Likes

Oh, I had totally misunderstood... I got it. Thank you for your help!