SwiftUI文本按钮点击颜色变化: @State与@GestureState实现
2025-01-04 07:44:08
SwiftUI 文本按钮交互颜色变换
在 SwiftUI 中,通过点击手势改变文本“按钮”的颜色,并在手指移开时恢复原色,是一种常见的交互需求。虽然这些并非传统意义上的 Button
组件,但可以通过巧妙的 Text
组件结合手势和状态管理来实现所需效果。下面提供几种实现方式,并分析其原理与应用。
使用 @State 管理状态
一个直接的解决方案是使用 @State
属性包装器来管理按钮的选中状态,并根据这个状态动态更改文本颜色和背景色。这是一种相对简单的方式,可以适用于简单的场景。
原理: @State
能够在视图结构中存储状态数据,当 @State
属性的值发生改变时,SwiftUI 将自动刷新视图。我们可以通过一个布尔值(例如isTapped
)来表示按钮是否被按下,并在 onTapGesture
闭包中更新这个值,以此触发视图更新。
步骤:
- 为每个按钮添加一个
@State
布尔值。 - 在
onTapGesture
闭包中切换这个布尔值。 - 根据这个布尔值动态地设置文本颜色和背景。
代码示例:
struct ContentView: View {
@State private var selectedIngredient: String?
let productIngredients = ["苹果", "香蕉", "橙子", "葡萄"] // 模拟数据
var body: some View {
LazyVGrid(columns: Array(repeating: GridItem(.flexible(), spacing: 5), count: 2), spacing: 2) {
ForEach(productIngredients, id: \.self) { ingredient in
Text(ingredient)
.font(.system(size: 14))
.fontWeight(.medium)
.foregroundColor(selectedIngredient == ingredient ? .white : .black)
.padding(8)
.background(RoundedRectangle(cornerRadius: 10).stroke(selectedIngredient == ingredient ? Color.blue : Color.black, lineWidth: 2).background(selectedIngredient == ingredient ? Color.blue : Color.clear))
.padding(.top,5)
.onTapGesture {
if selectedIngredient == ingredient {
selectedIngredient = nil
} else{
selectedIngredient = ingredient
}
}
}
}
}
}
代码解析:
- 使用了
selectedIngredient
字符串变量来保存当前被选中的按钮文本,默认为nil。 - 在 Text 中根据 selectedIngredient 来动态变更 foregroundColor 以及背景颜色和边框的颜色
onTapGesture
闭包在点击的时候会设置selectedIngredient
的值
操作: 直接将代码添加到你的SwiftUI项目并运行。
使用 @GestureState 实现瞬时颜色变化
若需要实现手指按下时变色,抬起时恢复原色的瞬时变化,单纯的@State
可能不够用,可以借助 @GestureState
属性包装器。 @GestureState
常与 DragGesture
等手势结合使用,但同样可以与 onTapGesture
搭配使用,以捕捉更细致的手势状态。
原理: @GestureState
与 @State
不同,其值在手势激活期间有效,手势结束时自动恢复初始值。 因此,它可以用来实现一种 "瞬间" 的状态变更效果,例如手指点击变色,抬起恢复原样。
步骤:
- 为文本视图添加一个
@GestureState
属性(例如isPressing
)。 - 修改文本视图的
onTapGesture
为gesture(_:including:)
- 修改颜色为
@GestureState
变量是否激活来设置 - 使用
onEnded
事件来将@GestureState
状态复位,以便下次可以再次触发
代码示例:
struct ContentView: View {
@State private var selectedIngredient: String?
@GestureState private var isPressing = false
let productIngredients = ["苹果", "香蕉", "橙子", "葡萄"] // 模拟数据
var body: some View {
LazyVGrid(columns: Array(repeating: GridItem(.flexible(), spacing: 5), count: 2), spacing: 2) {
ForEach(productIngredients, id: \.self) { ingredient in
Text(ingredient)
.font(.system(size: 14))
.fontWeight(.medium)
// 这里将 text 颜色使用 变量状态来进行变化
.foregroundColor(isPressing ? .white : ( selectedIngredient == ingredient ? .white : .black ))
.padding(8)
.background(RoundedRectangle(cornerRadius: 10).stroke( isPressing ? Color.blue : ( selectedIngredient == ingredient ? Color.blue : Color.black), lineWidth: 2).background( isPressing ? Color.blue : ( selectedIngredient == ingredient ? Color.blue : Color.clear )))
.padding(.top,5)
.gesture(
DragGesture(minimumDistance: 0)
.updating($isPressing) { _, state, _ in
state = true // 手指触摸的时候设置当前的状态变量
}.onEnded { _ in
if selectedIngredient == ingredient {
selectedIngredient = nil
} else{
selectedIngredient = ingredient
}
}
)
}
}
}
}
代码解析:
@GestureState
类型的变量isPressing
初始值为false- 当按下手指触摸Text时,isPressing状态变成true。
- 文本视图的颜色和背景色跟随
isPressing
的变化 - 当触摸事件结束后(也就是手放开),isPressing被自动复位为false。
.updating($isPressing)
用于在手势进行中同步isPressing
的状态.onEnded{}
用于在手势结束的时候触发响应,修改selectedIngredient
操作: 复制上述代码到 SwiftUI 项目,尝试点击“按钮”,可以观察到手指按下和抬起时,颜色的瞬时变化效果。
注意事项
- 状态管理的粒度: 复杂应用中,状态管理可以考虑使用
ObservableObject
、EnvironmentObject
来进行全局数据维护,减少视图的冗余代码和方便数据的管理与更新。 - 可访问性考虑:除了颜色,还需要考虑其他辅助措施来提示按钮的可交互性,例如文本内容的提示、触觉反馈等, 增强应用的用户体验和可访问性。
- 性能优化: SwiftUI视图的渲染机制在大型列表或者大量动态数据场景可能会引起性能问题,应避免不必要的视图重绘,做好列表的数据缓存,并在有需要的时候引入懒加载方式。
以上方法能够实现基础的文本“按钮”颜色交互,选用方案可根据项目的复杂度和交互要求灵活决定。