I have an UIImageView of an white color arrow image. I am animating it to rotate 90 degree's. I also want that UIImageView to change to a black color arrow image as it rotates.
The only solution I can think of is to stack the two images on top of each other, animate both when ever it rotates and change the white color arrow image's alpha (so it fades in and out).
Is there a simpler way to obtain this effect?
Is there a build in transition effect to fade one image out and another image in?
You seem to have a good grasp of what to do. Probably the easiest repeatable way to do this is to create a UIView subclass and add both of your arrows to it. Then add a couple of functions like these.
-(void)rotate90{
blackArrow.alpha = 0;
[self bringSubviewToFront:blackArrow];
[UIView animateWithDuration:1 animations:^{
blackArrow.alpha = 1;
blackArrow.transform = CGAffineTransformMakeRotation(M_PI_2);
whiteArrow.transform = CGAffineTransformMakeRotation(M_PI_2);
}];
}
-(void)rotate0{
whiteArrow.alpha = 0;
[self bringSubviewToFront:whiteArrow];
[UIView animateWithDuration:1 animations:^{
whiteArrow.alpha = 1;
blackArrow.transform = CGAffineTransformMakeRotation(0);
whiteArrow.transform = CGAffineTransformMakeRotation(0);
}];
}
Related
I started studying objective-c using the iPhone and iPad apps for Absolute Beginners by Rory Lewis book but i got stuck on the 5th chapter.
I want to make a button that shrinks an image.
My problem is, that after I wrote all the code, the image shrinks to the point (0, 0) of the UIImageView (the top left), while in the video the image shrinks to its center. I've done some research and found out that CGAffineTransform uses the center of the object to make translations, rotations etc.
So why does it not work in my case?
I have the latest Xcode version and haven't done anything strange.
I hope I've been clear. Sorry for my English.
EDIT
Sorry for the code, but i wrote the question from my phone.
Anyway the incriminated part goes something like this
- (IBAction)shrink:(id)sender {
if(hasShrink == NO){
[shrinkButton setTitle:#"Grow" forState:UIControlStateNormal];
}
else if(hasShrink == YES){
[shrinkButton setTitle:#"Shrink" forState:UIControlStateNormal];
}
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:1.0];
myIcon.transform = CGAffineTransformMakeScale(.25, .25);
hasShrink = YES;
[UIView commitAnimations];
}
All the animations are correctly written and the app does work, it just shrinks to the top left. Why is the point not set to the center of the UIImageView by default like it should be?
EDIT:
I figured out it was a problem caused by AutoLayout.
Once disabled everything went smooth ;)
If you are transforming a view mangled managed by auto-layout, you may experience some strange side-effects. See this answer for more details.
The solution I ended up employing was to encapsulate the view I wanted to transform inside another view of exactly the same size. The outer view had the appropriate layout constraints applied to it while the inner view was simply centered in its container. Applying a CGAffineTransform scale transform to the inner view now centers properly.
Old question... but just in case others come looking:
The CGAffineTransform acts (rotates, scales) around an anchorPoint.
If you are sizing to 0, 0 then your anchor point is set to 0, 0. If you want it to size to a different point, such as the center of the image, you need to change the anchor point.
The anchor is part of a layer
So if you have a UIImageView called imageview, you would make a call like this to set the anchor to the center of imageview:
[imageview.layer setAnchorPoint:CGPointMake(0.5, 0.5)];
Try something like this:
CGAffineTransform t = CGAffineTransformMakeScale(0.5, 0.5);
CGPoint center = imageView.center; // or any point you want
[UIView animateWithDuration:0.5
animations:^{
imageView.transform = t;
imageView.center = center;
}
completion:^(BOOL finished) {
/* do something next */
}];
Next time show your code. It will be easier to help you.
Check this project: https://github.com/djromero/StackOverflow/archive/Q-13706255.zip
You must study how autolayout and autoresize affect your views. In the project above both are disabled to let you see how it works.
Does anyone know how to animation an image in Objective-C like it is done at
Famous Brands (Requires Flash Player to see)
Take your image and slice it in parts with your favorite photo app. Create an imageView for each image and add it to a new UIView which play the role of the image container. Now add this UIView to your main view.
If you want to animate it simply iterate through all imageViews inside your container view and start the following animation:
[UIView animateWithDuration:0.3
delay: i * 0.3
options:UIViewAnimationCurveEaseInOut
animations:^{
currentImageView.alpha = 0.0;
currentImageView.transform = CGAffineTransformMakeScale(0.0, 1.0);
}
completion:nil
];
Where currentImageView is the current imageView in the loop and i the iterator var.
It would be even better to slice the original Image with CoreGraphics, so you don't have to do it manually as stated above but that's a little bit more work.
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.
I am trying to animate an image using following code:
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:40];
[UIView setAnimationCurve:UIViewAnimationCurveLinear];
CGAffineTransform rotate = CGAffineTransformMakeRotation(0.0);
CGAffineTransform moveRight = CGAffineTransformMakeTranslation(0, 0);
CGAffineTransform firstHalf = CGAffineTransformConcat(rotate, moveRight);
CGAffineTransform zoomIn = CGAffineTransformMakeScale(2,2);
CGAffineTransform transform = CGAffineTransformConcat(zoomIn, firstHalf);
fimage.transform = transform;
[UIView commitAnimations];
I have UIScrollView which has one subview UIImageView and fimage is that UIImageView. But when i try to detect the touch on it i am not able to do that. Can any one help me? Is is possible to detect touches on UIScrollView.
The main application is to display images in panning and when user clicks on it the web page containing that image should be loaded in next web view.
How can I achieve this?
I tested your code and it seems to be working without hiccups. It is likely that the image view object has its userInteractionEnabled set to NO. Set it to YES to make touches work.
Check this to see how you can load images in UIWebView object. As for panning, you should try out the UIPanGestureRecognizer if you aren't already.
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];