当前位置: > > > Swift - 从相册中选择照片并上传(使用UIImagePickerController)

Swift - 从相册中选择照片并上传(使用UIImagePickerController)

(本文代码已升级至Swift3)

选择本地图片并上传是应用开发中一个比较常见的功能。
      
我们使用 UIImagePickerController(图片选择器)可以很方便的从系统“照片”中选择图片,但我们会发现选择完毕后,通过图片的 info[UIImagePickerControllerReferenceURL] 得到的是一个引用路径,格式如下:
assets-library://asset/asset.PNG?id=90B54369-5E79-433D-B74A-E8E0870EAF27&ext=PNG
用这个路径是没法上传文件的。想要把选择的图片上传,通常我们会想到如下两种方式:

方法一:先将图片保存到一个临时文件夹下,再上传

下面样例在 imagePickerController 选择图片后,使用 fileManager 将其复制保存到应用的文档目录下,再将复制过来的图片上传。
import UIKit
import Alamofire

class ViewController: UIViewController, UIImagePickerControllerDelegate,
UINavigationControllerDelegate {
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    //选取相册
    @IBAction func fromAlbum(_ sender: Any) {
        //判断设置是否支持图片库
        if UIImagePickerController.isSourceTypeAvailable(.photoLibrary){
            //初始化图片控制器
            let picker = UIImagePickerController()
            //设置代理
            picker.delegate = self
            //指定图片控制器类型
            picker.sourceType = .photoLibrary
            //弹出控制器,显示界面
            self.present(picker, animated: true, completion: {
                () -> Void in
            })
        }else{
            print("读取相册错误")
        }
    }
    
    //选择图片成功后代理
    func imagePickerController(_ picker: UIImagePickerController,
                               didFinishPickingMediaWithInfo info: [String : Any]) {
        
        //获取选择的原图
        let pickedImage = info[UIImagePickerControllerOriginalImage] as! UIImage
        
        //将选择的图片保存到Document目录下
        let fileManager = FileManager.default
        let rootPath = NSSearchPathForDirectoriesInDomains(.documentDirectory,
                                                .userDomainMask, true)[0] as String
        let filePath = "\(rootPath)/pickedimage.jpg"
        let imageData = UIImageJPEGRepresentation(pickedImage, 1.0)
        fileManager.createFile(atPath: filePath, contents: imageData, attributes: nil)
        
        //上传图片
        if (fileManager.fileExists(atPath: filePath)){
            //取得NSURL
            let imageURL = URL(fileURLWithPath: filePath)
            
            //使用Alamofire上传
            Alamofire.upload(imageURL, to: "http://www.hangge.com/upload.php")
                .responseString { response in
                    print("Success: \(response.result.isSuccess)")
                    print("Response String: \(response.result.value ?? "")")
            }
        }
        
        //图片控制器退出
        picker.dismiss(animated: true, completion:nil)
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
}
不管使用模拟器还是真机调试,运行后可以看到图片上传成功了:

方法二:使用PhotoKit获取选择图片的真实路径,再上传

import UIKit
import Alamofire
import Photos

class ViewController: UIViewController, UIImagePickerControllerDelegate,
UINavigationControllerDelegate {
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    //选取相册
    @IBAction func fromAlbum(_ sender: Any) {
        //判断设置是否支持图片库
        if UIImagePickerController.isSourceTypeAvailable(.photoLibrary){
            //初始化图片控制器
            let picker = UIImagePickerController()
            //设置代理
            picker.delegate = self
            //指定图片控制器类型
            picker.sourceType = .photoLibrary
            //弹出控制器,显示界面
            self.present(picker, animated: true, completion: {
                () -> Void in
            })
        }else{
            print("读取相册错误")
        }
    }
    
    //选择图片成功后代理
    func imagePickerController(_ picker: UIImagePickerController,
                               didFinishPickingMediaWithInfo info: [String : Any]) {

        //选择图片的引用路径
        let pickedURL = info[UIImagePickerControllerReferenceURL] as! URL
        let fetchResult: PHFetchResult = PHAsset.fetchAssets(withALAssetURLs: [pickedURL],
                                                                            options: nil)
        let asset = fetchResult.firstObject
        
        PHImageManager.default()
            .requestImageData(for: asset!, options: nil, resultHandler: {
            (imageData:Data?, dataUTI:String?, orientation:UIImageOrientation,
            info:[AnyHashable : Any]?) in
                //获取实际路径
                let imageURL = info!["PHImageFileURLKey"] as! URL
                print("路径:",imageURL)
                print("文件名:",imageURL.lastPathComponent)
                
                //使用Alamofire上传
                Alamofire.upload(imageURL, to: "http://www.hangge.com/upload.php")
                    .responseString { response in
                        print("Success: \(response.result.isSuccess)")
                        print("Response String: \(response.result.value ?? "")")
                }
            })
        
        //图片控制器退出
        picker.dismiss(animated: true, completion:nil)
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
}
使用模拟器运行后,可以看到图片上传成功了:

但如果使用真机调试的话,虽然我们得到了图片的真实路径和文件名,但还是无法上传。所以上传图片还是建议使用方法一。


附录:

(1)本文样例使用 Alamofire 上传文件,对于Alamofire不熟悉的可参考我原来写过的几篇文章:
(2)服务端php代码如下:
<?php 
/** php 接收流文件
* @param  String  $file 接收后保存的文件名
* @return boolean
*/ 
function receiveStreamFile($receiveFile){   
    $streamData = isset($GLOBALS['HTTP_RAW_POST_DATA'])? $GLOBALS['HTTP_RAW_POST_DATA'] : ''; 
   
    if(empty($streamData)){ 
        $streamData = file_get_contents('php://input'); 
    } 
   
    if($streamData!=''){ 
        $ret = file_put_contents($receiveFile, $streamData, true); 
    }else{ 
        $ret = false; 
    } 
  
    return $ret;   
} 
 
//定义服务器存储路径和文件名
$receiveFile =  $_SERVER["DOCUMENT_ROOT"]."/uploadFiles/hangge.zip"; 
$ret = receiveStreamFile($receiveFile); 
echo json_encode(array('success'=>(bool)$ret)); 
?>
评论12
  • 12楼
    2017-11-30 14:27
    Allard

    只要是用UIImagePickerController从相册选图片,都是那种正方形的框选,没法选原片,有解决办法吗

    站长回复

    默认情况下UIImagePickerController就是选原片的啊。除非你把它的allowsEditing属性设置为true,才会出现正方形的框选,你检查下这个属性。

  • 11楼
    2017-09-16 17:26
    菜牙

    谢谢航哥,问题之前已经解决了,感谢您的平台,我是自学的开发,如果没有您平台的各种案例讲解,我可能早就坚持不下去了,现在我自己开发的app马上就要上线了,谢谢!

    站长回复

    不客气,能帮助到你我也很高兴。这里也祝你开发的APP早日成功上线,加油!

  • 10楼
    2017-09-03 21:33
    菜牙

    航哥您好,我好像遇到了一个您代码中的一个问题,就是方法一中,如果用户选择了不允许访问,UIImagePickerController.isSourceTypeAvailable(.photoLibrary) 返回的结果还是true,如果选择不允许访问的话并不会执行else中的代码。您可以抽空看一下吗,写程序的过程中刚好碰到了这个问题不知道怎么解决。谢谢谢谢!

    站长回复

    UIImagePickerController.isSourceTypeAvailable(.photoLibrary) 只是用来判断设备是否有图片库,跟是否有访问权限没关系。
    如果需要判断是否允许访问,可以再外面使用 PHPhotoLibrary.authorizationStatus() 进行判断,具体可以参考我之前写的这篇文章:Swift - 判断是否有某功能访问权限,没有则提示,并自动跳转到设置页

  • 9楼
    2017-07-27 17:17
    布袋

    是 let filePath = "\(rootPath)/pickedimage.jpg"
    还是
    let filePath = "\(rootPath)/\(pickedimage)"
    是不是我理解错了?

    站长回复

    是 let filePath = "\(rootPath)/pickedimage.jpg"

    我这里保存的本地图片名字就叫“pickedimage.jpg”

  • 8楼
    2017-07-27 04:47
    摸爬滚打几百年

    这个厉害了,我几次想解决这个问题几次都失败了,中间耽搁了好久,照这样看来我所搭建一个小app需要的技术已经全部齐全了!真的非常感谢!如果有对应的demo就更好了

    站长回复

    不客气。欢迎常来看看,我会持续更新下去的。

  • 7楼
    2017-01-04 21:03
    鲨鱼

    航哥,有时间写一篇关于怎么上传和展示多张图片的文章吗 类似于微信朋友圈分享的那种

    站长回复

    图片的多选功能可以参考我写的这篇文章:Swift - 相册图片多选功能的实现
    选择完毕后将图片上传即可。

  • 6楼
    2016-09-24 16:41
    小芝

    请问怎么在传图片的同时 传其他参数呢?

    站长回复

    参考我写的Alamofire文件上传相关文章,里面有讲如何带上其它参数:Swift - HTTP网络操作库Alamofire使用详解2(文件上传)

  • 5楼
    2016-06-14 23:00
    无忧乐活

    我在实际应用中发现了一个问题,当调用UIImagePickerController并弹出相册选择框之后,点导航的返回,只有标题和返回在动,另外viewDidAppear不会被调用。
    还有一个问题是self.presentViewController弹出相册选择界面,需要5秒左右的等待。

    站长回复

    我又测试了下,viewDidAppear是会调用的,而且界面弹出不需要等5秒那么久。你那边出现的问题很奇怪,我目前也想不出是什么原因。

  • 4楼
    2016-05-27 16:37
    glimy1314

    航哥,我在一个空项目打开系统相册是好使的,但是弄到我项目里,就没有相册了,怎么回事啊

    站长回复

    可能是跟你项目中某个代码冲突,具体需要你自己调试了。

  • 3楼
    2016-05-19 19:39
    浪客和剑心

    怎么获取相册里的视频呢??

    站长回复

    下个月会写篇相关文章,你可以关注下。

  • 2楼
    2016-05-19 16:43
    黑木宁

    为什么上传图片 控制台报 (lldb) 然后就中断了

    站长回复

    不太清楚,我测试了下是好的。

  • 1楼
    2016-05-13 09:40
    snailAlice

    涨知识了,谢谢航哥!!!

    站长回复

    不客气!