22、iOS应用开发:图像与附件处理全解析

iOS应用开发:图像与附件处理全解析

1. 用户隐私与相机访问权限设置

在开发应用时,用户隐私至关重要。以访问相机为例,iOS平台对此有严格要求。若要让应用获得相机访问权限,需在 info.plist 文件中添加特定的键值对。具体步骤如下:
1. 打开 info.plist 文件。
2. 在编辑器中,右键点击其他键值对下方,选择“Add Row”。
3. 输入“Privacy - Camera Usage Description”,Xcode会提供自动补全功能,建议使用以确保键名正确。
4. 该值的类型应自动设置为“String”,若未自动设置,需手动将其改为“String”。
5. 在“Value”列中,添加当用户首次尝试访问相机时要显示给用户的消息。例如,对于我们的应用,可以输入“We’ll use the camera to take photos and videos, and add them as attachments.”,不同应用需根据自身需求选择合适的消息。
6. 运行应用,此时就可以向文档中添加图像,这些图像会显示在附件列表中。

2. 附件查看功能实现

2.1 创建附件查看协议

由于存在多种不同类型的附件,为避免重复编写显示视图控制器的代码,我们创建一个 AttachmentViewer 协议。具体操作如下:
1. 打开 DocumentViewController.swift 文件。
2. 添加 AttachmentViewer 协议:

protocol AttachmentViewer : NSObjectProtocol {
    // The attachment to view. If this is nil,
    // the viewer should instead attempt to create a new
    // attachment, if applicable.
    var attachmentFile : FileWrapper? { get set }
    // The document attached to this file
    var document : Document? { get set }
}

遵循该协议的类需要有两个属性: attachmentFile (可选的 FileWrapper )和 document (可选的 Document )。这样,所有遵循该协议的视图控制器都可以以相同的方式处理, DocumentViewController 无需关心每个附件视图控制器的具体类型。

2.2 实现图像显示视图控制器

接下来,我们实现用于显示图像的视图控制器,步骤如下:
1. 打开“File”菜单,选择“New→File”。
2. 选择“Cocoa Touch Class”,点击“Next”。
3. 将新类命名为 ImageAttachmentViewController ,并使其成为 UIViewController 的子类。
4. 打开 ImageAttachmentViewController.swift 文件,让该类遵循 AttachmentViewer 协议:

