北京志远天辰科技有限公司-旗下
首页 » 产品教程 » iOS小知识:封装上传图片视图(支持删除和添加)

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

作者:钉钉硬件分类: 产品教程 时间:2022-1-13 16:41浏览:726次

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

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

点击文末原文下载demohttps://download.csdn.net/download/u011018979/15868813

image.png

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下载


demo源码下载:https://download.csdn.net/download/u011018979/15868813

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

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

image.png

@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