0
点赞
收藏
分享

微信扫一扫

iOS小知识:封装上传图片视图(支持删除和添加)


前言

应用场景: 上传和展示多张图片的场景,比如风险商户处理、发布商品图片

技术特点:使用UICollectionViewCell、UITableViewCell 控件进行搭建,使用Masonry 框架布局,采用MVVM结构。


效果图: iOS小知识:封装上传图片视图(支持删除和添加)_ios


I、 使用方法

先配置相册访问权限key

NSPhotoLibraryUsageDescription
The app's Info.plist must contain an NSPhotoLibraryUsageDescription key with a string value explaining to the user how the app uses this data.

1.1 初始化 cell

  • cell
case ERPRelease_commoditiesViewSection4UploadPic:{


return [ERPcomposePhotosTableViewCell tableViewCellWithTableView:tableView block:^(id _Nonnull sender) {

} models:self.viewModel.Model4UploadPictures];






}break;
  • 上传图片界面的初始模型数据
#pragma mark - ******** 上传图片界面的初始模型数据
+ (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;


}
  • 处理上传图片逻辑
- (void)Model4UploadPictures{
__weak __typeof__(self) weakSelf = self;

self.viewModel.Model4UploadPictures = [QCTCollectionModel getModel4ADDUploadPicturesWithBlock:^(UISwitch* sender) {




[weakSelf setupChooseimage];//上传图片


} ];





}

1.2 初始化 cellView

ERPcomposePhotosV4UploadPictures

- (ERPcomposePhotosV4UploadPictures *)cellView{
if (nil == _cellView) {
ERPcomposePhotosV4UploadPictures *tmpView = [[ERPcomposePhotosV4UploadPictures alloc]init];
_cellView = tmpView;

[tmpView setBackgroundColor:kcellColor];

[self.contentView addSubview:tmpView];

__weak __typeof__(self) weakSelf = self;
[tmpView mas_makeConstraints:^(MASConstraintMaker *make) {

make.left.equalTo(weakSelf.contentView).offset(kAdjustRatio(20));
make.right.equalTo(weakSelf.contentView).offset(- kAdjustRatio(20));
make.top.equalTo(weakSelf.contentView).offset(kAdjustRatio(0));

make.bottom.equalTo(weakSelf.contentView).offset(- kAdjustRatio(0));

}];


}
return _cellView;
}

1.3 约束的设置

根据模型数据,更新视图高度


mas_updateConstraints 计算宽度的时候采用UIScreen进行屏幕宽度的获取


- (void)setModels:( NSMutableArray*)models{
_models =models;

self.cellView.models = models;

NSInteger maxclos= 3;// 列数

CGFloat margin = 10;

//
// [self.cellView layoutIfNeeded];

// CGFloat w = (self.cellView.frame.size.width - margin*(maxclos-1))/maxclos;

CGFloat cellViewW =kWidth-kAdjustRatio(20*2) ;//计算宽度的时候采用UIScreen进行屏幕宽度的获取

CGFloat w = (cellViewW- margin*(maxclos-1))/maxclos;
CGFloat cell_H = w*(1);//宽高比
// NSLog(@"cell_H:%f",cell_H);//
NSInteger row = [QCT_Common getRowWithCount:models.count clos:maxclos];
[self.cellView mas_updateConstraints:^(MASConstraintMaker *make) {


make.height.mas_equalTo(kAdjustRatio(cell_H*row + (row -1)*10 ));

}];



}

设置sizeForItemAtIndexPath

-(CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
CGSize size;

NSInteger maxclos= 3;// 列数

CGFloat margin = 10;

CGFloat w = (self.frame.size.width - margin*(maxclos-1))/maxclos;

CGFloat cell_H = w*(1);//宽高比 123/234



size = CGSizeMake(cell_H, cell_H);






return size;
}

初始化collectionView,并设置每一行之间的间距

- (UICollectionView *)collectionView {
if (_collectionView == nil) {
UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init];
// 2.设置整个collectionView的内边距
//分别为上、左、下、右
flowLayout.sectionInset = UIEdgeInsetsMake(kAdjustRatio(0),kAdjustRatio(0),kAdjustRatio(0),kAdjustRatio(0));
//.设置每一行之间的间距
flowLayout.minimumLineSpacing = kAdjustRatio(10);
flowLayout.minimumInteritemSpacing = 0;
// flowLayout.itemSize = CGSizeMake((SCREEN_WIDTH-3*kAdjustRatio(10))/3.0, self.optionsView.height);
_collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:flowLayout];
_collectionView.backgroundColor = [UIColor whiteColor];
_collectionView.showsVerticalScrollIndicator = NO;
_collectionView.bounces = NO;

_collectionView.dataSource = self;
_collectionView.delegate = self;

[_collectionView registerClass:[ERPUploadPicturesUICollectionViewCell class] forCellWithReuseIdentifier:@"ERPUploadPicturesUICollectionViewCell"];
if (@available(iOS 11.0, *)) {
_collectionView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
} else {
// Fallback on earlier versions
}

_collectionView.scrollEnabled = NO;
// UICollectionViewScrollDirectionHorizontal

__weak __typeof__(self) weakSelf = self;

[self addSubview:_collectionView];
[_collectionView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.top.bottom.right.equalTo(weakSelf);
}];




}
return _collectionView;
}

1.4 处理图片添加成功的用法举例

- (void)UpdateImageToViewWithurl:(NSString*)picurl{

__weak __typeof__(self) weakSelf = self;
// 新增模型

QCTCollectionModel *tm = [QCTCollectionModel new];


tm.type = QCTCollectionModelType4ShowUploadPictures;
tm.imageType = ERPimageType4url;

tm.picurl = picurl;

[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];





}];


[tm setBlock:^(id _Nonnull sender) {



}];



[self.viewModel.Model4UploadPictures insertObject:tm atIndex:self.viewModel.Model4UploadPictures.count-1];

if(self.viewModel.Model4UploadPictures.count>maxcount4UploadPicturesInRelease_commodities){// 处理最大数量


[self.viewModel.Model4UploadPictures removeLastObject];



}

// 刷新视图
[self.viewModel.reloadProductFileUploadSubject sendNext:nil];





}

II、核心代码

2.0 处理是否已经包含添加图片视图

2.0.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;



}

2.0.2 添加按钮数据模型

+ (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;


}

2.0.3 判断是否已经包含添加图片视图,如果没有,且数量小于上传的最大张数,则显示添加按钮

[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];





}];

2.1 ShowImageView 显示图片的视图

2.1.1 .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

2.1.2.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;

}

2.2 数据模型 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;

2.3 完整Demo下载



  1. 应用场景: 上传和展示多张图片的场景,比如风险商户处理、发布商品图片
  2. 技术特点:使用UICollectionViewCell、UITableViewCell 控件进行搭建,使用Masonry 框架布局,采用MVVM结构。

iOS小知识:封装上传图片视图(支持删除和添加)_上传_02


III、注意事项

3.1 QMUIKit在iOS14 下首次唤起键盘卡住主线程的解决方案

​​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,就直接注释掉代码即可。

iOS小知识:封装上传图片视图(支持删除和添加)_上传_03

@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 版本发布。

see also

  • iOS 自定义折扣视图:支持添加/删除/选择折扣 【 应用场景:购物车界面选择整单折扣,无品收银台界面选择订单折扣】
  • 我的其他类似SDK pod 'KNPodlib'


举报

相关推荐

0 条评论