class ImageAttachmentViewController: UIViewController, AttachmentViewer {
  1. 添加 imageView 出口:
@IBOutlet weak var imageView : UIImageView?
  1. 添加 attachmentFile document 属性以遵循 AttachmentViewer 协议:
var attachmentFile : FileWrapper?
var document : Document?
  1. 实现 viewDidLoad 方法以从数据中加载图像:
override func viewDidLoad() {
    super.viewDidLoad()
    // If we have data, and can make an image out of it...
    if let data = attachmentFile?.regularFileContents,
        let image = UIImage(data: data) {
        // Set the image
        self.imageView?.image = image
    }
}

2.3 设置图像显示视图控制器的界面

设置界面的步骤如下:
1. 打开 Main.storyboard 文件,添加一个新的 UIViewController
2. 选择新的视图控制器,转到“Identity Inspector”。
3. 将其类改为 ImageAttachmentViewController
4. 选择视图,转到“Attributes Inspector”。
5. 将背景颜色改为黑色。
6. 拖入一个 UIImageView ,并添加约束使其填满屏幕。
7. 转到“Attributes Inspector”,将其“Content Mode”设置为“Aspect Fit”。
8. 按住“Control”键,从大纲中的图像附件视图控制器拖动到图像视图,从出现的列表中选择 imageView 出口。

2.4 创建连接图像附件视图控制器和文档视图控制器的segue

创建segue的步骤如下:
1. 在大纲中,按住“Control”键,从文档视图控制器拖动到图像附件视图控制器,提示时选择创建“present as popover”segue。
2. 选择新的segue,转到“Attributes Inspector”。
3. 将新segue的标识符设置为“ShowImageAttachment”。
4. 从“Attributes Inspector”中“Anchor”槽的井号拖动到文档视图控制器的视图,“Anchor”是弹出框将附着的视图。

2.5 触发segue以显示附件

为了在点击附件时触发相应的segue,我们需要检测附件的类型并确定使用哪个segue。具体步骤如下:
1. 打开 DocumentViewController.swift 文件,在文件顶部的其他导入语句旁边添加以下代码:

import MobileCoreServices
  1. didSelectItemAt indexPath 方法中添加代码以检测附件类型并触发segue:
func collectionView(_ collectionView: UICollectionView,
    didSelectItemAt indexPath: IndexPath) {
    // Do nothing if we are editing
    if self.isEditingAttachments {
        return
    }
    // Get the cell that the user interacted with; bail if we can't get it
    guard let selectedCell = collectionView
        .cellForItem(at: indexPath) else {
        return
    }
    // Work out how many cells we have
    let totalNumberOfCells = collectionView
        .numberOfItems(inSection: indexPath.section)
    // If we have selected the last cell, show the Add screen
    if indexPath.row == totalNumberOfCells - 1 {
        addAttachment(selectedCell)
    }
    else {
        // Otherwise, show a different view controller based on the type
        // of the attachment
        guard let attachment = self.document?
           .attachedFiles?[(indexPath as IndexPath).row] else {
            NSLog("No attachment for this cell!")
            return
        }
        let segueName : String?
        if attachment.conformsToType(kUTTypeImage) {
            segueName = "ShowImageAttachment"
        } else {
            segueName = nil
        }
        // If we have a segue, run it now
        if let theSegue = segueName {
            self.performSegue(withIdentifier: theSegue,
                sender: selectedCell)
        }
    }
}

2.6 传递附件给视图控制器

DocumentViewController 类中添加 prepare(for segue:, sender:) 方法,将附件传递给视图控制器:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    // If we're going to an AttachmentViewer...
    if let attachmentViewer
        = segue.destination as? AttachmentViewer {
        // Give the attachment viewer our document
        attachmentViewer.document = self.document!
        // If we were coming from a cell, get the attachment
        // that this cell represents so that we can view it
        if let cell = sender as? UICollectionViewCell,
            let indexPath =
                self.attachmentsCollectionView?.indexPath(for: cell),
            let attachment = self.document?.attachedFiles?[indexPath.row] {
            attachmentViewer.attachmentFile = attachment
        } else {
            // we don't have an attachment
        }
        // Don't close the document when showing the view controller
        self.shouldCloseOnDisappear = false
        // If this has a popover, present it from the the attachments list
        if let popover =
            segue.destination.popoverPresentationController {
            popover.sourceView = self.attachmentsCollectionView
            popover.sourceRect = self.attachmentsCollectionView.bounds
        }
    }
    else if segue.identifier == "ShowLocationSegue" {
        if let destination =
            segue.destination as? LocationAttachmentViewController {
            destination.locationAttachment = self.document?.locationWrapper
        }
    }
}

2.7 添加关闭按钮(针对iPhone)

在iPhone上,弹出框没有关闭按钮,我们需要添加该功能。具体步骤如下:
1. 在 prepare(for segue:, sender:) 方法中,设置视图控制器使弹出框控制器成为其委托:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    // If we're going to an AttachmentViewer...
    if let attachmentViewer
        = segue.destination as? AttachmentViewer {
        // Give the attachment viewer our document
        attachmentViewer.document = self.document!
        // If we were coming from a cell, get the attachment
        // that this cell represents so that we can view it
        if let cell = sender as? UICollectionViewCell,
            let indexPath =
                self.attachmentsCollectionView?.indexPath(for: cell),
            let attachment = self.document?.attachedFiles?[indexPath.row] {
            attachmentViewer.attachmentFile = attachment
        } else {
            // we don't have an attachment
        }
        // If this has a popover, present it from the the attachments list
        if let popover =
            segue.destination.popoverPresentationController {
            // Ensure that we add a close button to the popover on iPhone
            popover.delegate = self
            popover.sourceView = self.attachmentsCollectionView
            popover.sourceRect = self.attachmentsCollectionView.bounds
        }
    }
    else if segue.identifier == "ShowLocationSegue" {
        if let destination =
            segue.destination as? LocationAttachmentViewController {
            destination.locationAttachment = self.document?.locationWrapper
        }
    }
}
  1. 添加扩展使 DocumentViewController 遵循 UIPopoverPresentationControllerDelegate 协议:
