用途
在復雜頁面控制View視圖層級時,可以考慮用layer.zPosition屬性,通過設置它的值,達到某個view肉眼可見為最上層的效果
用法
view.layer.zPosition = 10;//數值越大肉眼所見越在最頂層
原理
本質為UIView雖然是二維坐標系,但是CALayer是三維坐標系,故可以通過設置z軸的屬性來使圖層處于最頂層
優勢
和bringSubViewToFront相比,layer.zPosition有以下優勢:
在頁面存在多個View 都會調用bringSubViewToFront 時,最后調用的會在最前面,這樣可能會引起潛在的Bug,即當前最上層視圖不一定是當前業務要求的視圖,所以可以用zPosition方式 只要保證設置的值為最大 即可保證在最上層
劣勢
假設上方容器View為A 調整過layer.zPosition屬性的View為B 肉眼所見的是viewB 在 viewA的上方 但本質容器viewB在viewA的下方 此時點擊viewB事件被viewA攔截
原因
layer.zPosition只是表面將圖層繪制在最上方,即肉眼看該視圖是最上方的視圖,但實際此視圖的容器View層,仍然處于下方。如果該View需要點擊交互事件的時候,就會發現該點擊失效,因為事件被覆蓋在其上方的容器View攔截
解決方案
- 可以關閉在容器上層viewA的交互 這樣事件就可以傳遞到下層
- 可以配合bringSubviewToFront的方式將ViewB也移動到上方
- 可以在ViewA里重寫hitTest方法,利用convertPoint轉換坐標系,[self convertPoint:point toView:viewB],self為viewA 這樣可得到點擊點point在viewB坐標系下的坐標,然后判斷該坐標點是否在viewB容器區域內,[viewB pointInside:point withEvent:event],如果存在,就返回viewB的點擊事件。
第一種和第二種方法不再贅述
第三種方案測試代碼
- 創建測試控制器的.h文件
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface TestViewControllerC : UIViewController
@end
NS_ASSUME_NONNULL_END
- 創建測試控制器的.m文件
#import "TestViewControllerC.h"
#import "CustomViewC.h"
@interface TestViewControllerC ()
@end
@implementation TestViewControllerC
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.view.backgroundColor = [UIColor whiteColor];
[self addTestSubViews];
}
- (void)addTestSubViews{
NSArray *arr = @[[UIColor redColor],[UIColor blueColor],[UIColor yellowColor]];
CGFloat startY = 100;
for (int i = 0; i < arr.count; i++) {
UIColor *color = arr[i];
CustomViewC *subView = [[CustomViewC alloc] init];
subView.backgroundColor = color;
subView.frame = CGRectMake(100, startY+50*i, 200, 100);
[self.view addSubview:subView];
subView.tag = 100 + I;
subView.userInteractionEnabled = YES;
[subView addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(clickSubView:)]];
UILabel *label = [[UILabel alloc] init];
label.text = [NSString stringWithFormat:@"測試第%d個View",i+1];
label.backgroundColor = [UIColor blackColor];
label.textColor = [UIColor whiteColor];
label.textAlignment = NSTextAlignmentCenter;
label.frame = CGRectMake(0, 0, 200, 20);
[subView addSubview:label];
if (i == 1) {
subView.layer.zPosition = 10;
}
}
}
- (void)clickSubView:(UITapGestureRecognizer *)tap{
int tag = (int)tap.view.tag - 100;
NSLog(@"點擊了第%d個view",tag+1);
}
@end
- 創建測試View的.h文件
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface CustomViewC : UIView
@end
NS_ASSUME_NONNULL_END
- 創建測試View的.m文件
#import "CustomViewC.h"
@implementation CustomViewC
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
UIView *targetView = [self.superview viewWithTag:101];//獲取想要響應點擊事件的視圖
CGPoint btnP = [self convertPoint:point toView:targetView];
if ([targetView pointInside:btnP withEvent:event]) {
return targetView;
}else{
return [super hitTest:point withEvent:event];
}
}
@end
運行結果
真實圖層
如上圖,肉眼可見是藍色View蓋住黃色View,但在真實圖層中為黃色View蓋住藍色View,二者中間為重疊部分,點擊此部分時,若不用上述解決方案,默認響應的是黃色View點擊事件。
綜上
layer.zPosition 使用場景較為單一,最好是有很多View同時在某個View的最上層,但這些View之間層級又有個優先級順序的需求,并且這些View之間沒有交互事件,或者說即便有交互事件的話也是彼此不重疊,如此,用layer.zPosition比較方便,其他情況下,則很雞肋。關閉其他View交互 容易引起潛在的Bug ,使用bringSubviewToFront的方式更是不可取,既然可以用bringSubviewToFront 解決,何必當初用此方案。用hitTest方法解決也并不是很好,需要保證這些View有個公共的父類,不然的話需要在其他View的子類里重寫hitTest處理。