SwiftUI:如何检测两个视图是不是相互交叉?

Posted

技术标签:

【中文标题】SwiftUI:如何检测两个视图是不是相互交叉?【英文标题】:SwiftUI: How can I detect if two views are intersecting each other?SwiftUI:如何检测两个视图是否相互交叉? 【发布时间】:2020-02-26 05:06:22 【问题描述】:

一个视图与另一个视图相交。我们如何在 SwiftUI 中检测到这个交叉点?

在 Swift 中,我只需几行代码即可实现:

import UIKit

class ViewController: UIViewController 

override func viewDidLoad() 
    super.viewDidLoad()

    let a = UIView(frame: CGRect(x: 100, y: 100, width: 150, height: 150))
    a.backgroundColor = .purple
    view.addSubview(a)

    let b = UIView(frame: CGRect(x: 150, y: 150, width: 150, height: 150))
    b.backgroundColor = .orange
    view.addSubview(b)

    if a.frame.intersects(b.frame) 
        print("yes!")
     else 
        print("no intersection!")
    

  


显然,它与 SwiftUI 无关:我们这里有另一个框架概念。什么可以代替?有人有想法吗?

更新:

非常感谢user3441734 和Asperi 的帮助和建议! 这是我使用矩形数组解决问题的尝试:

import SwiftUI

struct ContentView: View 

@State var rect = CGRect()
@State var aRects = [CGRect]()


var body: some View 

    VStack 
        ZStack 
            Rectangle()
                  .frame(width: 150, height: 300)
                  .foregroundColor(.purple)
                  .background(self.rectReader(self.$rect))
                  HStack 
                      Spacer()
                      Spacer()
                      Spacer()
                      Rectangle()
                          .frame(width: 150, height: 180)
                          .foregroundColor(.pink)
                         .background(self.rectReader(self.$rect))
                      Spacer()
                  
            

        Button(action: 
             //print("aRects: \(self.aRects)")
             self.checkIntersection()
         )
            Text("Check Intersection!")
         
    






// fill array of rects here
func rectReader(_ binding: Binding<CGRect>) -> some View 
    return GeometryReader  (geometry) -> AnyView in
        let rect = geometry.frame(in: .global)
        DispatchQueue.main.async 
            binding.wrappedValue = rect
            print("rect: \(self.rect)")
            self.aRects.append(self.rect)
        
        return AnyView(Rectangle().fill(Color.clear))
    


func checkIntersection() 
    if aRects.count == 2 
        if self.aRects[0].intersects(self.aRects[1]) 
            print("yes!")
         else 
            print("no!")
        
    




struct ContentView_Previews: PreviewProvider 
static var previews: some View 
    ContentView()


【问题讨论】:

很多。你有什么尝试吗?至少this? 我读了很多关于几何阅读器等的东西,但还没有找到一个简单的方法来做到这一点。对不起;如果我的问题看起来很愚蠢。 @Asperi 请看我的回答 :-) 尤其是最后一部分。 【参考方案1】:

你的问题一点都不傻!

好像很简单

    读取第一个视图的帧(在全局坐标中)并将其保存在首选项中 读取第二个视图的帧(在全局坐标中)并将其保存在首选项中 当偏好改变时计算交点

创建我们的偏好键结构很容易,之前已经在此网站上解释过(使用搜索了解更多详细信息:-))

struct Sizes: PreferenceKey 
    typealias Value = [CGRect]
    static var defaultValue: [CGRect] = []

    static func reduce(value: inout [CGRect], nextValue: () -> [CGRect]) 
        value.append(contentsOf: nextValue())
    

在后台视图修改器中使用 GeometryReader 已经解释过

struct SizeReader: View 
    var body: some View 
        GeometryReader  proxy in
            Color.clear
            .preference(key: Sizes.self, value: [proxy.frame(in: .global)])
        
    

现在我们可以使用它在我们的 Sizes 首选项键中保存框架矩形

RectView(color: .pink).background(SizeReader())

我们的 RectView 呢?为了演示我们的“简单解决方案”是如何工作的,假设有人使用随机大小和随机对齐方式创建它

struct RectView: View 
    let color: Color
    let size = CGFloat(Int.random(in: 50 ... 200))
    var body: some View 
        color.frame(width: size, height: size)
            .alignmentGuide(HorizontalAlignment.center) 
                CGFloat.random(in: 0 ..< $0.width)
            
            .alignmentGuide(VerticalAlignment.center) 
                CGFloat.random(in: 0 ..< $0.width)
            
    

到目前为止,一切都很好,我们准备好检查我们的“简单解决方案”

让我们完成我们的项目

struct ContentView: View 
    @State var id = UUID()
    var body: some View 
        VStack 
            ZStack 
                Color.black
                RectView(color: .pink)
                    .background(SizeReader())
                RectView(color: .yellow)
                    .background(SizeReader())
                    .blendMode(.difference)
            
            .onPreferenceChange(Sizes.self)  (value) in
                let intersection = value[0].intersection(value[1])
                print(value[0])
                print(value[1])
                print(intersection)
                print()
            
            .onTapGesture 
                self.id = UUID()
            
            Text("paceholder").id(id)
        
    

运行它,每次单击屏幕的黑色部分时,您都会看到两个随机大小和定位的矩形,并在调试窗口中打印出我们感兴趣的值。

警告检查打印输出,如果有问题,您会发现!

代码是错误的例子,我把它放在这里是为了证明你的问题一点也不愚蠢!关于 SwiftUI 布局及其在幕后的工作原理,有很多想法需要了解。

我希望,聪明的人会尝试解释问题所在,并指导我们如何使其发挥作用,欢迎所有专家!请不要更改 RectView,认为它是一些内置的 SwiftUI 组件

【讨论】:

user3441734,感谢您的建议和解释!从这一点开始并走得更远,对我来说是一个真正的帮助。你能看看我在帖子中的更新吗?我尝试使用矩形数组而不是首选项键。它似乎可以工作并且代码更少,但我很乐意听取您的意见。 @Roman preferenceKey 方法是正确的方法,一旦你了解它的工作原理,它就很容易使用。请参阅swiftui-lab.com/communicating-with-the-view-tree-part-1 和下一部分。我的示例表明,边界框和视觉外观可能完全不同。 是的,我看到了这种差异,谢谢。但是你也接受数组的用法吗? @Roman :-) 你必须喜欢它!您代码的异步部分有效,我不时使用类似的解决方案。您必须了解它非常脆弱,将来可能会停止工作。使用偏好是有据可查的可靠解决方案,这与 SwiftUI 用于布局所有 UI 组件的方式非常相似。 @Roman 我有一个提示让你解决我示例中的错误。它是在 RectView 中构建的,它还说明了为什么异步方法很脆弱 :-)(而且可能很难找到)

以上是关于SwiftUI:如何检测两个视图是不是相互交叉?的主要内容,如果未能解决你的问题,请参考以下文章

SwiftUI Drag 事件如何限制仅检测水平/垂直滚动

如何检测模态视图在 SwiftUI 中全局可见

如何检测 SwiftUI 视图何时被拖过

SwiftUI实现不同TabView标签页中任意导航层级视图之间自动相互跳转那些事儿

SwiftUI实现不同TabView标签页中任意导航层级视图之间自动相互跳转那些事儿

如何检测手表操作系统是不是关闭了 ios 应用程序 SWIFTUI