快速模糊UITableViewCell的contentView背景

4
我创建了一个遵循UITableViewDataSourceUITableViewDelegate协议的UIViewController,并将UITableView设置为它的子视图。
我设置了表格的backgroundView属性为UIImageView,以便将图片显示为表格的背景。
为了在单元格之间有自定义间距,我使行高比想要的高,并定制了单元格的contentView的大小,使其看起来像是有额外的空间(参考这个SO答案)。
我想给单元格添加模糊效果,以使背景变得模糊。我使用了 Brad Larson的GPUImage框架 来实现这个目的。这个方法能正常工作,但由于我希望随着滚动更新背景模糊效果,因此滚动变得非常卡顿。
我的代码如下:
//Gets called from the -scrollViewDidScroll:(UIScrollView *)scrollView method
- (void)updateViewBG
{
    UIImage *superviewImage = [self snapshotOfSuperview:self.tableView];

    UIImage* newBG = [self applyTint:self.tintColour image:[filter imageByFilteringImage:superviewImage]];

    self.layer.contents = (id)newBG.CGImage;
    self.layer.contentsScale = newBG.scale;
}

//Code to create an image from the area behind the 'blurred cell'
- (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);
    NSArray *hiddenViews = [self prepareSuperviewForSnapshot:superview];
    [superview.layer renderInContext:context];
    [self restoreSuperviewAfterSnapshot:hiddenViews];
    UIImage *snapshot = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return snapshot;
}

-(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 colorWithAlphaComponent:0.8] set];
        CGContextFillRect(ctx, area);
        CGContextRestoreGState(ctx);
        CGContextSetBlendMode(ctx, kCGBlendModeLighten);
        CGContextDrawImage(ctx, area, inImage.CGImage);
        newImage = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
    } else {
        newImage = inImage;
    }
    return newImage;
}

现在来谈谈问题:
有没有更好的方法添加模糊效果?也许可以使图层不必每次移动都重新渲染吗?iOS7的控制中心/通知中心似乎能够做到这一点而没有任何延迟。

也许可以使用GPUImageUIElement类?如果是这样,我该如何使用呢? 我看过的另一种方法是最初在背景图像上创建模糊,然后仅裁剪我需要使用的区域,但是我无法使其正常工作,因为图像可能与屏幕大小相同或不同,因此缩放是一个问题(使用CGImageCreateWithImageInRect()和rect是表格上单元格位置)。

我还发现必须将模糊添加到表视图本身,框架为单元格的框架,而单元格具有透明颜色。

提前致谢

编辑 根据请求,以下是我之前尝试过的图像裁剪代码:

- (void)updateViewBG
{
    //self.bgImg is the pre-blurred image, -getContentViewFromCellFrame: is a convenience method to get just the content area from the whole cell (since the contentarea is smaller than the cell)
    UIImage* bg = [self cropImage:self.bgImg 
                           toRect:[LATableBlur getContentViewFromCellFrame:[self.tableView rectForRowAtIndexPath:self.cellIndexPath]]];
    bg = [self applyTint:self.tintColour image:bg];
    self.layer.contents = (id)bg.CGImage;
    self.layer.contentsScale = bg.scale;
}

- (UIImage*)cropImage:(UIImage*)image toRect:(CGRect)frame
{
    CGSize imgSize = [image size];
    double heightRatio = imgSize.height/self.tableView.frame.size.height;
    double widthRatio = imgSize.width/self.tableView.frame.size.width;

    UIImage* cropped = [UIImage imageWithCGImage:CGImageCreateWithImageInRect(image.CGImage,
                                                                              CGRectMake(frame.origin.x*widthRatio,
                                                                                         frame.origin.y*heightRatio,
                                                                                         frame.size.width*widthRatio,
                                                                                         frame.size.height*heightRatio))];

    return cropped;
}

为什么不使用CIFilter? - Lefteris
@Moxy你有什么建议可以实现类似的结果吗?我正在考虑包括一个禁用功能,就是出于这个原因(或者不在<4s上启用它) - ABC
@Lefteris 据我所了解,CIFilters 被认为非常慢。从我所看到的情况来看,GPUImage 是最快,最高效的选择。 - ABC
@Moxy 所发生的一切只是错误的背景图像部分被显示在后面,也许我只是错过了一些微不足道的东西?.. - ABC
只是为了理解细胞中哪些部分模糊不清。一个可能的解决方案肯定需要缓存以不影响性能。所以关键是要尝试为您的情况找到一个hack... - Moxy
显示剩余5条评论
1个回答

