Swiftui: How to Implement a Custom Init With @Binding Variables

SwiftUI: How to implement a custom init with @Binding variables

Argh! You were so close. This is how you do it. You missed a dollar sign (beta 3) or underscore (beta 4), and either self in front of your amount property, or .value after the amount parameter. All these options work:

You'll see that I removed the @State in includeDecimal, check the explanation at the end.

This is using the property (put self in front of it):

struct AmountView : View {
@Binding var amount: Double

private var includeDecimal = false

init(amount: Binding<Double>) {

// self.$amount = amount // beta 3
self._amount = amount // beta 4

self.includeDecimal = round(self.amount)-self.amount > 0
}
}

or using .value after (but without self, because you are using the passed parameter, not the struct's property):

struct AmountView : View {
@Binding var amount: Double

private var includeDecimal = false

init(amount: Binding<Double>) {
// self.$amount = amount // beta 3
self._amount = amount // beta 4

self.includeDecimal = round(amount.value)-amount.value > 0
}
}

This is the same, but we use different names for the parameter (withAmount) and the property (amount), so you clearly see when you are using each.

struct AmountView : View {
@Binding var amount: Double

private var includeDecimal = false

init(withAmount: Binding<Double>) {
// self.$amount = withAmount // beta 3
self._amount = withAmount // beta 4

self.includeDecimal = round(self.amount)-self.amount > 0
}
}
struct AmountView : View {
@Binding var amount: Double

private var includeDecimal = false

init(withAmount: Binding<Double>) {
// self.$amount = withAmount // beta 3
self._amount = withAmount // beta 4

self.includeDecimal = round(withAmount.value)-withAmount.value > 0
}
}

Note that .value is not necessary with the property, thanks to the property wrapper (@Binding), which creates the accessors that makes the .value unnecessary. However, with the parameter, there is not such thing and you have to do it explicitly. If you would like to learn more about property wrappers, check the WWDC session 415 - Modern Swift API Design and jump to 23:12.

As you discovered, modifying the @State variable from the initilizer will throw the following error: Thread 1: Fatal error: Accessing State outside View.body. To avoid it, you should either remove the @State. Which makes sense because includeDecimal is not a source of truth. Its value is derived from amount. By removing @State, however, includeDecimal will not update if amount changes. To achieve that, the best option, is to define your includeDecimal as a computed property, so that its value is derived from the source of truth (amount). This way, whenever the amount changes, your includeDecimal does too. If your view depends on includeDecimal, it should update when it changes:

struct AmountView : View {
@Binding var amount: Double

private var includeDecimal: Bool {
return round(amount)-amount > 0
}

init(withAmount: Binding<Double>) {
self.$amount = withAmount
}

var body: some View { ... }
}

As indicated by rob mayoff, you can also use $$varName (beta 3), or _varName (beta4) to initialise a State variable:

// Beta 3:
$$includeDecimal = State(initialValue: (round(amount.value) - amount.value) != 0)

// Beta 4:
_includeDecimal = State(initialValue: (round(amount.value) - amount.value) != 0)

Init for a SwiftUI class with a @Binding var

Typically you'd initialize MultiplicationPractice in CoverPageView with a starting value:

@ObservedObject var someVar = MultiplicationPractice(NoN:123)

And of course, add a supporting init statement:

class MultiplicationPractice:ObservableObject {
init(NoN: Int) {
self.numberOfNumbers = val
}

and you wouldn't want to wrap your var with @Binding, instead wrap it with @Published:

class MultiplicationPractice:ObservableObject {

@Published var numberOfNumbers:Int
...

In your particular case I would even drop the numberOfNumbers var in your CoverPageView, and instead use the direct variable of the above someVar:

struct CoverPageView: View {
//removed @State var numberOfNumbers:Int
@ObservedObject var someVar = MultiplicationPractice(123)

...

TextField("Multiplication Upto:", value: self.$someVar.numberOfNumbers, formatter: NumberFormatter())

You'll notice that I passed in the sub-var of the @ObservedObject as a binding. We can do this with ObservableObjects.



Edit

I see now what you're trying to do, you want to pass a binding along across your ViewModel, and establish an indirect connection between your view and model. While this may not be the way I'd personally do it, I can still provide a working example.

Here is a simple example using your struct names:

struct MultiplicationGame {
@Binding var maxNumber:String
init(maxNumber: Binding<String>) {
self._maxNumber = maxNumber
print(self.maxNumber)
}
}

class MultiplicationPractice:ObservableObject {
var numberOfNumbers: Binding<String>
@Published var MulGame:MultiplicationGame

init(numberOfNumbers: Binding<String> ) {
self.numberOfNumbers = numberOfNumbers
self.MulGame = MultiplicationGame(maxNumber: numberOfNumbers)
}

}
struct ContentView: View {
@State var someText: String
@ObservedObject var mulPractice: MultiplicationPractice
init() {
let state = State(initialValue: "")
self._someText = state
self.mulPractice = MultiplicationPractice(numberOfNumbers: state.projectedValue)
}
var body: some View {
TextField("put your text here", text: $someText)
}
}

SwiftUI: Binding in initializer: default value

Use the constant factory method:

init(value: Binding<String> = .constant("")) {
self._value = value
}

Now you can do TestView().

How Do I Initialize @Binding SwiftUi

Update: I was able to figure it out. The following code appears to work:

struct RecipeItemDetailView_Previews: PreviewProvider {
static var previews: some View {
RecipeItemDetailView(recipeDetails: .constant([StoredRecipeModel].init()))
}

SwiftUI @Binding Initialize

When you use your LoggedInView in your app you do need to provide some binding, such as an @State from a previous view or an @EnvironmentObject.

For the special case of the PreviewProvider where you just need a fixed value you can use .constant(false)

E.g.

#if DEBUG
struct LoggedInView_Previews : PreviewProvider {
static var previews: some View {
LoggedInView(dismissView: .constant(false))
}
}
#endif

SwiftUI Views with a custom init

You can write your initializer like this:

struct CustomInput : View {
@Binding var text: String
var name: String

init(_ name: String, _ text: Binding<String>) {
self.name = name

// Beta 3
// self.$text = text

// Beta 4
self._text = text
}

var body: some View {
TextField(name, text: $text)
}
}

Initializer 'init(_:)' requires that 'BindingString' conform to 'StringProtocol' SwiftUI Text

Text displays an immutable String, hence it's initialiser takes a String, not a Binding<String>.

So inject the String, not a Binding to it.

Text(state.resultString)

Moreover, @State should only be used on Views. resultString should be @Published, not @State.

Pass binding to child view in init SwiftUI

One way is to declare imageCanvasView in body, like:

struct EditImageView: View {

@State private var currentSelectedText = "Hello"
@State private var currentSelectedFilter = Filter.noFilter

var body: some View {
let imageCanvasView = ImageCanvasView(imageText: $currentSelectedText, filter: $currentSelectedFilter)
VStack {
imageCanvasView
Button("Take screenshot") {
imageCanvasView.takeScreenshot()
}
}
}
}
g

Related Topics



Leave a reply



Submit