23、开发应用:照片管理应用的实现与优化

开发应用:照片管理应用的实现与优化

1. 填充照片网格

在维护了照片列表后,需要将其显示在集合视图中。集合视图通过实现 UICollectionViewDataSource 协议的数据源来填充数据。常见的做法是让视图控制器成为数据源,具体操作步骤如下:
1. 打开 Main.storyboard ,从集合视图按住 Control 键拖动到视图控制器。
2. 松开鼠标后,从菜单中选择 dataSource

接下来需要实现数据源协议的两个方法:
- collectionView:numberOfItemsInSection: :用于指定要显示的单元格数量。

extension ViewController: UICollectionViewDataSource {
    func collectionView(
        collectionView: UICollectionView,
        numberOfItemsInSection section: Int
        ) -> Int
    {
        return self.photos.count
    }
}
  • collectionView:cellForItemAtIndexPath: :用于为列表中的特定索引自定义每个单元格。

在配置单元格之前,需要创建自定义的单元格子类 PhotoCollectionViewCell

import UIKit
class PhotoCollectionViewCell: UICollectionViewCell {
    @IBOutlet var imageView: UIImageView!
    @IBOutlet var label: UILabel!
}

然后回到 storyboard 进行以下操作:
1. 在左侧的视图层次结构中找到单元格并点击它。
2. 选择 View | Utilities | Show Identify Inspector ,在类字段中输入 PhotoCollectionViewCell 以设置单元格的类。
3. 导航到 View | Utilities | Show Connections Inspector ,将 imageView label 分别连接到单元格中的图像视图和标签。
4. 返回属性检查器,在标识符文本字段中输入 DefaultCell 以设置单元格的重用标识符。

还需要在视图控制器中添加对集合视图的引用:

class ViewController: UIViewController {
    @IBOutlet var collectionView: UICollectionView!
    // ... 
}

最后实现 collectionView:cellForItemAtIndexPath: 方法:

extension ViewController: UICollectionViewDataSource {
    // ...

    func collectionView(
        collectionView: UICollectionView,
        cellForItemAtIndexPath indexPath: NSIndexPath
        ) -> UICollectionViewCell
    {
        let cell = collectionView
            .dequeueReusableCellWithReuseIdentifier(
            "DefaultCell",
            forIndexPath: indexPath
            ) as! PhotoCollectionViewCell

        let photo = self.photos[indexPath.item]
        cell.imageView.image = photo.image
        cell.label.text = photo.label

        return cell
    }
}

当添加照片时,集合视图不会自动重新加载数据,需要手动调用 reloadData insertItemsAtIndexPaths 方法。推荐使用 insertItemsAtIndexPaths 方法以实现动画效果:

let saveAction = UIAlertAction(
    title: "Save",
    style: .Default
    ) { action in
    let label = textField.text ?? ""
    let photo = Photo(image: image, label: label)
    self.photos.append(photo)
    let indexPath = NSIndexPath(
        forItem: self.photos.count - 1,
        inSection: 0
    )
    self.collectionView.insertItemsAtIndexPaths([indexPath])
}
2. 遵循 MVC 模式进行重构

之前的代码将大量业务逻辑放在了视图控制器中,不利于模型、视图和控制器层的分离。因此,创建一个 PhotoStore 类来负责存储照片并实现数据源协议。

首先,将照片属性移动到 PhotoStore 类:

import UIKit
class PhotoStore: NSObject {
    var photos = [Photo]()
}

然后添加回调属性:

class PhotoStore: NSObject {
    var photos = [Photo]()
    var cellForPhoto:
        (Photo, NSIndexPath) -> UICollectionViewCell

    init(
        cellForPhoto: (Photo,NSIndexPath) -> UICollectionViewCell
        )
    {
        self.cellForPhoto = cellForPhoto
        super.init()
    }
}

接着实现数据源方法:

extension PhotoStore: UICollectionViewDataSource {
    func collectionView(
        collectionView: UICollectionView,
        numberOfItemsInSection section: Int
        ) -> Int
    {
        return self.photos.count
    }