6
我用了一个方案解决了它,一开始我并没有想到这个方案会起作用。
生成多个模糊图像显然不是解决方案,因为成本很高。我只使用了一个模糊图像并将其缓存。
因此,我创建了一个UITableViewCell子类:
@interface BlurredCell : UITableViewCell
@end

我实现了两个类方法来访问缓存的图像(模糊和普通的)。
+(UIImage *)normalImage
{
    static dispatch_once_t onceToken;
    static UIImage *_normalImage;
    dispatch_once(&onceToken, ^{
        _normalImage = [UIImage imageNamed:@"bg.png"];
    });
    return _normalImage;
}

我使用了 REFrostedViewController 中 UIImage 的分类来生成模糊图像。
+(UIImage *)blurredImage
{
    static dispatch_once_t onceToken;
    static UIImage *_blurredImage;
    dispatch_once(&onceToken, ^{
    _blurredImage = [[UIImage imageNamed:@"bg.png"] re_applyBlurWithRadius:BlurredCellBlurRadius
                                                                 tintColor:[UIColorcolorWithWhite:1.0f
                                                                                            alpha:0.4f]
                                                     saturationDeltaFactor:1.8f
                                                                 maskImage:nil];
    });
    return _blurredImage;
}

为了在单元格内产生模糊帧的效果,但仍然可以在侧面看到非模糊图像,我使用滚动视图。
一个带有正常图像的图像视图,另一个带有模糊图像的图像视图。我将内容大小设置为图像大小,而contentOffset将通过界面进行设置。
因此,表视图最终会使每个单元格持有整个背景图像,但在某些偏移处对其进行裁剪并仍然显示整个图像。
@implementation BlurredCell

- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
    if (self) {
        // Initialization code
        [self.contentView addSubview:self.normalScrollView];
        [self.contentView addSubview:self.blurredScrollView];
    }
    return self;
}

-(UIScrollView *)normalScrollView
{
    if (!_normalScrollView) {
        _normalScrollView = [[UIScrollView alloc] initWithFrame:self.bounds];
        _normalScrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
        _normalScrollView.scrollEnabled = NO;
        UIImageView *imageView =[[UIImageView alloc] initWithFrame:[UIScreen mainScreen].bounds];
        imageView.contentMode = UIViewContentModeScaleToFill;
        imageView.image = [BlurredCell normalImage];
        _normalScrollView.contentSize = imageView.frame.size;
        [_normalScrollView addSubview:imageView];
    }
    return _normalScrollView;
}

-(UIScrollView *)blurredScrollView
{
    if (!_blurredScrollView) {
        _blurredScrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(BlurredCellPadding, BlurredCellPadding,
                                                                            self.bounds.size.width - 2.0f * BlurredCellPadding,
                                                                            self.bounds.size.height - 2.0f * BlurredCellPadding)];
        _blurredScrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
        _blurredScrollView.scrollEnabled = NO;
        _blurredScrollView.contentOffset = CGPointMake(BlurredCellPadding, BlurredCellPadding);
        UIImageView *imageView =[[UIImageView alloc] initWithFrame:[UIScreen mainScreen].bounds];
        imageView.contentMode = UIViewContentModeScaleToFill;
        imageView.image = [BlurredCell blurredImage];
        _blurredScrollView.contentSize = imageView.frame.size;
        [_blurredScrollView addSubview:imageView];
    }
    return _blurredScrollView;
}

-(void)setBlurredContentOffset:(CGFloat)offset
{
    self.normalScrollView.contentOffset = CGPointMake(self.normalScrollView.contentOffset.x, offset);
    self.blurredScrollView.contentOffset = CGPointMake(self.blurredScrollView.contentOffset.x, offset + BlurredCellPadding);
}

@end

每次表格视图的内容偏移改变时都应调用 setBlurredContentOffset:
因此,在表格视图代理的实现中(即视图控制器),我们在这两个方法中进行调用:
// For the first rows
-(void)tableView:(UITableView *)tableView willDisplayCell:(BlurredCell *)cell 
                                        forRowAtIndexPath:(NSIndexPath *)indexPath
{
    [cell setBlurredContentOffset:cell.frame.origin.y];
}

// Each time the table view is scrolled
-(void)scrollViewDidScroll:(UIScrollView *)scrollView
{
    for (BlurredCell *cell in [self.tableView visibleCells]) {
        [cell setBlurredContentOffset:cell.frame.origin.y - scrollView.contentOffset.y];
    }
}

这是一个完整的工作演示,请看这里

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接