(对于SwiftUI,不是普通的UIKit)非常简单的示例代码,例如,在灰色背景上显示红色框:
struct ContentView : View {
@State var points:[CGPoint] = [CGPoint(x:0,y:0), CGPoint(x:50,y:50)]
var body: some View {
return ZStack {
Color.gray
.tapAction {
// TODO: add an entry to self.points of the location of the tap
}
ForEach(self.points.identified(by: \.debugDescription)) {
point in
Color.red
.frame(width:50, height:50, alignment: .center)
.offset(CGSize(width: point.x, height: point.y))
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
我假设不是需要tapAction,而是需要TapGesture或其他东西?但即使在那儿,我也看不到任何方法来获取有关水龙头位置的信息。我将如何处理?
小智 8
我能够通过做到这一点DragGesture(minimumDistance: 0)
。然后使用startLocation
从Value
上onEnded
找到水龙头的第一个位置。
使用上面的一些答案,我制作了一个可能有用的 ViewModifier:
struct OnTap: ViewModifier {
let response: (CGPoint) -> Void
@State private var location: CGPoint = .zero
func body(content: Content) -> some View {
content
.onTapGesture {
response(location)
}
.simultaneousGesture(
DragGesture(minimumDistance: 0)
.onEnded { location = $0.location }
)
}
}
extension View {
func onTapGesture(_ handler: @escaping (CGPoint) -> Void) -> some View {
self.modifier(OnTap(response: handler))
}
}
Run Code Online (Sandbox Code Playgroud)
然后像这样使用:
Rectangle()
.fill(.green)
.frame(width: 200, height: 200)
.onTapGesture { location in
print("tapped: \(location)")
}
Run Code Online (Sandbox Code Playgroud)
小智 6
以防万一有人需要它,我已将上述答案转换为视图修饰符,该修饰符也将 CoordinateSpace 作为可选参数
import SwiftUI
import UIKit
public extension View {
func onTapWithLocation(coordinateSpace: CoordinateSpace = .local, _ tapHandler: @escaping (CGPoint) -> Void) -> some View {
modifier(TapLocationViewModifier(tapHandler: tapHandler, coordinateSpace: coordinateSpace))
}
}
fileprivate struct TapLocationViewModifier: ViewModifier {
let tapHandler: (CGPoint) -> Void
let coordinateSpace: CoordinateSpace
func body(content: Content) -> some View {
content.overlay(
TapLocationBackground(tapHandler: tapHandler, coordinateSpace: coordinateSpace)
)
}
}
fileprivate struct TapLocationBackground: UIViewRepresentable {
var tapHandler: (CGPoint) -> Void
let coordinateSpace: CoordinateSpace
func makeUIView(context: UIViewRepresentableContext<TapLocationBackground>) -> UIView {
let v = UIView(frame: .zero)
let gesture = UITapGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.tapped))
v.addGestureRecognizer(gesture)
return v
}
class Coordinator: NSObject {
var tapHandler: (CGPoint) -> Void
let coordinateSpace: CoordinateSpace
init(handler: @escaping ((CGPoint) -> Void), coordinateSpace: CoordinateSpace) {
self.tapHandler = handler
self.coordinateSpace = coordinateSpace
}
@objc func tapped(gesture: UITapGestureRecognizer) {
let point = coordinateSpace == .local
? gesture.location(in: gesture.view)
: gesture.location(in: nil)
tapHandler(point)
}
}
func makeCoordinator() -> TapLocationBackground.Coordinator {
Coordinator(handler: tapHandler, coordinateSpace: coordinateSpace)
}
func updateUIView(_: UIView, context _: UIViewRepresentableContext<TapLocationBackground>) {
/* nothing */
}
}
Run Code Online (Sandbox Code Playgroud)
也可以使用手势。
如果发生拖动或在点击或点击时触发动作,还有一些工作可以取消点击。
struct ContentView: View {
@State var tapLocation: CGPoint?
@State var dragLocation: CGPoint?
var locString : String {
guard let loc = tapLocation else { return "Tap" }
return "\(Int(loc.x)), \(Int(loc.y))"
}
var body: some View {
let tap = TapGesture().onEnded { tapLocation = dragLocation }
let drag = DragGesture(minimumDistance: 0).onChanged { value in
dragLocation = value.location
}.sequenced(before: tap)
Text(locString)
.frame(width: 200, height: 200)
.background(Color.gray)
.gesture(drag)
}
}
Run Code Online (Sandbox Code Playgroud)
好吧,经过一番修改,并感谢对我的另一个问题的回答,我找到了一种使用UIViewRepresentable的方法(但是,一定要让我知道是否有更简单的方法!)为了我!
struct ContentView : View {
@State var points:[CGPoint] = [CGPoint(x:0,y:0), CGPoint(x:50,y:50)]
var body: some View {
return ZStack(alignment: .topLeading) {
Background {
// tappedCallback
location in
self.points.append(location)
}
.background(Color.white)
ForEach(self.points.identified(by: \.debugDescription)) {
point in
Color.red
.frame(width:50, height:50, alignment: .center)
.offset(CGSize(width: point.x, height: point.y))
}
}
}
}
struct Background:UIViewRepresentable {
var tappedCallback: ((CGPoint) -> Void)
func makeUIView(context: UIViewRepresentableContext<Background>) -> UIView {
let v = UIView(frame: .zero)
let gesture = UITapGestureRecognizer(target: context.coordinator,
action: #selector(Coordinator.tapped))
v.addGestureRecognizer(gesture)
return v
}
class Coordinator: NSObject {
var tappedCallback: ((CGPoint) -> Void)
init(tappedCallback: @escaping ((CGPoint) -> Void)) {
self.tappedCallback = tappedCallback
}
@objc func tapped(gesture:UITapGestureRecognizer) {
let point = gesture.location(in: gesture.view)
self.tappedCallback(point)
}
}
func makeCoordinator() -> Background.Coordinator {
return Coordinator(tappedCallback:self.tappedCallback)
}
func updateUIView(_ uiView: UIView,
context: UIViewRepresentableContext<Background>) {
}
}
Run Code Online (Sandbox Code Playgroud)
一个简单的解决方案是使用DragGesture并将minimumDistance参数设置为 0,使其类似于点击手势:
Color.gray
.gesture(DragGesture(minimumDistance: 0).onEnded({ (value) in
print(value.location) // Location of the tap, as a CGPoint.
}))
Run Code Online (Sandbox Code Playgroud)
如果是点击手势,它将返回此点击的位置。但是,它还会返回拖动手势的结束位置——也称为“触摸事件”。可能不是想要的行为,所以请记住它。
我想出的最正确且与 SwiftUI 兼容的实现是这个。您可以像使用任何常规 SwiftUI 手势一样使用它,甚至可以将其与其他手势结合使用,管理手势优先级等...
import SwiftUI
struct ClickGesture: Gesture {
let count: Int
let coordinateSpace: CoordinateSpace
typealias Value = SimultaneousGesture<TapGesture, DragGesture>.Value
init(count: Int = 1, coordinateSpace: CoordinateSpace = .local) {
precondition(count > 0, "Count must be greater than or equal to 1.")
self.count = count
self.coordinateSpace = coordinateSpace
}
var body: SimultaneousGesture<TapGesture, DragGesture> {
SimultaneousGesture(
TapGesture(count: count),
DragGesture(minimumDistance: 0, coordinateSpace: coordinateSpace)
)
}
func onEnded(perform action: @escaping (CGPoint) -> Void) -> _EndedGesture<ClickGesture> {
self.onEnded { (value: Value) -> Void in
guard value.first != nil else { return }
guard let location = value.second?.startLocation else { return }
guard let endLocation = value.second?.location else { return }
guard ((location.x-1)...(location.x+1)).contains(endLocation.x),
((location.y-1)...(location.y+1)).contains(endLocation.y) else {
return
}
action(location)
}
}
}
Run Code Online (Sandbox Code Playgroud)
上面的代码定义了一个符合 SwiftUIGesture
协议的结构体。这个手势是 aTapGesture
和 a 的组合DragGesture
。这是确保手势是点击并同时检索点击位置所必需的。
该onEnded
方法检查两个手势是否都发生,并通过作为参数传递的转义闭包将位置作为 CGPoint 返回。最后两个guard
语句在这里处理多个点击手势,因为用户可以点击稍微不同的位置,这些线引入了 1 点的容差,如果想要更大的灵活性,可以更改。
extension View {
func onClickGesture(
count: Int,
coordinateSpace: CoordinateSpace = .local,
perform action: @escaping (CGPoint) -> Void
) -> some View {
gesture(ClickGesture(count: count, coordinateSpace: coordinateSpace)
.onEnded(perform: action)
)
}
func onClickGesture(
count: Int,
perform action: @escaping (CGPoint) -> Void
) -> some View {
onClickGesture(count: count, coordinateSpace: .local, perform: action)
}
func onClickGesture(
perform action: @escaping (CGPoint) -> Void
) -> some View {
onClickGesture(count: 1, coordinateSpace: .local, perform: action)
}
}
Run Code Online (Sandbox Code Playgroud)
最后,View
扩展被定义为提供onDragGesture
与其他原生手势相同的 API 。
像使用任何 SwiftUI 手势一样使用它:
struct ContentView : View {
@State var points:[CGPoint] = [CGPoint(x:0,y:0), CGPoint(x:50,y:50)]
var body: some View {
return ZStack {
Color.gray
.onClickGesture { point in
points.append(point)
}
ForEach(self.points.identified(by: \.debugDescription)) {
point in
Color.red
.frame(width:50, height:50, alignment: .center)
.offset(CGSize(width: point.x, height: point.y))
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
1456 次 |
最近记录: |