[转] SwiftUI Custom Styling

参考链接: https://swiftui-lab.com/custom-styling/

6个月前 152次点击 来自 移动端

标签: SwiftUI

样式对于SwiftUI就好比CSS相对于Web开发,原作者也详细阐述了相关概念

Button Custom Styles

Button一共有两个style协议:ButtonStyle和PrimitiveButtonStyle

struct MyButtonStyleExample: View {
    var body: some View {
        VStack {
            Button("Tap Me!") {
                print("button pressed!")
            }.buttonStyle(MyButtonStyle(color: .blue))
        }
    }
}

struct MyButtonStyle: ButtonStyle {
    var color: Color = .green
    
    public func makeBody(configuration: MyButtonStyle.Configuration) -> some View {
        
        configuration.label
            .foregroundColor(.white)
            .padding(15)
            .background(RoundedRectangle(cornerRadius: 5).fill(color))
            .compositingGroup()
            .shadow(color: .black, radius: 3)
            .opacity(configuration.isPressed ? 0.5 : 1.0)
            .scaleEffect(configuration.isPressed ? 0.8 : 1.0)
    }
}

通过实现PrimitiveButtonStyle协议,可以让我们控制按钮事件触发的时机,如下例演示的,当我们长按按钮超过1秒后,才会触发按钮的点击事件,触发后,会显示上方的文字:

struct ContentView: View {
    @State private var text = ""
    
    var body: some View {
        VStack(spacing: 20) {
            Text(text)
            
            Button("Tap Me!") {
                self.text = "Action Executed!"
            }.buttonStyle(MyPrimitiveButtonStyle(color: .red))
        }
    }
}

struct MyPrimitiveButtonStyle: PrimitiveButtonStyle {
    var color: Color

    func makeBody(configuration: PrimitiveButtonStyle.Configuration) -> some View {
        MyButton(configuration: configuration, color: color)
    }
    
    struct MyButton: View {
        @GestureState private var pressed = false

        let configuration: PrimitiveButtonStyle.Configuration
        let color: Color

        var body: some View {
            let longPress = LongPressGesture(minimumDuration: 1.0, maximumDistance: 0.0)
                .updating($pressed) { value, state, _ in state = value }
                .onEnded { _ in
                   self.configuration.trigger()
                 }

            return configuration.label
                .foregroundColor(.white)
                .padding(15)
                .background(RoundedRectangle(cornerRadius: 5).fill(color))
                .compositingGroup()
                .shadow(color: .black, radius: 3)
                .opacity(pressed ? 0.5 : 1.0)
                .scaleEffect(pressed ? 0.8 : 1.0)
                .gesture(longPress)
        }
    }
}

Custom Toggle Style

struct ContentView: View {
    @State private var flag = true

    var body: some View {        
        VStack {
            Toggle(isOn: $flag) {
                Text("Custom Toggle")
            }                
        }
        .toggleStyle(MyToggleStyle())
    }
}

struct MyToggleStyle: ToggleStyle {
    let width: CGFloat = 50
    
    func makeBody(configuration: Self.Configuration) -> some View {
        HStack {
            configuration.label

            ZStack(alignment: configuration.isOn ? .trailing : .leading) {
                RoundedRectangle(cornerRadius: 4)
                    .frame(width: width, height: width / 2)
                    .foregroundColor(configuration.isOn ? .green : .red)
                
                RoundedRectangle(cornerRadius: 4)
                    .frame(width: (width / 2) - 4, height: width / 2 - 6)
                    .padding(4)
                    .foregroundColor(.white)
                    .onTapGesture {
                        withAnimation {
                            configuration.$isOn.wrappedValue.toggle()
                        }
                }
            }
        }
    }
}

Special Consideration #1: Accessibility Activation Point

Special Consideration #2: Form (iOS)

当在Form控件中使用自定义的Toggle,显示与原生Toggle会有差别,如果此部分你认为需要与原生一致,可使用Spacer()填充到label与switch之间

Special Consideration #3: Form (macOS)

macOS存在同样的问题,修复的方式不同,请跳转原文查看

Putting It All Together

原作者贴心的一次性解决以上问题

struct MyToggleStyle2: ToggleStyle {
    #if os(iOS)
    let width: CGFloat = 50
    #else
    let width: CGFloat = 38
    #endif
    
    func makeBody(configuration: Self.Configuration) -> some View {
        HStack {
            configuration.label

            #if os(iOS)
            Spacer()
            #endif
            
            ZStack(alignment: configuration.isOn ? .trailing : .leading) {
                RoundedRectangle(cornerRadius: 4)
                    .frame(width: width, height: width / 2)
                    .foregroundColor(configuration.isOn ? .green : .red)
                
                RoundedRectangle(cornerRadius: 4)
                    .frame(width: (width / 2) - 4, height: width / 2 - 6)
                    .padding(4)
                    .foregroundColor(.white)
                    .onTapGesture {
                        withAnimation {
                            configuration.$isOn.wrappedValue.toggle()
                        }
                }
            }
        }
        .accessibility(activationPoint: configuration.isOn ? UnitPoint(x: 0.25, y: 0.5) : UnitPoint(x: 0.75, y: 0.5))
        .alignmentGuide(.leading, computeValue: { d in (d.width - self.width) })

    }
}

Styled Custom Views

原作者最后给出了一个自定义View的样式的案例:TripleToggle,其可以切换三种状态

详细代码请查阅:https://gist.github.com/swiftui-lab/4469338fd099285aed2d1fd00f5da745

Made with in Shangrao,China By Devler.

Copyright © Devler 2012 - 2022

赣ICP备19009883号-1