Push a UIImageView with another UIImageView, possible? - iphone

Is it possible to pan a UIImageView and when it intersects with the frame of another UIImageView, have that other UIImageView be pushed around by the other UIImageView without just passing over it? I hope that makes sense!
If it is possible, could you give me some ideas on how I'd go about implementing this?
I imagine it would go something like...
If frame intersects frame from left or right on the x/y axis, have the other frame move in that same direction with same distance as the pushing frame. While that logic somewhat makes sense to me, I'm not sure how I'd implement that in code.
I'd really appreciate any advice you can offer.

So, you're only concerned with left / right panning...
Let's assume you have 2 UIImageViews, a & b, as subviews of some other UIView and defined as class members of some class.
You can get help detecting panning gestures (dragging touches) on each of these views by creating instances of UIPanGestureRecognizer and adding them to each UIImageView using method addGestureRecognizer:
When creating each UIPanGestureRecognizer, you need to designate a selector to receive the gesture events. Let say it's called didMove:
Now, some sample code:
- (void) didMove:(UIPanGestureRecognizer*)recognizer {
UIView* view = [recognizer view];
// Remember our UIImageViews are class members called, a & b
//
UIView* otherView = view == a ? b : a;
if (recognizer.state == UIGestureRecognizerStateBegin
|| recognizer.state == UIGestureRecognizerStateChanged) {
// Moving left will have a negative x, moving right positive
//
CGPoint translation = [recognizer translationInView:view.superview];
view.center = CGPointMake(view.center.x + translation.x, view.center.y);
// Reset so we always track the translation from the current view position
//
[recognizer setTranslation:CGPointZero inView:view.superview];
if (CGRectIntersectsRect(view.frame, otherView.frame)) {
// Translate by the same number of points to keep views in sync
//
otherView.center = CGPointMake(otherView.center.x + translation.x, otherView.center.y);
}
}
}
It may not be exactly what you need, but it should give you a good idea how it could be done.

Sounds like you'd better use something like Cocos2D with it's sprites, intersections and some basic physics...

You can do it like this,
First, Create an UIImageView and assign an UIImage to it.
Second, When the user swipes or makes any gesture just change the coordinates of the card by setting its frame again.
Since u want the card to slide away, you have to change the x coordinate to a negative value such that it slides off from the view.
The above said code of reframing the imageview should be given within a UIView setAnimation group of methods...with a required delay.
To achieve the push effect have a method called within the the same animation block of codes which creates a new imageview (having same name as that of first imageview) and keep it out of the view. That is beyond the width of the screen. (dont forget to release the card which was pushes away previously)
Then subsequently again make a new frame for this imageview such that it appears in the centre of the screen....When above three steps of code are put within the animation block of code for UIView or UIImageView with a delay time , u will achieve the required effect.
Hope this helps....
happy coding....

Related

iOS Drawing a grid view for dragging/dropping objects that snap to that grid

