How I could get access to circles drawn in drawRect? - ios

So, I have custom view, in which I draw 3 circles. How I could pass a "colorView" as an argument? Other man added all of this views from IB (not from code). And how could I change frames or visibility of these circles? Could I add variables for them? Thank you.
- (void)drawRect:(CGRect)rect
{
CGContextRef con = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(con, colorOfView.CGColor);
CGContextFillEllipseInRect(con, CGRectMake(0,10,85,85));
CGContextSetFillColorWithColor(con, [UIColor clearColor].CGColor);
CGContextSetBlendMode(con, kCGBlendModeClear); // erase
CGContextFillEllipseInRect(con, CGRectMake(62, -1, 35, 35));
CGContextSetFillColorWithColor(con, colorOfView.CGColor);
CGContextSetBlendMode(con, kCGBlendModeNormal);
CGContextFillEllipseInRect(con, CGRectMake(65,0,30,30));
}
Now for update color I use other method:
- (void)setCustomTitle:(NSString *)title andIcon:(NSString *)iconName andCircle:(UIColor *)color
{
self.titleLabel.text = title;
self.iconImage.image = [UIImage imageNamed:iconName];
colorOfView = color;
self.circleView.backgroundColor = color;
[self setNeedsDisplay];
}

You should create a property for your color:
#property (nonatomic, strong) UIColor *colorOfView;
And then change colorOfView = color to self.colorOfView = color
And change CGContextSetFillColorWithColor(con, colorOfView.CGColor); to CGContextSetFillColorWithColor(con, self.colorOfView.CGColor);

Expanding upon the answers/comments you have received already ...
The easy way of doing this which will definitely work
If you want to animate your circles, then you will need to call drawRec every 1/50th of a second or so. The way to do this is to use NSTimer to "wait" your loop between every frame. This can be in the ViewController or the View. After you draw your View, set up another NSTimer instance to wait another 20 milliseconds (not repeating). The delegate triggers a redraw (setNeedsDisplay), which then instantiates another NSTimer to wait another 20 milliseconds and so on. If you want the animation to stop, you don't instantiate another NSTimer.
Don't just keep calling setNeedsRefresh without a delay, or you will find your ViewController will become unresponsive as all CPU cycles will be spent refreshing. And it will run at very different speeds on different hardware.
As for how you gain access to the circle size etc in your ViewController, you have two choices. Firstly, you could possibly move the code which needs the circle size into drawRec in which case you would also handle the NSTimer instance (and delegate) in your View and your problem goes away.
Secondly, (and probably better) you could set up the circle sizes, colours etc as properties in your View .h file. Use Interface Builder (the storyboard) and the Assistant Editor to get a reference to your View class in your ViewController as an IBOutlet. You can then directly read or set these view properties from within your ViewController. (It is much easier to create a reference to your View in your ViewController than vice-versa). Your NSTimer will be in your ViewController, not your View.
If your circles are large, then you may be disappointed in the refresh rate.
The slightly harder but much faster way of doing most of this
As Dan pointed out, there may be a better way. You could set up your circles as the images in an ImageView (very similar to a View, but be aware that you cannot perform a drawRec against an ImageView). You can then animate the position, size, transparency and rotation by setting the frame of the UIImageView. The advantage is that this is far more efficient (the transformations are essentially done in hardware). For size, draw a large circle and scale down so you don't get jaggies. As far as I know, you can't directly change the colour, but you can probably set up a cut-out in the shape of a circle and use it as a mask against a rectangle which you use memset (or something fast) to fill in the correct colour. This is pretty complicated to do, if you need to change colours I would definitely try option 1 first. You should read https://developer.apple.com/library/ios/qa/qa1708/_index.html to see how this option works in general and why it is better than Option 1.
So in summary I would try Option 1 first, and see if it is fast enough to give you smooth animation. If it doesn't, try Option 2 which (apart from changing colours) is easier than it probably sounds.
Good luck

Related

Using a UIButton to mask a moving UIView

