这是我参与11月更文挑战的第8天,活动详情查看:2021最后一次更文挑战。
前言
视图置顶的应用场景:
- 比如让日期控件置于窗口的最顶层
- 悬浮按钮(支持拖曳)
关于 bringSubviewToFront 和view.layer.zPosition的选择
1、使用bringSubviewToFront方法需要在重新刷新界面结构层次的时候调用;
2、使用view.layer.zPosition方法会获取不到view的点击事件
更多内容请关注#小程序:iOS逆向
,只为你呈现有价值的信息,专注于移动端技术研究领域。
I 视图置顶
1.1 方案一:bringSubviewToFront的用法
- 让日期控件置于窗口的最顶层
PGDatePickManager kunnan.blog.csdn.net/article/det…
@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系统上日期控件被筛选视图遮挡问题
- 推荐使用
[[UIApplication sharedApplication].delegate window]
获取window
在执行
didFinishLaunchingWithOptions:
这个代理方法时,调用[self.window makeKeyAndVisible]
;方法之前,通过[UIApplication sharedApplication].keyWindow 方法获取不到window, 但是无论何时都能获取到delegate.window。
在获取到window时最好使用[[UIApplication sharedApplication].delegate window]获取window
不要在keywindow为nil的时候给window上添加代码,例如添加弹窗。