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 {
-
添加
imageView出口:
@IBOutlet weak var imageView : UIImageView?
-
添加
attachmentFile和document属性以遵循AttachmentViewer协议:
var attachmentFile : FileWrapper?
var document : Document?
-
实现
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
-
在
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
}
}
}
-
添加扩展使
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)
}
}
- 运行应用,在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)
}
-
进入
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 实现删除功能
-
再次在
DocumentViewController.swift中添加AttachmentCellDelegate协议(前面已添加,为了完整性再次提及):
protocol AttachmentCellDelegate {
func attachmentCellWasDeleted(_ cell: AttachmentCell)
}
-
在
AttachmentCell类中添加delegate属性:
var delegate : AttachmentCellDelegate?
-
打开
Main.storyboard,在助理编辑器中打开DocumentViewController.swift。 -
按住“Control”键,从删除按钮拖动到
AttachmentCell类中的delete方法。 -
打开
Document.swift,在Document类中添加deleteAttachment方法以移除附件:
func deleteAttachment(_ attachment:FileWrapper) throws {
guard attachmentsDirectoryWrapper != nil else {
throw err(.cannotAccessAttachments)
}
attachmentsDirectoryWrapper?.removeFileWrapper(attachment)
self.updateChangeCount(.done)
}
-
回到
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应用中图像与附件的添加、查看和删除功能的开发,为用户提供了更完善的操作体验。
超级会员免费看
10

被折叠的 条评论
为什么被折叠?



