作为一名刚接触SwiftUI的开发者,在使用 Button 时还是遇到了不少的困惑。网上绝大部分教程都只是浅浅地说了下用法,授人以鱼不如授人以渔,let’s go~
1.Xcode使用(如已经熟练,请跳过)
在Xcode中,我们可以按住 command + shift + L
组合键,来调用出一个窗口,在这个窗口下,我们可以很方便的查找我们所需的一些 Controls,比如Button,Text等,如图所示:
在右侧位置,可以看到Button的一些相关说明,划到底部,我们可以访问它的官方文档地址。
在Xcode中,你可以通过拖拽的方式来快速生成一个Button组件,亦或者直接在代码中输入Button字样,也可以得到相关的编译提示,如图所示:
接着我们按住 command + 鼠标左键
,按回车去到Button的定义文件中
2. Button的参数
在定义文件中,将Button相关的先折叠起来,如图所示
其中 Label
为 Button中的泛型,它遵循了 View
协议,并决定了每个Button实例将使用什么类型的视图进行渲染。
根据不同的Label
,我们可以使用不同的初始化参数。
初始化参数(-):action与label
根据类型的定义可以知道,action参数
为一个逃逸闭包,当用户单击或点击按钮时它会执行相关操作。而label参数
为一个尾随闭包,我们可以在其中写一些自定义的视图(custom view)。所以对于这种初始化,我们可以写出以下代码:
// 正常写法
Button(action: {}, label: {
Text("按钮")
})
// 由于参数label是个尾随闭包,则可以省略关键字,用大括号直接打开
Button(action:{}){
Text("按钮")
}
// 由于label参数的返回类型是泛型Label,而泛型Label又遵循了View协议,所以我们能在其中写相关自定义的视图
Button(action:{}){
VStack{
Text("按钮")
Text("描述文字")
}
.foregroundColor(.red)
}
初始化参数(二): titleKey/title,action
根据类型的定义可以知道,在第一个参数中,我们可以传 LocalizedStringKey
或者是普通的 String
字符串,action
参数则同上。
什么是 LocalizedStringKey
?
对于LocalizedStringKey
官方文档是这么解释的:The key used to look up an entry in a strings file or strings dictionary file.
意思就是,当你的字符串用 LocalizedStringKey
创建或者使用其类型声明时,SwiftUI会根据你当前的语言环境,自动翻译成对应的字符串。比如:
let hello:LocalizedStringKey = "Hello"
let today = LocalizedStringKey("Today")
var body: some View {
VStack{
Text(hello) //在中文环境下,将会变成字符串 "你好"
Text(today) //在中文环境下,将会变成字符串 "今天"
}
.font(.largeTitle)
}
接下来我们看 preview 的效果
额…这不是没啥变化吗?在这里,我们需要做一些配置。我们先点击根项目,然后添加对应的中文简体语言,如图所示:
接着新建一个Strings File文件,使用其默认命名即可。
接着点击右侧检查器位置的 Localize 按钮,在弹出的弹窗中,点击Localize
同样在检查器 Localzation 中,选择中文简体,如图所示:
接着我们在 Localizable 文件夹中的两个文件内,写入对应的翻译。
Tips:笔者查了一圈下来,大部分的做法是在Preview中加上 .environment(\.locale, .init(identifier: "zh-Hans"))
,这样可以预览到语言的变化。但在笔者的Xcode中,这不管用,可能是我Xcode的版本(14.2)过高了。这里我们选择 command + R
运行模拟器来看效果。注意,你需要在模拟器的系统中,切换手机的系统语言,如图所示:
然后打开我们构建后的App应用,就可以看到效果了。
我们在原来代码的基础上,再添加一些:
let str = "Hello"
var body: some View {
VStack{
Text(hello) //在中文环境下,将会变成字符串 "你好"
Text(today) //在中文环境下,将会变成字符串 "今天"
Text("Hello") // 新增
Text(str) // 新增
}
.font(.largeTitle)
}
猜猜看,它的结果是什么?
在 SwiftUI 中,Button、Text
等视图,在传入文本参数时会优先进行语言本地化。如果你不想将文本翻译成本地化语言,可以像上方一样,将Text中的文本值抽出来,提前用let声明即可。
———————————分割线————————————
了解完 LocalizedStringKey
后,是不是第二种 Button
的传值方式便心领神会啦~我们写出以下的代码:
let str = "Hello"
var body: some View {
VStack{
Button("按钮", action: {})
Button("按钮"){}
}
.font(.largeTitle)
}
在后续的定义文件中,也就是ios15.0,我们看到了 SwiftUI 对于这两种初始化,加多了一个可选参数 role
,如图所示
在 role
传参中,有两种 ButtonRole
供你选择。
- destructive:用于删除用户数据或执行不可逆操作的按钮。
- cancel: 用于取消操作按钮。
说时迟那时快,相信有朋友已经快速写出以下代码试试水了
VStack{
Button("按钮",role: .cancel){}
Button("按钮",role: .destructive){}
}
.font(.largeTitle)
啊这…这不就按钮改个颜色吗,这也能单独抽个role
参数? 其实这两种 role 主要结合着 .swipeActions
和 .alert
进行使用,如下所示:
struct ButtonView: View {
@State private var isPresented = false
var body: some View {
VStack{
List{
Text("测试滑动")
.swipeActions {
Button("普通按钮", action: {})
Button("取消按钮", role: .cancel, action: {})
Button("删除按钮", role: .destructive, action: {})
}
}
Button("Show Alert", action: {
isPresented = true
})
.alert("Alert", isPresented: $isPresented) {
Button("取消按钮", role: .cancel, action: {})
Button("普通按钮", action: {})
Button("删除按钮", role: .destructive, action: {})
}
}
}
}
感兴趣的朋友可以看看具体的样式体现~由于今天我们主要讲Button
,就不对其他的进行拓展了(等会讲不完了)。
初始化参数(三):configuration
根据类型的定义可以得知,我们可以传入一个 PrimitiveButtonStyleConfiguration
类型的参数。根据上方的注释,我们相关例子定义一个struct
,这个struct
需要遵循 PrimitiveButtonStyle
协议,并且我们需要在 .buttonStyle
修饰符中进行使用,如图所示:
ok话不多说,我们先照猫画虎,把这个struct
按照例子先实现一下
struct ButtonView: View {
struct RedBorderedButtonStyle: PrimitiveButtonStyle {
func makeBody(configuration: Configuration) -> some View {
// 这里就是Button接收configuration的情况啦
Button(configuration)
.border(Color.red)
}
}
var body: some View {
VStack{
Button("按钮"){}
.buttonStyle(RedBorderedButtonStyle())
}
.font(.largeTitle)
}
}
通过预览后的UI我们可以看到,它为当前的按钮加了个红色边框。oh~不会绕了一圈,只是加了个红色边框而已吧? 别着急,我们还没使用 configuration
这个 struct 里面的内容呢。这里的Configuration
实际上就是 PrimitiveButtonStyleConfiguration
,我们来看看它这里面有啥。
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
public struct PrimitiveButtonStyleConfiguration {
public struct Label : View {.
public typealias Body = Never
}
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
public let role: ButtonRole?
/// A view that describes the effect of calling the button's action.
public let label: PrimitiveButtonStyleConfiguration.Label
/// Performs the button's action.
public func trigger()
}
role我们在上面已经讲过了,这里不再多讲。label
表示的是我们当前触发这个按钮后,需要展现出来的视图形式。 trigger
方法表示你可以通过调用 configuration.trigger()
的方式来主动触发按钮。我们先写出以下的代码:
struct ButtonView: View {
struct RedBorderedButtonStyle: PrimitiveButtonStyle {
func makeBody(configuration: Configuration) -> some View {
// 主动触发按钮
configuration.trigger()
return configuration.label.border(.red)
// configuration.label.onTapGesture {
// configuration.trigger()
// }
}
}
var body: some View {
VStack{
Button("按钮"){
print("按钮被触发了~")
}
.buttonStyle(RedBorderedButtonStyle())
}
.font(.largeTitle)
}
}
预览UI如图所示:
虽然我们从视图的展现形式来看是一样的效果,但不同的是,我们主动触发了按钮。也就是说它在展示出来的时候,按钮就已经被触发了,我们可以看控制台的输出。
Tips: Xcode到高版本后,输出只能在 Simulator
中查看,所以command + R
以查看相关输出。
3. Button的样式与交互
3-1: ButtonStyle
在阅读Button
文档的过程中,想必你注意到了这样一段话:
You can also create custom styles. To add a custom appearance with standard interaction behavior, create a style that conforms to the ButtonStyle
protocol. To customize both appearance and interaction behavior, create a style that conforms to the PrimitiveButtonStyle
protocol. Custom styles can also read the button’s role and use it to adjust the button’s appearance.
官方语言讲得就是很官方,我们还是来看看 ButtonStyle
的相关定义吧。
我们可以发现,这个 ButtonStyle
协议好像和PrimitiveButtonStyle
协议差不多。
接着看一下ButtonStyleConfiguration
,可以发现它里面提供的是 isPressed
常量,用来判断当前用户是否按下按钮。而之前的PrimitiveButtonStyleConfiguration
提供的是 trigger
方法,可以允许我们主动去触发按钮。
接下来我们通过一个需求来更好的体会 ButtonStyle
的作用。比如我想在用户按下按钮时,让按钮增加一些变化。如下所示:
struct ButtonView: 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 {
VStack{
Button("按钮"){
print("按钮被触发了~")
}
.buttonStyle(CustomButtonStyle())
}
.font(.largeTitle)
}
}
我们来看看实际的效果:
Tips: 可以发现,我们是在按下按钮执行对应的效果后,再触发 Button
的 action。
3-2:ButtonStyle 和 PrimitiveButtonStyle的区别
在做完以上的事情后,相信你对这两种 Style 的理解已经很透彻了。那么我们可以总结一下这两种Style的使用场景。
- 想对按钮的样式做一些修改并且主动触发按钮,我们可以使用
PrimitiveButtonStyle
。 - 想在按钮被按下时做一些效果交互,我们可以使用
ButtonStyle
。
3-3:使用Modifier添加自定义样式
除了以上的方法,我们还可以创建一个自定义的 Modifier 来修改相关按钮的样式。我们按下 command + N
创建一个 Styles
文件,并写上以下代码:
import SwiftUI
struct YellowButtonStyle:ViewModifier{
func body(content: Content) -> some View {
content
.padding()
.background(.yellow)
.cornerRadius(10)
.foregroundColor(.white)
}
}
接着我们可以在代码中这样去使用:
Button("黄色按钮"){}.modifier(YellowButtonStyle())
这样看起来方便了一些,但这个 modifier 看着很不顺眼,可以去掉吗?当然了。我们可以在Styles文件中,对 YellowButtonStyle
进行拓展,如下:
import SwiftUI
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())
}
}
然后我们这样去使用即可:
Button("黄色按钮"){}.yellowButtonStyle()
4.Button的拓展
上面我们提到过,在 Buttton 中提供了一个 Label 泛型。我们可以利用该泛型进行对应的拓展。比如我们想创建一个图像按钮,可以这样做:
struct ButtonView: 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)
}
}
}
效果如图所示:
你可能会很好奇,这个 “camera.shutter.button” 是怎么来的?Apple其实给我提供了一些内置的符号,我们可以在官网的设计资源中下载对应的软件。
以上就是全部内容了,感谢你坚持读完,欢迎在评论区进行交流~