iOS小技能:视图置顶(让一个View至于最顶端, 避免被其他子视图遮盖住)

14,324 阅读3分钟

这是我参与11月更文挑战的第8天,活动详情查看:2021最后一次更文挑战

前言

视图置顶的应用场景:

  1. 比如让日期控件置于窗口的最顶层
  2. 悬浮按钮(支持拖曳)

在这里插入图片描述

关于 bringSubviewToFront 和view.layer.zPosition的选择

1、使用bringSubviewToFront方法需要在重新刷新界面结构层次的时候调用;

2、使用view.layer.zPosition方法会获取不到view的点击事件

更多内容请关注#小程序:iOS逆向,只为你呈现有价值的信息,专注于移动端技术研究领域。

I 视图置顶

1.1 方案一:bringSubviewToFront的用法

  • 让日期控件置于窗口的最顶层

PGDatePickManager kunnan.blog.csdn.net/article/det…

mp.weixin.qq.com/s/rT4Iu_Fb8…

@implementation PGDatePickManager (ios12)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        
        NSArray *selStringsArray = @[@"viewWillLayoutSubviews"];
        
//        @"reloadRowsAtIndexPaths:withRowAnimation:", @"deleteRowsAtIndexPaths:withRowAnimation:", @"insertRowsAtIndexPaths:withRowAnimation:"];
        
        [selStringsArray enumerateObjectsUsingBlock:^(NSString *selString, NSUInteger idx, BOOL *stop) {
            NSString *mySelString = [@"sd_" stringByAppendingString:selString];
            
            Method originalMethod = class_getInstanceMethod(self, NSSelectorFromString(selString));
            Method myMethod = class_getInstanceMethod(self, NSSelectorFromString(mySelString));
            method_exchangeImplementations(originalMethod, myMethod);
        }];
    });
}
- (void)sd_viewWillLayoutSubviews{
    
    [self sd_viewWillLayoutSubviews];
    
        [UIApplication.sharedApplication.delegate.window bringSubviewToFront:self.view.superview];

}

  • listTableView
 [self.superview.window addSubview:self.listTableView];
    /// 避免被其他子视图遮盖住
    [self.superview.window bringSubviewToFront:self.listTableView];
    CGRect frame = CGRectMake(CGRectGetMinX(self.frame), CGRectGetMaxY(self.frame), CGRectGetWidth(self.frame), 0);

    //坐标转换

   CGRect convertRect=  [self.superview convertRect:frame toView:self.superview.window];

    [self.listTableView setFrame:convertRect];

1.2 方案二:同级Layer改变显示顺序

  • self.view.layer.zPosition
   self.view.layer.zPosition = MAXFLOAT; 999

II 案例: 悬浮按钮(支持拖曳)

下级订货单关于悬浮按钮的相关需求:

1、存在“待发货”记录时,显示“一键发货”按钮 点击一键发货:实现待发货的分配记录,都更新为待收货 2、存在“待收货”记录时,显示“一键代收货”按钮 点击一键代收货:实现待发货的分配记录,都更新为“已收货” 在这里插入图片描述

2.1 原理

1 、bringSubviewToFront 2、添加移动手势可以拖动 3、使用谓词进行判断是否存在特定条件的数据

    //添加移动手势可以拖动
    self.panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(dragAction:)];
    self.panGestureRecognizer.minimumNumberOfTouches = 1;
    self.panGestureRecognizer.maximumNumberOfTouches = 1;
    self.panGestureRecognizer.delegate = self;
    [self addGestureRecognizer:self.panGestureRecognizer];

2.2 用法

@property (strong, nonatomic) KNFrontV *  orangeView;




@end

@implementation QCTRecordViewController

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];
    
    [self.view bringSubviewToFront:self.orangeView];
    
    
    [self.orangeView layoutIfNeeded];
    
    self.orangeView.layer.cornerRadius =self.orangeView.height *0.5;

}

