Does anyone know how to animate 3 parallel lines to a right arrow as happens in navigation menu bar to open up a side menu by applying CGAfflineRotation to 3 views .
I really need help on these as so that at-least I can get an idea to start it.
This is what it will be look like some how tried to draw it:-
_______
_______ \
_______ to ___________\
/
/
Any idea or suggestion will be helpful.
As you said, you should use CGAffineRotation. I'm giving simple example of what you want, everything should be ofc packed into proper methods, views should be with some basic autolayouts/layoutFrames etc. I'm just posting possible solution for rotation, quickly written in viewDidAppear, what should be changed.
Another option is to use eg firstView.layer.anchorPoint and set it to proper position.
#define degreesToRadians(x) (M_PI * x / 180.0)
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
// let's create these 3 views as a lines
UIView *firstView = [[UIView alloc] initWithFrame:CGRectMake(50, 50, 50, 1)];
[firstView setBackgroundColor:[UIColor blackColor]];
[self.view addSubview:firstView];
UIView *secondView = [[UIView alloc] initWithFrame:CGRectMake(50, 53, 50, 1)];
[secondView setBackgroundColor:[UIColor blackColor]];
[self.view addSubview:secondView];
UIView *thirdView = [[UIView alloc] initWithFrame:CGRectMake(50, 56, 50, 1)];
[thirdView setBackgroundColor:[UIColor blackColor]];
[self.view addSubview:thirdView];
// now we can perform rotation, mind the degree and offsets in transform.
[UIView animateWithDuration:1 animations:^{
CGAffineTransform transform = CGAffineTransformMakeTranslation(24, 1);
transform = CGAffineTransformRotate(transform, degreesToRadians(45));
transform = CGAffineTransformTranslate(transform, -24, 1);
firstView.transform = transform;
transform = CGAffineTransformIdentity;
transform = CGAffineTransformMakeTranslation(24, -1);
transform = CGAffineTransformRotate(transform, degreesToRadians(-45));
transform = CGAffineTransformTranslate(transform, -24, -1);
thirdView.transform = transform;
} completion:^(BOOL finished) {}];
}
It is easier to do such animation with CALayer Animation instead of UIView Animation. You need to add three sub layers: top_line, middle_line, and bottom_line. Then draw the line on each layer and the right position (Hint: use CAShapeLayer for drawing lines). At last just rotate the top_line and bottom_line layer with a proper angle, you probably need to change the anchor point and layer's position as well. It is quite tricky at the last step, maybe just do some trials with different angle and anchor point values until you find the most proper ones.
When you successfully did such animation, try to do the reverse animation. Then create a Custom UIButton with these two animations embedded in. Congratulations! You have a hamburger button with cool animations which could be reused anywhere!
Related
NOTE: code written in browser; probably not perfectly accurate, but it should give the general idea.
I've got a stack of views, something like this:
CGRect theFrame = CGRectMake(0, 0, 10, 10);
UIView *v1 = [UIView alloc] initWithFrame: theFrame];
UIView *v2 = [UIView alloc] initWithFrame: theFrame];
UIView *v3 = [UIView alloc] initWithFrame: theFrame];
// Set all the background colors, so I can see them: snip
// set all the clipsToBounds = NO, so I can place however I want: snip
v2.center = CGPointMake(100, 80);
[v1 addSubview: v2];
v3.center = CGPointMake (57, 42);
[v2 addSubview: v3];
v1.center = CGPointMake (193, 44);
[self.view addSubview: v1];
// etc., time passes, user presses TEST button
CGAffineTransform sXfrm = CGAffineTransformMakeScale(3.5, 4.7);
CGAffineTransform rXfrm = CGAffineTransformMakeRotation(M_PI / 3.);
v1.transform = CGAffineTransformConcat(sXfrm, rXfrm);
// etc., rotate & scale v2, while we're at it. snip
// Leave v3 unrotated & unscaled.
Ok, at this point, everything is displaying on the screen exactly as desired. My question is:
Where (that is: where, in self.view's coordinate space) is v3.center?
Here's some code that gives the CLOSE answer, but not-quite right, and I can't seem to figure out what's wrong with it:
CGRect testRect = CGRectMake(0, 0, 20, 20);
UIView *testView = [[UIView alloc] initWithFrame: testRect];
testView.backgroundColor = [UIColor greebColor];
[self.view addSubview: testView];
CGPoint center = v1.center;
#if 1 // Apply Transform
CGPoint ctr2 = CGPointApplyAffineTransform(v2.center, v1.transform);
#else // use layer
CGPoint ctr2 = CGPointMake ((v2.layer.frame.origin.x + (v2.layer.frame.size.width / 2.)),
(v2.layer.frame.origin.y + (v2.layer.frame.size.height / 2.)) );
ctr2 = CGPointApplyAffineTransform(ctr2, v1.transform);
#endif
center.x += ctr2.x;
center.y += ctr2.y;
CGPoint ctr3 = CGPointApplyAffineTransform(v3.center, CGAffineTransformConcat(v2.transform, v1.transform));
center.x += ctr3.x;
center.y += ctr3.y;
testView.center = center; // I want this to lay on top of v3
[self.view addSubview: testView];
NOTE: In my actual code, I put in-between test-views at all the intermediate centers. What I get is: testView1 (center) is correct, testView2 (ctr2) is off by a little, testView3 (ctr3) is off by a little more.
The #if is because I was experimenting with using ApplyAffine vs layer. Turns out they both give the same result, but it's a tad off.
Hopefully clarifying image:
You can use the UIView's convert points methods:
convertPoint:toView:
convertPoint:fromView:
convertRect:toView:
convertRect:fromView:
However once you apply a transformation, you should stop using frames and use center + bounds instead (which might be the reason your code is not working), from apple docs (for frame):
Warning: If the transform property is not the identity transform, the
value of this property is undefined and therefore should be ignored.
The only other thing you have to be conscious about is, which view invoques the convert to what other view since the results change. (source coordinate system -> target coordinate system.)
EDIT:
Thanks to this answer and Chiquis' comments in the question, my (Olie's) final (working) code looks like this:
CGRect testRect = CGRectMake(0, 0, 20, 20);
UIView *testView[3];
for (int ii = 0 ; ii < 3 ; ++ii)
{
testView[ii] = [[UIView alloc] initWithFrame: testRect];
testView[ii].backgroundColor = [UIColor redColor];
[self.view addSubview: testView[ii]];
}
testView[0].center = v0.center;
testView[1].center = [v0 convertPoint: v1.center toView: self.view];
testView[2].center = [v1 convertPoint: v2.center toView: self.view];
To clarify some, v1 is a subview of v0, and v2 is a subview of v1; this dictates the receivers in the covertPoint: calls.
I try to let iOS do the heavy lifting for me when I need to transform points between different layers.
CGPoint point = v3.center;
CGPoint ctr3 = [v3.layer.presentationLayer convertPoint:point toLayer:self.layer.presentationLayer];
The presentation layer object represents the state of the layer as it currently appears onscreen. This can help avoid timing issues if animations are involved.
Just noticed the comment that came in while I was composing: yes, I use this to account for rotation transforms similar to what you describe.
I would like to implement a temporary alert into to a uitableviewcontroller to inform a user that their data has been saved.
I could do this with a uiAlertView - but for aesthetic reasons it would be preferable to implement something similar to the round cornered fade in/out alert view used in IOS7 for tasks such as showing volume control - as in this pic -
Is this a IOS7 class? I cant find any info on it (probably because I don't know what its called!), or would I need to extend the uiAlertView to replicate this functionality?
No, this is not a native iOS 7 subclass but there are plenty implementations of it in the wild. One example would be DTAlertView or CXAlertView or SDCAlertView. Check them out.
If you just want the volume-thingy type behavior, don't search too far. It's easily home-rolled with a full screen sized image view that's mostly transparent, and has in it's center a rounded-corner message rectangle, translucent if you like. Then build a convenience method to show/hide it with animation...
- (void)setAlertHidden:(BOOL)hidden animated:(BOOL)animated {
UIImageView *alertImageView = [self alertImageView];
BOOL currentlyHidden = alertImageView.alpha == 0.0;
if (currentlyHidden == hidden) return;
NSTimeInterval alpha = (hidden)? 0.0 : 1.0;
NSTimeInterval duration = (animated)? 0.3 : 0.0;
[UIView animateWithDuration:duration animations:^{
alertImageView.alpha = alpha;
} completion:^(BOOL finished) {
// if we showed the alert, hide it after 3 seconds
// you can make this duration a parameter
if (!hidden) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 3*NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[self setAlertHidden:YES animated:YES];
});
}
}];
}
All you need other than this is to build the image view lazily...
- (UIImageView *)alertImageView {
UIImageView *alertImageView = (UIImageView *)[self.view viewWithTag:999];
if (!alertImageView) {
alertImageView = [[UIImageView alloc] initWithFrame:self.view.bounds];
alertImageView.image = [UIImage imageNamed:#"fullscreen-volume-looking-thing.png"];
alertImageView.tag = 999;
[self.view addSubview:alertImageView];
}
return alertImageView;
}
Realize it's not exactly what you need, but this was pretty easy to paste together. It's the volume thing on a screen sized transparent background...
This is a quick implementation of a subview that will do what you want
#import "BellAlert.h"
#implementation BellAlert
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = [UIColor clearColor];
UIImageView *bell = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"Bell.png"]];
bell.frame = CGRectMake(0, 0, 50, 50);
bell.center = self.center;
[self addSubview:bell];
}
return self;
}
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, [UIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.1].CGColor);
CGContextMoveToPoint(context, 5, 0);
CGContextAddLineToPoint(context, 95, 0);
CGContextAddArcToPoint(context, 100, 0, 100, 5, 10);
CGContextAddLineToPoint(context, 100, 95);
CGContextAddArcToPoint(context, 100, 100, 95, 100, 10);
CGContextAddLineToPoint(context, 5, 100);
CGContextAddArcToPoint(context, 0, 100, 0, 95, 10);
CGContextAddLineToPoint(context, 0, 5);
CGContextAddArcToPoint(context, 0, 0, 5, 0, 10);
CGContextFillPath(context);
}
#end
If you want more complex functionality, you can implement public properties and a protocols
If you want to check it working look at it at GitHub... https://github.com/eharo2/BellAlert
for aesthetic reasons it would be preferable to implement something
similar to the round cornered fade in/out alert view used in IOS7
Rounding the corners of any view is incredibly easy:
someView.layer.cornerRadius = 5.0;
Making a view fade in and out is also trivial using Core Animation. For example, if you want to fade in someView, set its alpha property to 0 and then animate setting it to 1:
someView.alpha = 0.0;
[UIView animateWithDuration:1.0 animations:^{
someView.alpha = 1.0;
}];
With those tools at your disposal, you'll find it very easy to roll your own version of Apple's display.
I have an element in a UIView with a constraint that says it should always be 10 pixels from the bottom of the view. I am then attempting to animate this view's height so that it appears to slide down the screen. According to the constraint, the element should always be 10 pixels from the bottom of the view. This holds true when I add the view like so...
printView *vc = [self.storyboard instantiateViewControllerWithIdentifier:#"printView"];
[self addChildViewController:vc];
vc.view.frame = CGRectMake(0, 60, vc.view.frame.size.width, HEIGHT);
vc.view.layer.masksToBounds = FALSE;
[vc.view.layer setShadowOffset:CGSizeMake(0.0, 5.0)];
[vc.view.layer setShadowOpacity:0.8];
vc.view.layer.shadowColor = [[UIColor blackColor] CGColor];
vc.view.layer.shadowRadius = 8;
vc.view.clipsToBounds = TRUE;
[self.view insertSubview:vc.view aboveSubview:docWebView];
I can change HEIGHT to whatever I want and the element is always 10 pixels from the bottom of the view. The problem comes when I try to animate the height
printView *vc = [self.storyboard instantiateViewControllerWithIdentifier:#"printView"];
[self addChildViewController:vc];
vc.view.frame = CGRectMake(0, 60, vc.view.frame.size.width, 0);
vc.view.layer.masksToBounds = FALSE;
[vc.view.layer setShadowOffset:CGSizeMake(0.0, 5.0)];
[vc.view.layer setShadowOpacity:0.8];
vc.view.layer.shadowColor = [[UIColor blackColor] CGColor];
vc.view.layer.shadowRadius = 8;
vc.view.clipsToBounds = TRUE;
[self.view insertSubview:vc.view aboveSubview:docWebView];
[UIView animateWithDuration:5.0 animations:^{
vc.view.frame = CGRectMake(0, 60, vc.view.frame.size.width, 200);
[self.view setNeedsDisplay];
}];
The constraint is no longer honored and instead of the view sliding in with the element always 10 pixels from the bottom, it looks like the element is being uncovered because the element does not move with the view. I hope I am explaining this well enough. To put it another way, I'm going for the effect of a map being pulled down, but instead it looks like the map is already there and a piece of paper that is covering it is being pulled away. Thanks for your help.
When using constraints, you shouldn't be setting frames, you should be adjusting the constraints. When you initially set up your constraints, you should make an IBOutlet to the height constraint, and then animate its constant parameter in the animation block. If you had a height constraint called heightCon, you could do this:
[UIView animateWithDuration:5.0 animations:^{
self.heightCon.constant = 200
[self.view layoutIfNeeded];
}];
I'm not sure about your structure, that self.view might have to be vc.view instead.
After Edit:
This is the way to animate a height constraint, but I'm not sure that's what you want to do to accomplish the look you're after. I'm not really sure what to make of your last paragraph. If you're going for an effect of a map being pulled down, it seems like the bottom constraint needs to be either animated or eliminated.
I need to rotate a UIView around a fixed point.
bubbleTail = [[BubbleTail alloc] initWithFrame:bubbleTailFrame];
[self addSubview:bubbleTail];
bubbleTail.layer.anchorPoint = triangle_top_left;
CGAffineTransform transform = CGAffineTransformMakeRotation(M_PI * 180 / 180.0);
bubbleTail.transform = transform;
This works only if I comment the .anchorPoint line.
How do I rotate the view about a point inside the view?
The below should help. The tricky part is that setting the anchorpoint after adding a view with a frame then moves the view. I've gotten around this by initiating the UIView without a frame, then setting the position, bounds and anchorpoint of its layer. I then use the rotation to rotate around the anchor point.
CABasicAnimation *rotation;
rotation = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
rotation.fromValue = [NSNumber numberWithFloat:0];
rotation.toValue = [NSNumber numberWithFloat:((360*M_PI)/180)];
rotation.duration = 60.0;
rotation.repeatCount = HUGE_VALF;
UIView *myview = [[UIView alloc] init];
[self.view addSubview:myview];
[myview setBackgroundColor:[UIColor orangeColor]];
[myview.layer setPosition:CGPointMake(10, 10)];
[myview.layer setBounds:CGRectMake(0, 0, 100, 100)];
[myview.layer setAnchorPoint:CGPointMake(0, 0)];
[myview.layer addAnimation:rotation forKey:#"myAnimation"];
Someone please correct me if this is not the best method or if there are mistakes - I only know what I currently know - e.g. when I should use a . or f after a float.
So it seems CGAffineTransformMakeRotation rotates it around the center of the view?
Say you have point P (px,py) in view, you want view to be rotated around that.
Let's A(ax,ay) = P - center = (px - centerx, py - centery).
And you have angle alpha.
Then you could make a translate matrix with - A, then multipy it with rotation matrix with alpha, then multipy it with translate matrix with + A.
The functions you'll need are: CGAffineTransformMakeTranslation, CGAffineTransformMakeRotation, CGAffineTransformConcat.
P.S. Not sure if this will work (also not sure how view rect is represented, maybe it first assumption about rotation around center is wrong, then you shoud assume A = P), but that's how it's done with affine transforms.
Not sure if the title is very clear but I don't know the exact name for the thing I'm looking for. I have a gird that consists of UIButtons (basically a sequencer). I want a line similar to the vertical line in this image (between the 8th and 9th button of each row:
So basically when my grid is being played I want to indicate the progress. Any idea how I could do this? What kind of elements to use etc.
You can add a UIView that is very narrow, 1 or 1.5 pixels. Set the background color to white or a gradient. Add this view to the same subview as the buttons.
UIView *lineView = [[UIView alloc] initWithFrame:CGRectMake(100.0, 0.0, 1.5, 320.0)];
lineView.backgroundColor = [UIColor whiteColor];
[parentView addSubview:lineView];
To show progress you'll need to know how much is left until completion. Take that value and divide by 320 to get the lineView height.
CGRect frame = CGRectMake(100.0, 0.0, 1.5, 0);
lineView.frame = frame; // 0% complete
frame = CGRectMake(100.0, 0.0, 1.5, 160.0);
lineView.frame = frame; // 50% complete
frame = CGRectMake(100.0, 0.0, 1.5, 320.0);
lineView.frame = frame; // 100% complete
That is the idea. Hope this help.
Update to vertical bar moving across the screen
UIView *lineView = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, 1.5, 320.0)];
lineView.backgroundColor = [UIColor whiteColor];
[parentView addSubview:lineView];
As progress is made
- (void)progressWithValue(CGFloat)progress {
CGRect frame = lineView.frame;
frame.origin.x = progress; // Change x position
lineView.frame = frame;
}
You can use UISlider to show the progress of your events or activity(not so clear of your code) and customize it according to your needs.You can look at the UICatalog sample code by Apple to get to know-how of customizing a UISlider along with other elements or UISlider Tutorial