I want to bind two views to a button's border like a mask. The effect I want is seen in the image below
So I have a custom controller that uses a PageViewController to swipe between two UIViewControllers. I also have two views (the red one and the blue one). I can track the direction and degree of the page turning, so I can link that to the movement of the two views. All I need to do is apply a mask to the moving boxes so that all the movement stays within the button.
If anyone knows how to do this, I'd be eternally grateful for the information!
My understanding is that you have a view, then you want to transition between its two subviews with a sliding animation. This is fairly straight forward.
First set "Clip Subviews" to true (either in storyboard editor or setting view.clipToBounds=true). If you want a sphere like you show then set layer.cornerRadius = view.frame.size.width/2
The you can just set the frames of your subviews- either animating them with UIView animateWithDuration or setting the frames yourself in your animation loop.
The former in code (for sliding the first element to the left and right element in):
[UIView animateWithDuration:duration animations:^{
[subview1 setFrame:CGRectMake(-subview1.frame.size.width, 0, subview1.frame.size.width, subview1.frame.size.height)];
[subview2 setFrame:CGRectMake(0, 0, subview2.frame.size.width, subview2.frame.size.height)];
}];
The reverse animation is similar. If you're doing it in your own animation loop (which sounds like what you want) then you can do the interpolation yourself pretty easily.
If you want to be more advanced with your mask (i.e. based on an image) then look at something like this: How to mask UIViews in iOS
This solution may not be the optimized one, but do the trick you want. First i wanna make sure that i understand your requirement. I understand that you wanna go away to left the red image & bring in the right blue image in the button with the sliding effect.
If my understanding meet your requirement,Here is the trick
First decide which portion of red & blue image you needed. That must be easy as you know the direction & degree of the turning the UIPageViewController.
Then Crop that decided portions from the red & blue image using the code here.
Now draw both resulted cropped image on the coreGraphics side by side (red on the left & blue on the right) & retrieve the final resulted image.
set the image to the UIButton.
Finally to acquire the round shape you can set the radius property of layer property of the button.
For the third step, how you can you draw image on coreGraphics & retrieve apple give a detail explanation here
You have to do all these steps each time you wanna update the image of the UIButton. There may be a other good way to do this :)
You can use corner radious on the button layer
CGPoint saveCenter = roundedView.center;
CGRect newFrame = CGRectMake(roundedView.frame.origin.x, roundedView.frame.origin.y, newSize, newSize);
button.frame = newFrame;
button.layer.cornerRadius = newSize / 2.0;
button.center = saveCenter;

What's the best most CPU efficient way to draw views with a lot of animations in iOS?

