Swift从相册选择图片,图文混排并且可以保存、上传数据

来源:互联网 发布:windows dvd 播放器 编辑:程序博客网 时间:2024/05/13 20:07

博主最近突发奇想想做一个自己的日记本App,在过程中遇到了一些坑,摸索了很久才做出一个简单的日记本功能。

先来看看一下效果吧:
这里写图片描述

先来说说这次用到的一些东西吧:
1、UIImagePickerController:用来获取Photos里面的照片
2、UITexeView:用来做图文混排
3、NSKeyedArchiver和NSKeyedUnarchiver:保存和读取数据
具体的一些细节在过程中讲解。

从照片选择图片

首先,我们要实现从用户的Photos(照片)这个应用中读取用户的照片。最简单的方法就是用UIImagePickerController,要使用它,需要类遵循UIImagePickerControllerDelegate和UINavigationControllerDelegate这两个协议。
在做图片选择功能时,博主将其封装为三个函数(其中两个是协议中的函数)
第一个函数如下:

/// 选择图片    @objc func pickImage() {        self.imagePickerController = UIImagePickerController()        //设置代理        self.imagePickerController.delegate = self        //允许用户对选择的图片或影片进行编辑        self.imagePickerController.allowsEditing = true        //设置image picker的用户界面        self.imagePickerController.sourceType = .photoLibrary        //设置图片选择控制器导航栏的背景颜色        self.imagePickerController.navigationBar.barTintColor = UIColor.orange        //设置图片选择控制器导航栏的标题颜色        self.imagePickerController.navigationBar.titleTextAttributes = [NSAttributedStringKey.foregroundColor: UIColor.white]        //设置图片选择控制器导航栏中按钮的文字颜色        self.imagePickerController.navigationBar.tintColor = UIColor.white        //显示图片选择控制器        self.present(self.imagePickerController, animated: true, completion: nil)    }

这个函数中的东西都很简单,需要说的就只有一个那就是设置imagePickerController的sourceType,它的类型是UIImagePickerControllerSourceType的枚举,其中三个变量分别为photoLibrary(照片库)、camera(相机)、savedPhotosAlbum(保存的相册)。设置sourceType就是设置图片的来源,常用的为前两个。
然后就是两个协议中的方法了,一个是你选择了对应的图片

/// 图片选择完成    ///    /// - Parameters:    ///   - picker: 图片选择控制器    ///   - info: 图片信息    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {//        print("media type: \(String(describing: info["UIImagePickerControllerMediaType"]))")//        print("crop rect: \(String(describing: info["UIImagePickerControllerCropRect"]))")//        print("reference url: \(String(describing: info["UIImagePickerControllerReferenceURL"]))")        //获取选择到的图片        let image = info["UIImagePickerControllerEditedImage"] as? UIImage        //将图片加入到textView中        addImage(image: image!)        //关闭当前界面        self.dismiss(animated: true, completion: nil)    }

首先用获取到的info属性来获取你选中的相片,info是一个[String:Any]类型的字典,字典中有一些系统定义的key,这儿简单列举一下:

didFinishPickingMediaWithInfo参数的键值对:
UIImagePickerControllerMediaType: 用户选择的媒体类型,如kUTTypeImage or kUTTypeMovie
UIImagePickerControllerOriginalImage: 用户选择的未经剪裁的原始图片素材
UIImagePickerControllerEditedImage: 用户选择的经过编辑之后的图片素材
UIImagePickerControllerCropRect: 用户对原始图片进行剪裁的区域
UIImagePickerControllerMediaURL: 用户选择的影片素材在文件系统中的位置
UIImagePickerControllerReferenceURL: 用户选择的原始图片在文件系统中的位置。即使用户对图片或影片进行了编辑,仍然会返回原始素材的位置
UIImagePickerControllerMediaMetadata: 新拍摄的图片的meta信息。只有当source type为相机时才会返回该信息
UIImagePickerControllerLivePhoto: 用户选择的活拍摄的live photo。仅支持iOS 9.1及之后的版本

我们这里使用的是编辑过后的照片,然后在调用dismiss方法关闭当前的界面。
另一个就是在选择照片的过程中取消选择,这个方法很简单,直接调用dismiss关闭界面就可以了。代码如下:

/// 取消选择图片    ///    /// - Parameter picker: 图片选择控制器    func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {        self.dismiss(animated: true, completion: nil)    }

到这里,就实现了一个简单的图片选择器。

UITextView图文混排

