0

I am currently creating an iOS app based on the math card game "Set", and I am running into a problem simplifying the switch case statement. My goal is to avoid an ugly series of nested switches in my view. I come from a background of OOP and normally I would store references to an object and call them later where I want; however, I am trying to be functional. Below is a some code describing roughly what I would like to do.

struct CardView: View {
    var card: SetGame<CardShape, CardShading, CardColor>.Card
    
    var cardShape: some Shape {
        switch card.shape {
        case .diamond:
            return Diamond()
        case .squiggle:
            return Squiggle()
        case .oval:
            return Oval()
        }
    }
    
    var cardShading: some ViewModifier {
        switch card.shading {
        case .open:
            return .stroke()
        case .striped:
            return .stripe()
        case .solid:
            return .fill()
        }
    }
    
    var cardColor: Color
    {
        switch card.color {
        case .red:
            return Color.red
        case .green:
            return Color.green
        case .purple:
            return Color.purple
        }
    }
    
    var body: some View {
        VStack {
            ForEach(0..<card.count) { _ in
                cardShape.cardShading
            }
            .aspectRatio(contentAspectRatio, contentMode: .fit)
        }
        .foregroundColor(cardColor)
        .padding()
        .cardify(selected: card.selected)
    }
    
    // MARK: Drawing Constants
    let contentAspectRatio: CGSize = CGSize(width: 2, height: 1)
}

Now the above code does not work for obvious reasons, it is more about conveying what I am trying to accomplish. The easy brute force solution I see would be to have three nested switch/case statements, but I have to believe there is another way. I tried using type erasure in card shape doing something like this...

    var cardShape: some View {
        switch card.shape {
        case .diamond:
            return AnyView(Diamond())
        case .squiggle:
            return AnyView(Squiggle())
        case .oval:
            return AnyView(Oval())
        }
    }

and while this does "work", it is no longer a shape and I lose access to my shape modifiers (ie. fill, stripe, stroke). I am pretty sure I won't be able to store my ViewModifiers and call them at a different place, so I am sort of expecting there to be one switch/case inside of my var body. If someone has a completely different approach or if I am just doing something stupid, feel free to let me know! I am trying to learn the most "Swiftish" way of doing things. Thanks in advance!

Daniel
  • 457
  • 5
  • 20
  • 2
    "I would store references to an object and call them later where I want; however, I am trying to be functional" Nothing about using objects and calling methods on them necessarily means it's not functional. – Alexander May 10 '21 at 18:27
  • @Alexander my main point there is I cannot have a variable who's type is a protocol, and returns three different concrete types. I get the error "Function declares an opaque return type, but the return statements in its body do not have matching underlying types" because Diamond, Squiggle, and Oval are three different concrete types. And you're right, functional is probably not the right word. My goal is to use Structs and Protocols rather than classes and inheritance (hopefully that is more accurate). – Daniel May 10 '21 at 18:31
  • 1
    Instead, you should make an `extension` on `CardShape`, `CardShading` and `CardColor`. You should be able to call something like `card.shading.viewModifier`. You will have the same set of switches but you will put them when they belong. – Sulthan May 10 '21 at 18:33
  • @Sulthan CardShape, CardShading, and CardColor are enums. Would I still be able to do that / should I change that all together? – Daniel May 10 '21 at 18:37
  • 3
    I think the fundamental issue you're experiencing here is a lack of a model layer. You're trying to use CardView as the model which the business layer (the game logic) works with. You would have an easier time if you made yourself a `SetCard` struct with those properties, with no `AnyView`, etc. – Alexander May 10 '21 at 19:25
  • @DanielCopley Of course, `private extension CardShape { var viewModifier: ... { switch self { ... } } }`. That's a very common usage. – Sulthan May 10 '21 at 20:03
  • @Alexander I have a model layer, this is a view. I don't think I am trying to do any game logic here at all. I just want to display each card correctly by converting the card enum properties to their shapes/shadings. – Daniel May 11 '21 at 02:14
  • Next can be helpful https://stackoverflow.com/a/62605936/12299030. – Asperi May 11 '21 at 04:35

0 Answers0