85、照片库与相机开发指南

照片库与相机开发指南

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 视频的更好支持,以及更智能的照片编辑和分类功能。开发者需要不断关注这些变化,及时更新自己的知识和技能,以开发出更优秀的应用。

同时,用户隐私和数据安全也是非常重要的问题。在开发过程中,要始终遵循相关的法律法规,确保用户的照片和视频数据得到妥善的保护。

总之,照片库与相机开发是一个充满挑战和机遇的领域,希望开发者们能够充分利用这些技术,为用户带来更好的体验。

本 PPT 介绍了制药厂房中供配电系统的总体概念设计要点,内容包括: 洁净厂房的特点及其对供配电系统的特殊要求; 供配电设计的一般原则依据的国家/行业标准; 从上级电网到工厂变电所、终端配电的总体结构模块化设计思路; 供配电范围:动力配电、照明、通讯、接地、防雷消防等; 动力配电中电压等级、接地系统形式(如 TN-S)、负荷等级可靠性、UPS 配置等; 照明的电源方式、光源选择、安装方式、应急备用照明要求; 通讯系统、监控系统在生产管理消防中的作用; 接地等电位连接、防雷等级防雷措施; 消防设施及其专用供电(消防泵、排烟风机、消防控制室、应急照明等); 常见高压柜、动力柜、照明箱等配电设备案例及部分设计图纸示意; 公司已完成的典型项目案例。 1. 工程背景总体框架 所属领域:制药厂房工程的公用工程系统,其中本 PPT 聚焦于供配电系统。 放在整个公用工程中的位置:给排水、纯化水/注射用水、气体热力、暖通空调、自动化控制等系统并列。 2. Part 01 供配电概述 2.1 洁净厂房的特点 空间密闭,结构复杂、走向曲折; 单相设备、仪器种类多,工艺设备昂贵、精密; 装修材料工艺材料种类多,对尘埃、静电等更敏感。 这些特点决定了:供配电系统要安全可靠、减少积尘、便于清洁和维护。 2.2 供配电总则 供配电设计应满足: 可靠、经济、适用; 保障人身财产安全; 便于安装维护; 采用技术先进的设备方案。 2.3 设计依据规范 引用了大量俄语标准(ГОСТ、СНиП、SanPiN 等)以及国家、行业和地方规范,作为设计的法规基础文件,包括: 电气设备、接线、接地、电气安全; 建筑物电气装置、照明标准; 卫生安全相关规范等。 3. Part 02 供配电总览 从电源系统整体结构进行总览: 上级:地方电网; 工厂变电所(10kV 配电装置、变压
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值