Setting UIView position and size - ios

When creating a new UIView or panning/swiping a view to a new position, whats the proper way of setting the new position and size. Currently, to move views resulting from a pan/swipe action, I'm using ratios based on the original position of the view that I'm moving and its relation to other views and items (navigation bar).
When moving a view due to a swipe I do the following:
CGFloat swipeUpDelta = -1 *(self.view.bounds.size.height - (self.view.bounds.size.height - recordView.frame.origin.y) - (self.navigationController.navigationBar.bounds.size.height + ([UIApplication sharedApplication].statusBarFrame.size.height)));
[UIView animateWithDuration:.1 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
recordView.frame = CGRectOffset(recordView.frame, 0, swipeUpDelta);
} completion:nil];
Or creating a view that renders a sineWave across my viewcontroller:
self.sineWave = [[OSDrawWave alloc] initWithFrame:CGRectMake(-1*self.view.bounds.size.width, (.75*self.view.bounds.size.height), 2*self.view.bounds.size.width, .3125*(2*self.view.bounds.size.width))];
Everything works fine, but I wanted to know if there is a better way of doing these things.

I'm not sure I understand the question. initWithFrame: or myView.frame is considered the correct way of positioning a UIView. Using animateWithDuration: with a new frame size is also pretty standard.

I'm unsure of what you mean as well. When you move a UIView you can move the view a variety of ways, it depends on your code and what you want out of it. The below is a valid example of code to move a CALayer inside of a UIView to follow touch positions. You can use this, you can animate your view, you can use explicit animations when changing the position of the layer or center of the view (By default iOS animates any changed property values of a CALayer... you can ride off of this and enable this feature for UIViews if you please). If you're moving a UIView, it's a good idea to move the view according to the center property. This is pretty much how SpriteKit works and any high functioning animation engine (avoid always resetting the frame... especially if you don't need to).
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *t = [touches anyObject];
CGPoint p = [t locationInView:self];
boxLayer.position = p;
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *t = [touches anyObject];
CGPoint p = [t locationInView:self];
[CATransaction begin];
[CATransaction setDisableActions:YES];
boxLayer.position = p;
[CATransaction commit];
}
As a note, note the comment in the following line of code
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
/*UITouch *t = [touches anyObject];
CGPoint p = [t locationInView:self]; // this causes the animations to appear sluggish as there is a consistant change in destination location. The above code corrects this.
[boxLayer setPosition:p];*/
This is valid, and may provide the results you want... it all depends on what you want.

Related

CGRectContainsPoint for a UIButton during animation

