Let swift #Binding accept any value conforming to `BinaryFloatingPoint`? - swift

I have the following code:
class MyModel:ObservableObject {
#Published var time:Double = 0
}
struct ContentView: View {
#StateObject var model = MyModel()
#State var someValue:Float = 0
var body: some View {
TView(value: $model.time)
}
}
struct TView: View {
#Binding var value:Float
var body: some View {
Text("value: \(value)")
}
}
Obvisouly that code cannot work because the binding wants a Float and model.time is a Double. Error: Cannot convert value of type 'Binding<Double>' to expected argument type 'Binding<Float>'.
I'd like to mimic what Slider does, where it can bind to values as long as they conform to BinaryFloatingPoint.
I looked at Swift.Math.Floating and Double does conform to BinaryFloatingPoint.
...
extension Double : BinaryFloatingPoint {
...
Looking at the Slider init:
init<V>(value: Binding<V>, in bounds: ClosedRange<V> = 0...1, onEditingChanged: #escaping (Bool) -> Void = { _ in }) where V : BinaryFloatingPoint, V.Stride : BinaryFloatingPoint
How can I change TView, so that it can bind to any type conforming to BinaryFloatingPoint.
My objective is to do what Slider does, allowing me to pass in a Double, Float, etc...
I tried to change TView to:
struct TView: View {
#Binding var value:Float
init<V>(theValue:Binding<V>) where V : BinaryFloatingPoint {
self._value = theValue
}
var body: some View {
Text("value: \(value)")
}
}
But, it errors: Cannot assign value of type 'Binding<V>' to type 'Binding<Float>'
How can I change things so that TView can bind to any value conforming to BinaryFloatingPoint?

Move generics to view declaration level, like
struct TView<V: BinaryFloatingPoint>: View { // << here !!
#Binding var value:V // << here !!
init(value:Binding<V>) { // << here !!
self._value = value
}
var body: some View {
Text("value: \(Double(value))")
}
}
Tested with Xcode 13 / iOS 15

Related

Passing a bound value as an argument to a SwiftUI 2 View

I've got a radial slider which works with this somewhat wordy code:
struct MyView: View {
#ObservedObject var object: Object
#State var parameter: Double = 0 {
didSet {
object.some.nested?.parameter = Int(parameter)
}
}
var body: some View {
RadialSlider(value: $parameter, label: "my parameter")
.onAppear { parameter = Double(object.some.nested?.parameter ?? 0) }
.onChange(of: parameter) { object.some.nested?.parameter = Int($0) }
}
}
As I need to use this slider multiple times, I'd like to omit the onAppear and onChange lines (with the help of a ViewModifier for example). But I don't know how to go about passing my parameter to the RadialSlider so that it maintains the binding. The type of object.some.nested?.parameter can vary between being an Int and a String.
Alternately, Is there a simpler way to bind the value from my object to my radial slider's UI?
You can initialize parameter in init, like
struct MyView: View {
#ObservedObject var object: Object
#State var parameter: Double { // << no initial value !!
didSet {
object.some.nested?.parameter = Int(parameter)
}
}
init(object: Object) {
self.object = object
_parameter = State(initialValue: Double(object.some.nested?.parameter ?? 0))
}
var body: some View {
RadialSlider(value: $parameter, label: "my parameter")
.onChange(of: parameter) { object.some.nested?.parameter = Int($0) }
}
}

How to have the value of a key value pair in a dictionary used as a binding value to another view like a Stepper

I would think this should be pretty simple but I am getting an error saying that Int is not Hashable.
import SwiftUI
struct ContentView: View {
#State var testNumbers : [String : Int] = ["foo" : 1]
var body: some View {
Stepper("test", value: $testNumbers["foo", default: 1])
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Error message:
Subscript index of type '() -> Int' in a key path must be Hashable
Binding doesn't have any Dictionary-like subscripts built-in, so you'll need to make your own.
public extension Binding {
subscript<Key, Value_Value>(
key: Key,
default default: Value_Value
) -> Binding<Value_Value>
where Value == [Key: Value_Value] {
.init(
get: { wrappedValue[key, default: `default`] },
set: { wrappedValue[key] = $0 }
)
}
}
I use the stupid-looking-but-decent-compromise name Value_Value because Self.Value can't be used yet.
You can use a custom Binding to create a variable of type Binding <Int> which is needed in the Stepper:
struct ContentView: View {
#State var testNumbers: [String: Int] = ["foo": 1]
var binding: Binding<Int> {
.init(get: {
testNumbers["foo", default: 1]
}, set: {
testNumbers["foo"] = $0
})
}
var body: some View {
Stepper("test", value: binding)
}
}

Init for a SwiftUI class with a #Binding var

I have a class which I want to initialize with a Binding var that is set in another View.
View ->
struct CoverPageView: View {
#State var numberOfNumbers:Int
var body: some View {
NavigationView {
GeometryReader { geometry in
VStack(alignment: .center, spacing: 0){
TextField("Multiplication Upto:", value: self.$numberOfNumbers, formatter: NumberFormatter())
}
}
}
}
CLASS WHICH NEEDS TO BE INITIALIZED USING THE #Binding var $numberofNumbers -
import SwiftUI
class MultiplicationPractice:ObservableObject {
#Binding var numberOfNumbers:Int
var classNumofNumbers:Int
init() {
self.classNumofNumbers = self.$numberOfNumbers
}
}
The init statement obviously gives the error that self is not initialized and the instance var is being used to initialize which is not allowed.
How do I circumvent this? The class needs to be initialized with the number the user enters on the first view. I have written approx. code here so ignore any typos please.
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)
}
}
Okay, I don't really understand your question so I'm just going to list a few examples and hopefully one of them will be what you're looking for.
struct SuperView: some View {
#State var value: Int = 0
var body: some View {
SubView(value: self.$value)
}
}
struct SubView: View {
#Binding var value: Int
// This is the same as the compiler-generated memberwise initializer
init(value: Binding<Int>) {
self._value = value
}
var body: some View {
Text("\(value)")
}
}
If I misunderstood and you're just trying to get the current value, do this
struct SuperView: some View {
#State var value: Int = 0
var body: some View {
SubView(value: self.value)
}
}
struct SubView: View {
let value: Int
// This is the same as the compiler-generated memberwise initializer
init(value: Int) {
self.value = value
}
var body: some View {
Text("\(value)")
}
}

