Button ⟩ custom styles
注意:本例使用兩種不同方法來控制 Button 的外觀。
btn1 使用 view modifier,btn2 使用 custom button style。
用 view modifier 必須自行設定 State 變數 isPressed,然而 custom button style 則由 configuration.isPressed 自動監控,不需另行設定 State 變數。
由於 btn1 是由 State 變數來「切換」isPressed 的狀態,所以只要按一下 btn1,按鈕就會陷下去,而且不會彈回來。btn2 則由 configuration.isPressed 自動監控,所以只有持續按住,才會保持「按下的狀態」。
⬆️ 需要: .neumorphic() (modifier)
struct ContentView: View {
    
    @State private var isPressed: Bool = false
    let color = Color(white: 0.9)
    
    var body: some View {
        HStack {
            VStack(alignment: .trailing, spacing: 40) {
                btn1        // ⭐️ 1. use view modifier
                btn2        // ⭐️ 2. use custom button style
            }
            .offset(x: -80)
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background(color)
        
    }
    
    var btn1: some View {
        Button{
            self.isPressed.toggle()
        } label: {
            Label("Hello", systemImage: "person")
                // ⭐️ 1. view modifier
                .neumorphic(isPressed: $isPressed, color: color)
        }
        .background(color)
        .overlay { 
            Text("點一下就會下沉")
                .foregroundColor(.gray)
                .offset(x: 150)
        }
    }
    
    var btn2: some View {
        Button{} label: {
            Label("World", systemImage: "globe")
        }
        // ⭐️ 2. custom button style
        .buttonStyle(.neumorphic(color: color))   // 🌀 .neumorphic() 
        .overlay { 
            Text("要按住才會下沉")
                .foregroundColor(.gray)
                .offset(x: 150)
        }
    }
}import SwiftUI
public struct NeumorphicButtonStyle: ButtonStyle {
    
    var color: Color
    
    // ⭐ protocol's only requirement
    public func makeBody(configuration: Self.Configuration) -> some View {
        
        // ⭐ `configuration` contains:
        //   - .label    : content of the button (View)
        //   - .isPressed: state of the button (Bool)
        let isPressed = configuration.isPressed
        
        let s: CGFloat = isPressed ? 5: 15       // positive shadow offset
        let r: CGFloat = isPressed ? 7: 10       // shadow radius
        let k: CGFloat = isPressed ? 0.95 : 1    // scale
        
        configuration.label
            .foregroundColor(.black)
            .padding(20)
            .background(
                ZStack {
                    RoundedRectangle(cornerRadius: 10, style: .continuous)
                        .shadow(color: .white, radius: r, x: -s, y: -s)
                        .shadow(color: .black, radius: r, x: s, y: s)
                        .blendMode(.overlay)
                    RoundedRectangle(cornerRadius: 10, style: .continuous)
                        .fill(color)
                }
            )
            .scaleEffect(k)
            .foregroundColor(.primary)
            .animation(.spring())
    }
}
// 🌀 .neumorphic(color:)
//                    ╭───────── ⭐️ important ──────────╮
extension ButtonStyle where Self == NeumorphicButtonStyle {
    public static func neumorphic(color: Color) -> some ButtonStyle {
        NeumorphicButtonStyle(color: color)
    } 
}SwiftUI ⟩ Controls ⟩ Button ⟩
ButtonStyle (protocol) - standard button interaction behavior.
PrimitiveButtonStyle (protocol) - custom interaction behavior & appearance.
.automatic, .bordered, .borderedProminent, .borderless, .plain
.card (tvOS 14), .link (macOS 10.15)
Last updated
Was this helpful?