Mark
Mark

Reputation: 18204

How to store generic type to avoid internal struct requiring it in Swift?

The problem is best explained with some code.

Step 1

public struct Example<Content: View> {
    let content: () -> Content
    
    init(@ViewBuilder content: @escaping () -> Content) {
        self.content = content
    }
    
    func contentView() -> Content {
        self.content()
    }
}

Step 2

Now when I add an internal struct, the compiler will complain that static stored properties are not supported.

public struct Example<Content: View> {
    let content: () -> Content
    
    init(@ViewBuilder content: @escaping () -> Content) {
        self.content = content
    }
    
    func contentView() -> Content {
        self.content()
    }
    
    public struct ActionKey: Hashable, Equatable, RawRepresentable {
        public static let cancelButtonClicked = ActionKey("cancelButtonClicked") // Static stored properties not supported in generic types

        public static func == (lhs: ActionKey, rhs: ActionKey) -> Bool {
            return lhs.rawValue == rhs.rawValue
        }
        
        public let rawValue: String
        
        public init(rawValue: String) {
            self.rawValue = rawValue
        }
        
        public init(_ rawValue: String) {
            self.init(rawValue: rawValue)
        }
    }
}

Step 3

To get rid of the error we need to transform it into a computed variable.

public static var cancelButtonClicked: ActionKey { get { ActionKey("cancelButtonClicked") } }

Problem

Besides this already being annoying it gets worse. We also need to supply a generic parameter for a struct that does not depend on it at all.

_ = Example.ActionKey(rawValue: "cancelButtonClicked") // Generic parameter 'Content' could not be inferred

// Fix
_ = Example<AnyView>.ActionKey(rawValue: "cancelButtonClicked") 

If we could somehow avoid putting the generic type in the outer scope we can avoid it. However storing the type in a variable won't let me use it. So I am stuck. Does anyone have an answer?

public struct Example<Content: View> {
    let content: Any
    let contentType: Any.Type
    
    init<Content>(@ViewBuilder content: @escaping () -> Content) {
        self.content = content
        self.contentType = Content.self
    }
    
    func contentView() -> ?? {
        self.content() // ??
    }
    
    public struct ActionKey: Hashable, Equatable, RawRepresentable {
        public static var cancelButtonClicked: ActionKey { get { ActionKey("cancelButtonClicked") } }

        public static func == (lhs: ActionKey, rhs: ActionKey) -> Bool {
            return lhs.rawValue == rhs.rawValue
        }
        
        public let rawValue: String
        
        public init(rawValue: String) {
            self.rawValue = rawValue
        }
        
        public init(_ rawValue: String) {
            self.init(rawValue: rawValue)
        }
    }
}

Is the only solution to the problem putting the internal struct outside it all?

Upvotes: 2

Views: 607

Answers (1)

Asperi
Asperi

Reputation: 257779

Here is possible approach (however I'd keep it outside, like ButtonStyle is outside of Button)... anyway, here it is:

public struct Example {
    private let content: AnyView

    init<Content: View>(@ViewBuilder content: @escaping () -> Content) {
        self.content = AnyView(content())
    }

    func contentView() -> some View {
        self.content
    }

    public struct ActionKey: Hashable, Equatable, RawRepresentable {
        public static let cancelButtonClicked = ActionKey("cancelButtonClicked") // Static stored properties not supported in generic types

        public static func == (lhs: ActionKey, rhs: ActionKey) -> Bool {
            return lhs.rawValue == rhs.rawValue
        }

        public let rawValue: String

        public init(rawValue: String) {
            self.rawValue = rawValue
        }

        public init(_ rawValue: String) {
            self.init(rawValue: rawValue)
        }
    }
}

Upvotes: 1

Related Questions