    func collectionView(
        collectionView: UICollectionView,
        cellForItemAtIndexPath indexPath: NSIndexPath
        ) -> UICollectionViewCell
    {
        let photo = self.photos[indexPath.item]
        return self.cellForPhoto(photo, indexPath)
    }
}

再添加保存照片的方法:

func saveNewPhotoWithImage(
    image: UIImage,
    labeled label: String
    ) -> NSIndexPath
{
    let photo = Photo(image: image, label: label)
    self.photos.append(photo)
    return NSIndexPath(
       forItem: self.photos.count - 1,
       inSection: 0
    )
}

最后更新视图控制器以使用 PhotoStore

class ViewController: UIViewController {
    var photoStore: PhotoStore!

    func createCellForPhoto(
        photo: Photo,
        indexPath: NSIndexPath
        ) -> UICollectionViewCell
    {
        let cell = self.collectionView
            .dequeueReusableCellWithReuseIdentifier(
            "DefaultCell",
            forIndexPath: indexPath
            ) as! PhotoCollectionViewCell

        cell.imageView.image = photo.image
        cell.label.text = photo.label

        return cell
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        self.photoStore = PhotoStore(
            cellForPhoto: self.createCellForPhoto
        )
        self.collectionView.dataSource = self.photoStore
    }

    func createSaveActionWithTextField(
        textField: UITextField,
        andImage image: UIImage
        ) -> UIAlertAction
    {
        return UIAlertAction(
            title: "Save",
            style: .Default
            ) { action in
            let label = textField.text ?? ""
            let indexPath = self.photoStore.saveNewPhotoWithImage(
                image,
                labeled: label
            )
            self.collectionView.insertItemsAtIndexPaths([indexPath])
        }
    }
}
3. 永久保存照片

目前应用在退出后照片会丢失,需要将照片永久保存。选择将照片保存到文件系统,每个应用都有一个文档目录,可将照片以用户提供的标签命名存储在其中,并使用时间戳创建子目录以避免标签重复。

首先,在 PhotoStore 类中添加获取保存目录的方法:

private extension PhotoStore {
    func getSaveDirectory() throws -> NSURL {
        let fileManager = NSFileManager.defaultManager()
        return try fileManager.URLForDirectory(
            .DocumentDirectory,
            inDomain: .UserDomainMask,
            appropriateForURL: nil,
            create: true
        )
    }
}

然后在 Photo 结构体中添加保存照片到目录的方法:

struct Photo {
    // ...
    enum Error: String, ErrorType {
        case CouldntGetImageData = "Couldn't get data from image"
        case CouldntWriteImageData = "Couldn't write image data"
    }
    func saveToDirectory(directory: NSURL) throws {
        let timeStamp = "\(NSDate().timeIntervalSince1970)"
        let fullDirectory = directory
            .URLByAppendingPathComponent(timeStamp)
        try NSFileManager.defaultManager().createDirectoryAtURL(
            fullDirectory,
            withIntermediateDirectories: true,
            attributes: nil
        )
        let fileName = "\(self.label).jpg"
        let filePath = fullDirectory
            .URLByAppendingPathComponent(fileName)
        if let data = UIImageJPEGRepresentation(self.image, 1) {
            if !data.writeToURL(filePath, atomically: true) {
                throw Error.CouldntWriteImageData
            }
        }
        else {
            throw Error.CouldntGetImageData
        }
    }
}

更新 saveNewPhotoWithImage:labeled: 方法以使用 saveToDirectory: 方法:

func saveNewPhotoWithImage(
    image: UIImage,
    labeled label: String
    ) throws -> NSIndexPath
{
    let photo = Photo(image: image, label: label)
    try photo.saveToDirectory(self.getSaveDirectory())
    self.photos.append(photo)
    return NSIndexPath(
        forItem: self.photos.count - 1,
        inSection: 0
    )
}

在视图控制器中添加错误处理方法:

