Overlay UIImageViews with Multiply blend mode? - ios

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.

Related

How I could get access to circles drawn in drawRect?

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

CoreGraphics - Blending only *part* of a view

I recently came across this brilliant article about improving scroll performance with UITableViewCells: http://engineering.twitter.com/2012/02/simple-strategies-for-smooth-animation.html -- While many great tips can be found in this article, there is one in particular that has me intrigued:
Tweets in Twitter for iPhone 4.0 have a drop shadow on top of a subtle textured background. This presented a challenge, as blending is expensive. We solved this by reducing the area Core Animation has to consider non-opaque, by splitting the shadow areas from content area of the cell.
Using the iOS Simulator, clicking Debug - Color Blended Layers would reveal something like this:
The areas marked in red are blended, and the green area is opaque. Great. What the article fails to mention is: How do I implement this? It is my understanding that a UIView is either opaque or it's not. It seems to me that the only way to accomplish this would be with subviews, but the article explicitly states that as being a naive implementation:
Instead, our Tweet cells contain a single view with no subviews; a single drawRect: draws everything.
So how do I section off what is opaque, and what is not in my single drawRect: method?
In the example you show, I don't believe they're showing a background through the view. I think they're simulating a background in core graphics. In other words, in each cell they draw a light gray color for the background. They then draw the shadow (using transparency), and finally they draw the rest of the opaque content on the top. I could be wrong, but I don't believe you can make portions of the view transparent. If so, I'd be very, very interested in it because I use core graphics all the time, but I avoid rounded corners because blending the entire view for it just doesn't seem to be worth it.
Update
After doing some more research and looking through Apple's docs, I don't believe it's possible for only part of a view to be opaque. Also, after reading through Twitter's blog post, I don't think they are saying that they did so. Notice that when they say:
Instead, our Tweet cells contain a single view with no subviews; a single drawRect: draws everything.
They were specifically talking about UILabel and UIImageView. In other words, instead of using those views they're drawing the image directly using Core Graphics. As for the UILabels, I personally use Core Text since it has more font support but they may also be using something simpler like NSString's drawAtPoint:withFont: method. But the main point they're trying to get across is that the content of the cell is all one CG drawing.
Then they move to a new section: Avoid Blending. Here they make a point of saying that they avoid blending by:
splitting the shadow areas from content area of the cell.
The only way to do this is to use different views. There are two approaches they could be using, but first note that the cell dividers are themselves overlays (provided by the tableView). The first way is to use multiple views inside the cell. The second way is to underlay/overlay the shadows/blended-views behind/over the cells by inserting the appropriate views into the UIScrollView. Given their previous statement about having only one view/drawRect for each cell, this is probably what they're doing. Each method will have its challenges, but personally I think it would be easier to split the cell into 3 views (shadow, content, shadow). It would make it a lot easier to handle first/last cell situations.
I'd have to guess something along these lines
http://developer.apple.com/library/mac/#documentation/GraphicsImaging/Conceptual/drawingwithquartz2d/dq_shadows/dq_shadows.html
CGContextRef context = UIGraphicsGetCurrentContext();
UIBezierPath* path = [UIBezierPath bezierPathWithRoundedRect:self.bounds cornerRadius:10.0f];
CGContextSaveGState(context);
CGRect leftRect = CGRectZero;
CGContextClipToRect(context, leftRect );
CGContextSetBlendMode(context, kCGBlendModeNormal);
// draw shadow
// Call the function CGContextSetShadow, passing the appropriate values.
// Perform all the drawing to which you want to apply shadows.
CGContextSetShadowWithColor(context, CGSizeMake(1.0f, 1.0f), 10.0f, [UIColor blackColor].CGColor);
CGContextAddPath(context, path.CGPath);
CGContextDrawPath(context, kCGPathStroke);
CGContextRestoreGState(context);
CGContextSaveGState(context);
CGRect middleSection = CGRectZero;
CGContextClipToRect(context, middleSection);
CGContextSetFillColorWithColor(context, self.backgroundColor.CGColor);
CGContextFillRect(context, self.bounds);
// draw opaque
CGContextSetBlendMode(context, kCGBlendModeCopy);
CGContextRestoreGState(context);
My opinion is: Don't let Core Animation draw shadows using the various layer properties. Just draw a prerendered image to both sides, which is in fact a shadow. To factor variable height of a cell in a stretch draw may do the trick.
EDIT:
If the background is plain a prerendered shadow can be applied to both sides without know it is affecting visual appeal.
In case that is not applicable the tableview has to be shrunk to be of the size without the shadow. Then the shadow can be blended without doing it for every cell but just "on top". It really doesn't scroll. This will only work if the shadow is without any "texture", else one will notice it's just applied on top.

On iPhone and iPad, can we draw anything without using drawRect?

It seems that the standard way to draw dots, lines, circles, and Bezier paths is to draw them in inside of drawRect. We don't directly call drawRect, but just let iOS call it and we can use [self setNeedsDisplay] to tell iOS to try to call drawRect when it can...
It also seems that we cannot rely on
[self setClearsContextBeforeDrawing: NO];
to not clear the background of the view before calling drawRect. Some details are in this question: UIView: how to do non-destructive drawing?
How about directly drawing on the screen -- without putting those code in drawRect. For example, in ViewController.m, have some code that directly draw dots, lines, circles on the screen. Is that possible?
Without having to drop into OpenGL, the closest you can do to get around the erasure is to convert the context as an image using something like CGBitmapContextCreateImage. From there, you can retain the image in memory (or write it to disk if necessary), and then when you redraw the view, you first draw this original image into the context and then overlay it with new content.

On iOS, can something be drawn without having it done in drawRect?

Say, for an iOS app, if a user slides his finger on the screen, and then 50,000 dots are recorded. If the drawing is done for all these dots in drawRect, then next time the user touched the 50,001st dot, at the end of touchesMoved the following line
[self.view setNeedsDisplay];
will cause drawRect to run again and have all 50,001 dots drawn again. So for every 1 new dot (for any new movement of finger), all 50,001 dots will need to be redrawn and it is not an efficient method.
I tried just drawing the last dot in drawRect, and it will not "add to" the existing view, but "wipe everything out" and then draw one dot.
Is there a way to
1) draw that 1 extra dot without needing to have drawRect called?
2) or, can drawRect draw one extra dot without first wiping the whole screen out?
One approach for this would be to render into an image when the touches end, and then keep adding to the image as more paths are generated. You can store the paths in an array if you need and undo buffer or otherwise need to regenerate the drawing.
(you will need more code than this, obviously)
UIGraphicsBeginImageContextWithOptions(size, NO, 1.0);
CGContextRef context = UIGraphicsGetCurrentContext();
// do some drawing
UIImage * image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
Oh - fwiw, I have found setNeedsDisplayInRect: to be odd/buggy in iOS5. The first call to it is the full view rect, not the rect passed in as a param. At least that is what I found when I tried to use it. Maybe there is some implementation detail I overlooked.
Take a look at the method -(void)setNeedsDisplayInRect:. You can ask your view to redraw a specified rect of itself, but be careful when implementing drawRect: method -where you need to assume that the passed rect argument is just a piece of the whole rect of your view and probably your drawing logic will differ. Also you may consider the clearContextBeforeDrawing property of UIView.

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