Button 按钮

按钮的基本写法有以下几种:

title + action

Button( title: , action: ) ,由于最后一个参数是闭包,可以简写为 Button(""){ ... }

//当它只包含一些文本时,传递按钮的标题,以及点击按钮时应该运行的闭包即可
Button("Delete selection") { print("Now deleting…") }

//除了闭包,还可以给按钮传递具体的函数
Button("Delete selection", action: executeDelete)
func executeDelete() { print("Now deleting…") }

action + label (视图)

Button( action: { 动作闭包 } , label:{ 视图闭包 } ) ,简写为 Button { 动作闭包 } label: { 视图闭包 }

//例子1:
Button(
	action: { ... }, 
	label:{
    Text("忘记密码?").foregroundColor(.red)
  }
)

//例子2: 
Button( action: { ... } )
{
	Image("yosemite")
		.renderingMode(.original)
}
//纯图片按钮可能无法看见图片,这是由于图像渲染模式默认设置为“template”决定的
//template 意味着所有非透明区域都将使用强调色,遇到这种问题可以尝试更改渲染模式为 renderingMode(.original) 来解决

//例子3: 简写
Button {
    print("Button was tapped")
} label: {
    Text("Tap me!")
}

//例子4: 如果想要完全自定义的东西,可以使用第二个尾随闭包传递一个自定义标签
Button(
	action: { print("Delete tapped!") }
) 
{
    HStack {
        Image(systemName: "trash")
        Text("Delete")
    }
}


按钮设置

1. 角色定义 role

为按钮附加一个 role ,iOS 可以使用它来调整其外观,也更利于屏幕阅读器。例如,可以说 delete 按钮具有 destructive 作用;

Screenshot - 2023-05-23 15.06.17.png

Button("Delete", role: .destructive) { print("Delete") }

Button("Cancel", role: .cancel, action: executeCancel)

2. 内置样式 buttonStyle

可以为按钮使用内置样式: .bordered.borderedProminent 。它们可以单独使用,也可以与角色结合使用:

Button("Button 1") { ... }.buttonStyle(.bordered)
Button("Button 2", role: .destructive) { ... }.buttonStyle(.bordered)

// borderedProminent 的颜色更加强烈
Button("Button 3") { ... }.buttonStyle(.borderedProminent)
Button("Button 4", role: .destructive) { ... }.buttonStyle(.borderedProminent)

// 如果要自定义用于带边框按钮的颜色,请使用如下 tint() 修饰符:
Button("Button 3") { }
    .buttonStyle(.borderedProminent)
    .tint(.mint)

3. 常用修饰符

代码示例
添加边框 .overlay( RoundedRectangle(cornerRadius: 40).stroke(.purple, lineWidth: 5) )
增加可点击区域 .padding(20)
设置整体点击区域 .contentShape(Rectangle())
禁用按钮叠加颜色 Image("Button") .renderingMode(.original) 在 NavigationLink 也适用
禁用按钮叠加颜色 .buttonStyle(.plain)
禁用按钮叠加颜色 .buttonStyle(PlainButtonStyle()) Xcode 12 之前版本

<aside> 💡 区别很微妙,但很重要:在 List 内使用 Button 时,如果使用 buttonStyle(.plain) 将意味着只有按钮内容直接周围的空间可以点击;而如果使用 .renderingMode(.original) 则整个单元格仍然可点击。

</aside>

4. 按钮重复操作 buttonRepeatBehavior

有一个专用的 buttonRepeatBehavior() 修饰符,当用户按住按钮时,它会重复触发按钮的操作。该动作的触发速度越来越快,因此用户按住它的时间越长,触发的速度就越快。

// 例如,按下按钮时计数器会加 1,但如果按住按钮,它会继续以越来越快的速度加 1:
struct ContentView: View {
    @State private var tapCount = 0
    var body: some View {
        Button("Tap Count: \\(tapCount)") {
            tapCount += 1
        }
        .buttonRepeatBehavior(.enabled)
    }
}