func displayErrorWithTitle(title: String?, message: String) {
    let alert = UIAlertController(
        title: title,
        message: message,
        preferredStyle: .Alert
    )
    alert.addAction(UIAlertAction(
        title: "OK",
        style: .Default,
        handler: nil
    ))
    self.presentViewController(
        alert,
        animated: true,
        completion: nil
    )
}

func displayError(error: ErrorType, withTitle: String) {
    switch error {
    case let error as NSError:
        self.displayErrorWithTitle(
            title,
            message: error.localizedDescription
        )
    case let error as Photo.Error:
        self.displayErrorWithTitle(
            title,
            message: error.rawValue
        )
    default:
        self.displayErrorWithTitle(
            title,
            message: "Unknown Error"
        )
    }
}

func createSaveActionWithTextField(
    textField: UITextField,
    andImage image: UIImage
    ) -> UIAlertAction
{
    return UIAlertAction(
        title: "Save",
        style: .Default
        ) { action in
        do {
            let indexPath = try self.photoStore
               .saveNewPhotoWithImage(
                image,
                labeled: textField.text ?? ""
                )
            self.collectionView.insertItemsAtIndexPaths( 
                [indexPath]
            )
        }
        catch let error {
            self.displayError(
                error,
                withTitle: "Error Saving Photo"
            )
        }
    }
}
4. 从磁盘加载照片

为了在应用启动时显示保存的照片,需要从磁盘加载照片。在 Photo 结构体中添加从文件路径初始化的方法:

init(image: UIImage, label: String) {
    self.image = image
    self.label = label
}

init?(filePath: NSURL) {
    if let image = UIImage(
        contentsOfFile: filePath.relativePath!
        )
    {
        let label = filePath.URLByDeletingPathExtension?
            .lastPathComponent ?? ""
        self.init(image: image, label: label)
    }
    else {
        return nil
    }
}

PhotoStore 类中添加加载照片的方法:

func loadPhotos() throws {
    self.photos.removeAll(keepCapacity: true)

    let fileManager = NSFileManager.defaultManager()
    let saveDirectory = try self.getSaveDirectory()
    let enumerator = fileManager.enumeratorAtPath(
        saveDirectory.relativePath!
    )
    while let file = enumerator?.nextObject() as? String {
        let fileType = enumerator!.fileAttributes![NSFileType]
            as! String
        if fileType == NSFileTypeRegular {
            let fullPath = saveDirectory
                .URLByAppendingPathComponent(file)
            if let photo = Photo(filePath: fullPath) {
                self.photos.append(photo)
            }
        }
    }
}

最后在视图控制器的 viewWillAppear: 方法中调用加载照片的方法:

override func viewWillAppear(animated: Bool) {
    super.viewWillAppear(animated)
    do {
        try self.photoStore.loadPhotos()
        self.collectionView.reloadData()
    }
    catch let error {
        self.displayError(
            error,
            withTitle: "Error Loading Photos"
        )
    }
}

总结

通过以上步骤,实现了照片管理应用的核心功能,包括填充照片网格、遵循 MVC 模式重构代码以及永久保存和加载照片。这些操作不仅提高了代码的可维护性和可扩展性,还增强了应用的用户体验。

下面是整个流程的 mermaid 流程图:

graph TD
    A[开始] --> B[填充照片网格]
    B --> C[遵循 MVC 模式重构]
    C --> D[永久保存照片]
    D --> E[从磁盘加载照片]
    E --> F[结束]

同时,下面的表格总结了各个步骤的关键方法和类:
| 步骤 | 关键方法/类 | 作用 |
| ---- | ---- | ---- |
| 填充照片网格 | collectionView:numberOfItemsInSection: collectionView:cellForItemAtIndexPath: PhotoCollectionViewCell | 显示照片列表 |
| 遵循 MVC 模式重构 | PhotoStore createCellForPhoto | 分离业务逻辑 |
| 永久保存照片 | getSaveDirectory saveToDirectory saveNewPhotoWithImage:labeled: | 保存照片到文件系统 |
| 从磁盘加载照片 | Photo(filePath:) loadPhotos | 从文件系统加载照片 |