extension DocumentViewController : UIPopoverPresentationControllerDelegate {
    // called by the system to determine which view controller
    // should be the content of the popover
    func presentationController(_ controller: UIPresentationController,
        viewControllerForAdaptivePresentationStyle
        style: UIModalPresentationStyle) -> UIViewController? {
        // Get the view controller that we want to present
        let presentedViewController = controller.presentedViewController
        // If we're showing a popover, and that popover is being shown
        // as a full-screen modal (which happens on iPhone)...
        if style == UIModalPresentationStyle.fullScreen && controller
            is UIPopoverPresentationController {
            // Create a navigation controller that contains the content
            let navigationController = UINavigationController(
                rootViewController: controller.presentedViewController)
            // Create and set up a "Done" button, and add it to the
            // navigation controller.
            // It will call the 'dismissModalView' button, below
            let closeButton = UIBarButtonItem(title: "Done",
                style: UIBarButtonItemStyle.done, target: self,
                action: #selector(DocumentViewController.dismissModalView))
            presentedViewController.navigationItem
               .rightBarButtonItem = closeButton
            // Tell the system that the content should be this new
            // navigation controller.
            return navigationController
        } else {
            // Just return the content
            return presentedViewController
        }
    }
    func dismissModalView() {
        self.dismiss(animated: true, completion: nil)
    }
}
  1. 运行应用,在iPhone上就会有关闭按钮。

以下是图像显示视图控制器创建和设置的流程图:

graph TD;
    A[打开File菜单选择New→File] --> B[选择Cocoa Touch Class并点击Next];
    B --> C[命名为ImageAttachmentViewController并成为UIViewController子类];
    C --> D[打开ImageAttachmentViewController.swift并遵循AttachmentViewer协议];
    D --> E[添加imageView出口];
    E --> F[添加attachmentFile和document属性];
    F --> G[实现viewDidLoad方法];
    G --> H[打开Main.storyboard添加新UIViewController];
    H --> I[设置类为ImageAttachmentViewController];
    I --> J[设置背景颜色为黑色];
    J --> K[拖入UIImageView并添加约束];
    K --> L[设置Content Mode为Aspect Fit];
    L --> M[建立imageView出口连接];

2.8 不同类型附件处理总结

操作步骤 详细内容
创建协议 打开 DocumentViewController.swift ,添加 AttachmentViewer 协议
实现图像视图控制器 新建类、遵循协议、添加属性和方法
设置界面 Main.storyboard 中进行界面设置
触发segue 根据附件类型确定segue并触发
传递附件 prepare 方法中传递附件
添加关闭按钮 针对iPhone添加关闭按钮功能

3. 删除附件功能实现

3.1 认识手势识别器

在应用开发中,手势识别器能帮助我们检测用户的各种手势操作。除了之前提到的点击手势识别器,还有以下几种常见的手势识别器:
| 手势识别器类型 | 功能描述 |
| ---- | ---- |
| 平移识别器(Pan recognizers) | 检测手指在屏幕上的拖动操作 |
| 捏合识别器(Pinch recognizers) | 检测两个手指在屏幕上的捏合或张开操作 |
| 旋转识别器(Rotation recognizers) | 检测两个手指在屏幕上围绕中心点的旋转操作 |
| 滑动识别器(Swipe recognizers) | 检测手指的滑动动作 |
| 屏幕边缘滑动识别器(Screen - edge swipe recognizers) | 检测从屏幕边缘开始的滑动动作 |

为了实现删除附件的功能,我们将使用长按手势识别器。

3.2 添加委托协议

我们需要添加一个委托协议,让单元格能够通知其委托它已被删除。具体步骤如下:
1. 在 DocumentViewController.swift 中添加 AttachmentCellDelegate 协议:

protocol AttachmentCellDelegate {
    func attachmentCellWasDeleted(_ cell: AttachmentCell)
}
  1. 进入 DocumentViewController.swift 中的 AttachmentCell 类,添加以下代码:
class AttachmentCell : UICollectionViewCell {
    @IBOutlet weak var imageView : UIImageView?
    @IBOutlet weak var extensionLabel : UILabel?
    @IBOutlet weak var deleteButton : UIButton?

    var editMode = false {
        didSet {
            // Full alpha if we're editing, zero if we're not
            deleteButton?.alpha = editMode ? 1 : 0
        }
    }

    var delegate : AttachmentCellDelegate?