I am working on a project that requires a custom view segmented into squares. You need to be able to drag another view over it and when you drop that object it will snap to the grid squares. I also need to be able to iterate over every square in the grid and determine if an object is inside that particular grid square.
I realize this is a bit of a general question but I'm just looking for any direction on where to start, classes or frameworks that might already exist for reference. Any direction at all would be greatly appreciated. Thanks.
Question #1: User needs to be able to drag another view over it and when you drop that object it will snap to the grid squares.
Lets say you are dragging a UIView. On the touchesEnded of the UIView I would use the center property which contains the x and y coordinate center values of the UIView and pass that to a function that tests to see which grid square it is inside of.
This would look like this (assume UIView is name draggingView):
for (CGRect gridSquare in gridArray) {
if (CGRectContainsPoint(gridRect, draggingView.center)) {
// Return the gridSquare that contains the object
}
}
Now in case you are wondering what gridArray is, it is an array of all the grid squares that make up your game board. If you need help creating this let me know.
Question #2: User needs to be able to iterate over every square in the grid and determine if an object is inside that particular grid square.
If you were able to follow along above then this is quite easy. While iterating over the grid squares you could use the gridSquare origin values to see if any of the draggingView subviews have the same origin. Using this you can return the UIView that is inside a particular square. See below:
- (UIView *)findViewInSquare {
for (CGRect rect in gridArray) {
for (UIView *view in [self.view subViews]) {
if (CGPointEqualToPoint(view.frame.origin, rect.origin)) {
return view; // Returns the view in the grid square
}
}
}
return nil;
}
Hopefully this all makes sense, and let me know if you need any clarification.
I would probably start with a UICollectionView, as it's going to provide a lot of flexibility for laying out your views, as well as support for animating inserting/removing views.
When your user releases the view you want to insert, you'd probably need your UICollectionViewDataSource to provide a new cell at the desired location (make the cell transparent) and get its frame. Then use an animation to move the view to the cell's location, and in the animation's completion handler add it as a subview of the cell.
Some handwaving pseudo-code below:
UIView * viewToDrop; // your view being dropped
NSIndexPath * indexPathOfInsertedCell; // figure this out... not sure exactly how you would calculate this--you might need to interrogate the UICollectionViewLayout
UICollectionView * collectionView; // your collection view
UICollectionViewCell * dropCell = [collectionView cellForItemAtIndexPath:indexPathOfInsertedCell];
[UIView animateWithDuration:0.25 animations:^{
CGFloat dx = viewToDrop.frame.origin.x - dropCell.frame.origin.x;
CGFloat dy = viewToDrop.frame.origin.y - dropCell.frame.origin.y;
viewToDrop.transform = CGAffineTransformMakeTranslation(dx, dy);
} completion:^(BOOL finished) {
[dropCell addSubview:viewToDrop];
}];

User interaction outside the UIView bounds

What i'm trying to do is "navigating" into a bigger UIView with buttons and controls positioned in different places. I've made the size of the main UIView twice bigger than the usual. It is 640x480 instead of 320x480.
After clicking a button in the first part of the screen i've made a moving translation of -320px in the x-direction to show the second "hidden" part of the screen where other functions will reveals to the user. Everything works perfect apart the fact i can't get the UIView back to the original position. It seems that the button i use to get back to the original position and that is "outside the bounds" of the 320px, doesn't work.
My UIView is referenced as "introScreen", and the following is the function i call to translate the screen through the x direction:
- (void)secondPartOfTheScreen {
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.15];
introScreen.transform = CGAffineTransformMakeTranslation(-320, 0);
[UIView commitAnimations];
}
Calling this function my UIView moves correctly, but when my button appear in this part of the screen, it doesn't get any user interaction. If i move that button in the first part of the screen, it works correctly. It have something to do with the screen bounds? It is possibile to solve it?
Thanks in advance!
You should try to subclass your UIView and override its pointInside:withEvent: method so that also a point outside its bounds is recognized as belonging to the view. Otherwise, there is no chance that user interaction outside of the view bounds are handled as you would like to.
This is what the Event Handling Guide say:
In hit-testing, a window calls hitTest:withEvent: on the top-most view of the view hierarchy; this method proceeds by recursively calling pointInside:withEvent: on each view in the view hierarchy that returns YES, proceeding down the hierarchy until it finds the subview within whose bounds the touch took place. That view becomes the hit-test view.
You could use something like this in your custom UIView:
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
for (UIView* view in self.subviews) {
if (view.userInteractionEnabled && [view pointInside:[self convertPoint:point toView:view] withEvent:event]) {
return YES;
}
}
return NO;
}
You could do with this:
#interface MyView: UIView
#end
#implementation MyView
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
for (UIView* view in self.subviews) {
if (view.userInteractionEnabled && [view pointInside:[self convertPoint:point toView:view] withEvent:event]) {
return YES;
}
}
return NO;
}
#end
add this class definition to the beginning of the .m file where you are using the view. (You could also use separate .h/.m files, but for the sake of testing if everything works, it is enough). Then, replace UIView with MyView in the instantiation of your current UIView, and it should work.
You're not using the transform property properly, you should take into account the previous value of the property like this:
introScreen.transform = CGAffineTransformConcat(introScreen.transform, CGAffineTransformMakeTranslation(-320, 0));
Hope this helps, and check Apple's UIView documentation and CGAffineTransform documentation for more details.
2 things.
First why would you use transform when you are merely moving the object. You can set the frame just as easily.
CGRect frame = introScreen.frame;
frame.origin.x -= 320;
introScreen.frame = frame;
(Yes this will work inside an animation)
second, if you translate a view its contents should be within its bounds regardless of the size you want displayed in the first place.
therefore your container view (the one with the buttons) should be a size so that all contained pieces are within its bounds. Anything outside its bounds will not function. Having the main view twice its size is not as useful as having the contained view at twice its size.
Think of it like this.
Your table should be able to hold whatever your looking at. If you have a map you want to display you would slide it around on the table. the table does not need to be as big as the map but the map needs to be big enough to present you with all of its contents. if the map was say too short to display the poles and you would be without the information. No matter how much you transformed the map you would not be able to use the north or south poles..
To ensure the app reflects this you can turn on clip subviews on all the views in question, then if its outside the bounds it will not be visible.