- (KNFrontV *)orangeView{
    if (nil == _orangeView) {
        KNFrontV *tmpView = [[KNFrontV alloc] initWithFrame:CGRectMake(0, 0 , kAdjustRatio(53), kAdjustRatio(53))];
        
        
        _orangeView = tmpView;
        [self.view addSubview:_orangeView];
        
        __weak __typeof__(self) weakSelf = self;
        tmpView.button.titleLabel.numberOfLines = 0;
        tmpView.button.titleLabel.textAlignment = NSTextAlignmentCenter;

                tmpView.button.titleLabel.font = [UIFont systemFontOfSize:15.0];
        [tmpView.button setTitle:@"一键\n发货" forState:UIControlStateNormal];// 发货 购买\n开店数
        
        tmpView.backgroundColor =  rgb(255,54,87);
        //
        //        tmpView.layer.cornerRadius = 14;// layoutsubview
        
        //设置显示图片方式一:
//        tmpView.imageView.image = [UIImage imageNamed:@"icon_dayin"];
        //设置显示图片方式二:
        //    [logoView.button setBackgroundImage:[UIImage imageNamed:@"logo1024"] forState:UIControlStateNormal];
        
        
        
        [_orangeView mas_makeConstraints:^(MASConstraintMaker *make) {
            make.size.mas_equalTo(CGSizeMake(kAdjustRatio(53), kAdjustRatio(53)));
            
            make.right.offset(kAdjustRatio(-20));
            
            make.bottom.offset(kAdjustRatio(-90));
            
        }];
        
        
        tmpView.clickDragViewBlock = ^(KNFrontV *dragView){
            
            
            [weakSelf setupclickDragViewBlock];
                        
            
        };
                
    }
    return _orangeView;
}

- (void)setupclickDragViewBlock{
    
    
}

  • KNFrontV的定义
//
//  KNFrontV.h
//  Housekeeper
//
//  Created by mac on 2021/5/6.
//  Copyright © 2021 https://kunnan.blog.csdn.net/  . All rights reserved.
//

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

// 拖曳view的方向
typedef NS_ENUM(NSInteger, KNDragDirection) {
    KNDragDirectionAny,          /**< 任意方向 */
    KNDragDirectionHorizontal,   /**< 水平方向 */
    KNDragDirectionVertical,     /**< 垂直方向 */
};


@interface KNFrontV : UIView


/**
 是不是能拖曳,默认为YES
 YES,能拖曳
 NO,不能拖曳
 */
@property (nonatomic,assign) BOOL dragEnable;

/**
 活动范围,默认为父视图的frame范围内(因为拖出父视图后无法点击,也没意义)
 如果设置了,则会在给定的范围内活动
 如果没设置,则会在父视图范围内活动
 注意:设置的frame不要大于父视图范围
 注意:设置的frame为0,0,0,0表示活动的范围为默认的父视图frame,如果想要不能活动,请设置dragEnable这个属性为NO
 */
@property (nonatomic,assign) CGRect freeRect;

/**
 拖曳的方向,默认为any,任意方向
 */
@property (nonatomic,assign) KNDragDirection dragDirection;

/**
 contentView内部懒加载的一个UIImageView
 开发者也可以自定义控件添加到本view中
 注意:最好不要同时使用内部的imageView和button
 */
@property (nonatomic,strong) UIImageView *imageView;
/**
 contentView内部懒加载的一个UIButton
 开发者也可以自定义控件添加到本view中
 注意:最好不要同时使用内部的imageView和button
 */
@property (nonatomic,strong) UIButton *button;
/**
 是不是总保持在父视图边界,默认为NO,没有黏贴边界效果
 isKeepBounds = YES,它将自动黏贴边界,而且是最近的边界
 isKeepBounds = NO, 它将不会黏贴在边界,它是free(自由)状态,跟随手指到任意位置,但是也不可以拖出给定的范围frame
 */
@property (nonatomic,assign) BOOL isKeepBounds;
/**
 点击的回调block
 */
@property (nonatomic,copy) void(^clickDragViewBlock)(KNFrontV *dragView);
/**
 开始拖动的回调block
 */
@property (nonatomic,copy) void(^beginDragBlock)(KNFrontV *dragView);
/**
 拖动中的回调block
 */
