SwiftUI: How can I detect if two views are intersecting each other?

与世无争的帅哥 提交于 2021-02-17 06:54:09

问题


One view is intersecting the other one. How could we detect this intersection in SwiftUI?

In Swift I would reach this with a few lines of code:

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!")
    }

  }

}

Obviously, it is not relevant for the SwiftUI: we have another concept of frames here. What could work instead? Has somebody an idea?

Update:

Many thanks for user3441734 and Asperi for their help and suggestions! Here is my attempt to solve the problem, using array of rectangles:

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()
}
}


回答1:


Your question is not stupid at all!

It seems to be easy

  1. read frame of first View (in global coordinates) and save it in preferences
  2. read frame of second View (in global coordinates) and save it in preferences
  3. calculate intersection when preferences changed

create our preference key structure is easy, it was already explained at this web site before (use search for more details :-))

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

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

using GeometryReader in background View modifier was already explained

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

and now we can use it to save frame rectangles in our Sizes preference key

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

What about our RectView ? To demonstrate how our "easy solution" works imagine that somebody create it with random size and random alignment

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)
            }
    }
}

so far, so good, we are ready to check our "easy solution"

Lets finish our project with

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)
        }
    }
}

Run it and each time you click the black part of the screen you see two randomly sized and positioned rectangles and in the debug window printout of values ​​of our interest.

WARNING check the printout and you'll see that something if wrong!

The code is buggy example and I put it here to demonstrate, that your question is not stupid at all! There is a lot of thinks to understand about SwiftUI layout and how it works behind the scene.

I hope, somebody clever will try to explain what's wrong and will guide us on how to make it functional, all experts are welcome! Please don't change RectView, think that it is some build-in SwiftUI component



来源:https://stackoverflow.com/questions/60407125/swiftui-how-can-i-detect-if-two-views-are-intersecting-each-other

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!