Is there anyway to get UISnapBevahiour to only work along a linear x-axis?
I want the exact behaviour of a UISnapBehaviour but don't want it to 'shake' into position on both x & y axis, just the x axis.
This is for a UIView inside my contentView of a table cell.
// UIKitDynamics
self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self];
CGPoint centerPoint = self.contentView.center;
self.snapBehaviour = [[UISnapBehavior alloc] initWithItem:self.frontView snapToPoint:centerPoint];
[self.snapBehaviour setDamping:1.0f];
[self.animator addBehavior:self.snapBehaviour];
UIDynamicItemBehavior *itemBehaviour = [[UIDynamicItemBehavior alloc] initWithItems:#[self.frontView]];
itemBehaviour.elasticity = 0.0f;
itemBehaviour.allowsRotation = NO;
[self.animator addBehavior:itemBehaviour];
From UIDynamicBehavior documentation of it's action block:
#property(nonatomic, copy) void (^action)(void)
The dynamic animator calls the action block on every animation step.
So when creating your behaviour, do something like this:
UIView *view = ...
UIAttachmentBehavior *attachment = [[UIAttachmentBehavior alloc] initWithItem:view attachedToAnchor:point];
CGFloat xCoordinate = view.frame.origin.x;
attachment.action = ^{
if (view.frame.origin.x != xCoordinate) {
CGRect frame = view.frame;
frame.origin.x = xCoordinate;
view.frame = frame;
}
};
[self.dynamicAnimator addBehavior:attachment];
In my example I use a UIAttachmentBehavior but this method will also work for any other subclass of UIDynamicBehavior, in your case a UISnapBehavior.
I'll post the code I used to do a similar thing, as I mentioned in my comment it used UIView animation that leverage UIKit Dynamics underneath as opposed to using them directly...
[UIView animateWithDuration:0.5
delay:0.0
usingSpringWithDamping:0.5
initialSpringVelocity:0.3
options:UIViewAnimationOptionCurveEaseIn
animations:^{
[toView setFrame:_modalFrame];
}
completion:^(BOOL finished) {
[self.context completeTransition:YES];
}];
This presents modal view which springs up and oscillates a bit on presentation.
Related
I have created custom buttons and worked with custom UIViews in the past. However I am not sure how to go about with this.
I am trying to create something similar to the twitter heart animation.
What I am unable to get is those tiny colored circles around the heart as shown in this gif:
https://dribbble.com/shots/2416983-Twitter-Heart-Animation
Any ideas?
Also is it possible for me to simply have a gif and add the gif as a custom view in a UIButton and play the gif when button is pressed?
Here's a concept:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
UIView *bubble = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, 16.0, 16.0)];
bubble.layer.cornerRadius = 8.0;
bubble.center = self.view.center;
bubble.backgroundColor = [UIColor greenColor];
[self.view addSubview:bubble];
UIView *heart = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, 100.0, 100.0)];
heart.layer.cornerRadius = 50.0;
heart.center = self.view.center;
heart.backgroundColor = [UIColor blueColor];
[self.view addSubview:heart];
[UIView animateKeyframesWithDuration:2.0
delay:0.0
options:UIViewKeyframeAnimationOptionRepeat
animations:^{
// Heart expands
[UIView addKeyframeWithRelativeStartTime:0.0 relativeDuration:0.10 animations:^{
heart.transform = CGAffineTransformMakeScale(1.3, 1.3);
}];
// Heart contracts.
[UIView addKeyframeWithRelativeStartTime:0.15 relativeDuration:0.25 animations:^{
heart.transform = CGAffineTransformMakeScale(1.0, 1.0);
}];
// Bubble travels.
[UIView addKeyframeWithRelativeStartTime:0.0 relativeDuration:0.4 animations:^{
CGPoint destination = self.view.center;
destination.x += 100;
destination.y -= 100;
bubble.center = destination;
}];
// Bubble shrinks.
[UIView addKeyframeWithRelativeStartTime:0.6 relativeDuration:0.2 animations:^{
bubble.transform = CGAffineTransformMakeScale(0.3, 0.3);
}];
// Bubble fades.
[UIView addKeyframeWithRelativeStartTime:0.8 relativeDuration:0.2 animations:^{
bubble.alpha = 0.0;
}];
} completion:nil];
}
To get the full effect, you will need to add any a few more key frames so the bubble curves around before the fade. Given that you have a couple of bubbles, it might be a good idea to create a key frame generator.
Alternatively, you can use SKEmitterNode and add real particles to your button.
Late Answer but useful to other developers.
Here is the Link for exact same animation: https://github.com/xhamr/fave-button
Same Animation in Objective-C also: https://github.com/tljackyi/fave-button
Much like other apps, my app has a "welcome" page controller with a quick overview of the features. During this overview, I draw a UITabBar and have a circle show where the relevant feature is seen below:
I draw the circle using the following code that is executed every time a page is drawn:
double circleSize = 75;
[circleView removeFromSuperview];
circleView = [[UIView alloc] initWithFrame:CGRectMake(circleX,
circleY,
circleSize,
circleSize)];
circleView.layer.cornerRadius = (circleSize / 2);
circleView.layer.borderColor = [UIColor VancityTransitBlue].CGColor;
circleView.layer.borderWidth = 2;
I wish to have this circle appear to "breath" (grow slowly then shrink back to its original size). I use the exact code from my answer to this question:
[UIView animateWithDuration:1
delay:0
options:UIViewKeyframeAnimationOptionAutoreverse | UIViewKeyframeAnimationOptionRepeat
animations:^{
circleView.transform = CGAffineTransformMakeScale(1.5, 1.5);
}
completion:nil];
This circle is shared across three pages of the page controller and draws just fine. The animation works just fine on the first page, periodically works on the second page, and never works on the third page.
How can I have the animation play on every page for every circle view?
Here is a solution that works across all tabs. The animation can be moved around while in progress. You may want to use a better mechanism to manage the animation so that it can be cancelled efficiently. The code below is implemented in the Tab Controller. Ensure -showOverTabBarItem: and -hideCircleView are executed on main thread. It has been built, linked, ran and tested.
Show
-(void)showOverTabBarItem:(CGFloat)x {
[self hideCircleView];
self.circleView.frame =({
CGRect frame = self.circleView.frame;
frame.origin.x = x;
frame;
});
[self.view addSubview:self.circleView];
[UIView animateWithDuration:1
delay:0
options:UIViewKeyframeAnimationOptionAutoreverse | UIViewKeyframeAnimationOptionRepeat
animations:^{
self.circleView.transform = CGAffineTransformMakeScale(1.5, 1.5);
}
completion:nil];
}
Hide
-(void)hideCircleView
{
[self.circleView removeFromSuperview];
}
Initialize
Such as in viewDidLoad.
double circleSize = 75;
CGFloat circleY = self.view.bounds.size.height-(circleSize/2);
self.circleView = [[UIView alloc] initWithFrame:CGRectMake(0,
circleY,
circleSize,
circleSize)];
self.circleView.layer.cornerRadius = (circleSize / 2);
self.circleView.layer.borderColor = [UIColor blueColor].CGColor;
self.circleView.layer.borderWidth = 2;
Invoke
Pass the horizontal position to your animation: [self showOverTabBarItem: self.view.bounds.size.width/2];
Keep the view around
This is a critical step: when your -removeFromSuperView, you want to make sure that the object does not get recycled.
#interface TabController ()
#property (nonatomic, retain) UIView * circleView;
#end
Why have you alloc the circle every time a page drawn?
You can remove these lines (just need these for the first init):
if (circleView == nil) {
circleView = [[UIView alloc] initWithFrame:CGRectMake(circleX,
circleY,
circleSize,
circleSize)];
circleView.layer.cornerRadius = (circleSize / 2);
circleView.layer.borderColor = [UIColor VancityTransitBlue].CGColor;
circleView.layer.borderWidth = 2;
}
For add to another page: remove from current page
[circleView removeFromSuperview];
[circleView.layer removeAllAnimations];
and then add it to another page:
[self.view addSubView:circleView];
[UIView animateWithDuration:1
delay:0
options:UIViewKeyframeAnimationOptionAutoreverse | UIViewKeyframeAnimationOptionRepeat
animations:^{
circleView.transform = CGAffineTransformMakeScale(1.5, 1.5);
}
completion:nil];
I am new to programming in Objective-C, and am currently having an issue with the code below. I am trying to make an image drop when a button is touched, and it simply freezes when trying to do so. It needs to drop the image until it reaches the bottom of the screen, which then it should be released.
- (IBAction)itemDrop:(UIButton *)sender{
if (strncmp([[sender currentTitle] UTF8String], [copyPrimaryArray[0] UTF8String], 2) == 0)
{
UIImage *bubblImage = [UIImage imageNamed:#"Red_apple.png"];
UIImageView *appleImg = [[UIImageView alloc] initWithImage:bubblImage];
appleImg.translatesAutoresizingMaskIntoConstraints = NO;
[appleImg setContentMode:UIViewContentModeScaleAspectFit];
[appleImg sizeToFit];
[self.view addSubview:appleImg];
NSLog(#"You clicked the Apple");
int i = 0;
CGPoint coordinates = sender.frame.origin;
CGFloat x = coordinates.x;
CGFloat y = coordinates.y;
CGFloat z = UIScreen.mainScreen.bounds.size.height;
while (y < z) {
[UIView beginAnimations:nil context:nil];
appleImg.center = CGPointMake(x, y+i);
[UIView commitAnimations];
i++;
}
}
}
You do not need to move the image view one point at a time manually. That's what I seem to understand from your use of the while loop. Objective-c does a lot of things for you.
UIImage *bubblImage = [UIImage imageNamed:#"Red_apple.png"];
UIImageView *appleImg = [[UIImageView alloc] initWithImage:bubblImage];
// Set the frame of the original position here, for example:
CGRect originalPosition = CGRectMake(0, 0, appleImg.frame.size.width, appleImg.frame.size.height);
appleImg.frame = originalPosition;
// Then animate it to the new position:
[UIView animateWithDuration:1.0
animations:^{
CGRect newPosition = CGRectMake(0, 640, appleImg.frame.size.width, appleImg.frame.size.height);
appleImg.frame = newPosition;
}
completion:^(BOOL finished) {
NSLog(#"completion block"); // You can set the completion block to nil if you have nothing to do here.
}];
Make sure to start using block based animations.
The use of UIView's beginAnimations:context is highly discouraged since iOS 4.0.
At the moment I'm struggling to find a way to remove the offset glitch when I try to change the frame of a UIImageView which has a motionGroup attached to it.
This is the code for adding the motion effect:
UIInterpolatingMotionEffect *xAxis = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:#"center.x" type:UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis];
xAxis.minimumRelativeValue = #(-25.0);
xAxis.maximumRelativeValue = #(25.0);
self.motionGroup = [[UIMotionEffectGroup alloc] init];
self.motionGroup.motionEffects = #[xAxis];
[self.backImageView addMotionEffect:self.motionGroup];
now when i need to change the origin I do:
[self.backImageView removeMotionEffect:self.motionGroup];
[UIView animateWithDuration:0.3 animations:^{
CGRect frame = self.backImageView.frame;
frame.origin.x = self.originalBackImageViewOffset.x + 40;
self.backImageView.frame = frame;
} completion:^(BOOL finished) {
self.originalBackImageViewOffset = self.backImageView.frame.origin;
[self.backImageView addMotionEffect:self.motionGroup];
}];
this way there is a little glitch if the device is tilted. It appears right after removeMotionEffect: as the imageView transitions to it's original state and only then the frame changes. If the phone isn't tilted you won't even see the glitch (in the simulator).
If you don't remove the animationGroup then there is an even bigger glitch is you tilt the phone during the animation.
Does anyone know how to overcome this problem?
p.s. I don't have enough reputation to create a tag "UIMotionEffectGroup" or "UIMotionEffect".
Instead of animating the frame, apply a transform, then set the frame when the animation completes, without removing the motion effect:
[UIView animateWithDuration:0.3 animations:^{
self.backImageView.transform = CGAffineTransformMakeTranslation(40, 0);
} completion:^(BOOL finished) {
CGRect frame = self.backImageView.frame;
frame.origin.x = self.originalBackImageViewOffset.x + 40;
self.backImageView.frame = frame;
self.originalBackImageViewOffset = self.backImageView.frame.origin;
}];
I'm doing a simple animation of UIView height so that it reveals.
By default it seems to be revealing from top to bottom, and I want it to reveal bottom to top.
I have the UIView anchored to the bottom of the screen.
I'm sure it something simple i'm missing..... any tips?
Thanks
I really think the simplest way to accomplish this would be to animate BOTH the height and the y properties of the view. If they happen along the same curve, it should look completely seamless to the user. As you are animating the height to 0, also animate the y component to the original y + the original height.
UIView *view = ...;
float originalY = view.frame.origin.y;
float originalH = view.bounds.size.height;
[UIView animateWithDuration:1.2f delay:1.0f options:UIViewAnimationCurveEaseInOut animations:^{
view.frame = CGRectMake(view.frame.origin.x, (originalY + originalH), view.bounds.size.width, 0);
}completion:^(BOOL finished) {
NSLog(#"Animation is complete");
}];
I believe this would give the look and feel of a collapsing view. I haven't tried this out in code, but I see no reason why it wouldn't be possible like this.
hide under bottom
[self animateViewHeight:myView withAnimationType:kCATransitionFromBottom];
for reverse animation
[self animateViewHeight:myView withAnimationType:kCATransitionFromTop];
...
- (void)animateViewHeight:(UIView*)animateView withAnimationType:(NSString*)animType {
CATransition *animation = [CATransition animation];
[animation setType:kCATransitionPush];
[animation setSubtype:animType];
[animation setDuration:0.5];
[animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
[[animateView layer] addAnimation:animation forKey:kCATransition];
animateView.hidden = !animateView.hidden;
}
Like a dog with a bone I figured this out....
Instead of animating the frame height, I applied a transform to the view and set the anchor point of the layer.
//set the anchor point to the bottom of the view
[self setAnchorPoint:CGPointMake(0.5, 1.0) forView:hostView];
//Scale the height to close to zero
hostView.transform = CGAffineTransformMakeScale(1, 0.00001);
If I put 0 as the y scale, the view behaves weird.... at the end of the animation i just set it to hidden.
On the way back up I just use the Identity Transform (reset it)
hostView.transform = CGAffineTransformIdentity;
Note that changing my anchor point shifted the position of my view. See this post for the setAnchorPoint method which normalises the view after setting the anchorPoint
Changing my CALayer's anchorPoint moves the view
Instead you could try putting it in a view with clipsToBounds = YES and then animate it from the bottom to the middle of the view, like so:
viewToAnimate.frame = CGRectMake(viewToAnimate.frame.origin.x,
viewToAnimate.superview.frame.size.height,
viewToAnimate.frame.size.width,
viewToAnimate.frame.size.height);
[UIView animateWithDuration:0.5 animations:^{
viewToAnimate.center = viewToAnimate.superview.center;
}];
This way, you don't have to set the height to 0, and it solves any problems with autoresizing within the view.
As requested, this is the code that I'm using... I'm using a CAKeyFrameAnimation, which may be a bit more than what you're looking for. It would probably work the same with a CABasicAnimation, I'm just showing you this code because I already have it written.
-(id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
springLayer = [[CALayer alloc] init];
springLayer.backgroundColor = [UIColor redColor].CGColor;
springLayer.anchorPoint = CGPointMake(0, 1);
springLayer.frame = CGRectMake(125, 285, 100, 115);
[springLayer setNeedsDisplay];
[self.layer addSublayer:springLayer];
[self test];
}
return self;
}
-(void)test {
CAKeyframeAnimation *heightAnim = [[CAKeyframeAnimation alloc] init];
heightAnim.duration = 3;
heightAnim.removedOnCompletion = NO;
heightAnim.fillMode = kCAFillModeForwards;
heightAnim.beginTime = CACurrentMediaTime() + 0.25;
NSMutableArray *v = [[NSMutableArray alloc] init];
NSMutableArray *t = [[NSMutableArray alloc] init];
float dest = 250;
float difference = 135;
while (difference > 1.0) {
[v addObject:[NSNumber numberWithFloat:dest-difference]];
[t addObject:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]];
difference *= 0.7;
[v addObject:[NSNumber numberWithFloat:dest+difference]];
[t addObject:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]];
difference *= 0.7;
}
heightAnim.values = v;
heightAnim.timingFunctions = t;
[springLayer addAnimation:heightAnim forKey:#"bounds.size.height"];
}
one way I've done it with an AdWhirlView, hide it below the screen, then animate it up;
AdWhirlView *adWhirlView = [AdWhirlView requestAdWhirlViewWithDelegate:self];
adWhirlView.delegate = self;
adWhirlView.frame = CGRectMake(0, 430+kAdWhirlViewHeight, kAdWhirlViewWidth, kAdWhirlViewHeight);
[self.parentViewController.view insertSubview:adWhirlView belowSubview:self.view];
[UIView beginAnimations:#"AdWhirlIn" context:nil];
[UIView setAnimationDuration:.5];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
adWhirlView.frame = CGRectMake(0, 430, kAdWhirlViewWidth, kAdWhirlViewHeight);
[UIView commitAnimations];