开发应用:照片管理应用的实现与优化

5. 代码优化与注意事项

在实现照片管理应用的过程中,有一些代码优化和注意事项需要关注。

5.1 内存管理

集合视图为了处理大量单元格,会自动重用已滚动出屏幕的单元格以节省内存。在 collectionView:cellForItemAtIndexPath: 方法中,每次调用时都要重置单元格的配置,避免旧配置残留。例如:

extension ViewController: UICollectionViewDataSource {
    func collectionView(
        collectionView: UICollectionView,
        cellForItemAtIndexPath indexPath: NSIndexPath
        ) -> UICollectionViewCell
    {
        let cell = collectionView
           .dequeueReusableCellWithReuseIdentifier(
            "DefaultCell",
            forIndexPath: indexPath
            ) as! PhotoCollectionViewCell

        // 重置单元格配置
        cell.imageView.image = nil
        cell.label.text = nil

        let photo = self.photos[indexPath.item]
        cell.imageView.image = photo.image
        cell.label.text = photo.label

        return cell
    }
}
5.2 错误处理

在保存和加载照片的过程中,可能会出现各种错误,如无法获取图像数据、无法写入图像数据等。在代码中已经实现了错误处理,但可以进一步优化错误信息的显示,例如添加更多的错误类型和详细的错误描述。

5.3 性能优化

在调用 insertItemsAtIndexPaths 方法时,要确保在 collectionView:numberOfItemsInSection: 返回插入后的更新数量之后调用,否则可能会出现显示异常。另外,频繁调用 reloadData 会导致性能下降,优先使用 insertItemsAtIndexPaths 等更细粒度的方法。

6. 未来功能扩展

基于现有的照片管理应用,还可以扩展以下功能:

6.1 照片编辑

可以添加照片编辑功能,如裁剪、旋转、添加滤镜等。可以使用 UIImagePickerController 的编辑功能,或者集成第三方库来实现更复杂的编辑效果。

6.2 照片分类

允许用户对照片进行分类,例如按日期、地点、主题等分类。可以在 Photo 结构体中添加分类属性,并在集合视图中实现分类显示。

6.3 分享功能

添加分享照片的功能,让用户可以将照片分享到社交媒体、短信、邮件等。可以使用 UIActivityViewController 来实现分享功能。

7. 总结与回顾

通过一系列的操作,我们完成了照片管理应用的开发,包括填充照片网格、遵循 MVC 模式重构代码、永久保存和加载照片等核心功能。在开发过程中,我们学习了如何使用集合视图、如何处理数据源协议、如何进行文件操作以及如何处理错误等重要知识。

以下是开发过程中的关键步骤总结表格:
| 阶段 | 操作内容 | 关键代码 |
| ---- | ---- | ---- |
| 填充照片网格 | 配置数据源、自定义单元格、插入新单元格 | collectionView:numberOfItemsInSection: collectionView:cellForItemAtIndexPath: insertItemsAtIndexPaths |
| MVC 重构 | 创建 PhotoStore 类、分离业务逻辑 | PhotoStore cellForPhoto 回调 |
| 永久保存 | 获取保存目录、保存照片到文件系统 | getSaveDirectory saveToDirectory |
| 加载照片 | 从文件系统加载照片 | Photo(filePath:) loadPhotos |

下面是未来功能扩展的 mermaid 流程图:

graph TD
    A[现有应用] --> B[照片编辑]
    A --> C[照片分类]
    A --> D[分享功能]
    B --> E[裁剪]
    B --> F[旋转]
    B --> G[添加滤镜]
    C --> H[按日期分类]
    C --> I[按地点分类]
    C --> J[按主题分类]
    D --> K[分享到社交媒体]
    D --> L[分享到短信]
    D --> M[分享到邮件]

通过持续的优化和功能扩展,我们可以让照片管理应用更加完善,为用户提供更好的体验。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值