I asked a similar question recently on how to add a translucent effect to a UIView and got a good response.
However it used a lot of CPU power to process so I used some of the ideas behind the answer but did the filtering using GPUImage which is much more efficient.
It works well for a static screen, but I want to change the background UIImageView
's image with an animation. However when I set the UIView
to sample the background during the transition, it seems to ignore the transition and show the new Image before the animation has started.
The relevant code is as follows (ask if you need more!):
The superview containing the custom UIView
and the UIImageView
background:
//The Transition
[contentView setScheduled:YES]; //Starts the sampling every 0.2 seconds
//bg is the UIImageView and contentView is the 'translucent' UIView subclass
[UIView transitionWithView:bg duration:1.0 options:UIViewAnimationOptionTransitionCrossDissolve animations:^{
[bg setImage:newImage];
}completion:^(BOOL finished){
[contentView setScheduled:NO];
}];
Then in the UIView subclass:
- (UIImage *)snapshotOfSuperview:(UIView *)superview
{
CGFloat scale = 0.5;
if (([UIScreen mainScreen].scale > 1 || self.contentMode == UIViewContentModeScaleAspectFill))
{
CGFloat blockSize = 12.0f/5;
scale = blockSize/MAX(blockSize * 2, floor(self.blurRadius));
}
UIGraphicsBeginImageContextWithOptions(self.bounds.size, YES, scale);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(context, -self.frame.origin.x, -self.frame.origin.y);
self.hidden=YES; //Don't take a snapshot of the view
[superview.layer renderInContext:context];
self.hidden=NO;
UIImage *snapshot = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return snapshot;
}
-(void)updateViewBG{
UIImage *superviewImage = [self snapshotOfSuperview:self.superview];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
GPUImageGaussianBlurFilter* filter = [[GPUImageGaussianBlurFilter alloc] init];
filter.blurSize = 0.8f;
UIImage* newBG = [self applyTint:self.tintColour image:[filter imageByFilteringImage:superviewImage]];
dispatch_async(dispatch_get_main_queue(), ^{
self.layer.contents = (id)newBG.CGImage;
self.layer.contentsScale = newBG.scale;
});
});
}
-(UIImage*)applyTint:(UIColor*)colour image:(UIImage*)inImage{
UIImage *newImage;
if (colour) {
UIGraphicsBeginImageContext(inImage.size);
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGRect area = CGRectMake(0, 0, inImage.size.width, inImage.size.height);
CGContextScaleCTM(ctx, 1, -1);
CGContextTranslateCTM(ctx, 0, -area.size.height);
CGContextSaveGState(ctx);
CGContextClipToMask(ctx, area, inImage.CGImage);
[colour set];
CGContextFillRect(ctx, area);
CGContextRestoreGState(ctx);
CGContextSetBlendMode(ctx, kCGBlendModeLighten);
CGContextDrawImage(ctx, area, inImage.CGImage);
newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}else{
newImage = inImage;
}
return newImage;
}
-(void)displayLayer:(CALayer *)layer{
[self updateViewBG];
}
How can I get the background to follow the animation?
I think the problem is that as soon as you change the image, displayLayer is called (disregarding the transition being in progress) and thus the new image is used for the translucency.
You should modify updateViewBG
so that it does not update the layer content before the transition is finished. E.g., you could add a flag to your class, set it when you start the transition and reset it when it completes. When the flag is set you do not call updateViewBG
.
[UIView transitionWithView:bg duration:1.0 options:UIViewAnimationOptionTransitionCrossDissolve animations:^{
self.isTransitionInProgress = YES;
[bg setImage:newImage];
}completion:^(BOOL finished){
[contentView setScheduled:NO];
self.isTransitionInProgress = NO;
[contentView.layer setNeedsDisplay];
}];
-(void)displayLayer:(CALayer *)layer{
if (!self.isTransitionInProgress)
[self updateViewBG];
}