    @IBAction func delete() {
        self.delegate?.attachmentCellWasDeleted(self)
    }
}

3.3 跟踪编辑状态

DocumentViewController 中添加 isEditingAttachments 属性,用于跟踪每个单元格的删除按钮是否应该显示:

fileprivate var isEditingAttachments = false

cellForItemAt 方法中,根据视图控制器的编辑模式设置 AttachmentCell editMode 属性:

func collectionView(_ collectionView: UICollectionView,
    cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    // Work out how many cells we need to display
    let totalNumberOfCells =
        collectionView.numberOfItems(inSection: indexPath.section)
    // Figure out if we're being asked to configure the Add cell,
    // or any other cell. If we're the last cell, it's the Add cell.
    let isAddCell = indexPath.row == (totalNumberOfCells - 1)
    // The place to store the cell. By making it 'let', we're ensuring
    // that we never accidentally fail to give it a value - the
    // compiler will call us out.
    let cell : UICollectionViewCell
    // Create and return the 'Add' cell if we need to
    if isAddCell {
        cell = collectionView.dequeueReusableCell(
            withReuseIdentifier: "AddAttachmentCell", for: indexPath)
    } else {
        // This is a regular attachment cell
        // Get the cell
        let attachmentCell = collectionView
           .dequeueReusableCell(withReuseIdentifier: "AttachmentCell",
                for: indexPath) as! AttachmentCell
        // Get a thumbnail image for the attachment
        let attachment = self.document?.attachedFiles?[indexPath.row]
        var image = attachment?.thumbnailImage()
        // Give it to the cell
        if image == nil {
            // We don't know what it is, so use a generic image
            image = UIImage(named: "File")
            // Also set the label
            attachmentCell.extensionLabel?.text =
                attachment?.fileExtension?.uppercased()
        } else {
            // We know what it is, so ensure that the label is empty
            attachmentCell.extensionLabel?.text = nil
        }
        attachmentCell.imageView?.image = image
        // The cell should be in edit mode if the view controller is
        attachmentCell.editMode = isEditingAttachments

        // Use this cell
        cell = attachmentCell
    }
    return cell
}

3.4 进入和退出编辑模式

添加 beginEditMode 方法,使所有可见单元格进入编辑模式,并在导航栏添加“Done”按钮:

func beginEditMode() {
    self.isEditingAttachments = true
    UIView.animate(withDuration: 0.1, animations: { () -> Void in
        for cell in self.attachmentsCollectionView!.visibleCells {
            if let attachmentCell = cell as? AttachmentCell {
                attachmentCell.editMode = true
            } else  {
                cell.alpha = 0
            }
        }
    })
    let doneButton = UIBarButtonItem(barButtonSystemItem:
        UIBarButtonSystemItem.done, target: self,
                    action: #selector(DocumentViewController.endEditMode))
    self.navigationItem.rightBarButtonItem = doneButton
}

添加 endEditMode 方法,使所有可见的 AttachmentCell 退出编辑模式,确保添加单元格可见,并移除“Done”按钮:

func endEditMode() {
    self.isEditingAttachments = false
    UIView.animate(withDuration: 0.1, animations: { () -> Void in
        for cell in self.attachmentsCollectionView!.visibleCells {
            if let attachmentCell = cell as? AttachmentCell {
                attachmentCell.editMode = false
            } else {
                cell.alpha = 1
            }
        }
    })
    self.navigationItem.rightBarButtonItem = nil
}

didSelectItemAt 方法的开头添加代码,确保在编辑模式下不尝试查看附件:

// Do nothing if we are editing
if self.isEditingAttachments {
    return
}

3.5 添加删除按钮

在界面中添加删除按钮的步骤如下:
1. 打开 Main.storyboard ,在文档视图控制器中找到 AttachmentCell
2. 拖动一个 UIButton AttachmentCell 中。
3. 转到“Attributes Inspector”,将按钮类型改为“Custom”。
4. 移除按钮的标签,将图像设置为“Delete”。
5. 将按钮定位到单元格的右上角,并添加约束将顶部和右侧边缘固定到容器。
6. 打开 DocumentViewController.swift ,找到 AttachmentCell 类,从 deleteButton 出口旁边的井号拖动到刚刚添加的按钮。
7. 按住“Control”键,从 deleteButton 出口拖动到这个按钮。
8. 在 DocumentViewController collectionView(_, cellForItemAt indexPath:) 方法中添加长按手势识别器以进入删除模式:

// The cell should be in edit mode if the view controller is
attachmentCell.editMode = isEditingAttachments
// Add a long-press gesture to it, if it doesn't
// already have it
let longPressGesture = UILongPressGestureRecognizer(target: self,
    action: #selector(DocumentViewController.beginEditMode))
attachmentCell.gestureRecognizers = [longPressGesture]

3.6 实现删除功能

  1. 再次在 DocumentViewController.swift 中添加 AttachmentCellDelegate 协议(前面已添加,为了完整性再次提及):
protocol AttachmentCellDelegate {
    func attachmentCellWasDeleted(_ cell: AttachmentCell)
}
  1. AttachmentCell 类中添加 delegate 属性:
var delegate : AttachmentCellDelegate?
  1. 打开 Main.storyboard ,在助理编辑器中打开 DocumentViewController.swift
  2. 按住“Control”键,从删除按钮拖动到 AttachmentCell 类中的 delete 方法。
  3. 打开 Document.swift ,在 Document 类中添加 deleteAttachment 方法以移除附件:
func deleteAttachment(_ attachment:FileWrapper) throws {
    guard attachmentsDirectoryWrapper != nil else {
        throw err(.cannotAccessAttachments)
    }
    attachmentsDirectoryWrapper?.removeFileWrapper(attachment)
    self.updateChangeCount(.done)
}
  1. 回到 DocumentViewController.swift ,添加扩展使 DocumentViewController 遵循 AttachmentCellDelegate 协议:
extension DocumentViewController : AttachmentCellDelegate {
    func attachmentCellWasDeleted(_ cell: AttachmentCell) {
        guard let indexPath = self.attachmentsCollectionView?
           .indexPath(for: cell) else {
            return
        }
        guard let attachment = self.document?
           .attachedFiles?[indexPath.row] else {
            return
        }
        do {
            try self.document?.deleteAttachment(attachment)
        } catch {
            // Handle error
        }
    }
}

以下是删除附件功能实现的流程图:

graph TD;
    A[添加AttachmentCellDelegate协议] --> B[在AttachmentCell类添加属性和方法];
    B --> C[添加isEditingAttachments属性];
    C --> D[在cellForItemAt方法设置editMode];
    D --> E[实现beginEditMode方法];
    E --> F[实现endEditMode方法];
    F --> G[在didSelectItemAt方法添加编辑模式判断];
    G --> H[在Main.storyboard添加删除按钮];
    H --> I[在collectionView方法添加长按手势识别器];
    I --> J[实现deleteAttachment方法];
    J --> K[添加扩展使DocumentViewController遵循协议];

3.7 删除附件功能总结

操作步骤 详细内容
添加委托协议 DocumentViewController.swift 中添加 AttachmentCellDelegate 协议
跟踪编辑状态 添加 isEditingAttachments 属性并在相关方法中设置
进入和退出编辑模式 实现 beginEditMode endEditMode 方法
添加删除按钮 Main.storyboard 中添加并设置删除按钮,添加长按手势识别器
实现删除功能 Document 类中添加删除方法, DocumentViewController 遵循委托协议

通过以上步骤,我们完成了iOS应用中图像与附件的添加、查看和删除功能的开发,为用户提供了更完善的操作体验。

Delphi 12.3 作为一款面向 Windows 平台的集成开发环境,由 Embarcadero Technologies 负责其持续演进。该环境以 Object Pascal 语言为核心,并依托 Visual Component Library(VCL)框架,广泛应用于各类桌面软件、数据库系统及企业级解决方案的开发。在此生态中,Excel4Delphi 作为一个重要的社区开源项目,致力于搭建 Delphi Microsoft Excel 之间的高效桥梁,使开发者能够在自研程序中直接调用 Excel 的文档处理、工作表管理、单元格操作及宏执行等功能。 该项目以库文件组件包的形式提供,开发者将其集成至 Delphi 工程后,即可通过封装良好的接口实现对 Excel 的编程控制。具体功能涵盖创建编辑工作簿、格式化单元格、批量导入导出数据,乃至执行内置公式宏指令等高级操作。这一机制显著降低了在财务分析、报表自动生成、数据整理等场景中实现 Excel 功能集成的技术门槛,使开发者无需深入掌握 COM 编程或 Excel 底层 API 即可完成复杂任务。 使用 Excel4Delphi 需具备基础的 Delphi 编程知识,并对 Excel 对象模型有一定理解。实践中需注意不同 Excel 版本间的兼容性,并严格遵循项目文档进行环境配置依赖部署。此外,操作过程中应遵循文件访问的最佳实践,例如确保目标文件未被独占锁定,并实施完整的异常处理机制,以防数据损毁或程序意外中断。 该项目的持续维护依赖于 Delphi 开发者社区的集体贡献,通过定期更新以适配新版开发环境 Office 套件,并修复已发现的问题。对于需要深度融合 Excel 功能的 Delphi 应用而言,Excel4Delphi 提供了经过充分测试的可靠代码基础,使开发团队能更专注于业务逻辑用户体验的优化,从而提升整体开发效率软件质量。 资源来源于网络分享,仅用于学习交流使用,请勿用于商业,如有侵权请联系我删除!
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值