Detecting if UIView is intersecting other UIViews - objective-c

I have a bunch of UIViews on the screen. I would like to know what's the best to way to check if a particular view (which I have reference to) is intersection ANY other views. The way I am doing it right now is, iterating though all the subviews and check one by one if there's an intersection between the frames.
This doesn't seem very efficient. Is there a better way to do this?

There's a function called CGRectIntersectsRect which receives two CGRects as arguments and returns if the two given rects do intersect. And UIView has subviews property which is an NSArray of UIView objects. So you can write a method with BOOL return value that'll iterate through this array and check if two rectangles intersect, like such:
- (BOOL)viewIntersectsWithAnotherView:(UIView*)selectedView {
NSArray *subViewsInView = [self.view subviews];// I assume self is a subclass
// of UIViewController but the view can be
//any UIView that'd act as a container
//for all other views.
for(UIView *theView in subViewsInView) {
if (![selectedView isEqual:theView])
if(CGRectIntersectsRect(selectedView.frame, theView.frame))
return YES;
}
return NO;
}

To Achieve the same thing in swift as per the accepted answer then here is the function. Readymade Code. Just copy and use it as per the steps. By the way I am using Xcode 7.2 with Swift 2.1.1.
func checkViewIsInterSecting(viewToCheck: UIView) -> Bool{
let allSubViews = self.view!.subviews //Creating an array of all the subviews present in the superview.
for viewS in allSubViews{ //Running the loop through the subviews array
if (!(viewToCheck .isEqual(viewS))){ //Checking the view is equal to view to check or not
if(CGRectIntersectsRect(viewToCheck.frame, viewS.frame)){ //Checking the view is intersecting with other or not
return true //If intersected then return true
}
}
}
return false //If not intersected then return false
}
Now call this function as per the following -
let viewInterSected = self.checkViewIsInterSecting(newTwoPersonTable) //It will give the bool value as true/false. Now use this as per your need
Thanks.
Hope this helped.

In my case the views were nested, so I did this (works with both nested and not):
extension UIView {
func overlaps(other view: UIView, in viewController: UIViewController) -> Bool {
let frame = self.convert(self.bounds, to: viewController.view)
let otherFrame = view.convert(view.bounds, to: viewController.view)
return frame.intersects(otherFrame)
}
}
Hope it helps.

First, create some array that stores the frames of all of your UIViews and their associated reference.
Then, potentially in a background thread, you can run some collision testing using what's in the array. For some simple collision testing for rectangles only, check out this SO question: Simple Collision Algorithm for Rectangles
Hope that helps!

Related

Calculate in which UIView in Superview subviews a dragged UIView occupies bigger area

I would like to find out in row and column ordered UIViews, subviews inside a superview, while dragging one of them and then let it go with which one of all the others intersects its bigger area.
I need this to insert the draggable UIView between the UIViews that intersects.
Do I have to calculate frame.origin values? If yes could I have an example? Or this could happen using iOS SDK ?
I already aware of CGRectIntersectsRect but this gives me the first UIView that intersects. Though its most possible need to place the UIView between other UIViews.
I had an example using pointInside with no luck.
Code:
CGPoint centerInView = [viewDragged convertPoint:viewDragged.center toView:view]; // Checking with the views inside NSMutableArray in a for in statement
if ([view pointInside:centerInView withEvent:nil]) // This if statement almost never is true sometimes only
Other approach:
if (CGRectIntersectsRect(viewDragged.frame, view.frame)) // This always happens for the first UIView in subviews that intersects. Not so applicable for information
Basically the idea is to insert the draggable view between the nearest UIViews.
Imagine 1,2,3,4,5 same size views in the screen and try to drag the first let it go between 4,5 and then have 2,3,4,1,5. You get the idea.
This all should happen without the help of any new Grid. The only part I need is calculate that you put the 1 UIView between 4,5.
I could start calculating frames,origins but this does not sound efficient to me. Any thoughts?
Thank you.
I now it's an old question, but this might help. The view with the biggest CGRect Intersection gets 'selected'.
CGRect intersect = CGRectZero;
UIView *selectedDropView = nil;
for (UIView *dropView in dropViews) {
CGRect intersectTmp = (CGRectIntersection(self.frame, dropView.frame));
if (intersectTmp.size.width * intersectTmp.size.height > intersect.size.width * intersect.size.height) {
intersect = intersectTmp;
selectedDropView = dropView;
}
}

Change NSView subviews order

I'm working at a custom control that presents different handles. I want to be sure that the selected handle has a Z-index grater then the others handles.
Is there a way to swap view order?
I found this function sortSubviewsUsingFunction:context: but i can't understand whether this is the right solution.
Cocoa introduced a new API in macOS 10.0.
It's similar to iOS, you can pass another view to be displayed below or above.
[self.view addSubview:myView positioned:NSWindowBelow relativeTo:myViewInBackground];
Checkout the documentation; in my experience NSWindowBelow and NSWindowAbove seemed reversed though.
It is pretty simple, you can use a function that compare 2 subviews to reorder them. Here a simple solution based on view's tag:
[mainView sortSubviewsUsingFunction:(NSComparisonResult (*)(id, id, void*))compareViews context:nil];
...
NSComparisonResult compareViews(id firstView, id secondView, void *context) {
int firstTag = [firstView tag];
int secondTag = [secondView tag];
if (firstTag == secondTag) {
return NSOrderedSame;
} else {
if (firstTag < secondTag) {
return NSOrderedAscending;
} else {
return NSOrderedDescending;
}
}
}
Yes, sortSubviewsUsingFunction:context: can do what you're looking to do, however it might be overkill if all you'd like is for one particular view to sit on top of the (unordered) others. In that case, you should look into the bringSubviewToFront: method of UIView. That way, you could do something like this in your IBAction for bringing up the handle:
[self.view bringSubviewToFront: handle];
Assuming that handle is an object that is/inherits from UIView. If this isn't what you're looking for, then by all means, go with sortSubviewsUsingFunction:context:, in which case you'll need to alter the individual tag properties for each subview, and sort accordingly.
As i understand from you question you can loop all you view and add them to array then you have the option to add them to you view with index .. and you have the following functions.
[self.view insertSubview:view1 atIndex:2]
[self.view insertSubview:view1 aboveSubview:0]
[self.view insertSubview:view1 belowSubview:1]
then you can swap it's order as you need..

