MCCSframework 教程(六)图片上传

在上一篇教程中,我们介绍了如何用 MCCSframework 调用 iPhone 的相机和相册,接下来我们将继续上一篇教程的工作,介绍如何将用户选中的图片上传到后台。

在 MCCSframework 中,上传不属于网络 API,而是封装成了单独的模块。

图片的上传比较复杂,除了网络操作外,我们同样需要一些 UI 来展示用户上传成功了的图片,因此它也涉及了模型、cell 和子控制器。让我们首先从模型开始。

模型

首先,文件上传后,后台一般会以对象的形式来表示上传后的文件,我们将这个对象称作附件。我们需要创建一个模型类,用来代表附件。新建类 Attachment,类的定义如下:

@interface Attachment : JKModel


@property (nonatomic,copy)   NSString *effect;
@property (nonatomic,copy)   NSString *content;
@property (nonatomic,copy)   NSString *mongoId;
@property (nonatomic,copy)   NSString *id;
@property (nonatomic,copy)   NSString *fileName;
@property (nonatomic,copy)   NSString *title;
@property (nonatomic,copy)   NSString *formart;
@property (nonatomic,copy)   NSString *updateTime;
@property (nonatomic,copy)   NSString *remark;
@property (nonatomic,copy)   NSString *createTime;

@end

注意,每个后台对于附件的定义是不一样的,在真实项目中需要根据后台接口进行定义。

网络

虽然框架已经实现了通用的文件上传 API,但为了简化代码,以及不同项目的个性化需求,我们还是需要对上传逻辑进行进一步的封装。

首先新建一个类 UploadAPI,定义两个方法:

+(RACSignal*)uploadAsset:(PHAsset*)asset;
+(RACSignal*)uploadAssets:(NSArray<PHAsset*>*)assets;

这两个方法都是用于上传图片的,第一个是单个图片上传,第二个是多张图片上传。还记得上一篇教程中,我们已经将拍照后的照片也保存到相册中了,因此无论是用户从相册中选图片,还是直接拍照,我们在 PhotoPickSC 的 selectetAssets 数组中保存的都是 PHAsset 对象。因此这两个方法实际上都使用了 PHAsset 参数。

首先看第一个方法:

// MARK: ---- 上传单个 PHAsset
+(RACSignal*)uploadAsset:(PHAsset*)asset{
    return [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
        NSString* url = [NSString stringWithFormat:@"%@?i18n=%@",fileUploadUrl,[UWConfig userLanguage]];

        [[self uploadAsset:asset key:@"files" headers:[self.class headers] toUrl:url] subscribeNext:^(id  _Nullable x) {
            NSError* error;
            if([x[@"data"] isKindOfClass:[NSArray class]]){
                NSArray *data = x[@"data"];
                NSArray<Attachment*>* list = [Attachment arrayOfModelsFromDictionaries:data error:&error];
                if(error==nil&&list.count>0){
                    [subscriber sendNext:list[0]];
                    [subscriber sendCompleted];
                }else{
                    [subscriber sendError:APIError.dataParseError];
                }
            }else{
                [subscriber sendError: APIError.dataFormatError];
            }
        } error:^(NSError * _Nullable error) {
            [subscriber sendError:error];
        }];
        return nil;
    }];
}

代码很简单,调用了框架提供的 NSObject+Upload 分类的 uploadAsset:key:header:toUrl 方法。值得注意的是 key 参数,是个实际上是 form 表单中 file 控件的 name,不同的后台可能会有不同定义,比如这里例子里 file 控件的 name 就叫 files。

其次是 header 参数,这里用的是 [self.class headers],这个一般是用它来传递 token 值的,比如这样:

+(NSDictionary<NSString*,NSString*>*)headers{
    //  添加 token 到 headers 中
    NSDictionary* headers;
    NSString* token = appDelegate.auth.token;
    if(token){
        headers = @{@"Authorization":token};
    }
    return headers;
}

第二个方法是多文件上传。它更简单,只是做一个信号合并,将第一个方法的多个调用 merge 成一个信号而已:

