How to determine if the keyboard is hiding a subview...? - iphone

I have a scrollview with multiple views on it. All these views have text field on them and once clicked the keyboard pops up. In certain cases the keyboard may hide this subview and I want to determine if this can be calculated in advance. If yes than how can I do it..? Can the scrollRectToVisible of UIScrollView method be used here to any use.. ? you look at the attached image for further clarification. Thanks for any ideas..
EDIT:
These subviews are dynamically drawn so I can't determine and hard code this.

In Landscape mode :
Height of iPad : 768 pixels.
Height of Keyboard : 352 pixels.
Which means keyboard x co-ordinates : 0.0 and y co-ordinates : 416
Whenever your controls y co-ordinates are greater then 416. Understand it'll be hidden when keyboard will pop-up.

Yeah , you need to shift the subview a little up so that it can be visible, you can use this animation to shift it to any point. Just pass the layer and destination point. and once user pressed return on keyboard, shift it again to the previous point :)
-(void)moveLayer:(CALayer*)layer to:(CGPoint)point
{
// Prepare the animation from the current position to the new position
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"position"];
animation.fromValue = [layer valueForKey:#"position"];
animation.duration = 0.2;
animation.toValue = [NSValue valueWithCGPoint:point];
// Update the layer's position so that the layer doesn't snap back when the animation completes.
layer.position = point;
// Add the animation, overriding the implicit animation.
[layer addAnimation:animation forKey:#"position"];
}

Register for the following notification:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardDidChangeFrame:)
name:UIKeyboardDidChangeFrameNotification
object:nil];
Make sure you handle this:
- (void)keyboardDidChangeFrame:(NSNotification *)notification
{
CGRect keyboardEndFrame;
[[notification.userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] getValue:&keyboardEndFrame];
CGRect keyboardFrame = [self.view convertRect:keyboardEndFrame fromView:nil];
// Add your code to check if the keyboard is hiding your views.
// CGRectIntersectsRect should help you here.
// For each view you are worried about hiding, run CGRectIntersectsRect to check
// if an intersection occurs. If it does, then you can
// move your view using UIScrollView's setContentOffset: animated: method.
}

Related

detect touch of subview of uiview being animated using CAAnimation