SwiftUI Binding default value (Argument labels '(wrappedValue:)' do not match any available overloads)

In Swift you can define default values on a struct that can be overwritten on initialization:
struct myStruct {
var a: Int = 1
}
var instance1 = myStruct() // instance1.a -> 1
var instance2 = myStruct(a: 10) // instance2.a -> 10
However when I try to apply this to Bindings in a SwiftUI view I get an error:
struct MyView: View {
#Binding var a: Bool = Binding.constant(true)
var body: some View {
Text("MyView")
}
}
Argument labels '(wrappedValue:)' do not match any available overloads
I want to create a view which by default uses a constant boolean value but that can be overwritten by a "real" Binding:
struct ContainerView: View {
#State var hasSet = false
var body: some View {
Group {
MyView(a: $hasSet)
MyView() // should be equivalent to MyView(a: .constant(true))
}
}
}
Is it possible to define such a default value for a Binding in SwiftUI?
Here it is
struct MyView: View {
#Binding var a: Bool
init(a: Binding<Bool> = .constant(true)) {
_a = a
}
var body: some View {
Text("MyView")
}
}

How to pass binding to subview with SwiftUI when the variable is nested in an object?

This works
import SwiftUI
struct ContentView : View {
#State var val1: Int = 0
var body: some View {
MySubview(val1: $val1)
}
}
#if DEBUG
struct ContentView_Previews : PreviewProvider {
static var previews: some View {
ContentView(val1: 0)
}
}
#endif
struct MySubview : View {
#Binding var val1: Int
var body: some View {
return Text("Value = \(val1)")
}
}
But when the variable is nested in an Object, this fails
import SwiftUI
struct MyStruct {
let number: Int
}
struct ContentView : View {
#State var val1 = MyStruct(number: 7)
var body: some View {
MySubview(val1: $val1.number)
}
}
#if DEBUG
struct ContentView_Previews : PreviewProvider {
static var previews: some View {
ContentView(val1: 0)
}
}
#endif
struct MySubview : View {
#Binding var val1: Int
var body: some View {
return Text("Value = \(val1)")
}
}
Error shown: Generic parameter 'Subject' could not be inferred
How do i pass nested variable as a binding to a subview?
The error is very misleading. Number must be a var, not a let:
struct MyStruct {
var number: Int
}
Change it and it will work fine.
Your code was good except for needing var number: Int as kontiki pointed out.
To help with understanding of passing bindings about between views I prepared the following code which shows use of #Binding in slightly different ways:
import SwiftUI
struct Zoo { var shed: Shed }
struct Shed { var animals: [Animal] }
struct Animal { var legs: Int }
struct ZooView : View {
#State var zoo = Zoo( shed: Shed(animals:
[ Animal(legs: 2), Animal(legs: 4) ] ) )
var body: some View {
VStack {
Text("Legs in the zoo directly:")
Text("Animal 1 Legs: \(zoo.shed.animals[0].legs)")
Text("Animal 2 Legs: \(zoo.shed.animals[1].legs)")
Divider()
Text("And now with nested views:")
ShedView(shed: $zoo.shed)
}
}
}
struct ShedView : View {
#Binding var shed: Shed
var body: some View {
ForEach(shed.animals.indices) { index in
VStack {
Text("Animal: \(index+1)")
AnimalView(animal: self.$shed.animals[index])
}
}
}
}
struct AnimalView : View {
#Binding var animal: Animal
var body: some View {
VStack {
Text("Legs = \(animal.legs)")
Button(
action: { self.animal.legs += 1 }) {
Text("Another leg")
}
}
}
}
In particular ShedView is given a binding to a shed and it looks up an animal in the array of animals in the shed and passes a binding to the animal on to AnimalView.