SwfitUI creating a function that acts like a #published variable - swift

I have a class which has two published variables:
class Controller: ObservableObject {
#Published var myArray: Array<Int>: [1,2,3,4,5]
#Published var currIndex: Int = 0
func currItem() -> Int {
return myArray[curIndex]
}
}
I want my view to subscribe to the function "currItem" instead of the currIndex variable
Is there an elegant way to achieve it?
without subscribing to the function I need to write some boilerplate code:
struct myView: View {
var controller: Controller = Controller()
var body: some View {
Text(controller.myArray[controller.currIndex]) // <-- Replace this with controller.currItem()
}
}

You can do it even much better than that, like this:
import SwiftUI
struct ContentView: View {
#StateObject var controller: Controller = Controller()
var body: some View {
Text(controller.currItem?.description ?? "Error!")
}
}
class Controller: ObservableObject {
#Published var myArray: Array<Int> = [1,2,3,4,5]
#Published var currIndex: Int? = 0
var currItem: Int? {
get {
if let unwrappedIndex: Int = currIndex {
if myArray.indices.contains(unwrappedIndex) {
return myArray[unwrappedIndex]
}
else {
print("Error! there is no such index found!")
return nil
}
}
else {
print("Error! you did not provide a value for currIndex!")
return nil
}
}
}
}

Related

Binding #Binding / #State to the #Publisher to decouple VM and View Layer

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")
}
}

pass down a published var from an generic interactor that conforms to protocol

I am using SomeView in many places with different interactors, so it uses a general interactor that conforms to protocol InteractorProtocol. The problem is SomeView has several SomeButton views which take #Binding as argument and I can't pass down my someState1 and someState2 variables to SomeButton. I could pass my Interactor down to SomeButton and use the interactor variables there, but it feels wrong. Is there a way around this? Could the solution for the interactor maybe be different to make this work?
protocol InteractorProtocol {
var someState1: Bool { get set }
var someState1Published: Published<Bool> { get }
var someState1Publisher: Published<Bool>.Publisher { get }
var someState2: Bool { get set }
var someState2Published: Published<Bool> { get }
var someState2Publisher: Published<Bool>.Publisher { get }
}
class SomeInteractor: ObservableObject & InteractorProtocol {
#Published var someState1 = true
var someState1Published: Published<Bool> { _someState1 }
var someState1Publisher: Published<Bool>.Publisher { $someState1 }
#Published var someState2 = true
var someState2Published: Published<Bool> { _someState2 }
var someState2Publisher: Published<Bool>.Publisher { $someState2 }
}
struct SomeView<Interactor: InteractorProtocol & ObservableObject>: View {
#ObservedObject var interactor: Interactor
var body: some View {
HStack {
SomeButton(selected: self.interactor.$someState1) // not allowed
SomeButton(selected: self.interactor.$someState2) // not allowed
}
}
}
struct SomeButton: View {
#Binding var selected: Bool
var body: some View {
Text("text...")
.background(selected ? Color.green : Color.red)
}
}
The following should work:
protocol InteractorProtocol: ObservableObject {
var someState1: Bool { get set }
// ...
}
class SomeInteractor: InteractorProtocol {
#Published var someState1: Bool = true
// ...
}
struct SomeView<Interactor: InteractorProtocol>: View {
#ObservedObject var interactor: Interactor
var body: some View {
HStack {
SomeButton(selected: self.$interactor.someState1)
}
}
}

What is the correct way to create SwiftUI Binding with array of associative enum?

I got some (unexplained) crashes earlier today, and simplified my code to what is seen below. The crashing went away, but I am not 100% sure. Is the code below the correct way to create Binding on an array of enums? And if yes, can this code be made simpler?
import SwiftUI
enum TheEnum: Hashable {
case one(Int), two(Float)
}
class TestModel : ObservableObject {
#Published var enumArray = [TheEnum.one(5), TheEnum.two(6.0)]
}
struct ContentView: View {
#ObservedObject var testModel = TestModel()
var body: some View {
HStack {
ForEach(testModel.enumArray, id: \.self) { value -> AnyView in
switch value {
case .one(var intVal):
let b = Binding(get: {
intVal
}) {
intVal = $0
}
return AnyView(IntView(intVal: b))
case .two(var floatVal):
let b = Binding(get: {
floatVal
}) {
floatVal = $0
}
return AnyView(FloatView(floatVal: b))
}
}
}
}
}
struct IntView: View {
#Binding var intVal: Int
var body: some View {
Text("\(intVal)")
}
}
struct FloatView: View {
#Binding var floatVal: Float
var body: some View {
Text("\(floatVal)")
}
}

How to use Bind an Associative Swift enum?

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)
})
}
}

How to delete an object in SwiftUI which is marked with #ObjectBinding?

I want to delete an object which is marked as #ObjectBinding, in order to clean up some TextFields for example.
I tried to set the object reference to nil, but it didn't work.
import SwiftUI
import Combine
class A: BindableObject {
var didChange = PassthroughSubject<Void, Never>()
var text = "" { didSet { didChange.send() } }
}
class B {
var property = "asdf"
}
struct DetailView : View {
#ObjectBinding var myObject: A = A() //#ObjectBinding var myObject: A? = A() -> Gives an error.
#State var mySecondObject: B? = B()
var body: some View {
VStack {
TextField($myObject.text, placeholder: Text("Enter some text"))
Button(action: {
self.test()
}) {
Text("Clean up")
}
}
}
func test() {
//myObject = nil
mySecondObject = nil
}
}
If I try to use an optional with #ObjectBinding, I'm getting the Error
"Cannot convert the value of type 'ObjectBinding' to specified type
'A?'".
It just works with #State.
Regards
You can do something like this:
class A: BindableObject {
var didChange = PassthroughSubject<Void, Never>()
var form = FormData() { didSet { didChange.send() } }
struct FormData {
var firstname = ""
var lastname = ""
}
func cleanup() {
form = FormData()
}
}
struct DetailView : View {
#ObjectBinding var myObject: A = A()
var body: some View {
VStack {
TextField($myObject.form.firstname, placeholder: Text("Enter firstname"))
TextField($myObject.form.lastname, placeholder: Text("Enter lastname"))
Button(action: {
self.myObject.cleanup()
}) {
Text("Clean up")
}
}
}
}
I absolutely agree with #kontiki , but you should remember to don't use #State when variable can get outside. #ObjectBinding right way in this case. Also all new way of memory management already include optional(weak) if they need it.
Check this to get more information about memory management in SwiftUI
Thats how to use #ObjectBinding
struct DetailView : View {
#ObjectBinding var myObject: A
and
DetailView(myObject: A())