// 这种重复行为也适用于键盘快捷键,尽管它受到用户的键盘重复率的限制。
struct ContentView: View {
    @State private var tapCount = 0
    var body: some View {
        Button("Tap Count: \\(tapCount)") {
            tapCount += 1
        }
        .buttonRepeatBehavior(.enabled)
        // 这允许用户按住 Shift+Return 来重复触发我们的按钮
        .keyboardShortcut(.return, modifiers: .shift)
    }
}

5. 使用键盘快捷键

SwiftUI 可以轻松地为支持它的设备(例如 iPadOS 和 macOS)添加键盘快捷键,所有这些都使用 keyboardShortcut() 修饰符。您可以通过三种方式使用此修饰符,从最基本的开始:


ButtonStyle 协议

Swift提供了一个 ButtonStyle 的协议,让我们可以重复使用按钮样式。

1. 创建遵循 ButtonStyle 协议的结构体

// 创建新的按钮样式符合 ButtonStyle 协议
struct GradientBackgroundStyle: ButtonStyle {
	func makeBody(configuration: Self.Configuration) -> some View {
	    configuration.label
			    //把自己的样式修饰符都写到这
	        .frame(minWidth: 0, maxWidth: .infinity)
	        .padding()
	        .foregroundColor(.white)
	        .background(LinearGradient(gradient: Gradient(colors: [Color("DarkGreen"), Color("LightGreen")]), startPoint: .leading, endPoint: .trailing))
	        .cornerRadius(40)
	        .padding(.horizontal, 20)
	}
}

// 调用时:
Button(
	action: { print("Delete tapped!") }
){
	...
	}
}
// 对所有按钮应用相同的样式
.buttonStyle(GradientBackgroundStyle())

2. 结构体中加入参数

// 自定义按钮样式,带有背景颜色和圆角参数
struct CustomButtonStyle: ButtonStyle {
    let backgroundColor: Color
    let cornerRadius: CGFloat

    // 初始化方法,用于设置参数
    init(backgroundColor: Color, cornerRadius: CGFloat) {
        self.backgroundColor = backgroundColor
        self.cornerRadius = cornerRadius
    }

    func makeBody(configuration: Configuration) -> some View {
        configuration.label
            .padding(10)
            .background(backgroundColor)
            .foregroundColor(.white)
            .cornerRadius(cornerRadius)
            .scaleEffect(configuration.isPressed ? 0.95 : 1.0) // 添加按下效果
    }
}

// 使用时
struct ContentView: View {
    var body: some View {
        Button(action: { ... }) {
            Text("Custom Button")
        }
        .buttonStyle(CustomButtonStyle(backgroundColor: Color.blue, cornerRadius: 10))
    }
}

3. 使用 isPressed 属性

configurationisPressed 屬性。可以通过判断 isPressed 是否为true ,来判断按钮是否被按,以便直接设定按钮被按下时的样式。

struct GradientBackgroundStyle: ButtonStyle {
	func makeBody(configuration: Self.Configuration) -> some View {
	    configuration.label
	        .frame(minWidth: 0, maxWidth: .infinity)
					// 为真执行前面,为假执行后面
					.scaleEffect(configuration.isPressed ? 0.9 : 1.0)
	}
}

ControlGroup 按钮归组

ControlGroup 视图让我们告诉系统两个或多个视图应该组合在一起,因为它们是相关的。它对这些信息的作用取决于使用它们的上下文以及代码运行的平台。例如,在 iOS 和 macOS 上,这将显示水平连接的三个按钮,其样式有时称为“瞬时分段”:

ControlGroup {
    Button("First") { }
    Button("Second") { }
    Button("Third") { }
}
.padding()

ControlGroup 在创建可自定义的工具栏时特别有用,其中控件组中的按钮必须一起添加或删除,而不是分开。