709 lines
19 KiB
Objective-C
709 lines
19 KiB
Objective-C
//
|
|
// FXBlurView.m
|
|
//
|
|
// Version 1.6.4
|
|
//
|
|
// Created by Nick Lockwood on 25/08/2013.
|
|
// Copyright (c) 2013 Charcoal Design
|
|
//
|
|
// Distributed under the permissive zlib License
|
|
// Get the latest version from here:
|
|
//
|
|
// https://github.com/nicklockwood/FXBlurView
|
|
//
|
|
// This software is provided 'as-is', without any express or implied
|
|
// warranty. In no event will the authors be held liable for any damages
|
|
// arising from the use of this software.
|
|
//
|
|
// Permission is granted to anyone to use this software for any purpose,
|
|
// including commercial applications, and to alter it and redistribute it
|
|
// freely, subject to the following restrictions:
|
|
//
|
|
// 1. The origin of this software must not be misrepresented; you must not
|
|
// claim that you wrote the original software. If you use this software
|
|
// in a product, an acknowledgment in the product documentation would be
|
|
// appreciated but is not required.
|
|
//
|
|
// 2. Altered source versions must be plainly marked as such, and must not be
|
|
// misrepresented as being the original software.
|
|
//
|
|
// 3. This notice may not be removed or altered from any source distribution.
|
|
//
|
|
|
|
|
|
#import "FXBlurView.h"
|
|
#import <objc/runtime.h>
|
|
|
|
|
|
#pragma GCC diagnostic ignored "-Wobjc-missing-property-synthesis"
|
|
#pragma GCC diagnostic ignored "-Wdirect-ivar-access"
|
|
#pragma GCC diagnostic ignored "-Wgnu"
|
|
|
|
|
|
#import <Availability.h>
|
|
|
|
|
|
|
|
@implementation UIImage (FXBlurView)
|
|
|
|
- (UIImage *)blurredImageWithRadius:(CGFloat)radius iterations:(NSUInteger)iterations tintColor:(UIColor *)tintColor
|
|
{
|
|
//image must be nonzero size
|
|
if (floorf(self.size.width) * floorf(self.size.height) <= 0.0f) return self;
|
|
|
|
//boxsize must be an odd integer
|
|
uint32_t boxSize = (uint32_t)(radius * self.scale);
|
|
if (boxSize % 2 == 0) boxSize ++;
|
|
|
|
//create image buffers
|
|
CGImageRef imageRef = self.CGImage;
|
|
|
|
//convert to ARGB if it isn't
|
|
if (CGImageGetBitsPerPixel(imageRef) != 32 ||
|
|
CGImageGetBitsPerComponent(imageRef) != 8 ||
|
|
!((CGImageGetBitmapInfo(imageRef) & kCGBitmapAlphaInfoMask)))
|
|
{
|
|
UIGraphicsBeginImageContextWithOptions(self.size, NO, self.scale);
|
|
[self drawAtPoint:CGPointZero];
|
|
imageRef = UIGraphicsGetImageFromCurrentImageContext().CGImage;
|
|
UIGraphicsEndImageContext();
|
|
}
|
|
|
|
vImage_Buffer buffer1, buffer2;
|
|
buffer1.width = buffer2.width = CGImageGetWidth(imageRef);
|
|
buffer1.height = buffer2.height = CGImageGetHeight(imageRef);
|
|
buffer1.rowBytes = buffer2.rowBytes = CGImageGetBytesPerRow(imageRef);
|
|
size_t bytes = buffer1.rowBytes * buffer1.height;
|
|
buffer1.data = malloc(bytes);
|
|
buffer2.data = malloc(bytes);
|
|
|
|
//create temp buffer
|
|
void *tempBuffer = malloc((size_t)vImageBoxConvolve_ARGB8888(&buffer1, &buffer2, NULL, 0, 0, boxSize, boxSize,
|
|
NULL, kvImageEdgeExtend + kvImageGetTempBufferSize));
|
|
|
|
//copy image data
|
|
CFDataRef dataSource = CGDataProviderCopyData(CGImageGetDataProvider(imageRef));
|
|
memcpy(buffer1.data, CFDataGetBytePtr(dataSource), bytes);
|
|
CFRelease(dataSource);
|
|
|
|
for (NSUInteger i = 0; i < iterations; i++)
|
|
{
|
|
//perform blur
|
|
vImageBoxConvolve_ARGB8888(&buffer1, &buffer2, tempBuffer, 0, 0, boxSize, boxSize, NULL, kvImageEdgeExtend);
|
|
|
|
//swap buffers
|
|
void *temp = buffer1.data;
|
|
buffer1.data = buffer2.data;
|
|
buffer2.data = temp;
|
|
}
|
|
|
|
//free buffers
|
|
free(buffer2.data);
|
|
free(tempBuffer);
|
|
|
|
//create image context from buffer
|
|
CGContextRef ctx = CGBitmapContextCreate(buffer1.data, buffer1.width, buffer1.height,
|
|
8, buffer1.rowBytes, CGImageGetColorSpace(imageRef),
|
|
CGImageGetBitmapInfo(imageRef));
|
|
|
|
//apply tint
|
|
if (tintColor && CGColorGetAlpha(tintColor.CGColor) > 0.0f)
|
|
{
|
|
CGContextSetFillColorWithColor(ctx, [tintColor colorWithAlphaComponent:0.25].CGColor);
|
|
CGContextSetBlendMode(ctx, kCGBlendModePlusLighter);
|
|
CGContextFillRect(ctx, CGRectMake(0, 0, buffer1.width, buffer1.height));
|
|
}
|
|
|
|
//create image from context
|
|
imageRef = CGBitmapContextCreateImage(ctx);
|
|
UIImage *image = [UIImage imageWithCGImage:imageRef scale:self.scale orientation:self.imageOrientation];
|
|
CGImageRelease(imageRef);
|
|
CGContextRelease(ctx);
|
|
free(buffer1.data);
|
|
return image;
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
@interface FXBlurScheduler : NSObject
|
|
|
|
@property (nonatomic, strong) NSMutableArray *views;
|
|
@property (nonatomic, assign) NSUInteger viewIndex;
|
|
@property (nonatomic, assign) NSUInteger updatesEnabled;
|
|
@property (nonatomic, assign) BOOL blurEnabled;
|
|
@property (nonatomic, assign) BOOL updating;
|
|
|
|
@end
|
|
|
|
|
|
@interface FXBlurLayer: CALayer
|
|
|
|
@property (nonatomic, assign) CGFloat blurRadius;
|
|
|
|
@end
|
|
|
|
|
|
@implementation FXBlurLayer
|
|
|
|
@dynamic blurRadius;
|
|
|
|
+ (BOOL)needsDisplayForKey:(NSString *)key
|
|
{
|
|
if ([@[@"blurRadius", @"bounds", @"position"] containsObject:key])
|
|
{
|
|
return YES;
|
|
}
|
|
return [super needsDisplayForKey:key];
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
@interface FXBlurView ()
|
|
|
|
@property (nonatomic, assign) BOOL iterationsSet;
|
|
@property (nonatomic, assign) BOOL blurRadiusSet;
|
|
@property (nonatomic, assign) BOOL dynamicSet;
|
|
@property (nonatomic, assign) BOOL blurEnabledSet;
|
|
@property (nonatomic, strong) NSDate *lastUpdate;
|
|
@property (nonatomic, assign) BOOL needsDrawViewHierarchy;
|
|
|
|
- (UIImage *)snapshotOfUnderlyingView;
|
|
- (BOOL)shouldUpdate;
|
|
|
|
@end
|
|
|
|
|
|
@implementation FXBlurScheduler
|
|
|
|
+ (instancetype)sharedInstance
|
|
{
|
|
static FXBlurScheduler *sharedInstance = nil;
|
|
if (!sharedInstance)
|
|
{
|
|
sharedInstance = [[FXBlurScheduler alloc] init];
|
|
}
|
|
return sharedInstance;
|
|
}
|
|
|
|
- (instancetype)init
|
|
{
|
|
if ((self = [super init]))
|
|
{
|
|
_updatesEnabled = 1;
|
|
_blurEnabled = YES;
|
|
_views = [[NSMutableArray alloc] init];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)setBlurEnabled:(BOOL)blurEnabled
|
|
{
|
|
_blurEnabled = blurEnabled;
|
|
if (blurEnabled)
|
|
{
|
|
for (FXBlurView *view in self.views)
|
|
{
|
|
[view setNeedsDisplay];
|
|
}
|
|
[self updateAsynchronously];
|
|
}
|
|
}
|
|
|
|
- (void)setUpdatesEnabled
|
|
{
|
|
_updatesEnabled ++;
|
|
[self updateAsynchronously];
|
|
}
|
|
|
|
- (void)setUpdatesDisabled
|
|
{
|
|
_updatesEnabled --;
|
|
}
|
|
|
|
- (void)addView:(FXBlurView *)view
|
|
{
|
|
if (![self.views containsObject:view])
|
|
{
|
|
[self.views addObject:view];
|
|
[self updateAsynchronously];
|
|
}
|
|
}
|
|
|
|
- (void)removeView:(FXBlurView *)view
|
|
{
|
|
NSUInteger index = [self.views indexOfObject:view];
|
|
if (index != NSNotFound)
|
|
{
|
|
if (index <= self.viewIndex)
|
|
{
|
|
self.viewIndex --;
|
|
}
|
|
[self.views removeObjectAtIndex:index];
|
|
}
|
|
}
|
|
|
|
- (void)updateAsynchronously
|
|
{
|
|
if (self.blurEnabled && !self.updating && self.updatesEnabled > 0 && [self.views count])
|
|
{
|
|
NSTimeInterval timeUntilNextUpdate = 1.0 / 60;
|
|
|
|
//loop through until we find a view that's ready to be drawn
|
|
self.viewIndex = self.viewIndex % [self.views count];
|
|
for (NSUInteger i = self.viewIndex; i < [self.views count]; i++)
|
|
{
|
|
FXBlurView *view = self.views[i];
|
|
if (view.dynamic && !view.hidden && view.window && [view shouldUpdate])
|
|
{
|
|
NSTimeInterval nextUpdate = [view.lastUpdate timeIntervalSinceNow] + view.updateInterval;
|
|
if (!view.lastUpdate || nextUpdate <= 0)
|
|
{
|
|
self.updating = YES;
|
|
[view updateAsynchronously:YES completion:^{
|
|
|
|
//render next view
|
|
self.updating = NO;
|
|
self.viewIndex = i + 1;
|
|
[self updateAsynchronously];
|
|
}];
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
timeUntilNextUpdate = MIN(timeUntilNextUpdate, nextUpdate);
|
|
}
|
|
}
|
|
}
|
|
|
|
//try again, delaying until the time when the next view needs an update.
|
|
self.viewIndex = 0;
|
|
[self performSelector:@selector(updateAsynchronously)
|
|
withObject:nil
|
|
afterDelay:timeUntilNextUpdate
|
|
inModes:@[NSDefaultRunLoopMode, UITrackingRunLoopMode]];
|
|
}
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
@implementation FXBlurView
|
|
|
|
@synthesize underlyingView = _underlyingView;
|
|
|
|
+ (void)setBlurEnabled:(BOOL)blurEnabled
|
|
{
|
|
[FXBlurScheduler sharedInstance].blurEnabled = blurEnabled;
|
|
}
|
|
|
|
+ (void)setUpdatesEnabled
|
|
{
|
|
[[FXBlurScheduler sharedInstance] setUpdatesEnabled];
|
|
}
|
|
|
|
+ (void)setUpdatesDisabled
|
|
{
|
|
[[FXBlurScheduler sharedInstance] setUpdatesDisabled];
|
|
}
|
|
|
|
+ (Class)layerClass
|
|
{
|
|
return [FXBlurLayer class];
|
|
}
|
|
|
|
- (void)setUp
|
|
{
|
|
if (!_iterationsSet) _iterations = 3;
|
|
if (!_blurRadiusSet) [self blurLayer].blurRadius = 40;
|
|
if (!_dynamicSet) _dynamic = YES;
|
|
if (!_blurEnabledSet) _blurEnabled = YES;
|
|
self.updateInterval = _updateInterval;
|
|
self.layer.magnificationFilter = @"linear"; // kCAFilterLinear
|
|
|
|
unsigned int numberOfMethods;
|
|
Method *methods = class_copyMethodList([UIView class], &numberOfMethods);
|
|
for (unsigned int i = 0; i < numberOfMethods; i++)
|
|
{
|
|
Method method = methods[i];
|
|
SEL selector = method_getName(method);
|
|
if (selector == @selector(tintColor))
|
|
{
|
|
_tintColor = ((id (*)(id,SEL))method_getImplementation(method))(self, selector);
|
|
break;
|
|
}
|
|
}
|
|
free(methods);
|
|
|
|
}
|
|
|
|
- (id)initWithFrame:(CGRect)frame
|
|
{
|
|
if ((self = [super initWithFrame:frame]))
|
|
{
|
|
[self setUp];
|
|
self.clipsToBounds = YES;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (id)initWithCoder:(NSCoder *)aDecoder
|
|
{
|
|
if ((self = [super initWithCoder:aDecoder]))
|
|
{
|
|
[self setUp];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)dealloc
|
|
{
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
|
}
|
|
|
|
|
|
- (BOOL)viewOrSubviewNeedsDrawViewHierarchy:(UIView *)view
|
|
{
|
|
if ([view isKindOfClass:NSClassFromString(@"SKView")] ||
|
|
[view.layer isKindOfClass:NSClassFromString(@"CAEAGLLayer")] ||
|
|
[view.layer isKindOfClass:NSClassFromString(@"AVPlayerLayer")] ||
|
|
ABS(view.layer.transform.m34) > 0)
|
|
{
|
|
return YES;
|
|
}
|
|
for (UIView *subview in view.subviews)
|
|
{
|
|
if ([self viewOrSubviewNeedsDrawViewHierarchy:subview])
|
|
{
|
|
return YES;
|
|
}
|
|
}
|
|
return NO;
|
|
}
|
|
|
|
- (void)willMoveToSuperview:(UIView *)newSuperview
|
|
{
|
|
[super willMoveToSuperview:newSuperview];
|
|
if (!_underlyingView)
|
|
{
|
|
_needsDrawViewHierarchy = [self viewOrSubviewNeedsDrawViewHierarchy:newSuperview];
|
|
}
|
|
}
|
|
|
|
- (void)setIterations:(NSUInteger)iterations
|
|
{
|
|
_iterationsSet = YES;
|
|
_iterations = iterations;
|
|
[self setNeedsDisplay];
|
|
}
|
|
|
|
- (void)setBlurRadius:(CGFloat)blurRadius
|
|
{
|
|
_blurRadiusSet = YES;
|
|
[self blurLayer].blurRadius = blurRadius;
|
|
}
|
|
|
|
- (CGFloat)blurRadius
|
|
{
|
|
return [self blurLayer].blurRadius;
|
|
}
|
|
|
|
- (void)setBlurEnabled:(BOOL)blurEnabled
|
|
{
|
|
_blurEnabledSet = YES;
|
|
if (_blurEnabled != blurEnabled)
|
|
{
|
|
_blurEnabled = blurEnabled;
|
|
[self schedule];
|
|
if (_blurEnabled)
|
|
{
|
|
[self setNeedsDisplay];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)setDynamic:(BOOL)dynamic
|
|
{
|
|
_dynamicSet = YES;
|
|
if (_dynamic != dynamic)
|
|
{
|
|
_dynamic = dynamic;
|
|
[self schedule];
|
|
if (!dynamic)
|
|
{
|
|
[self setNeedsDisplay];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (UIView *)underlyingView
|
|
{
|
|
return _underlyingView ?: self.superview;
|
|
}
|
|
|
|
- (void)setUnderlyingView:(UIView *)underlyingView
|
|
{
|
|
_underlyingView = underlyingView;
|
|
_needsDrawViewHierarchy = [self viewOrSubviewNeedsDrawViewHierarchy:self.underlyingView];
|
|
[self setNeedsDisplay];
|
|
}
|
|
|
|
- (CALayer *)underlyingLayer
|
|
{
|
|
return self.underlyingView.layer;
|
|
}
|
|
|
|
- (FXBlurLayer *)blurLayer
|
|
{
|
|
return (FXBlurLayer *)self.layer;
|
|
}
|
|
|
|
- (FXBlurLayer *)blurPresentationLayer
|
|
{
|
|
FXBlurLayer *blurLayer = [self blurLayer];
|
|
return (FXBlurLayer *)blurLayer.presentationLayer ?: blurLayer;
|
|
}
|
|
|
|
- (void)setUpdateInterval:(NSTimeInterval)updateInterval
|
|
{
|
|
_updateInterval = updateInterval;
|
|
if (_updateInterval <= 0) _updateInterval = 1.0/60;
|
|
}
|
|
|
|
- (void)setTintColor:(UIColor *)tintColor
|
|
{
|
|
_tintColor = tintColor;
|
|
[self setNeedsDisplay];
|
|
}
|
|
|
|
- (void)clearImage {
|
|
self.layer.contents = nil;
|
|
[self setNeedsDisplay];
|
|
}
|
|
|
|
- (void)didMoveToSuperview
|
|
{
|
|
[super didMoveToSuperview];
|
|
[self.layer setNeedsDisplay];
|
|
}
|
|
|
|
- (void)didMoveToWindow
|
|
{
|
|
[super didMoveToWindow];
|
|
[self schedule];
|
|
}
|
|
|
|
- (void)schedule
|
|
{
|
|
if (self.window && self.dynamic && self.blurEnabled)
|
|
{
|
|
[[FXBlurScheduler sharedInstance] addView:self];
|
|
}
|
|
else
|
|
{
|
|
[[FXBlurScheduler sharedInstance] removeView:self];
|
|
}
|
|
}
|
|
|
|
- (void)setNeedsDisplay
|
|
{
|
|
[super setNeedsDisplay];
|
|
[self.layer setNeedsDisplay];
|
|
}
|
|
|
|
- (BOOL)shouldUpdate
|
|
{
|
|
__strong CALayer *underlyingLayer = [self underlyingLayer];
|
|
|
|
return
|
|
underlyingLayer && !underlyingLayer.hidden &&
|
|
self.blurEnabled && [FXBlurScheduler sharedInstance].blurEnabled &&
|
|
!CGRectIsEmpty([self.layer.presentationLayer ?: self.layer bounds]) && !CGRectIsEmpty(underlyingLayer.bounds);
|
|
}
|
|
|
|
- (void)displayLayer:(__unused CALayer *)layer
|
|
{
|
|
[self updateAsynchronously:NO completion:NULL];
|
|
}
|
|
|
|
- (id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)key
|
|
{
|
|
if ([key isEqualToString:@"blurRadius"])
|
|
{
|
|
//animations are enabled
|
|
CAAnimation *action = (CAAnimation *)[super actionForLayer:layer forKey:@"backgroundColor"];
|
|
if ((NSNull *)action != [NSNull null])
|
|
{
|
|
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:key];
|
|
animation.fromValue = [layer.presentationLayer valueForKey:key];
|
|
|
|
//CAMediatiming attributes
|
|
animation.beginTime = action.beginTime;
|
|
animation.duration = action.duration;
|
|
animation.speed = action.speed;
|
|
animation.timeOffset = action.timeOffset;
|
|
animation.repeatCount = action.repeatCount;
|
|
animation.repeatDuration = action.repeatDuration;
|
|
animation.autoreverses = action.autoreverses;
|
|
animation.fillMode = action.fillMode;
|
|
|
|
//CAAnimation attributes
|
|
animation.timingFunction = action.timingFunction;
|
|
animation.delegate = action.delegate;
|
|
|
|
return animation;
|
|
}
|
|
}
|
|
return [super actionForLayer:layer forKey:key];
|
|
}
|
|
|
|
- (UIImage *)snapshotOfUnderlyingView
|
|
{
|
|
__strong FXBlurLayer *blurLayer = [self blurPresentationLayer];
|
|
__strong CALayer *underlyingLayer = [self underlyingLayer];
|
|
CGRect bounds = [blurLayer convertRect:blurLayer.bounds toLayer:underlyingLayer];
|
|
|
|
self.lastUpdate = [NSDate date];
|
|
CGFloat scale = 0.5;
|
|
if (self.iterations)
|
|
{
|
|
CGFloat blockSize = 12.0/self.iterations;
|
|
scale = blockSize/MAX(blockSize * 2, blurLayer.blurRadius);
|
|
scale = 1.0/floor(1.0/scale);
|
|
}
|
|
CGSize size = bounds.size;
|
|
if (self.contentMode == UIViewContentModeScaleToFill ||
|
|
self.contentMode == UIViewContentModeScaleAspectFill ||
|
|
self.contentMode == UIViewContentModeScaleAspectFit ||
|
|
self.contentMode == UIViewContentModeRedraw)
|
|
{
|
|
//prevents edge artefacts
|
|
size.width = floor(size.width * scale) / scale;
|
|
size.height = floor(size.height * scale) / scale;
|
|
}
|
|
else if ([[UIDevice currentDevice].systemVersion floatValue] < 7.0 && [UIScreen mainScreen].scale == 1.0)
|
|
{
|
|
//prevents pixelation on old devices
|
|
scale = 1.0;
|
|
}
|
|
UIGraphicsBeginImageContextWithOptions(size, NO, scale);
|
|
CGContextRef context = UIGraphicsGetCurrentContext();
|
|
if (context)
|
|
{
|
|
CGContextTranslateCTM(context, -bounds.origin.x, -bounds.origin.y);
|
|
|
|
NSArray *hiddenViews = [self prepareUnderlyingViewForSnapshot];
|
|
if (self.needsDrawViewHierarchy)
|
|
{
|
|
__strong UIView *underlyingView = self.underlyingView;
|
|
[underlyingView drawViewHierarchyInRect:underlyingView.bounds afterScreenUpdates:YES];
|
|
}
|
|
else
|
|
{
|
|
[underlyingLayer renderInContext:context];
|
|
}
|
|
[self restoreSuperviewAfterSnapshot:hiddenViews];
|
|
UIImage *snapshot = UIGraphicsGetImageFromCurrentImageContext();
|
|
UIGraphicsEndImageContext();
|
|
return snapshot;
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
- (NSArray *)hideEmptyLayers:(CALayer *)layer
|
|
{
|
|
NSMutableArray *layers = [NSMutableArray array];
|
|
if (CGRectIsEmpty(layer.bounds))
|
|
{
|
|
layer.hidden = YES;
|
|
[layers addObject:layer];
|
|
}
|
|
for (CALayer *sublayer in layer.sublayers)
|
|
{
|
|
[layers addObjectsFromArray:[self hideEmptyLayers:sublayer]];
|
|
}
|
|
return layers;
|
|
}
|
|
|
|
- (NSArray *)prepareUnderlyingViewForSnapshot
|
|
{
|
|
__strong CALayer *blurlayer = [self blurLayer];
|
|
__strong CALayer *underlyingLayer = [self underlyingLayer];
|
|
while (blurlayer.superlayer && blurlayer.superlayer != underlyingLayer)
|
|
{
|
|
blurlayer = blurlayer.superlayer;
|
|
}
|
|
NSMutableArray *layers = [NSMutableArray array];
|
|
NSUInteger index = [underlyingLayer.sublayers indexOfObject:blurlayer];
|
|
if (index != NSNotFound)
|
|
{
|
|
for (NSUInteger i = index; i < [underlyingLayer.sublayers count]; i++)
|
|
{
|
|
CALayer *layer = underlyingLayer.sublayers[i];
|
|
if (!layer.hidden)
|
|
{
|
|
layer.hidden = YES;
|
|
[layers addObject:layer];
|
|
}
|
|
}
|
|
}
|
|
|
|
//also hide any sublayers with empty bounds to prevent a crash on iOS 8
|
|
[layers addObjectsFromArray:[self hideEmptyLayers:underlyingLayer]];
|
|
|
|
return layers;
|
|
}
|
|
|
|
- (void)restoreSuperviewAfterSnapshot:(NSArray *)hiddenLayers
|
|
{
|
|
for (CALayer *layer in hiddenLayers)
|
|
{
|
|
layer.hidden = NO;
|
|
}
|
|
}
|
|
|
|
- (UIImage *)blurredSnapshot:(UIImage *)snapshot radius:(CGFloat)blurRadius
|
|
{
|
|
return [snapshot blurredImageWithRadius:blurRadius
|
|
iterations:self.iterations
|
|
tintColor:self.tintColor];
|
|
}
|
|
|
|
- (void)setLayerContents:(UIImage *)image
|
|
{
|
|
self.layer.contents = (id)image.CGImage;
|
|
self.layer.contentsScale = image.scale;
|
|
}
|
|
|
|
- (void)updateAsynchronously:(BOOL)async completion:(void (^)())completion
|
|
{
|
|
if ([self shouldUpdate])
|
|
{
|
|
UIImage *snapshot = [self snapshotOfUnderlyingView];
|
|
if (async)
|
|
{
|
|
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
|
|
|
UIImage *blurredImage = [self blurredSnapshot:snapshot radius:self.blurRadius];
|
|
dispatch_sync(dispatch_get_main_queue(), ^{
|
|
|
|
[self setLayerContents:blurredImage];
|
|
if (completion) completion();
|
|
});
|
|
});
|
|
}
|
|
else
|
|
{
|
|
[self setLayerContents:[self blurredSnapshot:snapshot radius:[self blurPresentationLayer].blurRadius]];
|
|
if (completion) completion();
|
|
}
|
|
}
|
|
else if (completion)
|
|
{
|
|
completion();
|
|
}
|
|
}
|
|
|
|
@end
|