22、开发应用程序:从界面配置到拍照功能实现

开发应用程序:从界面配置到拍照功能实现

1. 项目文件概述

在开发应用程序时,有几个关键文件需要了解:
- Main.storyboard :包含视图的界面设计,当前只有一个由 ViewController 管理的视图,后续将在此添加和配置可视化组件。
- Assets.xcassets :用于存放应用中要显示的所有图像,几乎每个应用都会用到至少一张图片,所以该文件很重要。
- LaunchScreen.storyboard :用于管理应用启动时的显示界面,这是生产应用中非常重要的部分,因为它是用户每次启动应用时看到的第一个界面,设计良好的启动过程会带来很大的不同。不过,为了学习目的,暂时无需对该文件进行操作。

2. 配置用户界面

2.1 引入导航控制器

为了给应用添加顶部导航栏,建议使用导航控制器(Navigation Controller),而非直接添加导航栏,因为导航控制器能处理很多复杂情况,并且便于未来扩展。操作步骤如下:
1. 在右侧元素库中找到导航控制器,将其拖到左侧列出视图控制器场景(View Controller Scene)的面板中,此时列表会新增两个视图控制器。
2. 删除新添加的根视图控制器(Root View Controller),点击带有黄色图标的根视图控制器,然后按下删除键。
3. 使视图控制器场景成为根视图控制器,右键点击带有黄色图标的导航控制器,将其拖动到下方带有黄色图标的视图控制器上,视图控制器会高亮显示为蓝色。
4. 松开鼠标右键,在弹出的菜单中点击“Root View Controller”。
5. 让导航控制器成为应用中第一个显示的视图控制器,选择带有黄色图标的导航控制器,从主菜单中选择“View | Utilities | Show Attributes Inspector”,然后向下滚动并勾选“Is Initial View Controller”复选框。

2.2 自定义主视图

  1. 聚焦主视图,从左侧面板中选择视图控制器,双击标题并将其改为“Gallery”。
  2. 在导航栏添加“Take a Picture”按钮,在元素库中找到栏按钮项(bar button items),将其拖到工具栏右侧(靠近可放置位置时会变蓝)。默认按钮显示为“Item”,将其改为添加按钮,可选择将文本改为加号,更好的方法是添加按钮后,在主视图左侧的层次结构中找到该按钮项,选择它后在屏幕右侧配置选项,将“System Item”改为“Add”。同样的操作可用于导航栏左侧的“Edit”标识符按钮。
  3. 添加照片画廊,从元素库中拖动一个集合视图(Collection View)到视图中心。集合视图由可变数量的单元格以网格形式排列而成,每个单元格是模板单元格的副本,可在代码中配置以显示特定数据。拖动集合视图时会自动创建一个模板单元格,后续再进行配置。

2.3 定义集合视图的尺寸规则

为了让界面能适应不同屏幕尺寸,需要使用自动布局(Auto Layout)工具定义集合视图的尺寸规则,步骤如下:
1. 点击集合视图,选择屏幕右下角的“Pin”图标。
2. 配置窗口使其与之前的截图匹配,点击四个支柱使其高亮显示为红色,取消勾选“Constrain to margins”,并将所有测量值改为零。
3. 点击“Add 4 Constraints”,此时会出现黄色线条,表明视图的位置与刚创建的规则不一致。可以手动调整视图大小使其匹配,也可以让 Xcode 自动处理,点击屏幕左侧“Gallery Scene”旁边的黄色图标,在弹出的列表中点击黄色三角形,然后点击“Fix Misplacement”。
4. 将集合视图的背景颜色改为白色,选择集合视图,在属性检查器中将“Background”改为白色。

2.4 配置集合视图单元格

集合视图左上角的盒子就是单元格,需要更改其大小并添加图像和标签:
1. 更改单元格大小,若未选中集合视图则先选中它,从主菜单中选择“View | Utilities | Show Size Inspector”,将“Cell Size”改为宽 110 点、高 150 点。
2. 拖动图像视图(Image View)到单元格中,在尺寸检查器中将高度和宽度改为 110,x 和 y 坐标改为 0。
3. 在图像视图下方拖动一个标签(Label),配置标签在单元格内的放置规则:
- 选择图像视图,再次点击“Pin”图标,将其固定在左侧、顶部和右侧,不约束边距,三个测量值都设为零,点击“Add 3 Constraints”。
- 选择标签,将其固定在各个方向,不约束边距,所有测量值为零,勾选“Height”复选框将高度约束为 30 点,点击“Add 5 Constraints”,然后让 Xcode 从左侧菜单再次调整其大小。同时,在属性检查器中选择居中对齐,并将字体大小减小到 12。

2.5 运行应用

