【问题标题】:Saving Picked Image to CoreData将选取的图像保存到 CoreData
【发布时间】:2015-03-15 18:22:58
【问题描述】:

我可以从照片库中选择并显示图像,但我的目标是能够将选择的图像或文件路径保存到核心数据,以便在选择保存的记录时,该图像也会显示。

我有 CoreData 工作,我能够显示来自 CoreData 的文本很好,它只是阻碍我的图像。

@IBAction func addPic(sender: AnyObject) {
pickerController.delegate = self
pickerController.sourceType = UIImagePickerControllerSourceType.PhotoLibrary
// 2
self.presentViewController(pickerController, animated: true, completion: nil)

// Displays image
func imagePickerController(picker: UIImagePickerController!,didFinishPickingMediaWithInfo info: NSDictionary!){
image.image = info[UIImagePickerControllerOriginalImage] as? UIImage

self.dismissViewControllerAnimated(true, completion: nil)

【问题讨论】:

    标签: swift core-data uiimage uiimagepickercontroller


    【解决方案1】:

    跳至处理图像了解如何将UIImage 转换为NSData(这是Core Data 使用的)

    或从github下载

    核心数据设置:

    设置两个实体: 全分辨率和缩略图。 全分辨率是存储原始图像。 缩略图用于存储要在应用程序内部使用的较小版本。 例如,您可以在 UICollectionView 概览中使用较小的版本。

    图像在Core Data 中存储为Binary DataFoundation 中对应的类型是NSData。使用UIImage(data: newImageData) 转换回UIImage



    选中二进制数据字段的允许外部存储框。这将自动将图像保存在文件系统中并在 Core Data 中引用它们

    连接两个实体,在两者之间创建一对一的关系。

    转到编辑器,然后选择创建NSManagedObjectSubclass。 这将生成带有代表您的托管对象子类的类的文件。这些将出现在您的项目文件结构中。


    基本 ViewController 设置:

    导入以下内容:

    import UIKit
    import CoreData
    

    • 在 Interface Builder 中设置两个 UIButtons 和一个 UIImageView
    • 创建两个调度队列,一个用于 CoreData,一个用于 UIImage 转换

    class ViewController: UIViewController {
    
        // imageview to display loaded image
        @IBOutlet weak var imageView: UIImageView!
    
        // image picker for capture / load
        let imagePicker = UIImagePickerController()
    
        // dispatch queues
        let convertQueue = dispatch_queue_create("convertQueue", DISPATCH_QUEUE_CONCURRENT)
        let saveQueue = dispatch_queue_create("saveQueue", DISPATCH_QUEUE_CONCURRENT)
    
        // moc
        var managedContext : NSManagedObjectContext?
    
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            imagePickerSetup() // image picker delegate and settings
    
            coreDataSetup() // set value of moc on the right thread
    
        }
    
        // this function displays the imagePicker
        @IBAction func capture(sender: AnyObject) { // button action
            presentViewController(imagePicker, animated: true, completion: nil)
        }
    
        @IBAction func load(sender: AnyObject) { // button action
    
            loadImages { (images) -> Void in
                if let thumbnailData = images?.last?.thumbnail?.imageData {
                    let image = UIImage(data: thumbnailData)
                    self.imageView.image = image
                }
            }
        }
    }
    

    此函数在正确的线程上将值设置为managedContext。由于 CoreData 需要一个 NSManagedObjectContext 中的所有操作在同一个线程中发生。

    extension ViewController {
        func coreDataSetup() {
            dispatch_sync(saveQueue) {
                self.managedContext = AppDelegate().managedObjectContext
            }
        }
    }
    

    扩展UIViewController,使其符合UIImagePickerControllerDelegateUINavigationControllerDelegate UIImagePickerController 需要这些。

    创建一个设置函数,同时创建一个委托函数imagePickerController(picker: UIImagePickerController, didFinishPickingImage image: UIImage, editingInfo: [String : AnyObject]?)

    extension ViewController : UIImagePickerControllerDelegate, UINavigationControllerDelegate {
    
        func imagePickerSetup() {
    
            imagePicker.delegate = self
            imagePicker.sourceType = UIImagePickerControllerSourceType.Camera
    
        }
    
        // When an image is "picked" it will return through this function
        func imagePickerController(picker: UIImagePickerController, didFinishPickingImage image: UIImage, editingInfo: [String : AnyObject]?) {
    
            self.dismissViewControllerAnimated(true, completion: nil)
            prepareImageForSaving(image)
    
        }
    }
    

    立即关闭UIImagePickerController,否则应用程序将显示为冻结。


    处理图像:

    imagePickerController(picker: UIImagePickerController, didFinishPickingImage image: UIImage, editingInfo: [String : AnyObject]?) 中调用这个函数。

    • 首先使用timeIntervalSince1970 获取当前日期。这将在几秒钟内返回 NSTimerInterval。这很好地转换为Double。它将作为图像的唯一 ID 并作为对它们进行排序的一种方式。

    • 现在是移动到单独队列并释放主队列的好时机。我首先使用dispatch_async(convertQueue) 在单独的线程上完成繁重的工作。

    • 然后您需要将UIImage 转换为NSData,这是通过UIImageJPEGRepresentation(image, 1) 完成的。 1 代表质量,1 最高,0 最低。它返回一个可选的,所以我使用了可选绑定。

    • 将图像缩放到所需的缩略图大小,并转换为NSData

    代码:

    extension ViewController {
    
        func prepareImageForSaving(image:UIImage) {
    
            // use date as unique id
            let date : Double = NSDate().timeIntervalSince1970
    
            // dispatch with gcd.
            dispatch_async(convertQueue) {
    
                // create NSData from UIImage
                guard let imageData = UIImageJPEGRepresentation(image, 1) else {
                    // handle failed conversion
                    print("jpg error")
                    return
                }
    
                // scale image, I chose the size of the VC because it is easy
                let thumbnail = image.scale(toSize: self.view.frame.size)
    
                guard let thumbnailData  = UIImageJPEGRepresentation(thumbnail, 0.7) else {
                    // handle failed conversion
                    print("jpg error")
                    return
                }
    
                // send to save function
                self.saveImage(imageData, thumbnailData: thumbnailData, date: date)
    
            }
        }
    }
    

    这个函数进行实际的保存。

    • 使用dispatch_barrier_sync(saveQueue) 进入CoreData 线程
    • 首先将一个新的 FullRes 和一个新的 Thumbnail 对象插入到 托管对象上下文。
    • 设置值
    • 设置FullRes和Thumbnail的关系
    • 使用do try catch 尝试保存
    • 刷新托管对象上下文以释放内存

    通过使用dispatch_barrier_sync(saveQueue),我们确信我们可以安全地存储新图像,并且新的保存或加载将等到此完成。

    代码:

    extension ViewController {
    
        func saveImage(imageData:NSData, thumbnailData:NSData, date: Double) {
    
            dispatch_barrier_sync(saveQueue) {
                // create new objects in moc
                guard let moc = self.managedContext else {
                    return
                }
    
                guard let fullRes = NSEntityDescription.insertNewObjectForEntityForName("FullRes", inManagedObjectContext: moc) as? FullRes, let thumbnail = NSEntityDescription.insertNewObjectForEntityForName("Thumbnail", inManagedObjectContext: moc) as? Thumbnail else {
                    // handle failed new object in moc
                    print("moc error")
                    return
                }
    
                //set image data of fullres
                fullRes.imageData = imageData
    
                //set image data of thumbnail
                thumbnail.imageData = thumbnailData
                thumbnail.id = date as NSNumber
                thumbnail.fullRes = fullRes
    
                // save the new objects
                do {
                    try moc.save()
                } catch {
                    fatalError("Failure to save context: \(error)")
                }
    
                // clear the moc
                moc.refreshAllObjects()
            }
        }
    }
    

    加载图片:

    extension ViewController {
    
        func loadImages(fetched:(images:[FullRes]?) -> Void) {
    
            dispatch_async(saveQueue) {
                guard let moc = self.managedContext else {
                    return
                }
    
                let fetchRequest = NSFetchRequest(entityName: "FullRes")
    
                do {
                    let results = try moc.executeFetchRequest(fetchRequest)
                    let imageData = results as? [FullRes]
                    dispatch_async(dispatch_get_main_queue()) {
                        fetched(images: imageData)
                    }
                } catch let error as NSError {
                    print("Could not fetch \(error), \(error.userInfo)")
                    return
                }
            }
        }
    }
    

    用于缩放图像的函数:

    extension CGSize {
    
        func resizeFill(toSize: CGSize) -> CGSize {
    
            let scale : CGFloat = (self.height / self.width) < (toSize.height / toSize.width) ? (self.height / toSize.height) : (self.width / toSize.width)
            return CGSize(width: (self.width / scale), height: (self.height / scale))
    
        }
    }
    
    extension UIImage {
    
        func scale(toSize newSize:CGSize) -> UIImage {
    
            // make sure the new size has the correct aspect ratio
            let aspectFill = self.size.resizeFill(newSize)
    
            UIGraphicsBeginImageContextWithOptions(aspectFill, false, 0.0);
            self.drawInRect(CGRectMake(0, 0, aspectFill.width, aspectFill.height))
            let newImage:UIImage = UIGraphicsGetImageFromCurrentImageContext()
            UIGraphicsEndImageContext()
    
            return newImage
        }
    
    }
    

    【讨论】:

    • 感谢工作的好人。你能不能帮我把它转换回 UIImage 以便我可以显示它。
    • “所以我也生成缩略图并将它们保存在相关实体中。”您的意思是您将缩略图保存到实体并将大分辨率图像保存到相关实体?这样当你调用主实体时,还没有调用大分辨率图像?
    • 其实没关系。它们是相互关联的。但这是正确的。我通常有一个实体供应用程序使用,其中包含图像的所有数据。专辑标题、位置、时间、缩略图……以及另一个包含完整资源的导出。如果您应用过滤器,这也很方便。当用户不导出时,无需对全分辨率图像应用过滤器。他/她可能只是在玩弄设置。
    • @Wert 我不能再更新代码了,很抱歉:/我不再关注 Swift 和 iOS,也没有时间跟上所有最新的变化。随时建议对我的答案进行修改以帮助其他人。
    • 非常好的解释和教程!一句话:对于 UIGraphicsBeginImageContextWithOptions,我必须将缩放器设置为 1.0 才能获得所需的图像大小。使用 0.0 时,我在将数据转换回图像时总是得到两倍大的图像。
    【解决方案2】:

    Core Data 并不是为了保存像图像这样的大二进制文件。请改用文件系统中的文档目录。

    这是实现该目标的示例代码。

    let documentsDirectory = NSSearchPathForDirectoriesInDomains(.DocumentDirectory,.UserDomainMask, true).first as! String
     // self.fileName is whatever the filename that you need to append to base directory here.
    
    let path = documentsDirectory.stringByAppendingPathComponent(self.fileName)
    
    let success = data.writeToFile(path, atomically: true)
    if !success { // handle error }
    

    【讨论】:

    • 这不是在二进制数据字段上选择“允许外部存储”完成的吗?
    猜你喜欢
    • 2021-05-27
    • 2015-01-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-09-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多