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

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

前言

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

技术特点:使用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

目录
相关文章
|
6月前
|
JSON JavaScript 安全
iOS应用程序数据保护:如何保护iOS应用程序中的图片、资源和敏感数据
iOS应用程序数据保护:如何保护iOS应用程序中的图片、资源和敏感数据
60 1
|
iOS开发
iOS TextView插入表情或者图片后字体变大或变小
iOS TextView插入表情或者图片后字体变大或变小
113 1
|
Android开发 iOS开发
iOS 替换WebView网页图片为本地图片
iOS 替换WebView网页图片为本地图片
268 0
|
6月前
|
存储 缓存 安全
基于iOS平台的高效图片缓存策略实现
【4月更文挑战第22天】 在移动应用开发中,图片资源的加载与缓存是影响用户体验的重要因素之一。尤其对于iOS平台,由于设备存储空间的限制以及用户对流畅性的高要求,设计一种合理的图片缓存策略显得尤为关键。本文将探讨在iOS环境下,如何通过使用先进的图片缓存技术,包括内存缓存、磁盘缓存以及网络请求的优化,来提高应用的性能和响应速度。我们将重点分析多级缓存机制的设计与实现,并对可能出现的问题及其解决方案进行讨论。
|
6月前
|
存储 缓存 算法
实现iOS平台的高效图片缓存策略
【4月更文挑战第22天】在移动应用开发中,图片资源的处理是影响用户体验的重要因素之一。特别是对于图像资源密集型的iOS应用,如何有效地缓存图片以减少内存占用和提升加载速度,是开发者们面临的关键挑战。本文将探讨一种针对iOS平台的图片缓存策略,该策略通过结合内存缓存与磁盘缓存的机制,并采用先进的图片解码和异步加载技术,旨在实现快速加载的同时,保持应用的内存效率。
|
6月前
|
存储 缓存 编解码
实现iOS平台的高效图片缓存策略
【4月更文挑战第23天】在移动应用开发领域,尤其是图像处理密集型的iOS应用中,高效的图片缓存策略对于提升用户体验和节省系统资源至关重要。本文将探讨一种针对iOS平台设计的图片缓存方案,该方案通过结合内存缓存与磁盘缓存的多层次结构,旨在优化图片加载性能并降低内存占用。我们将深入分析其设计理念、核心组件以及在实际场景中的应用效果,同时对比其他常见缓存技术的优势与局限。
|
6月前
|
存储 Web App开发 Android开发
iOS不支持WebP格式图片解决方案和iPhone 7及其后硬件拍照的HEIC格式图片
iOS不支持WebP格式图片解决方案和iPhone 7及其后硬件拍照的HEIC格式图片
648 1
iOS不支持WebP格式图片解决方案和iPhone 7及其后硬件拍照的HEIC格式图片
|
6月前
按钮的image图片是非圆角,直接对UIButton设置圆角,iOS13系统没有圆角效果的问题及解决方案
按钮的image图片是非圆角,直接对UIButton设置圆角,iOS13系统没有圆角效果的问题及解决方案
49 0
|
6月前
|
存储 缓存 iOS开发
实现iOS平台的高效图片缓存策略
【4月更文挑战第4天】在移动应用开发中,图片资源的加载与缓存是影响用户体验的关键因素之一。尤其对于iOS平台,由于设备存储和内存资源的限制,设计一个高效的图片缓存机制尤为重要。本文将深入探讨在iOS环境下,如何通过技术手段实现图片的高效加载与缓存,包括内存缓存、磁盘缓存以及网络层面的优化,旨在为用户提供流畅且稳定的图片浏览体验。
|
6月前
|
存储 缓存 监控
实现iOS平台的高效图片缓存策略
【4月更文挑战第18天】在移动应用开发中,图片资源的加载与缓存是影响用户体验的重要因素之一。特别是对于iOS平台,合理设计图片缓存策略不仅能够提高应用的响应速度,还能降低内存消耗和网络流量。本文将探讨一种针对iOS环境的图片缓存方案,该方案通过多级缓存机制、内存管理和磁盘存储策略相结合,旨在提升图片加载效率并优化性能。