Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

A problem returning a SwiftUI (transformed) Path from a function

Tags:

swift

swiftui

I am working on a custom SwiftUI button that has a special shape depending on configuration.

A utility function must return a Shape depending on configuration. Since the path/shape is complicated to programme, transformations are used to get variations depending on configuration.

For demonstration purpose, a simplified version of the utility function looks like this:

func shape(i: Int) -> some Shape {

        let path = Path()

        switch i {
        case 0:
            return path
        case 1:
            return path.transform(CGAffineTransform(translationX:0,y:0))
        default:
            break
        }

        return path
    }

Unfortunately the compiler generate the well known error:

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

I am sure this happens due to the transformation, although it returns a struct

func transform(_ transform: CGAffineTransform) -> TransformedShape<Self>

that conforms to Shape (like Path do):

struct TransformedShape<Content> where Content : Shape

Documentation for Path states that Path conforms to Shape, and this makes me think that I am overlooking something or that this may be a bug in the compiler.

If someone knows a workaround or can see that I am doing something wrong - please let me known.

Thanks.

like image 594
Developer-1 Avatar asked Jun 01 '20 17:06

Developer-1


2 Answers

The problem is that you're promising to return a specific type, but you return two different types: Path and TransformedShape. To fix that, always return TransformedShape by applying CGAffineTransform.identity in cases where you don't want to change the shape. For example in your case:

func shape(i: Int) -> some Shape {

    let path = Path()

    switch i {
    case 0:
        return path.transform(.identity)
    case 1:
        return path.transform(CGAffineTransform(translationX:0,y:0))
    default:
        break
    }

    return path.transform(.identity)
}

Though I would probably write it this way (assuming that there are more cases, and that the zero-translate transform is just for demonstration):

func shape(i: Int) -> some Shape {

    let transform: CGAffineTransform
    switch i {
    case 1:
        transform = CGAffineTransform(translationX:0, y:0)
    default:
        transform = .identity
    }

    return Path().transform(transform)
}

some types do not mean "any type that conforms." They are opaque types, which means "this function returns some specific, known-at-compile-time type, but don't tell the caller what precise type it is."

like image 169
Rob Napier Avatar answered Nov 11 '22 09:11

Rob Napier


You must return the same concrete type in all branches when using a some return

In your case, I’d set up identity transforms so that you always end up with the same type.

Alternatively, you could erase the type. Depending on what you are doing, it could cause performance issues.

 let result: Shape = path
 return result

If you do this as part of a SwiftUI View hierarchy, then you will slow down the differ when it inspects this part of the view tree. You should not do that if you can help it.

like image 1
Lou Franco Avatar answered Nov 11 '22 10:11

Lou Franco