UIScrollView zoom issue with UIRotationGestureRecognizer

I have a scroll view which shows a image view. I am trying to handle UIRotationGestureRecognizer on the image view. I get the event for rotation and apply the required transform on the same. The image gets properly rotated in the scroll view. Then when I do any operations in the scroll view like zoom or pan the image rotation and position goes for a toss
_mainView is the subView of UIScrollView which is also used for zooming
UIRotationGestureRecognizer *rotationGesture=[[UIRotationGestureRecognizer alloc] initWithTarget:self action:#selector(rotationGesture:)];
[_mainView addGestureRecognizer:rotationGesture];
[rotationGesture release];
-(void) rotationGesture:(UIRotationGestureRecognizer *) sender {
if(sender.state == UIGestureRecognizerStateBegan ||
sender.state == UIGestureRecognizerStateChanged)
{
sender.view.transform = CGAffineTransformRotate(sender.view.transform,
sender.rotation);
_currRotation = _currRotation + sender.rotation;
[sender setRotation:0];
}
}
I will like to understand what would be the right way to handling rotation with-in the scroll view and it maintains that rotation even after zoom events in Scroll View.
Implement the gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer: method in your UIGestureRecognizerDelegate, and return all gestures you want to recognize simultaneously. If you still have trouble with it, check out the answers to UIImageView Gestures (Zoom, Rotate) Question.
Good luck!
EDIT: Your comment has me guessing that the issue is that you can only have one transform on at a time, and the scroll view applies a scale transform, replacing the rotation one. You could remove the native zoom recognizer (see this question), or nest another UIView in the scroll view, and apply the rotation transform to that. I like option two, it seems easier. If you go with option one, use CGAffineTransformConcat to apply both the zoom and rotate transformations independently.

Problem with collision images, collision before touch

Hi everyone I'm french so scuse me for my english.My problem is the following: I have an image in the center of the screen called viewToRotate, then I have an image called flakeView that is created outside of the screen and then it is moving to the center, and every second a Timer does this(create a flakeView outside the screen and then move it to the center of the screen).
What I wanted to do was : if flakeView and viewToRotate collide reduce the alpha of viewToRotate to 0.5. But when flakeView appears on the screen the action of reducing the alpha is called without the collision of viewToRotate and flakeView, so they collide before they touches. I don't know why. How can I solve this please . Here is the code :
UIImageView* flakeView = [[[UIImageView alloc] initWithImage:flakeImage] autorelease];
// use the random() function to randomize up our flake attributes
int startY = round(random() % 320);
// set the flake start position
flakeView.center = CGPointMake(490, startY);
flakeView.alpha = 1;
// put the flake in our main view
[self.view addSubview:flakeView];
[UIView animateWithDuration:7
animations:^{
// set the postion where flake will move to
flakeView.center = viewToRotate.center;
}];
}
-(void)checkCollision{
if(CGRectIntersectsRect(flakeView.frame, viewToRotate.frame) == 1)
{
viewToRotate.alpha=0.5;
}
}
I would not suggest using a timer on viewDidLoad. I believe that you should set the timer at some other function. Even if you do not use viewDidLoad for anything else.
The other thing is the UIView animations. Even if you have a timer at every 60fps the attributes of the animated objects are not changed by the animation. So before you create the animation you set those properties once. And when you create the animation you set the same properties for the second time. And that's it. From now on while the animation is running if you do checks on the properties you will always get the second values.
To have a working code one option is to create the UIView animations (and perform the property checks) for every step at a certain time interval. Or use OpenGL.
Before getting too deep into this issue, please also make sure that your viewToRotate.frame is actually sized the way you expect it (see my comment).
Once you are certain about those attributes, you might want to check the collision on the current presentationLayer of your animated objects and not on their original state.
UIView.layer.presentationLayer
Returns a copy of
the layer containing all properties as
they were at the start of the current
transaction, with any active
animations applied.
So you might adapt your collision detection like this:
-(void)checkCollision
{
if(CGRectIntersectsRect(flakeView.layer.presentationLayer.frame, viewToRotate.layer.presentationLayer.frame) == 1)
{
viewToRotate.alpha=0.5;
}
}
Also consider using the CoreAnimation Layer method hitTest: instead of your handmade collision detection.
hitTest: Returns the farthest
descendant of the receiver in the
layer hierarchy (including itself)
that contains a specified point.
These are the following things you would want to check.
Dont init with image, use init with frame and then do
flakeView.image = ----; Because your image could be wide enough
initially , and hence colliding from the time you initialize.
Try alternatives to .center, like .frame.size.x , because I have had
problems with .center as well
You could probably NSLog the bounds of both ur imageviews before and
after reducing alpha