I am creating a UIButton that follows the UITouch point, I want that when I Keep pressing the touch and the animation boundaries reach my touch point I perform some activity there.
Following is the code.
I am always getting x,y 0 for the UIButton.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[UIView beginAnimations:#"MoveAndStrech" context:nil];
[UIView setAnimationDuration:1];
[UIView setAnimationBeginsFromCurrentState:YES];
if([touches count] == 1) {
UITouch *touch = [touches anyObject];
self.uiButton.center = [touch locationInView:self.view];
}
[UIView commitAnimations];
UITouch *touchTemp = [touches anyObject];
CGPoint pCurrent = [touchTemp locationInView:self.view];
CGRect currentRect = self.uiButton.layer.bounds;
NSLog(#"Current Rect x:%f,Current y:%f",
currentRect.origin.x,
currentRect.origin.y);
NSLog(#"TouchPoint x:%f,TouchPoint y:%f",
pCurrent.x,
pCurrent.y);
if(CGRectContainsPoint(currentRect, pCurrent)) {
NSLog(#"Touched....in rect");
}
}
Regards
Sara
Replace
CGRect currentRect = self.uiButton.layer.bounds;
with
CGRect currentRect = self.uiButton.frame;
If that doesn't work, then
CGRect currentRect = self.uiButton.presentationLayer.frame;
Also, why aren't you using UIView animation blocks? It'd make your life much easier and your code much cleaner.
Several things. At first I thought you were missing the call to commitAnimations.
DO NOT PUT MULTIPLE STATEMENTS ON THE SAME LINE!!!! That is a very nasty coding habit that makes your code all but impossible to read.
Next, the old style beginAnimations/commitAnimations calls are outdated, and should not be used in iOS 4 or later. To quote the docs of beginAnimations:context:
Use of this method is discouraged in iOS 4.0 and later. You should use
the block-based animation methods to specify your animations instead.
You should be using the new block-based animation calls (of the form (animateWithDuration:animations:)
Next, UIView animations cause the object being animated to move to it's final location instantly. They only LOOK LIKE they are moving to their final location over time. They also disable user interaction during animations by default.
If you want to figure out where the view appears on the screen at any given moment then you need to check the view's layer's animationLayer. I have a sample project on Github (link) that includes a UIView animation of an image where you can tap on the image and the animation stops. This sample project shows how to use the view's layer's animation layer to figure out where the animation is located at the instant the user taps the screen.
If you want code that does something when the animated view gets to a certain point it would be more tricky. You might need to attach a CADisplayLink to your view (which will be called on each screen refresh) and use that to check the position of your animation and compare it to the current touch location.

Can't Detect UIView Children Touches Correctly when Parent UIView is Being Animated

I'm working on an iOS app.
I have a UIView that is being animated. It keeps moving from left to right.
This view has several UIView children. They move within their parent when this is being animated.
When I try to capture user touches on the children (by using a UITapGestureRecognizer), it doens't work as expected. It sort of detect some touches on the children, but not in the position where they currently are.
I've been struggling with this for a while now. Any ideas about how to solve this? I really need to detect which children the user is touching.
Thanks!
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch = [touches anyObject];
CGPoint touchLocation = [touch locationInView:self];
// Check every child subview
for (UIView *tickerItem in self.subviews)
{
// Check collision
if ([tickerItem.layer.presentationLayer hitTest:touchLocation])
{
MKTickerItemView *v = (MKTickerItemView*)tickerItem;
NSLog(#"Selected: %#", v.title);
// This button was hit whilst moving - do something with it here
break;
}
}
}
There is a UIViewAnimationOptions value...
UIViewAnimationOptionAllowUserInteraction
You can use this in the [UIView animateWithDuration... code.
This will enable the touch.

UIView bringSubviewToFront: does *not* bring view to front

I am implementing a simple iOS solitaire game that allows the user to drag the cards around in the usual way. The cards are represented with the UIView subclass CardView. All the card view's are siblings which are subviews of SolitaireView. The following snippet tries to "bring a card to the front" so that it is above all the other views as it is being dragged:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
if (touch.view.tag == CARD_TAG) {
CardView *cardView = (CardView*) touch.view;
...
[self bringSubviewToFront:cardView];
...
}
}
Unfortunately, the card's z-order remains unchanged during the drag. In the images below, I am dragging the King. Notice how it is correctly on top the Nine in the left image, but is incorrectly under the Two (under the entire stack actually) in the right image:
I also tried alter the layer.zPosition property as well to no avail.
How can I bring the card view to the front during the drag? I am mystified.
Confirmed. bringSubviewToFront: causes layoutSubview to be invoked. Since my version of layoutSubviews sets the z-orders on all the views, this was undoing the z-order I was setting in the touchesBegan:withEvent code above. Apple should mention this side effect in the bringSubviewToFront documentation.
Instead of using a UIView subclass, I created a CALayer subclass named CardLayer. I handle the touch in my KlondikeView subclass as listed below. topZPosition is an instance var that tracks the highest zPosition of all cards. Note that modifying the zPosition is usually animated -- I turn this off in the code below:
-(void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
UITouch *touch = [touches anyObject];
CGPoint touchPoint = [touch locationInView:self];
CGPoint hitTestPoint = [self.layer convertPoint:touchPoint
toLayer:self.layer.superlayer];
CALayer *layer = [self.layer hitTest:hitTestPoint];
if (layer == nil) return;
if ([layer.name isEqual:#"card"]) {
CardLayer *cardLayer = (CardLayer*) layer;
Card *card = cardLayer.card;
if ([self.solitaire isCardFaceUp:card]) {
//...
[CATransaction begin]; // disable animation of z change
[CATransaction setValue:(id)kCFBooleanTrue
forKey:kCATransactionDisableActions];
cardLayer.zPosition = topZPosition++; // bring to highest z
// ... if card fan, bring whole fan to top
[CATransaction commit];
//...
}
// ...
}
}

iPhone - Move object along touch and drag path

I am developing a simple animation where an UIImageView moves along a UIBezierPath, now I want to provide user interation to the moving UIImageView so that user can guide the UIImageView by touching the UIImageView and drag the UIImageview around the screen.
Change the frame of the position of the image view in touchesMoved:withEvent:.
Edit: Some code
- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
UITouch *touch = [[event allTouches] anyObject];
if ([touch.view isEqual: self.view] || touch.view == nil) {
return;
}
lastLocation = [touch locationInView: self.view];
}
- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event {
UITouch *touch = [[event allTouches] anyObject];
if ([touch.view isEqual: self.view]) {
return;
}
CGPoint location = [touch locationInView: self.view];
CGFloat xDisplacement = location.x - lastLocation.x;
CGFloat yDisplacement = location.y - lastLocation.y;
CGRect frame = touch.view.frame;
frame.origin.x += xDisplacement;
frame.origin.y += yDisplacement;
touch.view.frame = frame;
lastLocation=location;
}
You should also implement touchesEnded:withEvent: and touchesCanceled:withEvent:.
So you want the user to be able to touch an image in the middle of a keyframe animation along a curved path, and drag it to a different location? What do you want to happen to the animation at that point?
You have multiple challenges.
First is detecting the touch on the object while a keyframe animation is "in flight".
To do that, you want to use the parent view's layer's presentation layer's hitTest method.
A layer's presentation layer represents the state of the layer at any given instant, including animations.
Once you detect touches on your view, you will need to get the image's current location from the presentation layer, stop the animation, and take over with a touchesMoved/touchesDragged based animation.
I wrote a demo application that shows how to detect touches on an object that's being animated along a path. That would be a good starting point.
Take a look here:
Core Animation demo including detecting touches on a view while an animation is "in flight".
Easiest way would be subclassing UIImageView.
For simple dragging take a look at the code here (code borrowed from user MHC):
UIView drag (image and text)
Since you want to drag along Bezier path you'll have to modify touchesMoved:
-(void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *aTouch = [touches anyObject];
//here you have location of user's finger
CGPoint location = [aTouch locationInView:self.superview];
[UIView beginAnimations:#"Dragging A DraggableView" context:nil];
//commented code would simply move the view to that point
//self.frame = CGRectMake(location.x-offset.x,location.y-offset.y,self.frame.size.width, self.frame.size.height);
//you need some kind of a function
CGPoint calculatedPosition = [self calculatePositonForPoint: location];
self.frame = CGRectMake(calculatedPosition.x,calculatedPosition.y,self.frame.size.width, self.frame.size.height);
[UIView commitAnimations];
}
What exactly you would like to do in -(CGPoint) calculatePositionForPoint:(CGPoint)location
is up to you. You could for example calculate point in Bezier path that is the closest to location. For simple test you can do:
-(CGPoint) calculatePositionForPoint:(CGPoint)location {
return location;
}
Along the way you're gonna have to decide what happens if user wonders off to far from your
precalculated Bezier path.

How to use drag items in iOS

I'm not quite sure how to start explaining this, but I'll try.
I started reading iOS books awhile ago, and I'm trying to get a handle on Objective-C. I think I have an ok understanding of it.
What I'm doing in iOS is making a simple game that involves dragging numbers into a box, which the app then grabs the number that was dragged into the box. However, I have no idea how I do this.
I was wondering if you guys have any links/articles on something like this, if I should use a certain framework for this, or maybe even some example code.
Thanks for any answers.
do a search on 'cocos2d drag' ... also, here is an article that might help you get started.
This is a pretty general question with huge scope, but a simple approach could be to create a custom UIView subclass for each draggable number that handles the drawing. In a view controller you can create all of these custom UIViews and create a UILongPressGestureRecognizer for each one, with the allowableMovement property set to CGFLOAT_MAX, or some large number. Attach the recognizer to each draggable view with the action method some callback in the view controller.
Then, in the view controller gesture recognizer action method, you can just update the views center property to the gesture recognizers location in the view controllers view. Something like:
- (void)handleLongPressGesture:(UIGestureRecognizer *)recognizer
{
if ([recognizer state] == UIGestureRecognizerStateChanged)
{
[[recognizer view] setCenter:[recognizer locationInView:self.view]];
}
}
Cocos2D is fine, and I like it, but you don't really need to go to that level just to do some basic UITouch handling. If all you want to do is handle some touches, here is an example:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [[event allTouches] anyObject];
// Only move the placard view if the touch was in the placard view
if ([touch view] != placardView) {
// On double tap outside placard view, update placard's display string
if ([touch tapCount] == 2) {
[placardView setupNextDisplayString];
}
return;
}
// "Pulse" the placard view by scaling up then down
// Use UIView's built-in animation
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.5];
CGAffineTransform transform = CGAffineTransformMakeScale(1.2, 1.2);
placardView.transform = transform;
[UIView commitAnimations];
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.5];
transform = CGAffineTransformMakeScale(1.1, 1.1);
placardView.transform = transform;
[UIView commitAnimations];
// Move the placardView to under the touch
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.25];
placardView.center = [self convertPoint:[touch locationInView:self] fromView:placardView];
[UIView commitAnimations];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [[event allTouches] anyObject];
// If the touch was in the placardView, move the placardView to its location
if ([touch view] == placardView) {
CGPoint location = [touch locationInView:self];
location = [self convertPoint:location fromView:placardView];
placardView.center = location;
return;
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [[event allTouches] anyObject];
// If the touch was in the placardView, bounce it back to the center
if ([touch view] == placardView) {
CGPoint location = [touch locationInView:self];
location = [self convertPoint:location fromView:placardView];
UIView* dropZone; // assume this exists
CGRect dz = [dropZone frame];
if (CGRectContainsPoint(dz,location)) {
[self doDropMagic];
}
}
}
For more, check out the event handling guide which is where I got this hacked up code from.
I actually would point you to using Cocos2d. It's a nice framework on top of opengl and is pretty fast. It's so easy to create objects(in terms of Cocos2d it's a CCSprite) and put them on your view(CCLayer) and interact with them.
A drag and drop would actually consists of three phase (pseudo-code) using the touch handler methods (similar to UIKit's touch methods):
Pick up a number in ccTouchesBegan
move the number in ccTouchesMoves: update the numbers position with the touch position
Drop the number with ccTouchesEnded
The TouchesTest target in the repository shows a decent example on drag and drop. It implements a simple Pong like game:
Touch handling basics of cocos2d
There are tons of examples in the repository of cocos2d to understand the different parts better.
A decent book on cocos2d is the Learning Cocos2D.

Resources