SwiftUI——Button

36 阅读4分钟

一、常用的Button相关初始化的方法

public struct Button<Label> : View where Label : View {
@preconcurrency public init(action: @escaping @MainActor () -> Void, @ViewBuilder label: () -> Label)
}
extension Button where Label == Text {
@preconcurrency nonisolated public init(_ titleKey: LocalizedStringKey, action: @escaping @MainActor () -> Void)
 @preconcurrency nonisolated public init<S>(_ title: S, action: @escaping @MainActor () -> Void) where S : StringProtocol
}

其中 Label 为 Button中的泛型,它遵循了 View 协议,并决定了每个Button实例将使用什么类型的视图进行渲染。 根据不同的Label,我们可以使用不同的初始化参数

1、初始化参数 action 与 label

action参数为一个逃逸闭包,当用户单击或点击按钮时它会执行相关操作。而label参数为一个尾随闭包,我们可以在其中写一些自定义的视图(custom view)。

            // 1
            Button {
                print("按钮被点击")
            } label: {
                Text("我是一个按钮")
            }
            
            // 2
            Button(action: {}) {
                Text("尾随闭包的方式")
            }

            // 3
            Button {
                
            } label: {
                VStack {
                    Text("我是按钮")
                    Text("按钮说明")
                }.foregroundColor(.red)
            }

2、初始化参数 action 与 titleKey/title

LocalizedStringKey 国际化使用,当你的字符串用 LocalizedStringKey 创建或者使用其类型声明时,SwiftUI会根据你当前的语言环境,自动翻译成对应的字符串.

///  KText //会替换 国际化文件中的值
    Button(LocalizedStringKey("KText")) {

    }

    /// 直接显示字符串
    Button("直接显示") {

    }

3、可选参数 role

@preconcurrency nonisolated public init(role: ButtonRole?, action: @escaping @MainActor () -> Void, @ViewBuilder label: () -> Label)
@preconcurrency nonisolated public init<S>(_ title: S, role: ButtonRole?, action: @escaping @MainActor () -> Void) where S : StringProtocol
}

role的值

public struct ButtonRole : Equatable, Sendable {

    /// List {
    ///     ForEach(items) { item in
    ///         Text(item.title)
    ///             .swipeActions {
    ///                 Button(role: .destructive) { delete() } label: {
    ///                     Label("Delete", systemImage: "trash")
    ///                 }
    ///             }
    ///     }
    /// }
    
    public static let destructive: ButtonRole

    public static let cancel: ButtonRole

    
    ///
    /// ```
    /// struct NewContactSheet: View {
    ///     var body: some View {
    ///         NavigationStack {
    ///             NewContactEditor()
    ///                 .toolbar {
    ///                     Button(role: .confirm) {
    ///                         saveChanges()
    ///                     }
    ///                 }
    ///         }
    ///     }
    /// }
    /// ```
    @available(iOS 26.0, macOS 26.0, tvOS 26.0, watchOS 26.0, *)
    public static let confirm: ButtonRole

   
    /// ```
    /// struct NewContactSheet: View {
    ///     @Environment(\.dismiss) private var dismiss
    ///
    ///     var body: some View {
    ///         NavigationStack {
    ///             NewContactEditor()
    ///                 .toolbar {
    ///                     Button(role: .close) {
    ///                         dismiss()
    ///                     }
    ///                 }
    ///         }
    ///     }
    /// }
    /// ```
    @available(iOS 26.0, macOS 26.0, tvOS 26.0, watchOS 26.0, *)
    public static let close: ButtonRole

}

role 主要结合着 .swipeActions NavigationStack 和 .alert 进行使用

4、 初始化参数:configuration

extension Button where Label == PrimitiveButtonStyleConfiguration.Label {

    /// Creates a button based on a configuration for a style with a custom
    /// appearance and custom interaction behavior.
    ///
    /// Use this initializer within the
    /// ``PrimitiveButtonStyle/makeBody(configuration:)`` method of a
    /// ``PrimitiveButtonStyle`` to create an instance of the button that you
    /// want to style. This is useful for custom button styles that modify the
    /// current button style, rather than implementing a brand new style.
    ///
    /// For example, the following style adds a red border around the button,
    /// but otherwise preserves the button's current style:
    ///
    ///     struct RedBorderedButtonStyle: PrimitiveButtonStyle {
    ///         func makeBody(configuration: Configuration) -> some View {
    ///             Button(configuration)
    ///                 .border(Color.red)
    ///         }
    ///     }
    ///
    /// - Parameter configuration: A configuration for a style with a custom
    ///   appearance and custom interaction behavior.
    @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
    nonisolated public init(_ configuration: PrimitiveButtonStyleConfiguration)
}

