Dmitry
Dmitry

Reputation: 2158

How to bind to Published property with private setter

Here is my data model:

@MainActor
class TestModel: ObservableObject {
    @Published private(set) var data = ""
    
    private func randomAlphaNumericString(length: Int) -> String {
        let allowedChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
        let allowedCharsCount = UInt32(allowedChars.count)
        var randomString = ""

        for _ in 0 ..< length {
            let randomNum = Int(arc4random_uniform(allowedCharsCount))
            let randomIndex = allowedChars.index(allowedChars.startIndex, offsetBy: randomNum)
            let newCharacter = allowedChars[randomIndex]
            randomString += String(newCharacter)
        }

        return randomString
    }
    
    func update() {
        data = randomAlphaNumericString(length: 10)
    }
}

And the application view is:

import SwiftUI

struct TestView: View {
    @StateObject var model = TestModel()
    
    var body: some View {
        VStack {
            TestSubView(textData: $model.data)
            Button("Update") {
                model.update()
            }
        }
    }
}

where a subview is defined as follows:

import SwiftUI

struct TestSubView: View {
    @Binding var textData: String
    
    var body: some View {
        Text("Data : \(textData)")
    }
}

With this code I get a compilation error because of a private setter for a model field data. How can I stay with a private setter and get this binding to work? What is the correct pattern to follow?

Upvotes: 1

Views: 1112

Answers (2)

Fogmeister
Fogmeister

Reputation: 77631

I think you don't need a @Binding here at all. It looks like your TextSubView is not updating the text. You just want to make sure that it updates when the text updates.

So change your view like this...

import SwiftUI

struct TestView: View {
    @StateObject var model = TestModel()
    
    var body: some View {
        VStack {
            TestSubView(textData: model.data)
            Button("Update") {
                model.update()
            }
        }
    }
}

And your TextSubView like this...

import SwiftUI

struct TestSubView: View {
    let textData: String
    
    var body: some View {
        Text("Data : \(textData)")
    }
}

Doing this removes anything in the code that allows your view to update the text. Which you weren't doing anyway.

Upvotes: 1

jrturton
jrturton

Reputation: 119242

If none of the views can edit data, then you don't need a binding. Just pass the value of data as a let property. Any subviews contained within the TestView will be reevaluated when the value changes anyway:

import SwiftUI

struct TestView: View {
    @StateObject var model = TestModel()
    
    var body: some View {
        VStack {
            TestSubView(textData: model.data)
            Button("Update") {
                model.update()
            }
        }
    }
}

struct TestSubView: View {
    let textData: String
    
    var body: some View {
        Text("Data : \(textData)")
    }
}

Upvotes: 0

Related Questions