I'm trying to draw a graphic equaliser for an iOS project.
The equaliser will have 7 bars, representing different frequency bands, than move up and down based on real-time audio data.
Can anyone suggest the best way to approach this in iOS?
New frequency data comes in at about 11Hz, and so the bars would have to animate to a new size 11 times per second.
Do I create a UIView for each bar and dynamically resize it's frame height?
Do I draw the bars as thick CGStrokes and redraw them within the parent view as needed?
Another option?
Thanks in advance
You want to use Core Animation. The basic principle is to create a bunch of "layer" objects, which can either be bitmap images, vector shapes, or text. Each layer is stored on the GPU and most operations can be animated at 60 frames per second.
Think of layers like a DOM node in a HTML page, they can be nested inside each other and you can apply attributes to each one similar to CSS. The list of attributes available matches everything the GPU can do efficiently.
It sounds like you want vector shapes. Basically you create all your shapes at startup, for example in the awakeFromNib method of a UIView subclass. For simple rectangles use CALayer and set a background colour. For more complicated shapes create a CAShapeLayer and a UIBezierPath, then apply it with shapeLayer.path = bezierPath.CGPath;.
Then, whenever you want to change something, you apply those changes to the layer object. For example, here I'm rotating a layer with a 1 second linear animation:
[CATransaction begin];
[CATransaction setAnimationDuration:1];
[CATransaction setAnimationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]];
[self.needleLayer setValue:[NSNumber numberWithFloat:DegreesToRadians(degrees) forKeyPath:#"transform.rotation.z"];
[CATransaction commit];
// you'll want to declare this somewhere
CGFloat DegreesToRadians(CGFloat degrees)
{
return degrees * M_PI / 180;
}
More complicated animations, eg a series of changes scheduled to execute back to back, can be done using a CAKeyframeAnimation: https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/CoreAnimation_guide/CreatingBasicAnimations/CreatingBasicAnimations.html
Note Core Animation only does 2D graphics. Apple has Scene Kit which is basically the same thing for 3D, but so far it's only available on OS X. Hopefully iOS 8 will include it, but until then if you want 3D graphics on iOS you need to use Open GL.
CALayers which you resize on demand would probably be the most efficient way to do this, if the bars are solid colours. This allows you to optionally animate between sizes as well.
View resizing triggers off layout cycles, which you don't want. Drawing using CG calls is pretty slow.
Obviously the only real way to find out is to profile (on a device) using instruments and the core animation tool. But from experience, layer sizing is faster than drawing.
Definitely not a UIView for each - instead, a single UIView for the entire equalizer. Fill in the drawRect method with the appropriate CG calls to draw whatever is required. You can queue the view to refresh as needed with the appropriate data. Tapping into CADisplayLink will help you get the frame-rate you're looking for.
https://developer.apple.com/library/ios/documentation/QuartzCore/Reference/CADisplayLink_ClassRef/Reference/Reference.html
NOTE: You can also subclass CALayer and draw in it if you prefer something lighter-weight than UIView but I think you'll be fine with the former.

How to add, rotate and drag line in IOS?

What would be the best way in accomplishing this? Let's say I have a method (not defined) that would allow a line with a fixed size and color to be drawn on the screen. This line would need to then accept rotate gestures and panning gestures in order to move around the screen. It won't resize, it only needs to rotate and translate.
What is the best way of going about this? Should lines be subviews or sublayers to parent view? What is the method for drawing a line in ios? How to handle multiple lines on screen? I just want someone to lead me down the right path in the ios graphics jungle.
Firstly, you need to consider how complex the whole drawing is. From your description it sounds like the task is relatively simple. If that is the case, then Core Graphics would be the way to go. If the drawing is significantly more complex, you should look at OpenGL ES and GLKit, though using OGL involves a fair bit more work
Assuming Core Graphics, I'd store the centre point, angle and length of the line, and change the angle and size using the gesture recognizers, and calculate the points to draw using basic trig. Loop over the points to draw in the view -drawRect method and draw each one with the appropriate CG functions - call [view setNeedsDisplay] or [view setNeedsDisplayInRect:areaToRedraw]to trigger the redraws. (The second method only redraws the part of the view you specify, and can be used to improved performance).
The first of a series of tutorials on Core Graphics is here.- the part on 'drawing lines' will be most relevant. I haven't done this one (I used the old edition of this book), but I've followed a lot of others from this site and found them very helpful
As a side note you'll probably need a way to focus on a particular line if you have more than one on the screen- an easy way would be to find the line centre point closest to the point the user touched.
It seems that the best API for drawing lines like you want is with Core Graphics. Put this code within your UIView's drawRect method:
/* Set the color that we want to use to draw the line */
[[UIColor redColor] set];
/* Get the current graphics context */
CGContextRef currentContext =UIGraphicsGetCurrentContext();
/* Set the width for the line */
CGContextSetLineWidth(currentContext,5.0f);
/* Start the line at this point */
CGContextMoveToPoint(currentContext,50.0f, 10.0f);
/* And end it at this point */
CGContextAddLineToPoint(currentContext,100.0f, 200.0f);
/* Use the context's current color to draw the line */
CGContextStrokePath(currentContext);
For the gesture recognition, use UIGestureRecognizers. Use the following methods
- (IBAction)handleRotate:(UIRotationGestureRecognizer *)recognizer
- (IBAction)handlePinch:(UIPinchGestureRecognizer *)recognizer
- (IBAction)handlePan:(UIPanGestureRecognizer *)recognizer

Overlay UIImageViews with Multiply blend mode?

I have 2 UIImageViews that are shown on top of each other. One of them can be dragged around using a Gesture Recognizer.
Is there a way that the ImageViews can be rendered using a blend mode like Multiply? Such that when they move on top of each, they get rendered with that blend mode?
You have to override the drawRect: function on the parent view, in order to achieve something like this:
- (void)drawRect:(CGRect)rect
{
CGContextRef ctx = UIGraphicsGetCurrentContext();
[image1.image drawInRect:image1.frame blendMode:kCGBlendModeMultiply alpha:1];
[image2.image drawInRect:image2.frame blendMode:kCGBlendModeMultiply alpha:1];
[super drawRect:rect];
}
What it does is grab the current graphicsContext, and draws the two images into it, using a multiply blend mode.
To be able to see this, you'll need to set the alpha of the two images to 0, or the newly drawn content will be obscured. Since the parent view is redrawing them, you'll see the resulting multiplied versions.
Also, whenever the images' positions get updated, you'll need to call setNeedsDisplay on the parent view, to force it to call drawRect once again.
I'm certain there are probably more efficient ways to utilize Quartz 2D to achieve what you want, but this is probably the simplest.

Shape animation in iOS

I have an UIView in which I draw many shapes. I just want to keep redrawing this view in order to make kind of an animation. I searched for animation options, but all animations looks like they only work with properties, like transform, alpha... I just want a timed animation option, and that do not block the screen, I mean, that allows the application to realize the screen was tapped. Is it possible?
It is totally possible and you have a few different ways that you can go about doing it. If you want a simple png sequence style animation you can just fill a UIImageView like this:
imageView.animationImages = myImages;
imageView.animationDuration = 3;
[imageView startAnimating];
or you can override the drawrect function in a custom UIView, set up an NSTimer to tick however frequently you want to change the animation and call setNeedsDisplay on the view to draw the next frame.
Assuming you have a bezier path that represents each shape, try looking at CAShapeLayer - this can be used to draw a path on the screen, and you can animate the position, fill colour and many other properties of it.
Have one CAShapeLayer per shape, and add them as sub layers to your main view's layer.
You need to add the QuartzCore framework to use it, but it is very straightforward and there are plenty of tutorials out there.

Resources