根据类型的定义可以得知,我们可以传入一个 PrimitiveButtonStyleConfiguration 类型的参数。根据上方的注释,我们相关例子定义一个struct,这个struct需要遵循 PrimitiveButtonStyle 协议,并且我们需要在 .buttonStyle 修饰符中进行使用

struct JLButtonView: View {
    struct RedBorderButtonStyle: PrimitiveButtonStyle{
        func makeBody(configuration: Configuration) -> some View {
            Button(configuration)
                .border(Color.red)
        }
    }
    var body: some View {
        VStack {
            Button("按钮"){
                print("按钮被点击了")
            }.buttonStyle(RedBorderButtonStyle())
        }
        
    }
}

struct JLButtonView: View {
    struct RedBorderButtonStyle: PrimitiveButtonStyle{
        func makeBody(configuration: Configuration) -> some View {
            
            // 主动触发按钮点击
            configuration.trigger()
            return configuration.label.border(Color.red)
        }
    }
    var body: some View {
        VStack {
            Button("按钮"){
                print("按钮被点击了")
            }.buttonStyle(RedBorderButtonStyle())
        }
        
    }
}

二、样式

ButtonStyle 协议好像和PrimitiveButtonStyle 协议差不多。 接着看一下ButtonStyleConfiguration ,可以发现它里面提供的是 isPressed常量,用来判断当前用户是否按下按钮。而之前的PrimitiveButtonStyleConfiguration 提供的是 trigger 方法,可以允许我们主动去触发按钮。

struct JLButtonView2: View {
    
    struct CustomButtonStyle: ButtonStyle{
        func makeBody(configuration: Configuration) -> some View {
            
            configuration.label
                .padding()
                .background(.blue)
                .cornerRadius(10)
                .foregroundColor(.white)
                .scaleEffect(configuration.isPressed ? 2: 1)
                .animation(.easeOut(duration: 0.2), value: configuration.isPressed)
            
        }
    }
    
    var body: some View {
        Button("按钮") {
            print("被点击了")
        }
        .buttonStyle(CustomButtonStyle())
    }
}

按下按钮执行对应的效果后,再触发 Button 的 action。

1:ButtonStyle 和 PrimitiveButtonStyle的区别

  1. 想对按钮的样式做一些修改并且主动触发按钮,我们可以使用 PrimitiveButtonStyle
  2. 想在按钮被按下时做一些效果交互,我们可以使用ButtonStyle

2:使用Modifier添加自定义样式

struct YellowButtonStyle: ViewModifier{
    func body(content: Content) -> some View {
        content
            .padding()
            .background(.yellow)
            .cornerRadius(10)
            .foregroundColor(.white)
        
    }
}

struct JLButtonView3: View {
    var body: some View {
        Button("按钮", action: {
            
        })
        .modifier(YellowButtonStyle())
    }
}

进一步封装

struct YellowButtonStyle: ViewModifier{
    func body(content: Content) -> some View {
        content
            .padding()
            .background(.yellow)
            .cornerRadius(10)
            .foregroundColor(.white)
        
    }
}

extension View{
    func yellowButtonStyle() -> some View{
        modifier(YellowButtonStyle())
    }
}

struct JLButtonView3: View {
    var body: some View {
        Button("按钮", action: {
            
        })
        .yellowButtonStyle()
    }
}

3:Button的扩展

在 Buttton 中提供了一个 Label 泛型。我们可以利用该泛型进行对应的拓展。比如我们想创建一个图像按钮

struct JLButtonView4: View {
    var body: some View {
        VStack{
            Button(iconName: "camera.shutter.button"){}
        }
        .font(.largeTitle)
        .foregroundColor(.gray)
    }
}

extension Button where Label == Image{
    init(iconName: String, action: @escaping () -> Void) {
        self.init(action: action) {
            Image(systemName: iconName)
        }
    }
}