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)")
}
}
Related
How to observe property value in SwiftUI.
I know some basic publisher and observer patterns. But here is a scenario i am not able to implement.
class ScanedDevice: NSObject, Identifiable {
//some variables
var currentStatusText: String = "Pending"
}
here CurrentStatusText is changed by some other callback method that update the status.
Here there is Model class i am using
class SampleModel: ObservableObject{
#Published var devicesToUpdated : [ScanedDevice] = []
}
swiftui component:
struct ReviewView: View {
#ObservedObject var model: SampleModel
var body: some View {
ForEach(model.devicesToUpdated){ device in
Text(device.currentStatusText)
}
}
}
Here in UI I want to see the real-time status
I tried using publisher inside ScanDevice class but sure can to use it in 2 layer
You can observe your class ScanedDevice, however you need to manually use a objectWillChange.send(),
to action the observable change, as shown in this example code.
class ScanedDevice: NSObject, Identifiable {
var name: String = "some name"
var currentStatusText: String = "Pending"
init(name: String) {
self.name = name
}
}
class SampleViewModel: ObservableObject{
#Published var devicesToUpdated: [ScanedDevice] = []
}
struct ReviewView: View {
#ObservedObject var viewmodel: SampleViewModel
var body: some View {
VStack (spacing: 33) {
ForEach(viewmodel.devicesToUpdated){ device in
HStack {
Text(device.name)
Text(device.currentStatusText).foregroundColor(.red)
}
Button("Change \(device.name)") {
viewmodel.objectWillChange.send() // <--- here
device.currentStatusText = UUID().uuidString
}.buttonStyle(.bordered)
}
}
}
}
struct ContentView: View {
#StateObject var viewmodel = SampleViewModel()
var body: some View {
ReviewView(viewmodel: viewmodel)
.onAppear {
viewmodel.devicesToUpdated = [ScanedDevice(name: "device-1"), ScanedDevice(name: "device-2")]
}
}
}
I want to decouple my ViewModel and View layer to increase testability of my Views.
Therefore, I want to keep my property states inside view and only init them as I needed.
But I cannot initialize my #Binding or #States with #Published properties. Is there a way to couple them inside init function?
I just add example code below to
instead of
import SwiftUI
class ViewModel: ObservableObject {
#Published var str: String = "a"
#Published var int: Int = 1 { didSet { print("ViewModel int = \(int)")} }
init() {
print("ViewModel initialized")
}
}
struct ContentView: View {
#ObservedObject vM = ViewModel()
var body: some View {
Button(action: { vM.int += 1; print(int) }, label: {
Text("Button")
})
}
}
I want to achieve this without using #ObservedObject inside my view.
import SwiftUI
class ViewModel: ObservableObject {
#Published var str: String = "a"
#Published var int: Int = 1 { didSet { print("ViewModel int = \(int)")} }
init() {
print("ViewModel initialized")
}
}
struct ContentView: View {
#Binding var str: String
#Binding var int: Int
var body: some View {
Button(action: { int += 1; print(int) }, label: {
Text("Button")
})
}
}
extension ContentView {
init(viewModel:ObservedObject<ViewModel> = ObservedObject(wrappedValue: ViewModel())) {
// str: Binding<String> and viewModel.str: Published<String>.publisher
// type so that I cannot bind my bindings to viewModel. I must accomplish
// this by using #ObservedObject but this time my view couples with ViewModel
_str = viewModel.wrappedValue.$str
_int = viewModel.wrappedValue.$int
print("ViewCreated")
}
}
// Testing Init
ContentView(str: Binding<String>, int: Binding<Int>)
// ViewModel Init
ContentView(viewModel: ViewModel)
This way I can't bind them each other, I just want to bind my binding or state properties to published properties.
I have realized that by Binding(get:{}, set{}), I can accomplish that. if anyone want to separate their ViewModel and View layer, they can use this approach:
import SwiftUI
class ViewModel: ObservableObject {
#Published var str: String = "a"
#Published var int: Int = 1 { didSet { print("ViewModel int = \(int)")} }
init() {
print("ViewModel initialized")
}
}
struct ContentView: View {
#Binding var str: String
#Binding var int: Int
var body: some View {
Button(action: { int += 1; print(int) }, label: {
Text("Button")
})
}
}
extension ContentView {
init(viewModel:ViewModel = ViewModel()) {
_str = Binding ( get: { viewModel.str }, set: { viewModel.str = $0 } )
_int = Binding ( get: { viewModel.int }, set: { viewModel.int = $0 } )
print("ViewCreated")
}
}
I have simple viewModel:
final class EmployeeListViewModel: ObservableObject {
#Published var list = [Employee]()
init() {
// some request
self.list = [Employee, Employee]
}
}
And have a view:
struct EmployeeView: View {
#ObservedObject var viewModel = EmployeeListViewModel()
#State private var showContents: [Bool] = Array(repeating: false, count: viewModel.list.count)// <- error throws here
var body: some View {
GeometryReader { fullView in
ScrollView {
VStack(spacing: 40) {
ForEach(self.viewModel.list) { employee in
Text(employee.firstName).foregroundColor(.black)
}
}
}
}
}
}
Error text:
Cannot use instance member 'viewModel' within property initializer; property initializers run before 'self' is available
I tried change it with init:
struct EmployeeView: View {
#ObservedObject var viewModel = EmployeeListViewModel()
#State private var showContents: [Bool]
init() {
_showContents = State(initialValue: Array(repeating: false, count: viewModel.list.count)) // <- error
}
var body: some View {
GeometryReader { fullView in
ScrollView {
VStack(spacing: 40) {
ForEach(self.viewModel.list) { employee in
Text(employee.firstName).foregroundColor(.black)
}
}
}
}
}
}
But it also throws error:
'self' used before all stored properties are initialized
this throws on I call viewModel on init()
How to solve it? #State i use for card view. There I simplified views for easy understand.
First initialise the state variable to an empty array
#State private var showContents: [Bool] = []
then set it in the init
init() {
showContents = Array(repeating: false, count: viewModel.list.count)
}
You shouldn't initialise the view model property in the view but rather use dependency injection
init(viewModel: EmployeeListViewModel) {
self.viewModel = viewModel
showContents = Array(repeating: false, count: viewModel.list.count)
}
Here is possible solution
struct EmployeeView: View {
#ObservedObject var viewModel: EmployeeListViewModel // << declare
#State private var showContents: [Bool] // << declare
init() {
let vm = EmployeeListViewModel() // create here !!
// initialize both below
self.viewModel = vm
self._showContents = State(initialValue: Array(repeating: false,
count: vm.list.count))
}
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")
}
}
I have a GroupView that accepts a binding as a parameter because I want the GroupView to modify the data in the enum.
Can some help me on how to accomplish this?
import SwiftUI
struct ContentView: View {
#ObservedObject var viewModel = ViewModel()
var body: some View {
VStack {
GroupView(group: /* What do i put here? */) // <----------------
}
}
}
struct GroupView: View {
#Binding var group: Group
var body: some View {
Text("Hello World")
}
}
class ViewModel : ObservableObject {
#Published var instruction: Instruction!
init() {
instruction = .group(Group(groupTitle: "A Group struct"))
}
}
enum Instruction {
case group(Group)
}
struct Group { var groupTitle: String }
Well, this certainly will work... but probably there's a better approach to your problem. But no one is in a better position than you, to determine that. So I'll just answer your question about how to pass a binding.
struct ContentView: View {
#ObservedObject var viewModel = ViewModel()
var body: some View {
VStack {
GroupView(group: viewModel.groupBinding)
}
}
}
class ViewModel : ObservableObject {
#Published var instruction: Instruction!
init() {
instruction = .group(Group(groupTitle: "A Group struct"))
}
var groupBinding: Binding<Group> {
return Binding<Group>(get: {
if case .group(let g) = self.instruction {
return g
} else {
return Group(groupTitle: "")
}
}, set: {
self.instruction = .group($0)
})
}
}