I'm trying to detect a touch on a UISubview of a view being animated
Here's my detection code:
//simple but not very elegant way of getting correct frame
CGRect keepFrame = self.fishContainer.layer.frame;
self.fishContainer.layer.frame = [[self.fishContainer.layer presentationLayer] frame];
//get touch location, taking presentation layer into account. (See above)
CGPoint p = [sender locationInView:self.fishContainer];
CALayer *layer =[self.fishContainer.layer presentationLayer];
//apply relevant transform
p = CGPointApplyAffineTransform(p,layer.affineTransform);
EBLog(#"checking point %#",NSStringFromCGPoint(p));
UIView *vToRemove = nil;
//find topmost view containing touched point
for (UIView *v in self.parasites) {
EBLog(#"-BOUND %#",NSStringFromCGRect(v.frame));
if(CGRectContainsPoint(v.frame, p))
{
vToRemove = v;
}
}
//OK, we have a view. Let's remove it.
if(vToRemove)
{
EBLog(#"found one");
[vToRemove removeFromSuperview];
[self.parasites removeObject:vToRemove];
if ([self.parasites count] == 0) {
[self showWinnerScreen];
[self stopGame];
}
}
//restore view frame
self.fishContainer.layer.frame = keepFrame;
Everything works correctly as long as I don't animate parasiteArea parentview.
When I animate parasiteArea's parentview (A CAAnimation consisting of move of the view, scale of the view, and rotate of the view) , the touch is outside the bounds of the expected subview.
UPDATE
I manged to get the detection working in most cases (see code above), by using the presentationLayer property and CGPointApplyAffineTransform. There is however, still some cases where it dosnt work.
I guess I need to translate the touch point to the coordinate space of the CAAnimation.
Or something like that? any suggestions?
I ended up using UIView animateWithDuration instead of CAAninmation. For my purpose the limited animation possibles were enough.

Vertical UIScrollView?

Im struggling with some iOS development and I hope someone will be able to help me with an advice. I want to implement a stack of UIViews that will look like a deck of cards. User should be able to swipe out 'cards' with touch. I thought of UIScrollViews with pagingEnabled to give an impression that cards are separate. However, UIScrollView can swipe from horizontally where something appears from left or right side. I want my deck of cards to be in the middle of the screen so the user can swipe cards from the deck without seeing the card appearing on the left or right side.
Also, I was thinking of using Touch Methods ( touchesBegan, touchesMoved, etc.) to move views. It could work... but Im worried that I wont be able to reproduce proper mechanics of the card ( friction, bouncing effect when the swipe is to short and so on..).
Can someone please point me to a proper technique or advice some tutorials? I did some research already but I couldn't find anything that would be helpful.
Here is an image of the effect that I would like to achieve.
And what I want to achieve is to swipe cards out of the deck.
Thanks a lot!
I guess you want a swipe to take the top card of the stack and “shuffle” it to the bottom of the stack, revealing the second card on the stack, like this:
The animation is much smoother in real life. I had to use a low frame rate to make the animated GIF small.
One way to do this is to create a subclass of UIView that manages the stack of card views. Let's call the manager view a BoardView. We'll give it two public methods: one for shuffling the top card to the bottom, and one for shuffling the bottom card to the top.
#interface BoardView : UIView
- (IBAction)goToNextCard;
- (IBAction)goToPriorCard;
#end
#implementation BoardView {
BOOL _isRestacking;
}
We'll use the _isRestacking variable to track whether the board view is animating the movement of a card off to the side.
A BoardView treats all of its subviews as card views. It takes care of stacking them up, with the top card centered. In your screen shot, you offset the lower cards by slightly randomized amounts. We can make it look a little sexier by rotating the lower cards randomly. This method applies a small random rotation to a view:
- (void)jostleSubview:(UIView *)subview {
subview.transform = CGAffineTransformMakeRotation((((double)arc4random() / UINT32_MAX) - .5) * .2);
}
We'll want to apply that to each subview as it's added:
- (void)didAddSubview:(UIView *)subview {
[self jostleSubview:subview];
}
The system sends layoutSubviews whenever a view's size changes, or when a view has been given new subviews. We'll take advantage of that to lay out all of the cards in a stack in the middle of the board view's bounds. But if the view is currently animating a card out of the way, we don't want to do the layout because it would kill the animation.
- (void)layoutSubviews {
if (_isRestacking)
return;
CGRect bounds = self.bounds;
CGPoint center = CGPointMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds));
UIView *topView = self.subviews.lastObject;
CGFloat offset = 10.0f / self.subviews.count;
for (UIView *view in self.subviews.reverseObjectEnumerator) {
view.center = center;
if (view == topView) {
// Make the top card be square to the edges of the screen.
view.transform = CGAffineTransformIdentity;
}
center.x -= offset;
center.y -= offset;
}
}
Now we're ready to handle the shuffling. To “go to the next card”, we need to animate moving the top card off to the side, then move it to the bottom of the subview stacking order, and then animate it back to the middle. We also need to nudge the positions of all of the other cards because they've all moved closer to the top of the stack.
- (void)goToNextCard {
if (self.subviews.count < 2)
return;
First, we animate the movement of the top card off to the side. To make it look sexy, we rotate the card as we move it.
UIView *movingView = self.subviews.lastObject;
[UIView animateWithDuration:1 delay:0 options:UIViewAnimationCurveEaseInOut animations:^{
_isRestacking = YES;
CGPoint center = movingView.center;
center.x = -hypotf(movingView.frame.size.width / 2, movingView.frame.size.height / 2);
movingView.center = center;
movingView.transform = CGAffineTransformMakeRotation(-M_PI_4);
}
In the completion block, we move the card to the bottom of the stack.
completion:^(BOOL finished) {
_isRestacking = NO;
[self sendSubviewToBack:movingView];
And to move the now-bottom card back into the stack, and nudge all of the other cards, we'll just call layoutSubviews. But we're not supposed to call layoutSubviews directly, so instead we use the proper APIs: setNeedsLayout followed by layoutIfNeeded. We call layoutIfNeeded inside an animation block so the cards will be animated into their new positions.
[self setNeedsLayout];
[UIView animateWithDuration:1 delay:0 options:UIViewAnimationCurveEaseInOut | UIViewAnimationOptionAllowUserInteraction animations:^{
[self jostleSubview:movingView];
[self layoutIfNeeded];
} completion:nil];
}];
}
That's the end of goToNextCard. We can do goToPriorCard similarly:
- (void)goToPriorCard {
if (self.subviews.count < 2)
return;
UIView *movingView = [self.subviews objectAtIndex:0];
[UIView animateWithDuration:1 delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
_isRestacking = YES;
CGPoint center = movingView.center;
center.x = -movingView.frame.size.height / 2;
movingView.center = center;
movingView.transform = CGAffineTransformMakeRotation(-M_PI_4);
} completion:^(BOOL finished) {
_isRestacking = NO;
UIView *priorTopView = self.subviews.lastObject;
[self bringSubviewToFront:movingView];
[self setNeedsLayout];
[UIView animateWithDuration:1 delay:0 options:UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionAllowUserInteraction animations:^{
[self jostleSubview:priorTopView];
[self layoutIfNeeded];
} completion:nil];
}];
}
Once you have BoardView, you just need to attach a swipe gesture recognizer that sends it the goToNextCard message, and another swipe gesture recognizer that sends it the goToPriorCard message. And you need to add some subviews to act as cards.
Another detail: to get the edges of the cards to look smooth when they're jostled, you need to set UIViewEdgeAntialiasing to YES in your Info.plist.
You can find my test project here: http://dl.dropbox.com/u/26919672/cardstack.zip
Icarousel has that exact built in scrolling using UIImageViews.
https://github.com/nicklockwood/iCarousel. There's several different effects in there, I forget the name of the one you're describing.

