skip to Main Content

I write a code for SwiftUI that delivers a certain Shape depending on an Enum parameter. Unfortunately, a switch command, which seems natural for the purpose, complains that Rectangle(), Circle(), and Diamond() (my own Shape) have all mismatching types.
compiler complaints

How can I convince Xcode that all these are Shapes and all perfectly comply with SwiftUI modifiers in further code?

The code snippet demonstrating the problem:

import SwiftUI

enum MaskShapes {
    case rectangle, diamond, circle
}

struct ContentView: View {
    var body: some View {
        let shape = MaskShapes.diamond
        let mask = switch shape {
        case .rectangle: Rectangle() // Error: Branches have mismatching types 'Rectangle' and 'Circle'
        case .diamond: Diamond() // Error: Branches have mismatching types 'ContentView.Diamond' and 'Circle'
        case .circle: Circle()
        }
        
        Spacer()
        VStack {
            Image(systemName: "globe")
                .imageScale(.large)
            Text("Hello, world!")
        }
        .frame(width: 300, height: 200)
        .background(mask.foregroundColor(.red))
        Spacer()
    }
    
    struct Diamond: Shape {
        func path(in rect: CGRect) -> Path {
            var p = Path()
            p.move(to: CGPoint(x: rect.midX, y: 0))
            p.addLine(to: CGPoint(x: 0, y: rect.midY))
            p.addLine(to: CGPoint(x: rect.midX, y: rect.maxY))
            p.addLine(to: CGPoint(x: rect.maxX, y: rect.midY))
            p.closeSubpath()
            return p
        }
    }
}

#Preview {
    ContentView()
}

Expected effect – achieved with each Shape separately:
Rectangle()
Diamond()
Circle()

2

Answers


  1. What would be nice, would be if SwiftUI supported a ShapeBuilder attribute. Maybe you could write it yourself, but I don’t expect it will be easy. So here are two possible workarounds:

    1. Create a ViewBuilder function to create the specified Shape

    EDIT I thought if-else was needed here, but in fact a switch does work (thanks Rob Napier for the tip).

    @ViewBuilder
    func mask(shape: MaskShapes) -> some View {
        switch shape {
        case .rectangle: Rectangle()
        case .diamond: Diamond()
        case .circle: Circle()
        }
    }
    
    var body: some View {
        let shape = MaskShapes.diamond
    
        Spacer()
        VStack {
            Image(systemName: "globe")
                .imageScale(.large)
            Text("Hello, world!")
        }
        .frame(width: 300, height: 200)
        .background(mask(shape: shape).foregroundColor(.red))
        Spacer()
    }
    

    2. Supply the Shape to a generic function

    This approach retains the Shape type, so if you wanted to use a modifier that was Shape-specific then you could.

    EDIT A switch works here too.

    @ViewBuilder
    func bodyWithShapedBackground<S: Shape>(shape: S) -> some View {
        Spacer()
        VStack {
            Image(systemName: "globe")
                .imageScale(.large)
            Text("Hello, world!")
        }
        .frame(width: 300, height: 200)
        .background(shape.foregroundColor(.red))
        Spacer()
    }
    
    var body: some View {
        let shape = MaskShapes.diamond
        switch shape {
        case .rectangle: bodyWithShapedBackground(shape: Rectangle())
        case .diamond: bodyWithShapedBackground(shape: Diamond())
        case .circle: bodyWithShapedBackground(shape: Circle())
        }
    }
    
    Login or Signup to reply.
  2. By making your shape typed to AnyShape you can make the other shapes also conform to it by wrapping them in AnyShape(*shape*) for example: AnyShape(Rectangle())

    So your switch becomes:

    let mask: AnyShape = switch shape {
        case .rectangle: AnyShape(Rectangle())
        case .diamond: AnyShape(Diamond())
        case .circle: AnyShape(Circle())
    }
    

    Making these changes will make it compile + work!

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search