引言
在移动应用开发中,UI(用户界面)的设计往往是开发者和设计师的核心关注点。然而,随着功能和交互的复杂性增加,我们往往会遇到一个问题:如何使得UI更加灵活、动态,能够根据不同的数据状态实时更新而无需重复调整界面布局?
传统的UI设计可能会倾向于固定的布局和元素,而这些元素的状态和展示需要通过手动更新来实现。这种方法在开发过程中可能会导致UI逻辑与数据逻辑紧密耦合,增加了维护和扩展的难度。而通过“数据驱动UI”这一方法,UI的展示完全依赖于数据的变化,UI的更新将与数据的状态同步进行。这样,不仅能提高开发效率,还能使得应用的交互更加智能和动态,提升用户体验。
本篇博客将通过一个常见的相册上传功能为例,介绍如何通过数据驱动UI的更新。在这个场景中,我们将通过数据模型来控制界面中上传按钮、图片展示、上传状态等UI元素的显示与变化,而不需要在每次UI更新时手动修改布局。我们将一起探讨如何通过简单的逻辑,确保UI的灵活性和易维护性,同时为用户提供流畅的体验。
需求分析
我们需要做一个如上图所示的相册上传功能,该UI看起来简单清晰,但是如果没有好的实现方案,代码也有可能会变得十分复杂难以维护。我们首先来分析该UI对应的需求。
该UI设计用于展示用户已上传的相册图片,最多显示6个项。若已上传的图片少于6个,则会显示一个添加按钮,用户点击后可以选择多张图片进行上传。上传的图片总数(包括已上传和待上传的图片)不能超过6张。当用户的已上传图片达到6个时,加号按钮将不再显示。每张图片都有不同的状态,包括已上传、正在上传以及上传失败。通过这种方式,用户可以直观地看到上传进度和状态,同时避免上传超过限制。
同时每个图片点击时会根据不同的状态有不同的功能,这个我们可以稍后再讨论。
设计方案
关于UI首选当然还是一个横向滚动的UICollectionView,我们需要关注的重点如何区分加号按钮,以及不同状态的按钮。当获取用户当前相册内容的时候服务端为我们返回了一个数据结构,我们可以以此来创建已上传的用户相册内容的数据模型MWProfileAlbumModel。
public struct MWProfileAlbumModel: Mappable, Equatable {
/// 大图
public var srcUrl: String = ""
/// 小图
public var littleUrl: String = ""
public init() {
}
public init?(map: Map) {
}
public mutating func mapping(map: Map) {
srcUrl <- map["srcUrl"]
littleUrl <- map["littleUrl"]
}
}
另外我们还需要定义另外一个数据模型MWEditPhotoModel来标记我们需要操作的相册内容。
class MWEditPhotoModel: NSObject {
/// 已上传的 (如果有它,则表示已上传)
var albumModel:MWProfileAlbumModel?
/// 是否是+号
var isAdd:Bool = false
/// 图片数据
var image:UIImage?
/// 上传状态
var uploadStatus:MWPhotoUploadStatus = .none
}
MWEditPhotoModel是用来决定UI的关键,MWEditPhotoModel包含了已上传的数据模型,同时还标记了是否为加号按钮,待上传的图片数据,以及文件的上传状态。而我们只需要根据这些数据来渲染UICollcectionView列表即可。接下来我们就来一下具体的代码实现。
代码实现
数据模型定义和处理
根据第一部分的需求该我们需要定义图片的最大数据,以及用来渲染用户已经上传的相册数据的列表。并且在接收到列表数据时,来判断是否已经达到了最大值,如果没有达到则为数据添加一个添加按钮数据。
/// 相册列表数据
private var editAlbumList: [MWEditPhotoModel] = []
/// 最大值
private var maxCount: Int = 6
/// 渲染数据
func renderAlbumList(albumList: [MWEditPhotoModel]) {
editAlbumList = albumList
// 添加加号按钮
if albumList.count < maxCount {
let addModel = MWEditPhotoModel()
addModel.isAdd = true
editAlbumList.insert(addModel, at: 0)
}
self.collectionView.reloadData()
}
列表样式渲染
为了显示添加图标,我们需要注册两个不同类型的cell一个用来显示加号,另外一个用来显示图片资源,包括正在上传,上传失败以及已经上传完成的状态。然后根据列表的数据来决定显示何种类型的cell以及cell中图片的上传状态。
lazy var collectionView: UICollectionView = {
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: flowLayout)
collectionView.backgroundColor = .clear
// 图片cell
collectionView.register(MWEditProfileUploadItemCell.self, forCellWithReuseIdentifier: "MWEditProfileUploadItemCell")
// 上传的cell
collectionView.register(MWEditPhotoAddCell.self, forCellWithReuseIdentifier: "MWEditPhotoAddCell")
collectionView.showsHorizontalScrollIndicator = false
collectionView.showsVerticalScrollIndicator = false
collectionView.delegate = self
collectionView.dataSource = self
return collectionView
}()
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let model = editAlbumList[indexPath.row]
// 加号cell
if model.isAdd {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MWEditPhotoAddCell", for: indexPath) as! MWEditPhotoAddCell
return cell
}
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MWEditProfileUploadItemCell", for: indexPath) as! MWEditProfileUploadItemCell
if indexPath.row >= editAlbumList.count {
return cell
}
cell.renderEditPhotoCell(editPhotoModel: model)
return cell
}
图片状态渲染
关于加号类型的cell,就不需要过多解释了,只需要将cell的点击事件回传到视图控制器,然后利用系统提供的相册选择功能选择新的照片即可。而关于图片类型cell,我们则需要根据数据的上传状态来渲染不同的样式,比如正在上传,上传失败等等。
/// 渲染图片cell
/// - Parameter editPhotoModel: 编辑图片模型
func renderEditPhotoCell(editPhotoModel: MWEditPhotoModel) {
coverView.isHidden = true
loadingIcon.isHidden = true
reuploadIcon.isHidden = true
// 请求下来的图片
if let albumModel = editPhotoModel.albumModel , (editPhotoModel.uploadStatus == .noNeed || editPhotoModel.uploadStatus == .success) {
let albumUrl = albumModel.littleUrl.validResourceUrl()
imageView.sd_setImage(with: URL(string: albumUrl))
return
}
// 本地图片
if let image = editPhotoModel.image {
imageView.image = image
}
if editPhotoModel.uploadStatus == .fail {
// 上传失败
coverView.isHidden = false
reuploadIcon.isHidden = false
} else if editPhotoModel.uploadStatus == .uploading {
// 上传中
coverView.isHidden = false
loadingIcon.isHidden = false
}
}
- 对于已经上传完成的数据,我们采用它的远程图片来渲染。
- 对刚刚上传的数据,我们采用本地的image来渲染。
- 根据模型的上传状态决定选择正在上传的样式还是上传失败的样式。
上传新的图片
当从相册选择完新的一组图片之后,首先根据图片来构建MWEditPhotoModel数据模型,然后执行渲染操作,保证UI显示的是所有内容。然后同时执行上传操作,将整个列表传递到上传方法,方法内部会根据模型的上传状态来区分哪些需要上传,哪些不需要上传。模型会根据上传的结果设置上传状态,当所有模型上传完成之后,在回调中再次刷新列表。
/// 选择相册图片
/// - Parameter images: 图片数组
func uploadAlbum(images: [UIImage]) {
//1. 先刷新
for image in images {
let model = MWEditPhotoModel()
model.image = image
model.uploadStatus = .none
editUserPhotos.append(model)
}
self.tableView.reloadData()
//2.开始执行上传操作
self.presenter.requestUploadAlbumData(editPhotoModels: editUserPhotos) {[weak self] in
guard let self = self else { return }
self.tableView.reloadData()
}
}
- 根据选择选择图片数据构建新的数据模型,注意这里的.none表示了需要上传,而执行self.tableView.reloadData()会将editUserPhotos传递到我们定义的相册列表UICollectionView并执行刷新操作。
- 开始上传文件,上传完成之后再次给相册列表赋值并刷新UI。
结语
通过这篇博客,我们展示了如何使用数据驱动来实现一个灵活的相册上传功能。借助合理的数据模型设计和动态UI更新,代码不仅清晰易维护,还能带来更好的用户体验。希望这个示例能为你在处理类似问题时提供一些启发,也期待你在自己的项目中探索更多数据驱动UI的可能性!