How to pass a SwiftUI Shape as argument
Asked Answered
B

1

6

I am creating a Swift Package that will flash some text for a specified duration (like a toast implementation.) I want the user to have the option to specify a background shape in the call to the item, but when I just try to create a Shape parameter, I get a compile error on the declaration line (error 1):

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

and where I try to use it (error 2):

Protocol 'Shape' as a type cannot conform to the protocol itself

import SwiftUI

public struct Toast: View {
    
    @Binding var show: Bool

    var message: String = ""
    var duration: Double = 2.0
    var fontSize: Font = .title
    var textColor: Color = Color(.secondaryLabel)
    var backgroundColor : Color = Color (.clear)
    var encapsulate: Bool = false
    var shape: Shape = Capsule() //Error 1
    
    public init(show: Binding<Bool>,
                message: String,
                duration: Double = 2.0,
                fontSize: Font = .title,
                textColor: Color = Color(.secondaryLabel),
                backgroundColor: Color = Color (.clear),
                encapsulate: Bool = false,
                shape: Shape = Capsule()) { //same as error 1
        
        self._show = show
        self.message = message
        self.duration = duration
        self.fontSize = fontSize
        self.textColor = textColor
        self.backgroundColor = backgroundColor
        self.encapsulate = encapsulate
        self.shape = shape
    }
    
    
    public var body: some View {
        Text(message)
            .font(fontSize)
            .foregroundColor(textColor)
            .padding(.horizontal)
            .padding(.vertical, 2.0)
            .background(backgroundColor)
            .if(encapsulate, transform: { view in
                view.clipShape(shape) //error 2
            })
            .onAppear(){
                DispatchQueue.main.asyncAfter(deadline: .now() + duration) {
                    show = false
                }
            }
    }

}

public extension View {
    /// Applies the given transform if the given condition evaluates to `true`.
    /// - Parameters:
    ///   - condition: The condition to evaluate.
    ///   - transform: The transform to apply to the source `View`.
    /// - Returns: Either the original `View` or the modified `View` if the condition is `true`.
    @ViewBuilder func `if`<Content: View>(_ condition: Bool, transform: (Self) -> Content) -> some View {
        if condition {
            transform(self)
        } else {
            self
        }
    }
}

I have seen other posts with this type of error which use @ViewBuilders but I can't seem to figure out how to implement that here if that is indeed the solution.

Any help is appreciated.

Bichromate answered 16/9, 2021 at 20:25 Comment(1)
Use a Shape extension instead of a View extension?Anastomose
B
6

Here is the required changes to make it work:

For the class declaration:

public struct Toast<Content: Shape>: View { ... }

For property declaration:

var shape: Content 

For init declaration:

shape: Content
Bithynia answered 16/9, 2021 at 21:6 Comment(2)
Also note that using the conditional modifier is not a good idea: objc.io/blog/2021/08/24/conditional-view-modifiersBithynia
Thanks! That seems to have done the trick.Bichromate

© 2022 - 2024 — McMap. All rights reserved.