其次,就是将获取到的图片在UITextView中显示。

@objc func addImage(image: UIImage) {        //创建文本附件        let attachment = NSTextAttachment()        //设置附件的图片        attachment.image = image        //图片的缩放比例        let scale = (textView.frame.width - 2 * 5 ) / image.size.width        //设置附件的大小        attachment.bounds = CGRect(x: 0, y: 0, width: scale * image.size.width, height: scale * image.size.height)        //获取textView中的富文本        let attribute = NSMutableAttributedString(attributedString: textView.attributedText)        //在富文本中加入图片附件        attribute.append(NSAttributedString(attachment: attachment))        //设置textView中的富文本        textView.attributedText = attribute    }

这里要说一下的就是,你获取到的图片的大小往往比屏幕大很多,所以需要将图片缩放一下,当初这里百度了几十分钟才找到,原来只需要设置一下attachment的bounds就可以了,只能说百度太坑。

NSAttributedString(图文混排)保存与读取

最后就是将textView中的富文本保存和读取了,这个困惑了博主很久,因为第一时间想的是找教程,而不是看Swift的API,所以浪费了很多时间,在保存数据时耽误了很久,读取数据并显示时,耽误了更久。
先看看保存数据的代码吧:

@objc func nextPage() {        var url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first        url?.appendPathComponent("text.txt")        let attribute = textView.attributedText        let data = NSKeyedArchiver.archivedData(withRootObject: attribute as Any)        do {            try data.write(to: url!)        } catch {            print(error)        }        navigationController?.pushViewController(MyViewController(), animated: true)    }

我这里将其放在了跳转页面的过程中,因为只需要功能,所以没考虑效率、布局和MVC等。最开始博主以为可以将富文本转换成AnyObject来保存数据,可是读取出来,只有其中的文本内容,图片消失不见了,debug时才知道NSTextAttachment(附件)虽然在富文本当中,但它与文字是分开的,在直接转换成AnyObject时是不会保存图片的。然后百度了很久,看了很多大牛的操作,目前只需要做一个简单的图文混排保存,并不想太复杂,所以只能自己去看API,然后,恍然大悟,因为我看到了这个
这里写图片描述
NSAttributedString遵循了NSSecureCoding协议,这个协议是继承自NSCoding协议的。所以我们就可以直接用NSKeyedArchiver来编码数据并保存了(当时那个心情啊,直接放了几个小时三天三夜,室友都在旁边磨西瓜刀了)

NSKeyedArchiver怎么用?看这篇文章

本以为事情到这里就结束了,然而,编程就是这么有趣,又是几个小时的时间。算了,都是泪,先看代码:

private func getContent() {        var url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first        url?.appendPathComponent("text.txt")        if let data = try? Data(contentsOf: url!) {            let attribute = NSMutableAttributedString(attributedString: NSKeyedUnarchiver.unarchiveObject(with: data) as! NSAttributedString)            //枚举出富文本中所有的内容            attribute.enumerateAttributes(in: NSRange(location: 0, length: attribute.length), options: [], using: { (data, range, _) in                //找出富文本中的附件                if let attachment = data[.attachment] as? NSTextAttachment {                    //取出附件中的图片                    let image = (attachment.image)!                    //缩放                    let scale = (textView.frame.width - 2 * 5 ) / image.size.width                    //设置大小                    attachment.bounds = CGRect(x: 0, y: 0, width: image.size.width * scale, height: image.size.height * scale)                    //替换富文本中的附件                    attribute.replaceCharacters(in: range, with: NSAttributedString(attachment: attachment))                }            })            textView.attributedText = attribute        }    }

最开始本以为将数据解码,就能正常显示了,但是,图片,又变成原大小了,想想也是有原因的,在开始时我们设置的只是attachment(附件)的大小,而并没有修改图片的尺寸,所以在保存时,保存的当然就是原图,读取出来之后并没有设置其大小的代码,当然它就会以原图显示。所以,继续百度,其中有注释的内容就是百度了很久才百度到的。

原处是CSDN的小伙伴写的博文

最后附上所有代码:

AppDelegate.swift

window?.rootViewController = UINavigationController(rootViewController: ViewController())

修改App根视图为导航栏控制器

ViewController.swift

import UIKitclass ViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate {    var textView: UITextView!    var imagePickerController: UIImagePickerController!    override func viewDidLoad() {        super.viewDidLoad()        title = "write"        let next = UIBarButtonItem(barButtonSystemItem: .action, target: self, action: #selector(nextPage))        navigationItem.rightBarButtonItem = next        let add = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(pickImage))        navigationItem.leftBarButtonItem = add        textView = UITextView(frame: self.view.frame)        textView.font = UIFont.systemFont(ofSize: 36)        textView.becomeFirstResponder()        self.view.addSubview(textView)    }    @objc func addImage(image: UIImage) {        let attachment = NSTextAttachment()        attachment.image = image        let scale = (textView.frame.width - 2 * 5 ) / image.size.width        attachment.bounds = CGRect(x: 0, y: 0, width: scale * image.size.width, height: scale * image.size.height)        let attribute = NSMutableAttributedString(attributedString: textView.attributedText)        attribute.append(NSAttributedString(attachment: attachment))        textView.attributedText = attribute    }    @objc func nextPage() {        var url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first        url?.appendPathComponent("text.txt")        let attribute = textView.attributedText        let data = NSKeyedArchiver.archivedData(withRootObject: attribute as Any)        do {            try data.write(to: url!)        } catch {            print(error)        }        navigationController?.pushViewController(MyViewController(), animated: true)    }    /// 选择图片    @objc func pickImage() {        self.imagePickerController = UIImagePickerController()        //设置代理        self.imagePickerController.delegate = self        //允许用户对选择的图片或影片进行编辑        self.imagePickerController.allowsEditing = true        //设置image picker的用户界面        self.imagePickerController.sourceType = .photoLibrary        //设置图片选择控制器导航栏的背景颜色        self.imagePickerController.navigationBar.barTintColor = UIColor.orange        //设置图片选择控制器导航栏的标题颜色        self.imagePickerController.navigationBar.titleTextAttributes = [NSAttributedStringKey.foregroundColor: UIColor.white]        //设置图片选择控制器导航栏中按钮的文字颜色        self.imagePickerController.navigationBar.tintColor = UIColor.white        //显示图片选择控制器        self.present(self.imagePickerController, animated: true, completion: nil)    }    /// 图片选择完成    ///    /// - Parameters:    ///   - picker: 图片选择控制器    ///   - info: 图片信息    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {//        print("media type: \(String(describing: info["UIImagePickerControllerMediaType"]))")//        print("crop rect: \(String(describing: info["UIImagePickerControllerCropRect"]))")//        print("reference url: \(String(describing: info["UIImagePickerControllerReferenceURL"]))")        let image = info["UIImagePickerControllerEditedImage"] as? UIImage        addImage(image: image!)        self.dismiss(animated: true, completion: nil)    }    /// 取消选择图片    ///    /// - Parameter picker: 图片选择控制器    func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {        self.dismiss(animated: true, completion: nil)    }    override func didReceiveMemoryWarning() {        super.didReceiveMemoryWarning()        // Dispose of any resources that can be recreated.    }}

MyViewController.swift

import UIKitclass MyViewController: UIViewController {    var textView: UITextView!    override func viewDidLoad() {        super.viewDidLoad()        title = "read"        textView = UITextView(frame: self.view.frame)        textView.isEditable = false        self.view.addSubview(textView)        getContent()    }    private func getContent() {        var url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first        url?.appendPathComponent("text.txt")        if let data = try? Data(contentsOf: url!) {            let attribute = NSMutableAttributedString(attributedString: NSKeyedUnarchiver.unarchiveObject(with: data) as! NSAttributedString)            //枚举出富文本中所有的内容            attribute.enumerateAttributes(in: NSRange(location: 0, length: attribute.length), options: [], using: { (data, range, _) in                //找出富文本中的附件                if let attachment = data[.attachment] as? NSTextAttachment {                    //取出附件中的图片                    let image = (attachment.image)!                    //缩放                    let scale = (textView.frame.width - 2 * 5 ) / image.size.width                    //设置大小                    attachment.bounds = CGRect(x: 0, y: 0, width: image.size.width * scale, height: image.size.height * scale)                    //替换富文本中的附件                    attribute.replaceCharacters(in: range, with: NSAttributedString(attachment: attachment))                }            })            textView.attributedText = attribute        }    }    override func didReceiveMemoryWarning() {        super.didReceiveMemoryWarning()        // Dispose of any resources that can be recreated.    }}

老实说,这个效果,只能满足基本需求。博主下一篇博文会介绍更漂亮一点的图文混排;再下一篇会升级图片选择,可以一次选择多张照片,敬请期待!

原创粉丝点击