+(RACSignal*)uploadAssets:(NSArray<PHAsset*>*)assets{
    NSMutableArray* signals = [NSMutableArray new];
    
    for(int i= 0;i < assets.count;i++){
        RACSignal* sig = [UploadAPI uploadAsset:assets[i]];
        if(sig)
            [signals addObject:sig];
    }
    if(signals.count ==0)
        return nil;
    // 信号合并,当所有信号发送后,合并
    return [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
        [[RACSignal combineLatest:signals] subscribeNext:^(RACTuple * _Nullable x) {
            NSArray* arr = x.allObjects;
            [subscriber sendNext:arr];
            [subscriber sendCompleted];
        } error:^(NSError * _Nullable error) {
            [subscriber sendError:error];
        }];
        
        return nil;
    }];
}

一般来说,上传功能只会是在 ViewController 中调用,因此将上面两个方法的调用封装到 ViewController 的分类中,会更方便一些。

新建 UIViewController 分类 UIViewController+Upload,在 .h 文件中添加方法:

-(void)uploadAssets:(NSArray<PHAsset*>*) assets completion:(void (^)(NSArray<Attachment*>* attachments))completion;

在 .m 文件中实现方法:

-(void)uploadAssets:(NSArray<PHAsset*>*) assets completion:(void (^)(NSArray<Attachment*>* attachments))completion{
    NSMutableArray<Attachment*>* attachments = [NSMutableArray new];
    
    if(assets && assets.count>0){
        [self showHUDIndeterminate];
        [[UploadAPI uploadAssets:assets] subscribeNext:^(NSArray*  _Nullable x) {
            [self hideHUDIndeterminate];
            
            for(id obj in x){
                if([obj isKindOfClass:Attachment.class])
                    [attachments addObject:obj];
            }
            if(completion)
                completion(attachments);
        } error:^(NSError * _Nullable error) {
            [self hideHUDIndeterminate];
            [self showHint:error.localizedDescription];
        }];
    }else{
        if(completion)
            completion(attachments);
    }
}

这样,打开 MeetingAddVC.m 中,找到 configPageHeader 方法,添加一句:

	@weakify(self)
	self.pageHeader.rightAction = ^{
		@strongify(self)
		[self upload];
	};

然后在 upload 方法中调用 uploadAssets 方法:

		if(self.selectedAssets && self.selectedAssets.count > 0){// 如果有附件先上传附件再完成
            [self uploadAssets:self.selectedAssets completion:^(NSArray<Attachment *> * _Nonnull x) {
                
                self.meetingInfo.baseFileStorePlus = @{@"attachments":x};

                [self publish];
            }];
        }else{// 如果无附件,直接完成
            [self publish];
        }

很简单,调用 uploadAssets 方法上传图片,然后在上传成功后取得后台返回的附件列表,将它塞进 meetingInfo 对象的 baseFileStorePlus (一个字典)中,连着整个表单一起提交到服务器。其中 publish 方法会调用后台的新增会议接口,这与我们今天的主题无关,跳过。

附件的展现

上传图片到服务器其实还算简单,但我们不能只是调一下 upload 接口就完了,上传后的图片同样需要展现给用户,特别是,如果我们想将新增界面和编辑界面共用一个界面时就更需要如此。比如,我们将 MeetingAddVC 同时用于新增会议和编辑会议。

这需要新的子控制器。

AttachmentsPreviewSC

新建子控制器 AttachmentsPreviewSC。在 .h 文件中,声明几个属性:

@property (strong, nonatomic) NSMutableArray<Attachment*>* attachments;
@property (strong, nonatomic) NSMutableArray<PHAsset*>* selectedAssets;
@property (strong, nonatomic) NSMutableArray<UIImage*>* selectedPhotos;
@property (strong, nonatomic) void(^photoArrayChangeBlock)(NSArray<PHAsset*>* assets);

跟 PhotoPickSC 很像,但多出了一个 attachments 数组。这很自然,因为除了用户选择的图片外,我们还要包含会议自身已有的附件图片。