完成上述配置后,无需编写任何代码即可运行应用查看效果:
1. 从顶部菜单栏选择要运行应用的模拟器。
2. 点击运行按钮(黑色三角形图标),会打开一个新的模拟器窗口运行应用。
3. 可以从“Hardware”菜单旋转虚拟设备,查看旋转时的效果,也可以尝试在不同的模拟器上运行,因为之前已经配置视图以适应任何屏幕尺寸。

3. 实现拍照功能

3.1 连接按钮动作

为了让用户能够拍照,需要编写代码,使每次用户点击添加按钮时执行相应操作。操作步骤如下:
1. 显示助理编辑器(Assistant Editor),从主菜单选择“View | Assistant Editor | Show Assistant Editor”,并点击编辑器顶部的栏确保其配置为自动模式,该模式会使第二个视图根据左侧所选内容自动切换到最合适的文件,这里会显示视图控制器的代码。
2. 创建按钮与新方法的连接,右键点击添加按钮,将其拖动到 didReceiveMemoryWarning 方法下方。
3. 松开鼠标右键,在弹出的小窗口中,从“Connection”菜单选择“Action”,输入 didTapTakePhotoButton ,点击“Connect”,Xcode 会为你创建一个新方法并将其连接到按钮,方法左侧会出现一个实心灰色圆圈表示已连接。该方法开头有 @IBAction ,这是连接到界面元素的方法所必需的。

3.2 呈现拍照界面

要让上述方法为用户呈现拍照界面,可以使用苹果提供的 UIImagePickerController 类,代码如下:

@IBAction func didTapTakePhotoButton(sender: AnyObject) {
    let imagePicker = UIImagePickerController()
    if UIImagePickerController.isSourceTypeAvailable(.Camera) {
        imagePicker.sourceType = .Camera
    }
    self.presentViewController(
        imagePicker,
        animated: true,
        completion: nil
    )
}

代码解释:
- 第一行创建了一个图像选择器实例。
- 第二行使用 UIImagePickerController isSourceTypeAvailable: 类方法检查当前设备是否有相机,如果相机可用,则在第三行将其设置为图像选择器的源类型;否则,默认情况下图像选择器会让用户从相册中选择图片。由于模拟器不支持拍照,在模拟应用时会显示图像选择器而非相机。
- 最后一行让视图控制器以动画形式在屏幕上呈现图像选择器。 presentViewController:animated:completion: UIViewController 类( ViewController 的超类)实现的方法,方便我们呈现新的视图控制器。

运行应用并点击添加按钮,会提示请求访问照片的权限,然后显示照片选择器。点击右上角的“Cancel”按钮可关闭图像选择器控制器,但选择照片后目前不会有任何反应。

3.3 处理照片选择

为了处理照片选择,需要让图像选择器有一个委托(delegate),当选择图像时委托会收到方法调用。将视图控制器设为图像选择器的委托并实现其协议,操作如下:
1. 在上述动作方法中,在调用呈现图像选择器之前添加一行代码:

imagePicker.delegate = self

添加这行代码后会出现编译错误,提示视图控制器未实现必要的协议。
2. 实现协议,建议在同一文件中以单独的扩展形式实现每个协议,以实现更好的代码分离。根据错误提示,需要实现 UIImagePickerControllerDelegate UINavigationControllerDelegate 协议,关键代码如下:

extension ViewController: UINavigationControllerDelegate {}
extension ViewController: UIImagePickerControllerDelegate {
    func imagePickerController(
        picker: UIImagePickerController,
        didFinishPickingImage image: UIImage!,
        editingInfo: [NSObject : AnyObject]!
        )
    {
        self.dismissViewControllerAnimated(true, completion: nil)
    }
}

目前 UINavigationControllerDelegate 的实现为空,而 imagePickerController:picker:didFinishPickingImage:editingInfo: 方法只是简单地关闭当前呈现的视图控制器,让用户返回上一个屏幕。运行应用并选择照片后,会返回到上一个屏幕,但照片不会有其他处理,后续需要编写更多代码来保存照片并在集合视图中显示。

4. 临时保存照片

4.1 引入照片数组

为了临时将照片存储在内存中,可以在视图控制器中添加一个图像数组属性:

class ViewController: UIViewController {
    var photos = [UIImage]()
    // ...
}

在图像选择器委托方法中,当回调被调用时,将新图像添加到 photos 属性中:

func imagePickerController(
    picker: UIImagePickerController,
    didFinishPickingImage image: UIImage!,
    editingInfo: [NSObject : AnyObject]!
    )
{
    self.photos.append(image)
    self.dismissViewControllerAnimated(true, completion: nil)
}

4.2 引入照片结构体

