任何等效于 UIKit 的 "touch down" 的 SwiftUI 按钮,即当您的手指触摸时激活按钮?
Any SwiftUI Button equivalent to UIKit's "touch down", i.e. activate button when your finger touches?
对于 SwiftUI,默认按钮行为等同于 UIKit 的 "touch up inside",它会在您的手指触摸按钮然后在按钮范围内抬起时激活。
有什么方法可以将其更改为 "touch down",以便在您的手指触摸按钮时立即关闭操作 运行?
您可以使用最小距离为零的 DragGesture 并为向下 (onChanged()) 或向上 (onEnded()) 定义闭包:
struct ContentView: View {
@State private var idx = 0
var body: some View {
let g = DragGesture(minimumDistance: 0, coordinateSpace: .local).onChanged({
print("DOWN: \([=10=])")
}).onEnded({
print("UP: \([=10=])")
})
return Rectangle().frame(width: 100, height: 50).gesture(g)
}
}
您可以创建自定义视图修饰符:
extension View {
func onTouchDownGesture(callback: @escaping () -> Void) -> some View {
modifier(OnTouchDownGestureModifier(callback: callback))
}
}
private struct OnTouchDownGestureModifier: ViewModifier {
@State private var tapped = false
let callback: () -> Void
func body(content: Content) -> some View {
content
.simultaneousGesture(DragGesture(minimumDistance: 0)
.onChanged { _ in
if !self.tapped {
self.tapped = true
self.callback()
}
}
.onEnded { _ in
self.tapped = false
})
}
}
struct MyView: View {
var body: some View {
Text("Hello World")
.onTouchDownGesture {
print("View did tap!")
}
}
}
我设法通过一个简单的按钮修改器实现了这一点:
struct TouchedButtonStyle: PrimitiveButtonStyle {
func makeBody(configuration: Configuration) -> some View {
configuration
.label
.onTapGesture(perform: configuration.trigger)
}
}
现在您只需将修饰符分配给您的按钮即可:
YourButton()
.buttonStyle(TouchedButtonStyle())
您可以在 View
上使用隐藏的 _onButtonGesture
方法,即 public。它甚至不需要附加到 Button
,但它看起来更好,因为你看到了按下效果。
代码:
struct ContentView: View {
@State private var counter = 0
@State private var pressing = false
var body: some View {
VStack(spacing: 30) {
Text("Count: \(counter)")
Button("Increment") {
counter += 1
}
._onButtonGesture { pressing in
self.pressing = pressing
} perform: {}
Text("Pressing button: \(pressing ? "yes" : "no")")
}
}
}
结果:
由于帧速率的原因,作为 GIF 看起来不太好,但只要按下 pressing
就会转到 true
,然后在释放时 false
.
虽然 DragGesture
在许多情况下效果很好,但它可能会产生一些不需要的副作用,例如在滚动视图中使用时,它将优先于滚动视图的 built-in 手势处理。
在 SwiftUI 中,Button
s 已经跟踪按下状态,因此解决此问题的方法是使用自定义 ButtonStyle
以允许挂钩 isPressed
状态。
这是一个可行的解决方案:
定义自定义 ButtonStyle
:
struct CustomButtonStyle: ButtonStyle {
var onPressed: () -> Void
var onReleased: () -> Void
// Wrapper for isPressed where we can run custom logic via didSet (or willSet)
@State private var isPressedWrapper: Bool = false {
didSet {
// new value is pressed, old value is not pressed -> switching to pressed state
if (isPressedWrapper && !oldValue) {
onPressed()
}
// new value is not pressed, old value is not pressed -> switching to unpressed state
else if (oldValue && !isPressedWrapper) {
onReleased()
}
}
}
// return the label unaltered, but add a hook to watch changes in configuration.isPressed
func makeBody(configuration: Self.Configuration) -> some View {
return configuration.label
.onChange(of: configuration.isPressed, perform: { newValue in isPressedWrapper = newValue })
}
}
您也可以直接在 onChange
修饰符的 perform
块中编写 didSet
逻辑,但我认为这使它看起来很干净。
用 Button
包装您的可点击视图
struct ExampleView: View {
@State private var text: String = "Unpressed"
var body: some View {
Text(self.text)
Button(action: { ... }, label: {
// your content here
}).buttonStyle(CustomButtonStyle(onPressed: {
self.text = "Pressed!"
}, onReleased: {
self.text = "Unpressed"
}))
}
}
然后你可以将任何你想要的逻辑传递给 onPressed
和 onReleased
参数给 CustomButtonStyle
.
我用它来允许自定义 onPressed
处理 ScrollView
中的可点击行。
对于 SwiftUI,默认按钮行为等同于 UIKit 的 "touch up inside",它会在您的手指触摸按钮然后在按钮范围内抬起时激活。
有什么方法可以将其更改为 "touch down",以便在您的手指触摸按钮时立即关闭操作 运行?
您可以使用最小距离为零的 DragGesture 并为向下 (onChanged()) 或向上 (onEnded()) 定义闭包:
struct ContentView: View {
@State private var idx = 0
var body: some View {
let g = DragGesture(minimumDistance: 0, coordinateSpace: .local).onChanged({
print("DOWN: \([=10=])")
}).onEnded({
print("UP: \([=10=])")
})
return Rectangle().frame(width: 100, height: 50).gesture(g)
}
}
您可以创建自定义视图修饰符:
extension View {
func onTouchDownGesture(callback: @escaping () -> Void) -> some View {
modifier(OnTouchDownGestureModifier(callback: callback))
}
}
private struct OnTouchDownGestureModifier: ViewModifier {
@State private var tapped = false
let callback: () -> Void
func body(content: Content) -> some View {
content
.simultaneousGesture(DragGesture(minimumDistance: 0)
.onChanged { _ in
if !self.tapped {
self.tapped = true
self.callback()
}
}
.onEnded { _ in
self.tapped = false
})
}
}
struct MyView: View {
var body: some View {
Text("Hello World")
.onTouchDownGesture {
print("View did tap!")
}
}
}
我设法通过一个简单的按钮修改器实现了这一点:
struct TouchedButtonStyle: PrimitiveButtonStyle {
func makeBody(configuration: Configuration) -> some View {
configuration
.label
.onTapGesture(perform: configuration.trigger)
}
}
现在您只需将修饰符分配给您的按钮即可:
YourButton()
.buttonStyle(TouchedButtonStyle())
您可以在 View
上使用隐藏的 _onButtonGesture
方法,即 public。它甚至不需要附加到 Button
,但它看起来更好,因为你看到了按下效果。
代码:
struct ContentView: View {
@State private var counter = 0
@State private var pressing = false
var body: some View {
VStack(spacing: 30) {
Text("Count: \(counter)")
Button("Increment") {
counter += 1
}
._onButtonGesture { pressing in
self.pressing = pressing
} perform: {}
Text("Pressing button: \(pressing ? "yes" : "no")")
}
}
}
结果:
由于帧速率的原因,作为 GIF 看起来不太好,但只要按下 pressing
就会转到 true
,然后在释放时 false
.
虽然 DragGesture
在许多情况下效果很好,但它可能会产生一些不需要的副作用,例如在滚动视图中使用时,它将优先于滚动视图的 built-in 手势处理。
在 SwiftUI 中,Button
s 已经跟踪按下状态,因此解决此问题的方法是使用自定义 ButtonStyle
以允许挂钩 isPressed
状态。
这是一个可行的解决方案:
定义自定义 ButtonStyle
:
struct CustomButtonStyle: ButtonStyle {
var onPressed: () -> Void
var onReleased: () -> Void
// Wrapper for isPressed where we can run custom logic via didSet (or willSet)
@State private var isPressedWrapper: Bool = false {
didSet {
// new value is pressed, old value is not pressed -> switching to pressed state
if (isPressedWrapper && !oldValue) {
onPressed()
}
// new value is not pressed, old value is not pressed -> switching to unpressed state
else if (oldValue && !isPressedWrapper) {
onReleased()
}
}
}
// return the label unaltered, but add a hook to watch changes in configuration.isPressed
func makeBody(configuration: Self.Configuration) -> some View {
return configuration.label
.onChange(of: configuration.isPressed, perform: { newValue in isPressedWrapper = newValue })
}
}
您也可以直接在 onChange
修饰符的 perform
块中编写 didSet
逻辑,但我认为这使它看起来很干净。
用 Button
包装您的可点击视图
struct ExampleView: View {
@State private var text: String = "Unpressed"
var body: some View {
Text(self.text)
Button(action: { ... }, label: {
// your content here
}).buttonStyle(CustomButtonStyle(onPressed: {
self.text = "Pressed!"
}, onReleased: {
self.text = "Unpressed"
}))
}
}
然后你可以将任何你想要的逻辑传递给 onPressed
和 onReleased
参数给 CustomButtonStyle
.
我用它来允许自定义 onPressed
处理 ScrollView
中的可点击行。