CAAnimation - User Input Disabled

I am trying to animate a button. When I do so (using button.layer addAnimation) the button becomes disabled. Is there any way to allow user interaction when animating? I also tried wrapping everything in a block using animateWithDuration passing in the option UIViewAnimationOptionAllowUserInteraction, but it still doesn't work.
EDIT: It's odd. If I click in the upper corner (where I placed my button) it fires off the event. It's almost like the frame of the button does not follow the animation.
EDIT: What I ended up doing is create an event that fires every 0.1 seconds that sets the button.frame equal to the [[button.layer presentationLayer] frame]. That seemed to do the trick.
Use the following UIView method with UIViewAnimationOptionAllowUserInteraction in the UIViewAnimationOptions parameter:
+(void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion
the best solution is to change the position of the layer after you call the animation:
[button.layer addAnimation:animation forKey:#"animation"];
button.layer.position = endPosition;
What I ended up doing is create an event that fires every 0.1 seconds that sets the button.frame equal to the button.layer
To me the event firing seemed a bit hacky.
However your mentioning of the [[button.layer presentationLayer] frame] brought me onto the right track :-)
So by attaching a gesture recognizer to the superview of the moving view I can do the following detection:
CGPoint tapLocation = [gestureRecognizer locationInView:self.view];
for (UIView* childView in self.view.subviews)
{
CGRect frame = [[childView.layer presentationLayer] frame];
if (CGRectContainsPoint(frame, tapLocation))
{
....
}
}
I wanted the button to zoom in. So I scale down the layer before I start the animation:
layer.transform = CATransform3DScale (layer.transform, 0.01, 0.01, 0.01);
// some animation code
After the animation I scaled it back.
// some animation code
CATransform3D endingScale = CATransform3DScale (layer.transform, 100, 100, 100);
// some animation code
[layer addAnimation:animation forKey:#"transform"];
layer.transform = endingScale;
Looks like if you directly assign it to the layer the frame will change. However using an animation won't change the frame.

iPad - No user interaction on view animated on-screen

Alright this is a newbie question, so I apologize in advance. I have a UIView which I laid out in Interface Builder off-screen. When the user presses a button, I would like to animate this view on-screen. It works, but I can't interact with any buttons in the UIView. I have read that only the view is moved when you animate and that the actual objects retain their positions, so I have tried setting the position of the UIViews' layers, but it causes my UIView menu to display an extra 81 pixels to the left. When this happens, I can interact with the buttons, but I need it to be flush with the right of the screen. Here is my code in the IBAction on the openMenu button:
CABasicAnimation *moveView = [CABasicAnimation animationWithKeyPath:#"position"];
moveView.delegate = self;
moveView.duration=0.5;
// If the menu is displayed, close it!
CGPoint currentPosView = [[entirePage layer] position];
CGPoint destView;
[moveView setFromValue:[NSValue valueWithCGPoint:currentPosView]];
if (menuDisplayed) {
// Move right to our destination
destView.x = currentPosView.x;
destView.y = currentPosView.y + 81;
[moveView setToValue:[NSValue valueWithCGPoint:destView]];
// Otherwise, open it
} else {
// Move left to our destination
destView.x = currentPosView.x;
destView.y = currentPosView.y - 81;
[moveView setToValue:[NSValue valueWithCGPoint:destView]];
}
// Animate the view
[[entirePage layer] addAnimation:moveView forKey:#"move"];
// Set the final position of our layer
[[entirePage layer] setPosition:destView];
menuDisplayed = !menuDisplayed;
And then in the animationDidStop method:
CGPoint currentPosMenu = [[menuBar layer] position];
if (menuDisplayed) {
currentPosMenu.x -= 81;
} else {
currentPosMenu.x += 81;
}
[[menuBar layer] setPosition:currentPosMenu];
Help???
You shouldn't need to mess with the layers. You could probably achieve the effect you want by laying out the views in Interface Builder at the positions they will be in after they are animated into view. In other words, first lay things out in their final/correct positions. Don't do anything funky in IB.
In your code, after the app is launched or in a -viewDidAppear: method if you're using a UIViewController, you could offset the views by some amount and then restore them to their original position in an animation block:
myView.transform = CGAffineTransformMakeTranslation(0,81);
[UIView beginAnimations:#"slideUp" context:nil];
myView.transform = CGAffineTransformIdentity;
[UIView commitAnimations];
As for the lack of user interaction on your views, that could be a number of things. If all of your views are subviews of something like a UIImageView, UIImageView has userInteractionEnabled set to NO by default (at least if you build one in code - I'm not sure off hand what IB's default settings are for an image view). It could also mean that the superview of your views is actually too small and its frame does not contain the subviews. (If clipping was turned on for the superview, you wouldn't even be able to see subviews that are having this particular problem.)
EDIT:
I read things more carefully and see you want things to move in response to a button press, so in other words the views will need to be hidden from the start. In that case you could still lay everything out in IB in their final position, but just set them as hidden in IB. When you push the button, set the views visible (hidden=NO) before translating them. That should allow them to be laid out in their final/correct place in IB making sure that they are positioned on the proper superviews, etc while still getting the animation effect you want.
Forget about the animations for now; you should move your view by setting its frame property.
E.g.:
CGRect menuFrame = entirePage.frame;
if (menuDisplayed)
{
menuFrame.origin.x += 81;
}
else
{
menuFrame.origin.x -= 81;
}
entirePage.frame = menuFrame;
// Do your animation here, after the view has been moved
Don't worry about the animation until you've verified that your view is being placed in the correct location. Once you've verified that that code is working correctly, then do your animation after you set the frame property.
Note that you probably don't need to use the CoreAnimation API at all, unless you're doing something complex. UIKit can do animation on its own. E.g:
[UIView beginAnimations:#"MyAnimation" context:NULL];
myFirstView.frame = ...;
myOtherView.frame = ...;
[UIView commitAnimations];

Can Scrolling control an Animation?

I have a UILabel that displays the current date for the current view of my scroll view. When I scroll this view to the left (to the left pages), I'd like this label to change to the day before, with a crossing effect between the 2 dates.
I take as reference the fading effect of the Springboard when you scroll to the Spotlight page. I think this is similar but I have no idea how to implement it !
Where do I put the code, do I have to use 2 different UILabel or maybe CoreAnimation ? When do I change the date ?
Thanks in advance !
In your scrollViewDidScroll: delegate method, check if the scroll view's contentOffset indicates that the user has scrolled to another page.
If so, change the label's text to the new date. It won't animate yet, but do this before you try the animation.
For the animation, add a CATransition to the label's layer and configure it to your liking:
// Create a transition animation and add it to the label's layer:
CATransition *transition = [CATransition animation];
transition.type = kCATransitionFade;
transition.duration = 0.25;
[self.dateLabel.layer addAnimation:transition forKey:#"textTransition"];
// Change the label's text as before:
self.dateLabel.text = newDateString;
This effect is a little different from the fading of the Springboard icons (since those fade continuously as you scroll further to the Spotlight page) but I think my approach is more appropriate for your application. To emulate the Springboard effect, you should indeed use two separate labels (with the same frame) and change their opacity values as the scroll view's contentOffset changes.
Using Core Animation with two labels would work just fine. Changing the property of a Core Animation layer result in a smooth transition by default, so you can use that to your advantage. For example:
- (void)crossfadeDateLabels
{
date1Label.layer.opacity = 0.0f;
date2Label.layer.opacity = 1.0f;
}
Or you can use the explicit animation model if you want more control:
- (void)crossfadeDateLabels
{
CABasicAnimation *date1Anim = [CABasicAnimation animationWithKeyPath:#"opacity"];
date1Anim.duration = 1.0f;
date1Anim.fromValue = [NSNumber numberWithFloat:1.0f];
date1Anim.toValue = [NSNumber numberWithFloat:0.0f]
// Do something similar for date2Anim...
[date1Label.layer addAnimation:date1Anim forKey:nil];
[date2Label.layer addAnimation:date2Anim forkey:nil];
}