持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第3天,点击查看活动详情
引言
本文为iOS上传图片视图的封装的上篇,介绍核心处理逻辑及注意事项,下篇介绍用法kunnan.blog.csdn.net/article/det…
I 核心处理逻辑
- 封装单元格视图:单元格视图ShowImageView的核心是显示上传成功图片,以及删除按钮。
- 上传图片视图的最后一个单元格视图的处理:判断视图模型数组中是否已经包含添加图片数据,如果没有包含,且总上传图片数量小于上传的最大张数,则显示添加按钮。
- 判断当前单元格显示的图片是背景图,还是上传的图片。
例如上传身份证的单元有身份证背景示例图,需要判断当前单元格显示的图片是背景图,还是上传的图片;才能友好的响应用户的点击事件,比如是应该放大图片,还是弹出选择图片方式视图。
1.1 判断是否已经包含添加图片单元格
- 判断是否已经包含添加图片单元格
+ (BOOL)idContainsUploadPicturesAddIconWithArr:(NSArray*)arr{
NSPredicate* predicate = [NSPredicate predicateWithFormat:@"type == %d",QCTCollectionModelType4UploadPicturesAddIcon];
NSArray *tmparr = [arr filteredArrayUsingPredicate:predicate];
if(tmparr.count>0){
return YES;
}
return NO;
}
复制代码
- 构建添加按钮的数据模型
+ (NSMutableArray*)getModel4ADDUploadPicturesWithBlock:(void (^)(id sender))block {
NSMutableArray *tmpD = @[
@{@"block":block,@"imgName":@"img_zhuece_tianjia",@"imageType":[NSNumber numberWithInt:ERPimageType4name],@"type":[NSNumber numberWithInt:QCTCollectionModelType4UploadPicturesAddIcon],@"isHiddenDelBtn":@1},
];
NSMutableArray *tmp = [[self class] mj_objectArrayWithKeyValuesArray:tmpD];
return tmp;
}
复制代码
- 判断是否已经包含添加图片单元格,如果没有,且数量小于上传的最大张数,则显示添加按钮
[tm setDelblock:^(QCTCollectionModel* sender) {
[weakSelf.viewModel.Model4UploadPictures removeObject:sender];
//判断是否已经包含添加图片视图,如果没有,且数量小于上传的最大张数,则显示添加按钮
if(self.viewModel.Model4UploadPictures.count<maxcount4UploadPicturesInRelease_commodities && ( ![QCTCollectionModel idContainsUploadPicturesAddIconWithArr:self.viewModel.Model4UploadPictures])){// 处理最大数量
[weakSelf.viewModel.Model4UploadPictures addObject:[QCTCollectionModel getModel4ADDUploadPicturesWithBlock:^(UISwitch* sender) {
[weakSelf setupChooseimage];
} ].firstObject];
}
[weakSelf.viewModel.reloadProductFileUploadSubject sendNext:nil];
}];
复制代码
1.2 判断当前单元格显示的图片是背景图,还是上传的图片
例如上传身份证的单元有身份证背景示例图,需要判断当前单元格显示的图片是背景图,还是上传的图片;才能友好的响应用户的点击事件,比如是应该放大图片,还是弹出选择图片方式视图。
UITapGestureRecognizer *cutTap = [[UITapGestureRecognizer alloc] init];
[[cutTap rac_gestureSignal] subscribeNext:^(id x) {
// 判断当前图片是否为背景图
NSData *imagedata1 = UIImagePNGRepresentation(tmpView.image);
NSData *imagedata2 = UIImagePNGRepresentation([UIImage imageNamed:self.models.backImgName]);
BOOL isShowSelectedPic =[imagedata1 isEqualToData:imagedata2];//当前图片为背景图时,弹出选择图片方式视图
if(tmpView.image == nil){// 没有背景图的情况
isShowSelectedPic = YES;
}
if(!isShowSelectedPic){// 已经上传图片,进行放大
//进行放大查看
if ( weakSelf.models.bigblock) {
weakSelf.models.bigblock(weakSelf.models);
}
}else{//弹出选择图片方式视图
if ( weakSelf.models.block) {
weakSelf.models.block(weakSelf.models);
}
}
}];
[tmpView addGestureRecognizer:cutTap];
复制代码
II 注意事项及单元格视图的封装
2.1 QMUIKit在iOS14 下首次唤起键盘卡住主线程的解决方案
- `Main Thread Checker: UI API called on a background thread: -[UIWindow windowScene]
`
=================================================================
Main Thread Checker: UI API called on a background thread: -[UIWindow windowScene]
PID: 580, TID: 21138, Thread name: (none), Queue name: com.apple.root.user-initiated-qos, QoS: 25
Backtrace:
4 retail 0x000000010576b628 __62+[UIWindow(QMUIUserInterfaceStyleWillChangeNotification) load]_block_invoke_3 + 296
Main Thread Checker: UI API called on a background thread: -[UIWindow traitCollection]
PID: 509, TID: 22376, Thread name: (none), Queue name: com.apple.root.user-initiated-qos, QoS: 25
Backtrace:
4 Housekeeper 0x0000000100f3c000 __62+[UIWindow(QMUIUserInterfaceStyleWillChangeNotification) load]_block_invoke_3 + 92
复制代码
- 解决方案:如果你没使用QMUITheme,就直接注释掉代码即可。
@implementation UIWindow (QMUIUserInterfaceStyleWillChangeNotification)
#ifdef IOS13_SDK_ALLOWED
+ (void)load {
return ;
}
复制代码
如果你使用QMUITheme,则及时你更新4.2.1版本也无法根本性解决
这是因为系统自己在子线程访问了这些方法,只是 Main Thread Checker 对其做了兼容,发现 App 自己修改了这些方法的实现,才报错,没修改则不报错。
检测方式可以打条件符号断点,然后把 QMUI 那段代码注释掉,运行起来后会发现依然能命中这个断点,说明系统自身确实是在子线程访问了(UIKit 这种行为特别多,不只是这里)。 所以从原理上看,QMUI 命中这个主线程检测是不可避免的,目前只是做了一些优化,只有真正使用了 QMUITheme 组件时才会出现这个情况,没使用的时候就不会命中,以减少一部分的出错场景。这个优化将会跟随 4.2.1 版本发布。
2.2 完整Demo下载
demo源码下载:https://download.csdn.net/download/u011018979/15868813
应用场景: 上传和展示多张图片的场景,比如风险商户处理、发布商品图片
效果图:
技术特点:使用UICollectionViewCell、UITableViewCell 控件进行搭建,使用Masonry 框架布局,采用MVVM结构。
我的其他类似SDK pod 'KNPodlib'
2.3 单元格视图的封装
单元格视图ShowImageView的核心是显示上传成功图片,以及删除按钮。 .h
#import <UIKit/UIKit.h>
#import "QCTCollectionModel.h"
#define KNDeleteH 7 //删除按钮的高度的一半
NS_ASSUME_NONNULL_BEGIN
/**
显示图片的视图
*/
@interface ERPShowImageView : UIView
@property (nonatomic,strong) QCTCollectionModel *model;
@property (nonatomic,weak) UIImageView *imageView;
@property (nonatomic,weak) UIButton *deleteBtn;
@end
复制代码
.m
#import "ERPShowImageView.h"
@implementation ERPShowImageView
- (UIImageView *)imageView{
if (nil == _imageView) {
UIImageView *tmpView = [[UIImageView alloc]init];
_imageView = tmpView;
tmpView.contentMode = UIViewContentModeScaleAspectFill;
tmpView.clipsToBounds = YES;
[self addSubview:_imageView];
__weak __typeof__(self) weakSelf = self;
[tmpView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.offset(kAdjustRatio(KNDeleteH));
make.left.offset(kAdjustRatio(KNDeleteH));
make.right.offset(kAdjustRatio(-KNDeleteH));
make.bottom.offset(kAdjustRatio(-KNDeleteH));
}];
tmpView.userInteractionEnabled = YES;
UITapGestureRecognizer *cutTap = [[UITapGestureRecognizer alloc] init];
// __weak __typeof__(self) weakSelf = self;
[[cutTap rac_gestureSignal] subscribeNext:^(id x) {
if(weakSelf.model.type != QCTCollectionModelType4UploadPicturesAddIcon
){
return ;
}
NSLog(@" 上传图片 ");
if (weakSelf.model.block) {
weakSelf.model.block(weakSelf.model);
}
}];
[tmpView addGestureRecognizer:cutTap];
}
return _imageView;
}
- (UIButton *)deleteBtn{
if (nil == _deleteBtn) {
UIButton *tmpView = [[UIButton alloc]init];
_deleteBtn = tmpView;
[tmpView addTarget:self action:@selector(clickDeleteBtn) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:_deleteBtn];
//
__weak __typeof__(self) weakSelf = self;
[tmpView mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerY.equalTo(weakSelf.imageView.mas_top);
make.centerX.equalTo(weakSelf.imageView.mas_right);
make.width.height.mas_equalTo(kAdjustRatio(2*KNDeleteH));
}];
}
return _deleteBtn;
}
- (void)clickDeleteBtn{
// [self removeFromSuperview];
if(self.model.delblock){
self.model.delblock(self.model);
}
}
- (instancetype)initWithFrame:(CGRect)frame{
self = [super initWithFrame:frame];
NSLog(@"kninitWithFrame");
if (self) {
//构建子控件
[self setupSubviews];
}
return self;
}
- (void)setupSubviews{
NSLog(@"setupSubviews");
self.imageView.hidden = NO;
[self.deleteBtn setImage:[self imageWithImageName:@"icon_xinzengmendian_shanchu.png"] forState:UIControlStateNormal];
}
- (UIImage*)imageWithImageName:(NSString*)name{
return [UIImage imageNamed:name];
//
}
- (void)layoutSubviews{
[super layoutSubviews];
// self.imageView.frame = self.bounds;
[self layoutIfNeeded];
}
- (void)setModel:(QCTCollectionModel *)model{
_model = model;
// tm.imageType = ;
switch (model.imageType) {
case ERPimageType4name:
{
self.imageView.image = [UIImage imageNamed:model.imgName];
}
break;
case ERPimageType4url:
{
[self.imageView sd_setImageWithURL:[NSURL URLWithString:model.picurl] placeholderImage:[UIImage imageNamed:@"占位"]];
}
break;
default:
break;
}
self.deleteBtn.hidden = model.isHiddenDelBtn;
}
复制代码
单元格的数据模型 QCTCollectionModel
typedef enum : NSUInteger {
ERPimageType4name,
ERPimageType4url,
ERPimageType4Uiimage,
} ERPimageType;
@property (nonatomic,assign) ERPimageType imageType;
//
@property (nonatomic , copy) NSString *picurl;
/**
默认NO 显示删除按钮
*/
@property (nonatomic,assign) BOOL isHiddenDelBtn;
@property (nonatomic,copy) NSString *imgName;
复制代码