swiftswiftuiswift5

Get Shape for view dynamically in SwiftUI


Using Swift 5.2 I would like to create a function to dynamically change the Shape

I have a view like

import SwiftUI

struct CardView: View {
    let suit : Suite
    let rank : Rank
    var body: some View {
        getShape(suite: .heart)
        .fill(Color.red)  // .fill(suit.color)
        .frame(width: 100, height: 100)
     }
}

I would like to create a function with a protocol return type of Shape, I substituted my custom shaps for generic in the example below

func getShape(suite:Suite) -> Shape {
    switch suite {
    case .heart:
        return Circle() // Heart()
    case .diamond:
        return Rectangle() // Diamond()
    case .spade:
        return Circle() // Heart()
    case .club:
        return Circle() // Club()

    }
}

I cannot use an opaque type with some because I am returning different types and I get a compile error

Function declares an opaque return type, but the return statements in its body do not have matching underlying types 

Nor can I leave it as is with the protocol type because I get the error

Protocol 'Shape' can only be used as a generic constraint because it has Self or associated type requirements

Is there any way I can achieve this elegantly?


Solution

  • By combining @Asperi's answer with

    struct AnyShape: Shape {
        init<S: Shape>(_ wrapped: S) {
            _path = { rect in
                let path = wrapped.path(in: rect)
                return path
            }
        }
    
        func path(in rect: CGRect) -> Path {
            return _path(rect)
        }
    
        private let _path: (CGRect) -> Path
    }
    

    I can change it to

    func getShape(suite:Suite) -> some Shape {
        switch suite {
        case .club:
            return AnyShape(Club())
        case .diamond:
            return AnyShape(Diamond())
        case .heart:
            return AnyShape(Heart())
    
        case .spade:
            return AnyShape(Spade())
        }
    }
    
    
    struct CardView: View {
        let suit : Suite
        let rank : Rank
        var body: some View {
    
        getShape(suite: suit)
          .fill(Color.red)
          .frame(width: 100, height: 100)
     }