Subviews not displaying when using replaceSubview:with:

I'm writing a dynamic wizard application using cocoa/objective c on osx 10.6. The application sequences through a series of views gathering user input along the way. Each view that is displayed is provided by a loadable bundle. When the app starts up, a set of bundles are loaded and as the controller sequences through them, it asks each bundle for its view to display. I use the following to animate the transition between views
[[myContentView animator] replaceSubview:[oldView retain] with:newView];
This works fine most of the time. Every once in a while, a view is displayed and some of the subviews are not displayed. It may be a static text field, a checkbox, or even the entire set of subviews. If, for example, a checkbox is not displayed, I can still click where it should be and it then gets displayed.
I thought it might have something to do with the animation so I tried it like this
[myContentView replaceSubview:[oldView retain] with:newView];
with the same result. Any ideas on what's going on here? Thanks for any assistance.
I don't think is good to use this [oldView retain]. This retain do not make sense. The function replaceSubview will retain it if it is necessary.
Because it work "most of the time" I think it is a memory problem. You try to use a released thing. Test without it and see what happens.
I got the same problem, replaceSubview didn't work.
Finally i found something wrong with my code. so here are the rules :
Both subviews should be in MyContentview's subviews array.
OldView should be the topmost subview on MyContentView or replacesubview will not take place.
here is a simple function to perform replacesubview
- (void) replaceSubView:(NSView *)parentView:(NSView *)oldView:(NSView *)newView {
//Make sure every input pointer is not nill and oldView != newView
if (parentView && oldView && newView && oldView!=newView) {
//if newview is not present in parentview's subview array
//then add it to parentview's subview
if ([[parentView subviews] indexOfObject:newView] == NSNotFound) {
[parentView addSubview:newView];
}
//Note : Sometimes you should make sure that the oldview is the top
//subview in parentview. If not then the view won't change.
//you should change oldview with the top most view on parentview
//replace sub view here
[parentView replaceSubview:oldView with:newView];
}
}

Trying to detect collision between animating UIImageViews

I am very new to cocoa . Well I have multiple UIImageView (animationView)flying around the screen. I am using UIAnimation class to animate them. While I have one more UIImageView (myObject).I m trying to collide these while i move "myObject" around the screen using touchesMoved method. The problem is I am not able to detect the collision. I m using the following method :
if (CGRectIntersectsRect(animationView.frame, myObject.frame)) {
NSLog(#"Collision occurred");
}
I assume you are talking about CAAnimation, not UIAnimation.
In Core Animation the current values of animated properties are not reflected in the original objects (views, layers) on which they have been applied. Instead, you have to look at a layer's presentationLayer to get the currently effective value:
CGRect viewFrame = ((CALayer*)[animationView.layer presentationLayer]).frame;
CGRect objectFrame = ((CALayer*)[myObject.layer presentationLayer]).frame;
if (CGRectIntersectsRect(viewFrame, objectFrame)) {
NSLog(#"Collision occurred");
}

Determining which NSView instance initiated a mouseDown:

I have a gameboard with 25 tiles of myGameTile, a subclass of NSView.
In mouseDown: I want to determine which tile I clicked on and set an ivar to a representative value.
e.g. If I click on tile 12, set clickedTile to "12" or some value that uniquely represents that particular instance.
I'm open anything from the integer value 12 all the way to some sort of introspection/reflection, though built-in features and elegance are preferable to hacks, runtime wrappers, and modification. Still, I'm aware that I may have no choice but to rely on those solutions, so please to answer with those as well. I'd like to know all my options. Thanks!
You could subclass NSView and override the tag method, as written in the documentation.
You have several possibilities:
If you handle the mouseDown in the Tile view, then you need to map self to the tile ID. There are three easy ways to do this:
Pre configure Tile.tag to be the tile ID, then use self.tag.
Search for Tile in the array of Tiles to find the index [parent.tiles indexOfObject:self]
Create a dictionary mapping Tile or tile ID [[parent.tiles objectForKey:self] intValue]
Clearly, using the tag is the easiest, as long as you are not using the tag for anything else.
Alternatively, you could implement hitTest on the parent view, and return the parent view, and then handle the mouseDown in the parent view. Then mouseDown will know where the hit is and hence which tile it is.
I think hitTest: will do what you want. Something like this:
- (void)mouseDown:(NSEvent *)theEvent {
NSPoint aPoint = [theEvent locationInWindow];
NSView* viewHit = [self hitTest:aPoint];
if( viewHit != nil && viewHit != self ) {
// viewHit is our tile.
}
}