问题
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
- read frame of first View (in global coordinates) and save it in preferences
- read frame of second View (in global coordinates) and save it in preferences
- 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