Detecting the collision b/w two objects in Swift 3.0?
// The Pan Gesture
func createPanGestureRecognizer(targetView: UIImageView)
{
var panGesture = UIPanGestureRecognizer(target: self, action:("handlePanGesture:"))
targetView.addGestureRecognizer(panGesture)
}
// THE HANDLE
func handlePanGesture(panGesture: UIPanGestureRecognizer) {
// get translation
var translation = panGesture.translationInView(view)
panGesture.setTranslation(CGPointZero, inView: view)
println(translation)
//create a new Label and give it the parameters of the old one
var label = panGesture.view as UIImageView
label.center = CGPoint(x: label.center.x+translation.x, y: label.center.y+translation.y)
label.multipleTouchEnabled = true
label.userInteractionEnabled = true
if panGesture.state == UIGestureRecognizerState.Began {
//add something you want to happen when the Label Panning has started
}
if panGesture.state == UIGestureRecognizerState.Ended {
//add something you want to happen when the Label Panning has ended
}
if panGesture.state == UIGestureRecognizerState.Changed {
//add something you want to happen when the Label Panning has been change ( during the moving/panning )
}
else {
// or something when its not moving
}
}
How to see if two UIImageView's have touched
You can check if UIImageViews
are overlapping like that:
if img1.bounds.contains(img2.bounds)
{
print("overlapped")
}
How to create a Collision Detection between a label and an Image
You want to use contains()
. If you use intersects()
then it will return true as soon as the two rectangles touch. With contains()
, it won't return true until the dragged view is fully inside the target view, which seems much more intuitive.
I just wrote a sample app that implements this and it works perfectly.
I created a simple subclass of UIView I called BoxedView that just sets a border around it's layer so you can see it.
I set up a view controller with 2 boxed views, a larger "targetView", and a smaller view that the user could drag.
The target/action for my gesture recognizer moves the dragged view's frame as the user drags, and if target view contains the dragged view, it sets a Bool highlightTargetView, which causes the box around the target view to get thicker.
The entire view controller class' code looks like this:
class ViewController: UIViewController {
@IBOutlet weak var targetView: BoxedView!
var viewStartingFrame: CGRect = CGRect.zero
var highlightTargetView: Bool = false {
didSet {
targetView.layer.borderWidth = highlightTargetView ? 5 : 1
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
@IBAction func userDraggedView(_ gesture: UIPanGestureRecognizer) {
switch gesture.state {
case .began:
viewStartingFrame = gesture.view?.frame ?? CGRect.zero
case .changed:
let offset = gesture.translation(in: view)
gesture.view?.frame = viewStartingFrame.offsetBy(dx: offset.x, dy: offset.y)
highlightTargetView = targetView.frame.contains(gesture.view?.frame ?? CGRect.zero)
case .ended:
gesture.view?.frame = viewStartingFrame
highlightTargetView = false
default:
break
}
}
}
In order for the math to work, I use the frame property of both views, which is in the coordinate system of the parent view (The parent view is the view controller's content view in this case, but the key thing is that we compare 2 rectangles using the same coordinate system for both.) If you used the bounds property of either view your math wouldn't work because bounds
of a view is in the local coordinate system of that view.
Here's what that program looks like when running:
For comparison, I modified the program to also show what it looks like using the intersects()
function, and created a video of what that looks like:
How to detect Collision between 2 UIViews
Right, so first you check for overlapping rectangles (which you've already done). When you find an overlap, then you need to refine the collision test. For each corner of the offending rectangle, compute the distance from the center of your character to the corner of the offending rectangle. If the distance from the center to any of the corners is less than the radius of the circle, then you have a collision. To optimize the code a little bit, compute (dx * dx + dy * dy) and compare that to the radius squared. That way you don't have to compute any square roots.
Upon further review, you also need to do an edge check in addition to the corner check. For example, if the top y
value is above the center of the circle and the bottom y
value is below the center of the circle, then compute the difference in x
between the rectangle left edge and the center of the circle, and if that distance is less than the radius, then a collision has occurred. Likewise for the other three edges of the rectangle.
Here's some pseudo-code for the corner checking
int dx, dy, radius, radiusSquared;
radiusSquared = radius * radius;
for ( each rectangle that overlaps the player rectangle )
{
for ( each corner of the rectangle )
{
dx = corner.x - center.x;
dy = corner.y - center.y;
if ( dx * dx + dy * dy < radiusSquared )
Collision!!!
}
}
Detecting collisions between rotated UIViews
when you rotate a view, its bounds won't change but its frame changes.
So, for my view with backgroundColor blue,
the initial frame i set to was
frame = (30, 150, 150, 35);
bounds={{0, 0}, {150, 35}};
but after rotating by 45 degree, the frame changed to
frame = (39.5926 102.093; 130.815 130.815);
bounds={{0, 0}, {150, 35}};
Because the frame always return the smallest enclosing rectangle of that view.
So, in your case, even-though it looks both views are not intersecting,their frames intersect.
To solve it you can use, separating axis test.
If you want learn on it, link here
I tried to solve it and finally got the solution.
If you like to check, below is the code.
Copy paste the below code into an empty project to check it out.
In .m file
@implementation ViewController{
UIView *nonRotatedView;
UIView *rotatedView;
}
- (void)viewDidLoad
{
[super viewDidLoad];
nonRotatedView =[[UIView alloc] initWithFrame:CGRectMake(120, 80, 150, 40)];
nonRotatedView.backgroundColor =[UIColor blackColor];
[self.view addSubview:nonRotatedView];
rotatedView =[[UIView alloc] initWithFrame:CGRectMake(30, 150, 150, 35)];
rotatedView.backgroundColor =[UIColor blueColor];
[self.view addSubview:rotatedView];
CGAffineTransform t=CGAffineTransformMakeRotation(M_PI_4);
rotatedView.transform=t;
CAShapeLayer *layer =[CAShapeLayer layer];
[layer setFrame:rotatedView.frame];
[self.view.layer addSublayer:layer];
[layer setBorderColor:[UIColor blackColor].CGColor];
[layer setBorderWidth:1];
CGPoint p=CGPointMake(rotatedView.bounds.size.width/2, rotatedView.bounds.size.height/2);
p.x = -p.x;p.y=-p.y;
CGPoint tL =CGPointApplyAffineTransform(p, t);
tL.x +=rotatedView.center.x;
tL.y +=rotatedView.center.y;
p.x = -p.x;
CGPoint tR =CGPointApplyAffineTransform(p, t);
tR.x +=rotatedView.center.x;
tR.y +=rotatedView.center.y;
p.y=-p.y;
CGPoint bR =CGPointApplyAffineTransform(p, t);
bR.x +=rotatedView.center.x;
bR.y +=rotatedView.center.y;
p.x = -p.x;
CGPoint bL =CGPointApplyAffineTransform(p, t);
bL.x +=rotatedView.center.x;
bL.y +=rotatedView.center.y;
//check for edges of nonRotated Rect's edges
BOOL contains=YES;
CGFloat value=nonRotatedView.frame.origin.x;
if(tL.x<value && tR.x<value && bR.x<value && bL.x<value)
contains=NO;
value=nonRotatedView.frame.origin.y;
if(tL.y<value && tR.y<value && bR.y<value && bL.y<value)
contains=NO;
value=nonRotatedView.frame.origin.x+nonRotatedView.frame.size.width;
if(tL.x>value && tR.x>value && bR.x>value && bL.x>value)
contains=NO;
value=nonRotatedView.frame.origin.y+nonRotatedView.frame.size.height;
if(tL.y>value && tR.y>value && bR.y>value && bL.y>value)
contains=NO;
if(contains==NO){
NSLog(@"no intersection 1");
return;
}
//check for roatedView's edges
CGPoint rotatedVertexArray[]={tL,tR,bR,bL,tL,tR};
CGPoint nonRotatedVertexArray[4];
nonRotatedVertexArray[0]=CGPointMake(nonRotatedView.frame.origin.x,nonRotatedView.frame.origin.y);
nonRotatedVertexArray[1]=CGPointMake(nonRotatedView.frame.origin.x+nonRotatedView.frame.size.width,nonRotatedView.frame.origin.y);
nonRotatedVertexArray[2]=CGPointMake(nonRotatedView.frame.origin.x+nonRotatedView.frame.size.width,nonRotatedView.frame.origin.y+nonRotatedView.frame.size.height);
nonRotatedVertexArray[3]=CGPointMake(nonRotatedView.frame.origin.x,nonRotatedView.frame.origin.y+nonRotatedView.frame.size.height);
NSInteger i,j;
for (i=0; i<4; i++) {
CGPoint first=rotatedVertexArray[i];
CGPoint second=rotatedVertexArray[i+1];
CGPoint third=rotatedVertexArray[i+2];
CGPoint mainVector =CGPointMake(second.x-first.x, second.y-first.y);
CGPoint selfVector =CGPointMake(third.x-first.x, third.y-first.y);
BOOL sign;
sign=[self crossProductOf:mainVector withPoint:selfVector];
for (j=0; j<4; j++) {
CGPoint otherPoint=nonRotatedVertexArray[j];
CGPoint otherVector = CGPointMake(otherPoint.x-first.x, otherPoint.y-first.y);
BOOL checkSign=[self crossProductOf:mainVector withPoint:otherVector];
if(checkSign==sign)
break;
else if (j==3)
contains=NO;
}
if(contains==NO){
NSLog(@"no intersection 2");
return;
}
}
NSLog(@"intersection");
}
-(BOOL)crossProductOf:(CGPoint)point1 withPoint:(CGPoint)point2{
if((point1.x*point2.y-point1.y*point2.x)>=0)
return YES;
else
return NO;
}
Hope this helps.
UIImages or UIViews are not colliding each other in iOS Swift?
The issue is that the animator only recognizes the collision at the origin of the greenSquare. In this case it is at the top of the screen. You can update the collision location on the animator after you move the greenSquare. Add animator?.updateItem(usingCurrentState: greenSquare!)
to the end of the draggedView
method. You do not need to use UISnapBehavior
for this so you can remove the IBAction func pan
method at the top of your code. The updateItem(usingCurrentState:)
call will reset where the collision barrier is when you are moving the view manually and it is not being done by the physics engine.
How do I detect the collision of two SKShapeNodes in Swift without affecting their physics?
You have no contactTestBitMask
on your sprites, so your didBegin
is never called.
SwiftUI: How can I detect if two views are intersecting each other?
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
Related Topics
Wkwebview Allowslinkpreview to False Breaks Text Selection
How to Make a Countdown Timer Like in a Music Player
Adding an Skscene to a Uiviewcontroller
How Is a Swift Cgvector Created with Dx and Dy (Derivative)
App Could Not Authenticate with Facebook and Firebase After Conversion to Swift 3 Syntax
Uidocumentinteractioncontroller Does Not Open Other App in iOS 11
No Such Module Iqkeyboardmanagerswift
-Webkit-Overflow-Scrolling: Touch' Broken for Initially Offscreen Elements in iOS7
Convert Rgb Image to 1 Channel Image (Black/White)
Sync Video in Avplayerlayer and Avplayerviewcontroller
Strange Custom Background Color on Uipickerview Swift
How to Make a Reusable Tableview for Different Screens in the Same Application
Resize All Subview in Scrollviewdidzoom
Core Data - Fetch All Entities Using the Same Field
Cannot Subscript a Value of [Anyobject]? with an Index of Type Int
How to Use @Objc Protocol with Optional and Extensions at the Same Time