6

I am trying to observe changes of a bool value contained in an ObservableObject which is a value in an enum case. Here is an example of what I am trying to achieve but with the current approach I receive the error Use of unresolved identifier '$type1Value'.

import SwiftUI
import Combine

class ObservableType1: ObservableObject {
    @Published var isChecked: Bool = false
}

enum CustomEnum {
    case option1(ObservableType1)
}

struct Parent: View {
    var myCustomEnum: CustomEnum
    var body: AnyView {
        switch myCustomEnum {
        case .option1(let type1Value):
            AnyView(Child(isChecked: $type1Value.isChecked)) // <- error here
        }
    }
}

struct Child: View {
    @Binding var isChecked: Bool
    var body: AnyView {
        AnyView(
            Image(systemName: isChecked ? "checkmark.square" : "square")
            .onTapGesture {
                self.isChecked = !self.isChecked
        })
    }
}

I am trying to update the value of isChecked from the interface but since I want to have the ObservableObject which contains the property in an enum like CustomEnum not sure how to do it or if it is even possible. I went for an enum because there will be multiple enum options with different ObservableObject values and the Parent will generate different subviews depending on the CustomEnum option. If it makes any relevance the Parent will receive the myCustomEnum value from an Array of CustomEnum values. Is this even possible? If not, what alternatives do I have? Thank you!

Bogdan
  • 402
  • 2
  • 8
  • 18
  • The issue is that you only get the `$obj.prop` syntax for variables declared as `@ObservedObject` -- not on `ObservableObject`. – Lou Franco May 26 '20 at 19:15

2 Answers2

4

Well, never say never... I've found interesting solution for this scenario, which even allows to remove AnyView. Tested with Xcode 11.4 / iOS 13.4

Provided full testable module, just in case.

// just for test
struct Parent_Previews: PreviewProvider {
    static var previews: some View {
        Parent(myCustomEnum: .option1(ObservableType1()))
    }
}

// no changes
class ObservableType1: ObservableObject {
    @Published var isChecked: Bool = false
}

// no changes
enum CustomEnum {
    case option1(ObservableType1)
}

struct Parent: View {
    var myCustomEnum: CustomEnum

    var body: some View {
        self.processCases() // function to make switch work
    }

    @ViewBuilder
    private func processCases() -> some View {
        switch myCustomEnum {
        case .option1(let type1Value):
            ObservedHolder(value: type1Value) { object in
                Child(isChecked: object.isChecked)
            }
    }
}

// just remove AnyView
struct Child: View {
    @Binding var isChecked: Bool
    var body: some View {
        Image(systemName: isChecked ? "checkmark.square" : "square")
            .onTapGesture {
                self.isChecked = !self.isChecked
            }
    }
}

Here is a playmaker

struct ObservedHolder<T: ObservableObject, Content: View>: View {
    @ObservedObject var value: T
    var content: (ObservedObject<T>.Wrapper) -> Content

    var body: some View {
        content(_value.projectedValue)
    }
}
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Asperi
  • 228,894
  • 20
  • 464
  • 690
  • This really works, but if you have a second type like `.option2(let type2Value)` which has a value of `Child2` the return type still needs to be wrapped in `AnyView` but regardless, this works as intended. Thank you mlilions! – Bogdan Jun 02 '20 at 19:21
-1

This could work for your case:

import SwiftUI

class ObservableType1: ObservableObject {
    @Published var isChecked: Bool = false
}

enum CustomEnum {
    case option1(ObservableType1)
}

struct Parent: View {
    var myCustomEnum: CustomEnum
    var body: AnyView {
        switch (myCustomEnum) {
            case .option1:
                return AnyView(Child())
            default: return AnyView(Child())
        }
    }
}

struct Child: View {

    @ObservedObject var type1 = ObservableType1()

    var body: AnyView {
        AnyView(
            Image(systemName: self.type1.isChecked ? "checkmark.square" : "square")
            .onTapGesture {
                self.type1.isChecked.toggle()
        })
    }
}
Simon
  • 1,754
  • 14
  • 32