iOS 8系統爲咱們提供了UIVisualEffectView
。咱們能夠利用這個類來完成高斯模糊的效果。函數
@interface ViewController ()
@property (nonatomic, strong) UIView *boxView;
@property (nonatomic, strong) UIVisualEffectView *blurView;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
UIImageView *imageView = [[UIImageView alloc] initWithFrame:self.view.bounds];
imageView.contentMode = UIViewContentModeScaleAspectFill;
imageView.image = [UIImage imageNamed:@"sao"];
[self.view addSubview:imageView];
self.boxView = [[UIView alloc] initWithFrame:CGRectMake(0, self.view.bounds.size.height - 83.0, self.view.bounds.size.width, 83.0)];
[self.view addSubview:self.boxView];
UIBlurEffect *blurEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight];
self.blurView = [[UIVisualEffectView alloc] initWithEffect:blurEffect];
self.blurView.frame = self.boxView.bounds;
[self.boxView addSubview:self.blurView];
CGFloat width = self.boxView.bounds.size.width / 4;
for (int i = 0; i < 4; i++) {
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
button.frame = CGRectMake(width * i, 0, width, self.boxView.bounds.size.height);
[button setTitleColor:UIColor.blackColor forState:UIControlStateNormal];
[button setTitle:[NSString stringWithFormat:@"Btn%d", i] forState:UIControlStateNormal];
[self.boxView addSubview:button];
}
}
複製代碼
咱們可能在作轉場動畫的時候須要對這個視圖進行"截圖",或者說可能利用的某個三方庫的實現就是截圖,那麼咱們會遇到一個bug。flex
在利用截圖視圖進行動畫的時候,咱們發現高斯模糊效果沒有了,顯示的是一個半透明的背景。動畫
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[super touchesBegan:touches withEvent:event];
// 截圖
UIView *snapView = [self.boxView snapshotViewAfterScreenUpdates:NO];
snapView.frame = self.boxView.frame;
[self.view addSubview:snapView];
self.boxView.hidden = YES;
[UIView animateWithDuration:0.8 animations:^{
if (self.boxView.transform.ty > 0) {
snapView.transform = CGAffineTransformMakeTranslation(0, -self.boxView.frame.size.height);
} else {
snapView.transform = CGAffineTransformMakeTranslation(0, self.boxView.frame.size.height);
}
} completion:^(BOOL finished) {
if (self.boxView.transform.ty > 0) {
self.boxView.transform = CGAffineTransformIdentity;
} else {
self.boxView.transform = CGAffineTransformMakeTranslation(0, self.boxView.frame.size.height);
}
self.boxView.hidden = NO;
[snapView removeFromSuperview];
}];
}
複製代碼
這裏的截圖嘗試了3種寫法:ui
// 1
UIView *snapView = [self.boxView snapshotViewAfterScreenUpdates:NO];
// 2
UIView *snapView = [self.boxView snapshotViewAfterScreenUpdates:YES];
// 3
@implementation UIView (Snapshot)
- (UIImageView *)yc_snapshotImageView
{
UIImage *image;
UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 0);
CGContextRef context = UIGraphicsGetCurrentContext();
if (context) {
[self.layer renderInContext:context];
image = UIGraphicsGetImageFromCurrentImageContext();
}
UIGraphicsEndImageContext();
if (image) {
return [[UIImageView alloc] initWithImage:image];
}
return nil;
}
@end
UIImageView *snapView = [self.boxView yc_snapshotImageView];
複製代碼
嘗試以後發現均不能解決這個「透明」問題。可是若是咱們直接用高斯模糊視圖進行動畫,發現是有效果的。atom
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[super touchesBegan:touches withEvent:event];
// 不使用截圖
[UIView animateWithDuration:0.8 animations:^{
if (self.boxView.transform.ty > 0) {
self.boxView.transform = CGAffineTransformIdentity;
} else {
self.boxView.transform = CGAffineTransformMakeTranslation(0, self.boxView.frame.size.height);
}
}];
}
複製代碼
下面咱們來思考一下,爲何截圖不行呢?spa
高斯模糊的效果是怎麼來的?3d
咱們能夠看到,高斯模糊的效果實際上是對後面圖像的一個「濾鏡」效果。也就是說,若是背後沒有圖像,那麼高斯模糊是沒有效果的。code
截圖時發生了什麼?orm
截圖的時候,咱們是直接獲取視圖圖像的。不管是使用 snapshotViewAfterScreenUpdates: 仍是利用 renderInContext:,其實都 只能拿到該視圖的圖像,沒法獲取它和後面圖層的混合效果 。因此最後只獲到一個半透明的白色,這個是由UIBlurEffectStyleLight
提供的一個半透明的圖像。cdn
如今底層的實現爲 UIView *snapView = [self.boxView yc_snapshotImageView]; ,如何解決存在半透明視圖的問題呢?
咱們知道 使用高斯模糊視圖作動畫是沒問題的 ,那咱們的思路能夠是先隱藏原有的高斯模糊視圖,而後進行截圖,最後在它的下面添加一個實際的用高斯模糊視圖。
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[super touchesBegan:touches withEvent:event];
// 容器
UIView *snapBoxView = [[UIView alloc] initWithFrame:self.boxView.frame];
[self.view addSubview:snapBoxView];
// 實際高斯模糊視圖
UIBlurEffect *blurEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight];
UIVisualEffectView *blurView = [[UIVisualEffectView alloc] initWithEffect:blurEffect];
blurView.frame = snapBoxView.bounds;
[snapBoxView addSubview:blurView];
// 只渲染非高斯模糊部分的視圖
self.blurView.hidden = YES;
UIImageView *snapView = [self.boxView yc_snapshotImageView];
self.blurView.hidden = NO;
[snapBoxView addSubview:snapView];
// 動畫
self.boxView.hidden = YES;
[UIView animateWithDuration:0.8 animations:^{
if (self.boxView.transform.ty > 0) {
snapBoxView.transform = CGAffineTransformMakeTranslation(0, -self.boxView.frame.size.height);
} else {
snapBoxView.transform = CGAffineTransformMakeTranslation(0, self.boxView.frame.size.height);
}
} completion:^(BOOL finished) {
if (self.boxView.transform.ty > 0) {
self.boxView.transform = CGAffineTransformIdentity;
} else {
self.boxView.transform = CGAffineTransformMakeTranslation(0, self.boxView.frame.size.height);
}
self.boxView.hidden = NO;
[snapBoxView removeFromSuperview];
}];
}
複製代碼
在實際狀況中,咱們的視圖可能被封裝了起來,像下面這樣:
@interface BottomView : UIView
@property (nonatomic, strong) UIVisualEffectView *blurView;
@end
@implementation BottomView
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.blurView = [self innerBlurView];
[self addSubview:self.blurView];
CGFloat width = self.bounds.size.width / 4;
for (int i = 0; i < 4; i++) {
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
button.frame = CGRectMake(width * i, 0, width, self.bounds.size.height);
[button setTitleColor:UIColor.blackColor forState:UIControlStateNormal];
[button setTitle:[NSString stringWithFormat:@"Btn%d", i] forState:UIControlStateNormal];
[self addSubview:button];
}
}
return self;
}
- (UIVisualEffectView *)innerBlurView
{
UIBlurEffect *blurEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight];
UIVisualEffectView *blurView = [[UIVisualEffectView alloc] initWithEffect:blurEffect];
blurView.frame = self.bounds;
return blurView;
}
@end
@implementation BottomView (Snapshot)
- (nullable UIImageView *)yc_snapshotImageView
{
// 容器
UIImageView *snapBoxView = [[UIImageView alloc] initWithFrame:self.bounds];
// 實際高斯模糊視圖
UIVisualEffectView *blurView = [self innerBlurView];
[snapBoxView addSubview:blurView];
// 只渲染非高斯模糊部分的視圖
self.blurView.hidden = YES;
UIImageView *snapView = nil;
UIImage *image;
UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 0);
CGContextRef context = UIGraphicsGetCurrentContext();
if (context) {
[self.layer renderInContext:context];
image = UIGraphicsGetImageFromCurrentImageContext();
}
UIGraphicsEndImageContext();
if (image) {
snapView = [[UIImageView alloc] initWithImage:image];
[snapBoxView addSubview:snapView];
}
self.blurView.hidden = NO;
return snapBoxView;
}
@end
複製代碼
那麼如今問題來了:
@implementation UIView (Snapshot)
- (nullable UIImageView *)yc_snapshotImageView
{
...
}
@end
@implementation BottomView (Snapshot)
- (nullable UIImageView *)yc_snapshotImageView
{
...
}
@end
複製代碼
咱們有兩個Category,均實現了同一個方法,那麼執行哪個呢?
這裏不帶你們挨着走dyld的加載流程了,咱們只說重點部分attachCategories 和 attachLists 函數:
static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
int flags)
{
if (slowpath(PrintReplacedMethods)) {
printReplacements(cls, cats_list, cats_count);
}
if (slowpath(PrintConnecting)) {
_objc_inform("CLASS: attaching %d categories to%s class '%s'%s",
cats_count, (flags & ATTACH_EXISTING) ? " existing" : "",
cls->nameForLogging(), (flags & ATTACH_METACLASS) ? " (meta)" : "");
}
constexpr uint32_t ATTACH_BUFSIZ = 64;
method_list_t *mlists[ATTACH_BUFSIZ];
property_list_t *proplists[ATTACH_BUFSIZ];
protocol_list_t *protolists[ATTACH_BUFSIZ];
uint32_t mcount = 0;
uint32_t propcount = 0;
uint32_t protocount = 0;
bool fromBundle = NO;
bool isMeta = (flags & ATTACH_METACLASS);
auto rw = cls->data();
// 遍歷分類
for (uint32_t i = 0; i < cats_count; i++) {
auto& entry = cats_list[i];
// 獲取每一個分類的方法
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
if (mcount == ATTACH_BUFSIZ) {
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
rw->methods.attachLists(mlists, mcount);
mcount = 0;
}
mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
fromBundle |= entry.hi->isBundle();
}
// 獲取每一個分類的屬性
property_list_t *proplist =
entry.cat->propertiesForMeta(isMeta, entry.hi);
if (proplist) {
if (propcount == ATTACH_BUFSIZ) {
rw->properties.attachLists(proplists, propcount);
propcount = 0;
}
proplists[ATTACH_BUFSIZ - ++propcount] = proplist;
}
// 獲取每一個分類的協議
protocol_list_t *protolist = entry.cat->protocolsForMeta(isMeta);
if (protolist) {
if (protocount == ATTACH_BUFSIZ) {
rw->protocols.attachLists(protolists, protocount);
protocount = 0;
}
protolists[ATTACH_BUFSIZ - ++protocount] = protolist;
}
}
// 遍歷完還有方法、屬性、分類,再掃個尾
if (mcount > 0) {
prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount, NO, fromBundle);
rw->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
if (flags & ATTACH_EXISTING) flushCaches(cls);
}
rw->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount);
rw->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);
}
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
if (hasArray()) {
// many lists -> many lists
// 要擴充多列表
uint32_t oldCount = array()->count;
uint32_t newCount = oldCount + addedCount;
// 從新分配內存空間
setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
// 更新總數
array()->count = newCount;
// 把老的列表放在後移,放在addedCount以後
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
// 新列表放在頭部
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
else if (!list && addedCount == 1) {
// 0 lists -> 1 list
// 只有一個列表直接賦值
list = addedLists[0];
}
else {
// 1 list -> many lists
// 只有1個列表,要擴充爲多列表
List* oldList = list;
uint32_t oldCount = oldList ? 1 : 0;
uint32_t newCount = oldCount + addedCount;
// 分配內存空間
setArray((array_t *)malloc(array_t::byteSize(newCount)));
// 更新總數
array()->count = newCount;
// 老列表接在尾部
if (oldList) array()->lists[addedCount] = oldList;
// 新列表放在頭部
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
}
複製代碼
從上面的代碼咱們能夠看出:
分類在加載方法的時候,分類的方法是放在方法列表的頭部的。
同一個類的方法,根據編譯順序,越後面的的方法放在方法列表的越前面。
@implementation UIView (Snapshot)
- (nullable UIImageView *)yc_snapshotImageView
{
...
}
@end
@implementation BottomView (Snapshot)
- (nullable UIImageView *)yc_snapshotImageView
{
...
}
@end
複製代碼
對於不一樣繼承類的同一個Category方法,這兩個方法是分別在UIView
和BottomView
的類對象中的。
BottomView
會在查找本身類的方法列表時找到 yc_snapshotImageView 方法,用本身的實現而非父類的實現。下面咱們驗證一下:
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[super touchesBegan:touches withEvent:event];
// 容器
UIImageView *snapBoxView = [self.boxView yc_snapshotImageView];
snapBoxView.frame = self.boxView.frame;
[self.view addSubview:snapBoxView];
// 動畫
self.boxView.hidden = YES;
[UIView animateWithDuration:0.8 animations:^{
if (self.boxView.transform.ty > 0) {
snapBoxView.transform = CGAffineTransformMakeTranslation(0, -self.boxView.frame.size.height);
} else {
snapBoxView.transform = CGAffineTransformMakeTranslation(0, self.boxView.frame.size.height);
}
} completion:^(BOOL finished) {
if (self.boxView.transform.ty > 0) {
self.boxView.transform = CGAffineTransformIdentity;
} else {
self.boxView.transform = CGAffineTransformMakeTranslation(0, self.boxView.frame.size.height);
}
self.boxView.hidden = NO;
[snapBoxView removeFromSuperview];
}];
}
複製代碼
咱們再試試直接調用父類的 yc_snapshotImageView 方法。
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[super touchesBegan:touches withEvent:event];
// 容器
struct objc_super superClass = {
self.boxView,
class_getSuperclass([self.boxView class])
};
UIImageView *snapBoxView = objc_msgSendSuper(&superClass, @selector(yc_snapshotImageView));
snapBoxView.frame = self.boxView.frame;
[self.view addSubview:snapBoxView];
// 動畫
self.boxView.hidden = YES;
[UIView animateWithDuration:0.8 animations:^{
if (self.boxView.transform.ty > 0) {
snapBoxView.transform = CGAffineTransformMakeTranslation(0, -self.boxView.frame.size.height);
} else {
snapBoxView.transform = CGAffineTransformMakeTranslation(0, self.boxView.frame.size.height);
}
} completion:^(BOOL finished) {
if (self.boxView.transform.ty > 0) {
self.boxView.transform = CGAffineTransformIdentity;
} else {
self.boxView.transform = CGAffineTransformMakeTranslation(0, self.boxView.frame.size.height);
}
self.boxView.hidden = NO;
[snapBoxView removeFromSuperview];
}];
}
複製代碼
重要
使用objc_msgSendSuper時,可能編譯器會報錯:
Too many arguments to function call, expected 0, have 3
解決辦法:在Build Setting修改Enable Strict Checking of objc_msgSend Calls爲No。
經過實際的運行結果也驗證了咱們的理論。
若是以爲本文對你有所幫助,給我點個贊吧~ 👍🏻