Search code examples
swiftswiftui

Is there other alternative other than extending Button for ButtonStyle?


My goal is to have conditional button style. However, .buttonStyle(selected : .bordered : .borderedProminent) can't be used.

struct DepartureArrivalView: View {
    @Binding var value: String
    @Binding var selected: Bool
    
    var body: some View {
        VStack(spacing: 0) {
            Button {
                
            } label: {
                Text(value)
                    .frame(maxWidth: .infinity, alignment: .leading)
                    .font(.title)
            }
            .customButtonStyle(selected)
            .padding(16)
        }
    }
}
extension Button {
    @ViewBuilder
    func customButtonStyle(_ selected: Bool) -> some View {
        switch selected {
        case true:
            self.buttonStyle(.bordered)
        case false:
            self.buttonStyle(.borderedProminent)
        }
    }
}

#if DEBUG
struct DepartureArrivalView_Previews: PreviewProvider {
    static var previews: some View {
        DepartureArrivalView(value: .constant("A"), selected: .constant(true))
    }
}
#endif

Solution

  • Note that .bordered and .borderedProminent don't conform to ButtonStyle. They conform to PrimitiveButtonStyle.

    So, one way to solve this is by introducing a PrimitiveButtonStyle that wraps the two styles you want to choose from. To do it in a generic, reusable way, make it generic over the two styles:

    enum Either<Left, Right> {
        case left(Left)
        case right(Right)
    }
    
    extension Either: PrimitiveButtonStyle where Left: PrimitiveButtonStyle, Right: PrimitiveButtonStyle {
        func makeBody(configuration: Configuration) -> some View {
            switch self {
            case .left(let left):
                left.makeBody(configuration: configuration)
            case .right(let right):
                right.makeBody(configuration: configuration)
            }
        }
    }
    

    Then use it like this:

    struct DepartureArrivalView: View {
        @Binding var value: String
        @Binding var selected: Bool
    
        private var style: some PrimitiveButtonStyle {
            return selected ? Either.left(.bordered) : .right(.borderedProminent)
        }
    
        var body: some View {
            VStack(spacing: 0) {
                Button {
                    
                } label: {
                    Text(value)
                        .frame(maxWidth: .infinity, alignment: .leading)
                        .font(.title)
                }
                .buttonStyle(style)
                .padding(16)
            }
        }
    }