在 .m 文件中同样如此,大部分代码和 PhotoPickSC 一样。因为这个类本来就是从 PhotoPickSC 复制而来的。不同的地方在于:

  1. numberOfItems 方法

    - (NSInteger)numberOfItems{
    NSInteger i = _attachments==nil? 0: _attachments.count;
    i += _selectedAssets.count;
    i ++ ;// 最后一颗是”添加“按钮
    return i;
    }
    
    

    很简单,现在 cell 的数目不仅仅是用户拍照或选择的照片了,还应当包含已有附件的数目。

  2. isAttachmentsAtIndex 方法

    -(BOOL)isAttachmentsAtIndex:(NSInteger)index{
    if(self.countOfAttachments==0){
        return NO;
    }else{
        return index >=0 && index < self.countOfAttachments;
    }
    }	
    

    这个方法是增加的,根据 cell 的 index 判断该 cell 是否是展现附件的 cell。

  3. isSelectedAssetsAtIndex 方法

    -(BOOL)isSelectedAssetsAtIndex:(NSInteger)index{
    return ![self isAttachmentsAtIndex:index];
    }
    

    很简单,这个 cell 如果不是用于展现附件的,那么就是用于展现 PHAsset 的。

  4. cellForItemAtIndex 方法

    在这个方法中,多出了这段代码:

    if([self isAttachmentsAtIndex:index]){
        Attachment* attachment = _attachments[index];
        //        fileVO.thumbnail = nil;
        
        [AppUtil downloadWithImageId:attachment.id placeholdImage:@"logo" completion:^(UIImage * _Nonnull img) {
            cell.imageView.image = img;
        }];
    }else{
        cell.imageView.image = _selectedPhotos[index - self.countOfAttachments];
    }
    

    如果 cell 是用于展现附件的,那么从网络获取要展现的图片,否则直接从 selectedPhotos 中获取图片用于渲染 cell。

  5. deleteHandler 方法

    if([self isAttachmentsAtIndex:index]){// 删除附件
        [_attachments removeObjectAtIndex:index];
    }else{// 删除图片
        [_selectedAssets removeObjectAtIndex:index-self.countOfAttachments];
        [_selectedPhotos removeObjectAtIndex:index-self.countOfAttachments];
        if(_photoArrayChangeBlock){
            _photoArrayChangeBlock(_selectedAssets);
        }
    }
    

    对于附件 cell,删除时删的是 attachments 数组。否则删的是 selectedAssets 和 selectedPhotos 数组。

  6. didSelectItemAtIndex 方法

    增加了附件 cell 的大图预览:

    if([self isAttachmentsAtIndex:index]){
        Attachment* attachment = _attachments[index];
        
        if(attachment){
            YBImageBrowser *browser = [YBImageBrowser new];
            
            YBImageBrowseCellData *data = [YBImageBrowseCellData new];
            data.thumbImage = cell.imageView.image;// 低分图
            
            data.url= remoteImgAddr(attachment.id);
            data.sourceObject = cell;
            browser.dataSourceArray = @[data];
            
            browser.currentIndex = 0;
            [browser.defaultToolBar hideOperationButton];
            [browser show];
        }
    }else{
    

OK,好了,剩下的代码和 PhotoPickSC 一模一样,没有必要介绍了。

现在还有一件事,就是将 这个子控制器添加到 MeetingAddVC 中。

添加子控制器

首先增加属性:

@property (strong, nonatomic) AttachmentsPreviewSC* attachmentsSC;

然后子控制器的初始化:

-(AttachmentsPreviewSC*)attachmentsSC{// 附件预览 SC
    if(!_attachmentsSC){
        _attachmentsSC = [AttachmentsPreviewSC new];
        _attachmentsSC.inset = UIEdgeInsetsMake(15, 20, 5, 20);
        
        _attachmentsSC.attachments = [_meetingInfo.baseFileStorePlus[@"baseFilesList"] mutableCopy];
        
        @weakify(self)
        _attachmentsSC.photoArrayChangeBlock = ^(NSArray<PHAsset*>* _Nonnull assets) {
            @strongify(self)
            self.selectedAssets = assets;
        };
        
    }
    return _attachmentsSC;
}

添加到主控制器:

[self addSC:_meetingInfo.id ? self.attachmentsSC : self.photoSC];

这里讨了一点巧,判断 meetingInfo 的 id 是否有值,有值说明这是对一个已有会议记录的编辑,那么使用的是 AttachmentsPreviewSC 子控制器;否则说明是新建,使用的就是 PhotoPickSC 子控制器。

好了,整个图片上传的教程就介绍到这里了,感谢阅读,下次再见。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值