照片库与相机开发指南
1. 照片库与相关框架概述
用户通过照片应用访问的存储照片和视频构成了设备的照片库。在开发应用时,可借助
UIImagePickerController
类为用户提供探索照片库的界面。同时,Photos 框架(也称为 PhotoKit)能让开发者以编程方式访问照片库及其内容,甚至可以修改照片图像,使用时需导入
Photos
。
UIImagePickerController
不仅能用于浏览照片库,还能为用户提供类似相机应用的界面,让用户拍摄照片和视频,拍摄的图像可像相机应用那样存储到照片库中。更深层次上,AV Foundation 框架可直接控制相机硬件,使用时需导入
AVFoundation
。而像
kUTTypeImage
这类常量由 Mobile Core Services 框架提供,也需要导入
MobileCoreServices
。
2. 使用 UIImagePickerController 浏览照片库
UIImagePickerController
是一个视图控制器,能为用户提供从照片库中选择项目的界面,类似于照片应用。通常应将其作为呈现的视图控制器来处理,在 iPad 上,文档建议使用弹出框形式,但全屏展示可能更实用。
2.1 呈现图像选择控制器
若要让用户从照片库中选择项目,需实例化
UIImagePickerController
并为其
sourceType
属性赋值,可选值如下:
-
.photoLibrary
:向用户展示所有相册列表,用户可进入任意相册。
-
.savedPhotosAlbum
:理论上用户只能访问相机胶卷相册内容,但自 iOS 8 起,用户会看到 Moments 界面并展示库中所有项目,这可能是个 bug。
在呈现选择器前,应先调用类方法
isSourceTypeAvailable(_:)
,若返回
false
,则不要以该源类型呈现选择器。同时,可能需要指定感兴趣的媒体类型数组,该数组通常包含
kUTTypeImage
、
kUTTypeMovie
或两者,也可通过调用类方法
availableMediaTypes(for:)
指定所有可用类型。
若满足以下两个条件,
UIImagePickerController
可将实时照片作为实时照片返回:
- 选择器的
mediaTypes
同时包含
kUTTypeLivePhoto
和
kUTTypeImage
,且
availableMediaTypes(for:)
的结果中不包含
kUTTypeLivePhoto
,需手动添加。
- 选择器的
allowsEditing
属性为
false
(默认值)。
videoExportPreset
属性可设置用户选择视频时使用的转码格式,预设名称列在
AVAssetExportSession
类文档页面末尾。还可将选择器的
allowsEditing
属性设置为
true
,对于图像,界面允许用户缩放和移动图像以按预设矩形裁剪;对于视频,用户可像使用
UIVideoEditorController
一样修剪视频。
配置好选择器并提供委托(采用
UIImagePickerControllerDelegate
和
UINavigationControllerDelegate
)后,可按以下代码呈现选择器:
let src = UIImagePickerController.SourceType.photoLibrary
guard UIImagePickerController.isSourceTypeAvailable(src)
else {return}
guard let arr = UIImagePickerController.availableMediaTypes(for:src)
else {return}
let picker = UIImagePickerController()
picker.sourceType = src
picker.mediaTypes = arr
picker.delegate = self
picker.videoExportPreset = AVAssetExportPreset640x480 // 示例
self.present(picker, animated: true)
2.2 图像选择控制器委托
当用户使用完图像选择控制器后,委托会收到以下消息之一:
-
imagePickerController(_:didFinishPickingMediaWithInfo:)
:用户从照片库中选择了项目,
info
参数描述该项目。
-
imagePickerControllerDidCancel(_:)
:用户点击了取消。
若未实现
UIImagePickerControllerDelegate
方法,视图控制器会在应调用该方法时自动关闭,但建议实现两个委托方法并自行关闭视图控制器。
第一个委托方法中的
info
参数是一个包含所选项目信息的字典,其键(
UIImagePickerController.InfoKey
)取决于媒体类型:
| 媒体类型 | 键及说明 |
| ---- | ---- |
| 图像 |
.mediaType
值为
kUTTypeImage
,其他键有
.phAsset
(表示照片库中图像的
PHAsset
)、
.originalImage
(
UIImage
)、
.imageURL
(保存到临时目录的图像数据副本的文件 URL)。若
allowsEditing
为
true
,还可能有
.cropRect
(包装
CGRect
的
NSValue
)、
.editedImage
(应使用的图像)。 |
| 实时照片 |
.mediaType
值为
kUTTypeLivePhoto
,除图像相关键外,还有
.livePhoto
(
PHLivePhoto
)。 |
| 视频 |
.mediaType
值为
kUTTypeMovie
,其他键有
.phAsset
(表示照片库中视频的
PHAsset
)、
.mediaURL
(保存到临时目录的视频数据副本的文件 URL)。 |
以下是第一个委托方法的实现示例:
func imagePickerController(_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo
info: [UIImagePickerController.InfoKey : Any]) {
let asset = info[.phAsset] as? PHAsset
let url = info[.mediaURL] as? URL
var im = info[.originalImage] as? UIImage
if let ed = info[.editedImage] as? UIImage {
im = ed
}
let live = info[.livePhoto] as? PHLivePhoto
let imurl = info[.imageURL] as? URL
self.dismiss(animated:true) {
// 在此处理所选项目
}
}
呈现图像选择控制器无需用户授权访问照片库,但要在委托方法中获取完整信息则需要用户授权,否则只能获取图像和媒体 URL。
3. 处理图像选择控制器的结果
前面的代码收集了用户从照片库中选择项目的信息,但未对其进行处理,这是
dismiss
完成函数的任务。
呈现
UIImagePickerController
的常见原因是在界面中显示用户所选项目,此时需针对用户可能选择的不同类型进行不同处理。在 iOS 11 之前,
info
字典的
.mediaType
能充分区分可能的类型(
kUTTypeImage
、
kUTTypeLivePhoto
或
kUTTypeMovie
),但 iOS 11 引入了两种新的可能图像类型,因此
.mediaType
不够精确,应使用
.phAsset
键返回的
PHAsset
并检查其
playbackStyle
。
PHAsset.PlaybackStyle
有五种可能值:
-
.image
:收到的是
UIImage
,适合在
UIImageView
中显示,图像可能很大,为节省内存,应将其缩小到界面实际显示所需的最大尺寸和分辨率。
-
.imageAnimated
:收到的是动画 GIF,iOS 本身没有直接显示动画 GIF 的能力,可将收到的
UIImage
作为静态图像显示,或使用图像 URL 加载 GIF 数据并自行转换为图像序列进行动画显示。
-
.livePhoto
:收到的是
PHLivePhoto
,要在界面中显示,需使用
PHLivePhotoView
(由 Photos UI 框架提供,需导入
PhotosUI
),只需设置其
frame
和
contentMode
即可。
-
.video
:收到的是临时目录中导出视频的文件 URL,适合使用
AVPlayer
及其他 AV Foundation 和 AVKit 类进行显示。
-
.videoLooping
:收到的是应用了 Loop 或 Bounce 效果的实时照片,以视频文件 URL 形式提供,可使用
AVPlayerLooper
对象实现循环播放。
静态图像的元数据可通过
.imageURL
存储的图像数据,使用 Image I/O 框架提取为字典:
let src = CGImageSourceCreateWithURL(imurl! as CFURL, nil)!
let d = CGImageSourceCopyPropertiesAtIndex(src,0,nil) as! [AnyHashable:Any]
4. Photos 框架
Photos 框架(PhotoKit)可让开发者探索照片库内容,还能操作相册、添加照片甚至编辑用户照片。照片库由
PHPhotoLibrary
类及其共享实例表示,可通过
shared
方法获取,无需保留共享实例。
照片库中的实体由以下类表示:
-
PHAsset
:单个照片或视频文件。
-
PHCollection
:表示各种集合的抽象类,其具体子类有:
-
PHAssetCollection
:照片集合,如相册和时刻。
-
PHCollectionList
:资产集合的集合,如相册文件夹、按年分组的时刻集合。
更细致的类型区分通过类型和子类型属性实现:
-
PHAsset
有
mediaType
和
mediaSubtypes
属性。
-
PHAssetCollection
有
assetCollectionType
和
assetCollectionSubtype
属性。
-
PHCollectionList
有
collectionListType
和
collectionListSubtype
属性。
所有照片实体类都是
PHObject
的子类,
PHObject
赋予它们
localIdentifier
属性,作为持久唯一标识符。
访问照片库需要用户授权,可使用
PHPhotoLibrary
类,通过调用类方法
authorizationStatus
了解当前授权状态,若状态为
.notDetermined
,可调用类方法
requestAuthorization(_:)
请求授权。
Info.plist
中必须包含系统授权请求警报可用于解释应用为何需要访问的文本,对于照片库,相关键为 “Privacy — Photo Library Usage Description”(
NSPhotoLibraryUsageDescription
)。
5. 查询照片库
若想了解照片库中的内容,可从表示所需实体类型的照片实体类开始,调用以
fetch
开头的类方法,根据起始标准选择合适的方法。例如:
- 要获取一个或多个
PHAsset
,可调用
PHAsset
的获取方法,可按本地标识符、媒体类型或包含的资产集合进行获取。
- 可按标识符、类型和子类型或是否包含给定
PHAsset
来获取
PHAssetCollection
。
- 可按标识符或是否包含给定
PHAssetCollection
来获取
PHCollectionList
。
除各种获取方法的参数外,还可提供
PHFetchOptions
对象进一步细化结果,可设置其
predicate
限制请求结果,
sortDescriptors
确定结果顺序,
fetchLimit
限制返回结果数量,
includeAssetSourceTypes
指定结果来源,如排除云端项目。
获取方法返回的不是图像或视频,而是信息,返回的是
PHObjects
列表,以
PHFetchResult
形式表示,其行为类似数组,但在 Swift 中不能直接使用
for...in
枚举。
以下是一些查询示例:
// 查询按年分组的时刻集合
let opts = PHFetchOptions()
let desc = NSSortDescriptor(key: "startDate", ascending: true)
opts.sortDescriptors = [desc]
let result = PHCollectionList.fetchCollectionLists(
with: .momentList, subtype: .momentListYear, options: opts)
let lists = result.objects(at: IndexSet(0..<result.count))
for list in lists {
let f = DateFormatter()
f.dateFormat = "yyyy"
print(f.string(from:list.startDate!))
}
// 查询某个时刻集合中的时刻簇
let result = PHAssetCollection.fetchMoments(inMomentList:list, options: nil)
let colls = result.objects(at: IndexSet(0..<result.count))
for (ix,coll) in colls.enumerated() {
if ix == 0 {
print("======= \(result.count) clusters")
}
f.dateFormat = ("yyyy-MM-dd")
let count = coll.estimatedAssetCount
print("starting \(f.string(from:coll.startDate!)):", count)
}
// 查询用户创建的所有相册
let result = PHAssetCollection.fetchAssetCollections(
with: .album, subtype: .albumRegular, options: nil)
let albums = result.objects(at: IndexSet(0..<result.count))
for album in albums {
let count = album.estimatedAssetCount
print("\(album.localizedTitle!):",
"approximately \(count) photos")
}
// 查询某个相册的内容
let result = PHAsset.fetchAssets(in:album, options: nil)
let assets = result.objects(at: IndexSet(0..<result.count))
for asset in assets {
print(asset.localIdentifier)
}
// 查询用户最近智能相册中的十张普通照片(无视频和 HDR 照片)
let recentAlbums = PHAssetCollection.fetchAssetCollections(
with: .smartAlbum, subtype: .smartAlbumRecentlyAdded, options: nil)
guard let rec = recentAlbums.firstObject else {return}
let options = PHFetchOptions()
let pred = NSPredicate(
format: "mediaType == %d && !((mediaSubtype & %d) == %d)",
PHAssetMediaType.image.rawValue,
PHAssetMediaSubtype.photoHDR.rawValue,
PHAssetMediaSubtype.photoHDR.rawValue)
options.predicate = pred // 仅照片,无 HDR
options.fetchLimit = 10 // 限制数量
let photos = PHAsset.fetchAssets(in:rec, options: options)
6. 修改照片库
对照片库的结构修改通过与要修改的照片实体类对应的更改请求类进行,更改请求类的名称是照片实体类名后加 “ChangeRequest”。
要使用更改请求,需在共享照片库上调用
performChanges
方法,通常是
performChanges(_:completionHandler:)
,该方法接受两个函数:第一个函数描述要执行的更改,第二个函数是更改执行完成后回调的完成函数。
以下是一些更改请求类的方法示例:
-
PHAssetChangeRequest
:类方法包括
deleteAssets(_:)
、
creationRequestForAssetFromImage(atFileURL:)
等,默认情况下,创建
PHAsset
会立即将其放入用户的相机胶卷相册。若从原始数据创建资产,可使用
PHAssetCreationRequest
类,它是
PHAssetChangeRequest
的子类,提供了如
addResource(with:data:options:)
等实例方法。
-
PHAssetCollectionChangeRequest
:类方法包括
deleteAssetCollections(_:)
和
creationRequestForAssetCollection(withTitle:)
,还有初始化方法
init(for:)
以及实例方法
addAssets(_:)
、
removeAssets(_:)
等。
以下是创建名为 “Test Album” 相册的示例:
// 简单创建相册
PHPhotoLibrary.shared().performChanges({
let t = "TestAlbum"
typealias Req = PHAssetCollectionChangeRequest
Req.creationRequestForAssetCollection(withTitle:t)
})
// 带完成处理和占位符的创建相册
var ph : PHObjectPlaceholder?
PHPhotoLibrary.shared().performChanges({
let t = "TestAlbum"
typealias Req = PHAssetCollectionChangeRequest
let cr = Req.creationRequestForAssetCollection(withTitle:t)
ph = cr.placeholderForCreatedAssetCollection
}) { ok, err in
if ok, let ph = ph {
self.newAlbumId = ph.localIdentifier
}
}
综上所述,通过
UIImagePickerController
和 Photos 框架,开发者可以方便地实现照片库的浏览、查询和修改等功能,为用户提供丰富的照片处理体验。在实际开发中,需根据具体需求合理运用这些技术,并注意用户授权等相关问题。
照片库与相机开发指南
7. 开发流程总结与流程图
为了更清晰地展示整个照片库与相机开发的流程,下面给出一个 mermaid 格式的流程图:
graph LR
classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px
classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px
A([开始]):::startend --> B{是否需要用户授权?}:::decision
B -->|是| C(请求用户授权):::process
B -->|否| D(实例化 UIImagePickerController):::process
C --> D
D --> E{设置 sourceType}:::decision
E -->|.photoLibrary| F(展示相册列表):::process
E -->|.savedPhotosAlbum| G(展示 Moments 界面):::process
F --> H(设置 mediaTypes):::process
G --> H
H --> I{是否允许编辑?}:::decision
I -->|是| J(设置 allowsEditing 为 true):::process
I -->|否| K(设置 allowsEditing 为 false):::process
J --> L(设置 videoExportPreset):::process
K --> L
L --> M(设置委托):::process
M --> N(呈现选择器):::process
N --> O{用户操作}:::decision
O -->|选择项目| P(调用 imagePickerController(_:didFinishPickingMediaWithInfo:)):::process
O -->|取消| Q(调用 imagePickerControllerDidCancel(_:)):::process
P --> R{处理所选项目类型}:::decision
R -->|.image| S(处理普通图像):::process
R -->|.imageAnimated| T(处理动画 GIF):::process
R -->|.livePhoto| U(处理实时照片):::process
R -->|.video| V(处理视频):::process
R -->|.videoLooping| W(处理循环视频):::process
S --> X(可能进行图像缩放):::process
T --> Y(加载 GIF 数据):::process
U --> Z(使用 PHLivePhotoView 显示):::process
V --> AA(使用 AVPlayer 显示):::process
W --> AB(使用 AVPlayerLooper 实现循环):::process
X --> AC([结束]):::startend
Y --> AC
Z --> AC
AA --> AC
AB --> AC
Q --> AC
这个流程图涵盖了从开始开发到处理用户选择项目的整个过程,包括用户授权、选择器的设置、用户操作以及不同类型项目的处理。
8. 开发中的注意事项
在使用上述技术进行开发时,有一些重要的注意事项需要牢记:
-
用户授权
:虽然呈现
UIImagePickerController无需用户授权访问照片库,但要在委托方法中获取完整信息则需要用户授权。在Info.plist中必须包含 “Privacy — Photo Library Usage Description” 键,为用户提供清晰的授权说明。例如:
<key>NSPhotoLibraryUsageDescription</key>
<string>本应用需要访问您的照片库以让您选择照片用于展示。</string>
-
内存管理
:获取的图像可能非常大,为了避免内存问题,对于普通图像,应将其缩小到界面实际显示所需的最大尺寸和分辨率。例如,在处理
UIImage时,可以使用以下代码进行缩放:
func resizeImage(image: UIImage, targetSize: CGSize) -> UIImage {
let size = image.size
let widthRatio = targetSize.width / size.width
let heightRatio = targetSize.height / size.height
let newSize: CGSize
if widthRatio > heightRatio {
newSize = CGSize(width: size.width * heightRatio, height: size.height * heightRatio)
} else {
newSize = CGSize(width: size.width * widthRatio, height: size.height * widthRatio)
}
let rect = CGRect(origin: .zero, size: newSize)
UIGraphicsBeginImageContextWithOptions(newSize, false, 1.0)
image.draw(in: rect)
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage!
}
-
动画 GIF 处理
:iOS 本身没有直接显示动画 GIF 的能力,需要自行加载 GIF 数据并转换为图像序列进行动画显示。可以使用第三方库如
SDWebImage来简化这个过程:
import SDWebImage
let imageView = UIImageView()
let url = URL(string: "your_gif_url")
imageView.sd_setImage(with: url, completed: nil)
-
异步操作
:修改照片库的操作是异步的,如创建相册时,需要使用完成函数来处理后续操作,并使用
PHObjectPlaceholder来引用创建的对象。
9. 实际应用场景举例
以下是几个实际应用中可能会用到上述技术的场景:
-
社交应用
:在社交应用中,用户可能需要上传照片或视频到自己的动态中。可以使用
UIImagePickerController让用户从照片库中选择照片或拍摄新的照片、视频,然后将其上传到服务器。例如:
func uploadSelectedMedia(info: [UIImagePickerController.InfoKey : Any]) {
if let image = info[.originalImage] as? UIImage {
// 上传图像到服务器
// 这里可以使用网络库如 Alamofire 进行上传
} else if let url = info[.mediaURL] as? URL {
// 上传视频到服务器
}
}
- 相册管理应用 :相册管理应用需要对照片库进行查询和修改操作。可以使用 Photos 框架来查询用户的相册、照片和视频,还可以创建新的相册、删除照片等。例如,创建一个新的相册并将用户选择的照片添加到该相册中:
func createAlbumAndAddPhoto(photoAsset: PHAsset) {
var placeholder: PHObjectPlaceholder?
PHPhotoLibrary.shared().performChanges({
let albumRequest = PHAssetCollectionChangeRequest.creationRequestForAssetCollection(withTitle: "New Album")
placeholder = albumRequest.placeholderForCreatedAssetCollection
if let albumPlaceholder = placeholder {
let albumChangeRequest = PHAssetCollectionChangeRequest(for: albumPlaceholder)
albumChangeRequest?.addAssets([photoAsset] as NSFastEnumeration)
}
}) { (success, error) in
if success {
print("相册创建并添加照片成功")
} else {
print("操作失败: \(error?.localizedDescription ?? "")")
}
}
}
10. 总结与展望
通过对照片库与相机开发相关技术的学习,我们了解了如何使用
UIImagePickerController
让用户选择照片和视频,以及如何使用 Photos 框架对照片库进行查询和修改。这些技术为开发者提供了强大的功能,能够开发出丰富多样的应用。
在未来的开发中,随着移动设备相机功能的不断增强和用户对照片处理需求的增加,可能会有更多的新特性和功能被引入。例如,对高分辨率图像和 8K 视频的更好支持,以及更智能的照片编辑和分类功能。开发者需要不断关注这些变化,及时更新自己的知识和技能,以开发出更优秀的应用。
同时,用户隐私和数据安全也是非常重要的问题。在开发过程中,要始终遵循相关的法律法规,确保用户的照片和视频数据得到妥善的保护。
总之,照片库与相机开发是一个充满挑战和机遇的领域,希望开发者们能够充分利用这些技术,为用户带来更好的体验。
超级会员免费看
1451

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