@property (nonatomic,copy) void(^duringDragBlock)(KNFrontV *dragView);
/**
 结束拖动的回调block
 */
@property (nonatomic,copy) void(^endDragBlock)(KNFrontV *dragView);

@end

NS_ASSUME_NONNULL_END

  • KNFrontV的实现
//
//  KNFrontV.m
//  Housekeeper
//
//  Created by mac on 2021/5/6.
//  Copyright © 2021 https://kunnan.blog.csdn.net/  . All rights reserved.
//

#import "KNFrontV.h"

@interface  KNFrontV ()


@property (nonatomic,strong) UIView *contentViewForDrag;

/**
 内容view,命名为contentViewForDrag,因为很多其他开源的第三方的库,里面同样有contentView这个属性
 ,这里特意命名为contentViewForDrag以防止冲突
 */
@property (nonatomic,assign) CGPoint startPoint;
@property (nonatomic,strong) UIPanGestureRecognizer *panGestureRecognizer;
@property (nonatomic,assign) CGFloat previousScale;


@end

@implementation KNFrontV

-(UIImageView *)imageView{
    if (_imageView==nil) {
        _imageView = [[UIImageView alloc]init];
        _imageView.userInteractionEnabled = YES;
        _imageView.clipsToBounds = YES;
        [self.contentViewForDrag addSubview:_imageView];
    }
    return _imageView;
}
-(UIButton *)button{
    if (_button==nil) {
        _button = [UIButton buttonWithType:UIButtonTypeCustom];
        _button.clipsToBounds = YES;
        _button.userInteractionEnabled = NO;
        [self.contentViewForDrag addSubview:_button];
    }
    return _button;
}
-(UIView *)contentViewForDrag{
    if (_contentViewForDrag==nil) {
        _contentViewForDrag = [[UIView alloc]init];
        _contentViewForDrag.clipsToBounds = YES;
    }
    return _contentViewForDrag;
}
- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        [self addSubview:self.contentViewForDrag];
        [self setUp];
    }
    return self;
}
- (instancetype)initWithCoder:(NSCoder *)coder
{
    self = [super initWithCoder:coder];
    if (self) {
        [self setUp];
    }
    return self;
}

-(void)layoutSubviews{
    [super layoutSubviews];
    if (self.freeRect.origin.x!=0||self.freeRect.origin.y!=0||self.freeRect.size.height!=0||self.freeRect.size.width!=0) {
        //设置了freeRect--活动范围
    }else{
        //没有设置freeRect--活动范围,则设置默认的活动范围为父视图的frame
        self.freeRect = (CGRect){CGPointZero,self.superview.bounds.size};
    }
    _imageView.frame = (CGRect){CGPointZero,self.bounds.size};
    _button.frame = (CGRect){CGPointZero,self.bounds.size};
    self.contentViewForDrag.frame =  (CGRect){CGPointZero,self.bounds.size};
}
-(void)setUp{
    self.dragEnable = YES;//默认可以拖曳
    self.clipsToBounds = YES;
    self.isKeepBounds = NO;
    self.backgroundColor = [UIColor lightGrayColor];
    UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(clickDragView)];
    [self addGestureRecognizer:singleTap];
    
    //添加移动手势可以拖动
    self.panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(dragAction:)];
    self.panGestureRecognizer.minimumNumberOfTouches = 1;
    self.panGestureRecognizer.maximumNumberOfTouches = 1;
    self.panGestureRecognizer.delegate = self;
    [self addGestureRecognizer:self.panGestureRecognizer];
}
//-(BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer{
//    return self.dragEnable;
//}
/**
 拖动事件
 @param pan 拖动手势
 */
