Create an account

Very important

  • To access the important data of the forums, you must be active in each forum and especially in the leaks and database leaks section, send data and after sending the data and activity, data and important content will be opened and visible for you.
  • You will only see chat messages from people who are at or below your level.
  • More than 500,000 database leaks and millions of account leaks are waiting for you, so access and view with more activity.
  • Many important data are inactive and inaccessible for you, so open them with activity. (This will be done automatically)


Thread Rating:
  • 236 Vote(s) - 3.41 Average
  • 1
  • 2
  • 3
  • 4
  • 5
SwiftUI: How to implement a custom init with @Binding variables

#1
I am working on a money input screen and I need to implement a custom `init` to set a state variable based on the initialized amount.

I thought the following would work:

```lang-swift
struct AmountView : View {

@Binding var amount: Double
@State var includeDecimal = false

init(amount: Binding<Double>) {
self.amount = amount
self.includeDecimal = round(amount)-amount > 0
}
}
```

However, this gives me a compiler error as follows:

> Cannot assign value of type 'Binding<Double>' to type 'Double'

How do I implement a custom `init` method which takes in a `Binding` struct?
Reply

#2
You can achieve this either with static function or with custom init.

import SwiftUI
import PlaygroundSupport

struct AmountView: View {
@Binding var amount: Double
@State var includeDecimal: Bool
var body: some View {
Text("The amount is \(amount). \n Decimals \(includeDecimal ? "included" : "excluded")")
}
}

extension AmountView {
static func create(amount: Binding<Double>) -> Self {
AmountView(amount: amount, includeDecimal: round(amount.wrappedValue) - amount.wrappedValue > 0)
}
init(amount: Binding<Double>) {
_amount = amount
includeDecimal = round(amount.wrappedValue) - amount.wrappedValue > 0
}
}
struct ContentView: View {
@State var amount1 = 5.2
@State var amount2 = 5.6
var body: some View {
AmountView.create(amount: $amount1)
AmountView(amount: $amount2)
}
}

PlaygroundPage.current.setLiveView(ContentView())

Actually you don't need custom init here at all since the logic could be easily moved to `.onAppear` unless you need to explicitly set initial state externally.

struct AmountView: View {
@Binding var amount: Double
@State private var includeDecimal = true

var body: some View {
Text("The amount is \(amount, specifier: includeDecimal ? "%.3f" : "%.0f")")
Toggle("Include decimal", isOn: $includeDecimal)
.onAppear {
includeDecimal = round(amount) - amount > 0
}
}
}

This way you keep your @State private and initialized internally as [documentation suggests][1].

> Don’t initialize a state property of a view at the point in the view
> hierarchy where you instantiate the view, because this can conflict
> with the storage management that SwiftUI provides. To avoid this,
> always declare state as private, and place it in the highest view in
> the view hierarchy that needs access to the value

.


[1]:

[To see links please register here]

Reply

#3
The accepted answer is one way but there is another way too

struct AmountView : View {
var amount: Binding<Double>

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

var body: some View { ... }
}

You remove the @Binding and make it a var of type Binding<Double>
The tricky part is while updating this var. You need to update it's property called wrapped value. eg

amount.wrappedValue = 1.5 // or
amount.wrappedValue.toggle()


Reply

#4
You should use underscore to access the synthesized storage for the property wrapper itself.

In your case:

init(amount: Binding<Double>) {
_amount = amount
includeDecimal = round(amount)-amount > 0
}

Here is the quote from Apple document:

> The compiler synthesizes storage for the instance of the wrapper type by prefixing the name of the wrapped property with an underscore (_)—for example, the wrapper for someProperty is stored as _someProperty. The synthesized storage for the wrapper has an access control level of private.

Link:

[To see links please register here]

-> propertyWrapper section
Reply

#5
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):

```swift
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):

```swift
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.***

```swift
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
}
}
```

```swift
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][1] 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<Bool> 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:

```swift
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:

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

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

[1]:

[To see links please register here]

Reply

#6
**State:**

To manages the storage of any property you declare as a **state**. When the **state** value changes, the view invalidates its appearance and recomputes the body and You should only access a **state** property from inside the view’s body, or from methods called.

***Note**: To pass a state property to another view in the view hierarchy, use the variable name with the **$** prefix operator.*

struct ContentView: View {
@State private var isSmile : Bool = false
var body: some View {
VStack{
Text(isSmile ? "😄" : "😭").font(.custom("Arial", size: 120))
Toggle(isOn: $isSmile, label: {
Text("State")
}).fixedSize()
}
}
}

[![enter image description here][1]][1]

**Binding:**


The parent view declares a property to hold the `isSmile` **state**, using the **State** property wrapper to indicate that this property is the value’s source of deferent view.

struct ContentView: View {
@State private var isSmile : Bool = false
var body: some View {
VStack{
Text(isSmile ? "😄" : "😭").font(.custom("Arial", size: 120))
SwitchView(isSmile: $isSmile)
}
}
}

Use a **binding** to create a two-way connection between a property that stores data, and a view that displays and changes the data.

struct SwitchView: View {
@Binding var isSmile : Bool
var body: some View {
VStack{
Toggle(isOn: $isSmile, label: {
Text("Binding")
}).fixedSize()
}
}
}

[![enter image description here][2]][2]


[1]:

[2]:
Reply

#7
Since it's mid of 2020, let's recap:

As to `@Binding amount`

1. `_amount` is only recommended to be used during initialization. And never assign like this way `self.$amount = xxx` during initialization

2. `amount.wrappedValue` and `amount.projectedValue` are not frequently used, but you can see cases like

```Swift
@Environment(\.presentationMode) var presentationMode

self.presentationMode.wrappedValue.dismiss()
```

3. A common use case of @binding is:

```Swift
@Binding var showFavorited: Bool

Toggle(isOn: $showFavorited) {
Text("Change filter")
}
```
Reply

#8
You said (in a comment) “I need to be able to change `includeDecimal`”. What does it mean to change `includeDecimal`? You apparently want to initialize it based on whether `amount` (at initialization time) is an integer. Okay. So what happens if `includeDecimal` is `false` and then later you change it to `true`? Are you going to somehow force `amount` to then be non-integer?

Anyway, you can't modify `includeDecimal` in `init`. But you can initialize it in `init`, like this:

struct ContentView : View {
@Binding var amount: Double

init(amount: Binding<Double>) {
$amount = amount
$$includeDecimal = State(initialValue: (round(amount.value) - amount.value) != 0)
}

@State private var includeDecimal: Bool

(Note that [at some point](

[To see links please register here]

) the `$$includeDecimal` syntax will be changed to `_includeDecimal`.)
Reply



Forum Jump:


Users browsing this thread:
1 Guest(s)

©0Day  2016 - 2023 | All Rights Reserved.  Made with    for the community. Connected through