How to duplicate rendering of DELETE button on UITableViewCell Swipe for a UISwipeGestureRecognizer?

Basically I have created a UISwipeGestureRecognizer for my UITableView for when the user swipes from left to right. When the user swipes right to left, the DELETE renders and functions as it should.
I'm trying to get a new custom UIButton to render exactly the same way the DELETE button renders when swiped. It doesn't slide in but sort of slowly reveals itself from right to left?
I would like to do this for my custom UIButton to appear in the same place as DELETE except this time only when the user swipes right to left.
I have this method set as the #selector after a swipe has been detected right to left:
- (void)displayAddArchiveView:(UIGestureRecognizer *)gestureRecognizer {
// Assume this is the view container that will contain my UIButton
GreenGradientView *archiveView = [[GreenGradientView alloc] initWithFrame:CGRectMake(211.0, 3.0, 63.0, 30.0)];
if (gestureRecognizer.state == UIGestureRecognizerStateEnded) {
CGPoint swipeLocation = [gestureRecognizer locationInView:self.reportsTableView];
NSIndexPath *swipedIndexPath = [self.reportsTableView indexPathForRowAtPoint:swipeLocation];
ReportsTableViewCell *swipedCell = (ReportsTableViewCell *)[self.reportsTableView cellForRowAtIndexPath:swipedIndexPath];
[self tableView:reportsTableView willBeginEditingRowAtIndexPath:swipedIndexPath];
// HERE I should do some sort of UIView animation block to slowly reveal it
// from the right to left or something? Any suggestions. Not sure how to go about it?
[swipedCell addSubview:archiveView];
}
}
I have indicated by comments inside the code block my thoughts. The custom UIView actually does show up after swiping so I know it works. It's just a matter of getting the animation right, identical to how the DELETE button reveals itself? Suggestions?
Thanks!
You could try something like this
CGRect finalFrame = archiveView.frame;
archiveView.frame = CGRectMake(finalFrame.origin.x + finalFrame.size.width, finalFrame.origin.y, 0, finalFrame.size.height);
[UIView animateWithDuration:0.3 animations:^{
archiveView.frame = finalFrame;
}];
Basically I've reset the frame so it has no width and set the origin on the far right (so that it grows from the right towards the left). Then in the animation block, i set the frame to what I want it to be at the end. In your case you can just save the final frame in a local variable before you reset the frame to have zero width.