为了给每张照片添加标签,创建一个名为 Photo 的新结构体,包含图像和标签属性。操作步骤如下:
1. 在“LearningCamera”文件夹中创建三个组:“Model”、“View”和“Controller”,右键点击“LearningCamera”文件夹,选择“New Group”。
2. 将 ViewController.swift 文件移动到“Controller”组中。
3. 在“Model”组中创建一个新的 Photo.swift 文件,右键点击“Model”组,选择“New File…”,选择普通的 Swift 文件即可。在 Photo.swift 文件中定义 Photo 结构体:

import UIKit
struct Photo {
    let image: UIImage
    let label: String
}
  1. 返回 ViewController.swift 文件,将 photos 属性更新为 Photo 类型的数组:
var images = [Photo]()

4.3 提示用户输入标签

现在需要提示用户为图像输入标签,使用 UIAlertController 类来显示标准警报。由于 UIKit 不允许从同一个视图控制器同时呈现多个视图控制器,所以需要先关闭照片选择器,等待完成后再显示警报,代码如下:

self.dismissViewControllerAnimated(true) {
    // Ask User for Label
    let alertController = UIAlertController(
        title: "Photo Label",
        message: "How would you like to label your photo?",
        preferredStyle: .Alert
    )
    alertController.addTextFieldWithConfigurationHandler()
    {
        textField in
        let saveAction = UIAlertAction(
            title: "Save",
            style: .Default
            ) { action in
            let label = textField.text ?? ""
            let photo = Photo(image: image, label: label)
            self.photos.append(photo)
        }
        alertController.addAction(saveAction)
    }
    self.presentViewController(
        alertController,
        animated: true,
        completion: nil
     )
}

代码解释:
- 使用 dismissViewControllerAnimated:completion: 方法的尾随闭包语法,当视图控制器完成动画消失后调用该闭包。
- 创建一个带有标题、消息和警报样式的警报控制器。
- 在显示警报控制器之前,添加一个文本字段和保存操作。使用 addTextFieldWithConfigurationHandler: 方法的尾随闭包来配置文本字段。
- 创建一个保存操作,当用户选择该操作时,从文本字段获取文本,与图像一起创建一个新的 Photo 实例,并将其添加到 photos 数组中。
- 将保存操作添加到警报控制器,然后显示警报控制器。

运行应用后,选择照片后会提示输入标签,但目前保存的照片不会显示,后续需要实现显示保存照片的功能。

下面是配置用户界面和实现拍照功能的流程图:

graph LR
    A[开始] --> B[配置用户界面]
    B --> B1[引入导航控制器]
    B --> B2[自定义主视图]
    B --> B3[定义集合视图尺寸规则]
    B --> B4[配置集合视图单元格]
    B --> B5[运行应用]
    B5 --> C[实现拍照功能]
    C --> C1[连接按钮动作]
    C1 --> C2[呈现拍照界面]
    C2 --> C3[处理照片选择]
    C3 --> D[临时保存照片]
    D --> D1[引入照片数组]
    D1 --> D2[引入照片结构体]
    D2 --> D3[提示用户输入标签]
    D3 --> E[结束]

综上所述,通过以上步骤,我们完成了应用程序从界面配置到拍照功能实现以及临时保存照片的过程。后续将继续实现显示保存照片的功能,让应用更加完善。

5. 显示保存的照片

5.1 配置集合视图数据源

要在集合视图中显示保存的照片,需要为集合视图设置数据源。在 ViewController 中实现 UICollectionViewDataSource 协议。首先,在 ViewController 类声明处添加协议:

class ViewController: UIViewController, UICollectionViewDataSource {
    var images = [Photo]()
    // ...
}

然后,实现协议的两个必需方法:

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return images.count
}

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "PhotoCell", for: indexPath) as! PhotoCollectionViewCell
    let photo = images[indexPath.item]
    cell.imageView.image = photo.image
    cell.label.text = photo.label
    return cell
}
  • numberOfItemsInSection 方法返回集合视图中项目的数量,这里返回 images 数组的元素个数。
  • cellForItemAt 方法为每个单元格配置数据,从 images 数组中获取对应索引的 Photo 对象,将图像和标签设置到单元格的 imageView label 上。

5.2 创建自定义单元格类

为了更好地管理集合视图单元格,创建一个自定义的单元格类 PhotoCollectionViewCell 。在“View”组中创建一个新的 PhotoCollectionViewCell.swift 文件,代码如下:

import UIKit

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

Main.storyboard 中,选择集合视图单元格,将其类设置为 PhotoCollectionViewCell ,并将 imageView label 与代码中的属性进行关联。

5.3 注册单元格

ViewController viewDidLoad 方法中注册单元格:

override func viewDidLoad() {
    super.viewDidLoad()
    let nib = UINib(nibName: "PhotoCollectionViewCell", bundle: nil)
    collectionView.register(nib, forCellWithReuseIdentifier: "PhotoCell")
    collectionView.dataSource = self
}

这里使用 UINib 注册自定义单元格,并将集合视图的数据源设置为 self

5.4 刷新集合视图

imagePickerController 方法中,当添加新照片后,需要刷新集合视图以显示新照片:

func imagePickerController(
    picker: UIImagePickerController,
    didFinishPickingImage image: UIImage!,
    editingInfo: [NSObject : AnyObject]!
    )
{
    self.dismissViewControllerAnimated(true) {
        // Ask User for Label
        let alertController = UIAlertController(
            title: "Photo Label",
            message: "How would you like to label your photo?",
            preferredStyle: .Alert
        )
        alertController.addTextFieldWithConfigurationHandler()
        {
            textField in
            let saveAction = UIAlertAction(
                title: "Save",
                style: .Default
                ) { action in
                let label = textField.text ?? ""
                let photo = Photo(image: image, label: label)
                self.images.append(photo)
                self.collectionView.reloadData()
            }
            alertController.addAction(saveAction)
        }
        self.presentViewController(
            alertController,
            animated: true,
            completion: nil
         )
    }
}

在保存照片后,调用 collectionView.reloadData() 方法刷新集合视图。

6. 优化用户体验

6.1 错误处理

在拍照和选择照片过程中,可能会出现各种错误,例如相机不可用、权限被拒绝等。在 didTapTakePhotoButton 方法中添加错误处理:

@IBAction func didTapTakePhotoButton(sender: AnyObject) {
    let imagePicker = UIImagePickerController()
    if UIImagePickerController.isSourceTypeAvailable(.Camera) {
        imagePicker.sourceType = .Camera
    } else {
        let alertController = UIAlertController(
            title: "Camera Unavailable",
            message: "The camera on this device is not available.",
            preferredStyle: .Alert
        )
        let okAction = UIAlertAction(title: "OK", style: .Default, handler: nil)
        alertController.addAction(okAction)
        self.presentViewController(alertController, animated: true, completion: nil)
        return
    }
    imagePicker.delegate = self
    self.presentViewController(
        imagePicker,
        animated: true,
        completion: nil
    )
}

当相机不可用时,显示一个警报提示用户。

6.2 动画效果

为了提升用户体验,可以为集合视图的单元格添加动画效果。在 cellForItemAt 方法中添加动画:

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "PhotoCell", for: indexPath) as! PhotoCollectionViewCell
    let photo = images[indexPath.item]
    cell.imageView.image = photo.image
    cell.label.text = photo.label

    cell.alpha = 0
    UIView.animate(withDuration: 0.5, delay: 0.1 * Double(indexPath.item), options: .curveEaseInOut, animations: {
        cell.alpha = 1
    }, completion: nil)

    return cell
}

这里使用 UIView.animate 方法为单元格添加淡入动画,每个单元格的动画有一定的延迟,形成逐个显示的效果。

6.3 性能优化

如果照片数量较多,可能会影响应用的性能。可以使用分页加载或懒加载的方式优化性能。例如,只在用户滚动到特定位置时加载更多照片。

7. 总结与展望

7.1 总结

通过以上步骤,我们完成了一个简单的拍照应用的开发,包括界面配置、拍照功能实现、照片临时保存和显示等功能。主要步骤如下表所示:
| 步骤 | 操作 |
| ---- | ---- |
| 配置用户界面 | 引入导航控制器、自定义主视图、定义集合视图尺寸规则、配置集合视图单元格、运行应用 |
| 实现拍照功能 | 连接按钮动作、呈现拍照界面、处理照片选择 |
| 临时保存照片 | 引入照片数组、引入照片结构体、提示用户输入标签 |
| 显示保存的照片 | 配置集合视图数据源、创建自定义单元格类、注册单元格、刷新集合视图 |
| 优化用户体验 | 错误处理、动画效果、性能优化 |

7.2 展望

未来可以进一步扩展该应用的功能,例如:
- 实现照片编辑功能,如裁剪、滤镜等。
- 支持照片分享到社交媒体。
- 实现照片的永久存储,如保存到相册或云存储。

以下是显示保存照片和优化用户体验的流程图:

graph LR
    A[显示保存的照片] --> A1[配置集合视图数据源]
    A1 --> A2[创建自定义单元格类]
    A2 --> A3[注册单元格]
    A3 --> A4[刷新集合视图]
    A4 --> B[优化用户体验]
    B --> B1[错误处理]
    B --> B2[动画效果]
    B --> B3[性能优化]
    B3 --> C[结束]

通过不断地改进和扩展,这个应用可以变得更加完善和实用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值