-(void)dragAction:(UIPanGestureRecognizer *)pan{
    if(self.dragEnable==NO)return;
    switch (pan.state) {
        case UIGestureRecognizerStateBegan:{//开始拖动
            if (self.beginDragBlock) {
                self.beginDragBlock(self);
            }
            //注意完成移动后,将translation重置为0十分重要。否则translation每次都会叠加
            [pan setTranslation:CGPointZero inView:self];
            //保存触摸起始点位置
            self.startPoint = [pan translationInView:self];
            break;
        }
        case UIGestureRecognizerStateChanged:{//拖动中
            //计算位移 = 当前位置 - 起始位置
            if (self.duringDragBlock) {
                self.duringDragBlock(self);
            }
            CGPoint point = [pan translationInView:self];
            float dx;
            float dy;
            switch (self.dragDirection) {
                case WMDragDirectionAny:
                    dx = point.x - self.startPoint.x;
                    dy = point.y - self.startPoint.y;
                    break;
                case WMDragDirectionHorizontal:
                    dx = point.x - self.startPoint.x;
                    dy = 0;
                    break;
                case WMDragDirectionVertical:
                    dx = 0;
                    dy = point.y - self.startPoint.y;
                    break;
                default:
                    dx = point.x - self.startPoint.x;
                    dy = point.y - self.startPoint.y;
                    break;
            }
            
            //计算移动后的view中心点
            CGPoint newCenter = CGPointMake(self.center.x + dx, self.center.y + dy);
            //移动view
            self.center = newCenter;
            //  注意完成上述移动后,将translation重置为0十分重要。否则translation每次都会叠加
            [pan setTranslation:CGPointZero inView:self];
            break;
        }
        case UIGestureRecognizerStateEnded:{//拖动结束
            [self keepBounds];
            if (self.endDragBlock) {
                self.endDragBlock(self);
            }
            break;
        }
        default:
            break;
    }
}
//点击事件
-(void)clickDragView{
    if (self.clickDragViewBlock) {
        self.clickDragViewBlock(self);
    }
}
//黏贴边界效果
- (void)keepBounds{
    //中心点判断
    float centerX = self.freeRect.origin.x+(self.freeRect.size.width - self.frame.size.width)/2;
    CGRect rect = self.frame;
    if (self.isKeepBounds==NO) {//没有黏贴边界的效果
        if (self.frame.origin.x < self.freeRect.origin.x) {
            CGContextRef context = UIGraphicsGetCurrentContext();
            [UIView beginAnimations:@"leftMove" context:context];
            [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
            [UIView setAnimationDuration:0.5];
            rect.origin.x = self.freeRect.origin.x;
            self.frame = rect;
            [UIView commitAnimations];
        } else if(self.freeRect.origin.x+self.freeRect.size.width < self.frame.origin.x+self.frame.size.width){
            CGContextRef context = UIGraphicsGetCurrentContext();
            [UIView beginAnimations:@"rightMove" context:context];
            [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
            [UIView setAnimationDuration:0.5];
            rect.origin.x = self.freeRect.origin.x+self.freeRect.size.width-self.frame.size.width;
            self.frame = rect;
            [UIView commitAnimations];
        }
    }else if(self.isKeepBounds==YES){//自动粘边
        if (self.frame.origin.x< centerX) {
            CGContextRef context = UIGraphicsGetCurrentContext();
            [UIView beginAnimations:@"leftMove" context:context];
            [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
            [UIView setAnimationDuration:0.5];
            rect.origin.x = self.freeRect.origin.x;
            self.frame = rect;
            [UIView commitAnimations];
        } else {
            CGContextRef context = UIGraphicsGetCurrentContext();
            [UIView beginAnimations:@"rightMove" context:context];
            [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
            [UIView setAnimationDuration:0.5];
            rect.origin.x =self.freeRect.origin.x+self.freeRect.size.width - self.frame.size.width;
            self.frame = rect;
            [UIView commitAnimations];
        }
    }
    
    if (self.frame.origin.y < self.freeRect.origin.y) {
        CGContextRef context = UIGraphicsGetCurrentContext();
        [UIView beginAnimations:@"topMove" context:context];
        [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
        [UIView setAnimationDuration:0.5];
        rect.origin.y = self.freeRect.origin.y;
        self.frame = rect;
        [UIView commitAnimations];
    } else if(self.freeRect.origin.y+self.freeRect.size.height< self.frame.origin.y+self.frame.size.height){
        CGContextRef context = UIGraphicsGetCurrentContext();
        [UIView beginAnimations:@"bottomMove" context:context];
        [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
        [UIView setAnimationDuration:0.5];
        rect.origin.y = self.freeRect.origin.y+self.freeRect.size.height-self.frame.size.height;
        self.frame = rect;
        [UIView commitAnimations];
    }
}



@end

2.3 使用 NSPredicate判断是否存在“待收货”记录

/**
 
 下级订货单

 
 1、存在“待发货”记录时,显示“一键发货”按钮
 点击一键发货:实现待发货的分配记录,都更新为待收货
 2、存在“待收货”记录时,显示“一键代收货”按钮
 点击一键代收货:实现待发货的分配记录,都更新为“已收货”

 
 我的订货单
 存在“待收货”记录时,显示“一键收货”按钮
 点击一键收货:实现待发货的分配记录,都更新为“已收货”

 */
- (void) updateorangeView{
    //
    
    if(![self isShoworangeView]){
        self.orangeView.hidden = YES;
        
    }else{
        [self orangeView];
        
        self.orangeView.hidden = NO;
        
        [self.orangeView.button setTitle:self.orangeViewM.showStr forState:UIControlStateNormal];// 发货 购买\n开店数

        
    }
    
    
}

- (BOOL)isShoworangeView{
    
    self.orangeViewM = [KNFrontVM new];
    
    
    if(self.model.isLowerOrder){// 下级
//        1、存在“待发货”记录时,显示“一键发货”按钮// 优先显示
        
        NSPredicate* predicate = [NSPredicate predicateWithFormat:@"receivingState == %@", @"0"];

        
        NSArray *arFiltered = [  self.Detailmodels filteredArrayUsingPredicate:predicate];//以一定的条件(特定日期)过滤maTemp数组,即进行大数据搜索。
        
        if(arFiltered.count>0){
            
            self.orangeViewM.isShow = YES;
            
            self.orangeViewM.showStr = @"一键\n发货";
            self.orangeViewM.type = ReceivingDelieverEnum4Deliever;
            
            return self.orangeViewM.isShow;


        }

        
//        2、存在“待收货”记录时,显示“一键代收货”按钮

         predicate = [NSPredicate predicateWithFormat:@"receivingState == %@", @"1"];
        
        
        
        
        

        arFiltered = [  self.Detailmodels filteredArrayUsingPredicate:predicate];//
        
        
        if(arFiltered.count>0){
            
            self.orangeViewM.isShow = YES;
            
            self.orangeViewM.showStr = @"一键\n代收货";
            self.orangeViewM.type = ReceivingDelieverEnum4ProReceiving;
            
            

        }
        

        
        
        
        
    }else{// 本级
        
//        存在“待收货”记录时,显示“一键收货”按钮

        
        NSPredicate* predicate = [NSPredicate predicateWithFormat:@"receivingState == %@", @"1"];
        
        
        
        
        

        NSArray *arFiltered = [  self.Detailmodels filteredArrayUsingPredicate:predicate];//以一定的条件(特定日期)过滤maTemp数组,即进行大数据搜索。
        
        if(arFiltered.count>0){
            
            self.orangeViewM.isShow = YES;
            
            self.orangeViewM.showStr = @"一键\n收货";
            self.orangeViewM.type = ReceivingDelieverEnum4Receiving;
            
            

        }
        

        
        
        
        
    }
    
    return self.orangeViewM.isShow;
    
    
    
}


see also

更多内容请关注#小程序:iOS逆向,只为你呈现有价值的信息,专注于移动端技术研究领域。

iOS视图置顶的应用:适配iOS12系统上日期控件被筛选视图遮挡问题

mp.weixin.qq.com/s/rT4Iu_Fb8…

  • 推荐使用[[UIApplication sharedApplication].delegate window]获取window

在执行 didFinishLaunchingWithOptions: 这个代理方法时,调用[self.window makeKeyAndVisible];方法之前,通过[UIApplication sharedApplication].keyWindow 方法获取不到window, 但是无论何时都能获取到delegate.window。

  1. 在获取到window时最好使用[[UIApplication sharedApplication].delegate window]获取window

  2. 不要在keywindow为nil的时候给window上添加代码,例如添加弹窗。