4

I am trying to read/display an image from Firebase. I am first encoding the image and then posting this encoded String to Firebase. This runs fine. When I try and decode the encoded string from Firebase and convert it to an image, I am getting a nil value exception.

This is how I am saving the image to Firebase

var base64String: NSString!
func imagePickerController(picker: UIImagePickerController, didFinishPickingImage image: UIImage, editingInfo: [String : AnyObject]?) {

    self.dismissViewControllerAnimated(true, completion: nil)

    imageToPost.image = image

    var uploadImage = image as! UIImage
    var imageData = UIImagePNGRepresentation(uploadImage)!
    self.base64String = imageData.base64EncodedStringWithOptions(NSDataBase64EncodingOptions.Encoding64CharacterLineLength)
    let ref = Firebase(url: "https://XXX.firebaseio.com")

    var quoteString = ["string": self.base64String]
    var usersRef = ref.childByAppendingPath("goalImages")
    var users = ["image": quoteString]
    usersRef.setValue(users)

    displayAlert("Image Posted", message: "Your image has been successfully posted!")
}

This is how I am trying to read the image from Firebase

//  ViewController.swift

import UIKit
import Firebase

class ViewController: UIViewController {
@IBOutlet weak var image: UIImageView!
var base64String: NSString!

@IBAction func buttonClicked(sender: AnyObject) {

    sender.setTitle("\(sender.tag)", forState: UIControlState.Normal)

}

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.

    let ref = Firebase(url: "https://XXX.firebaseio.com/goalImages/image/string")

    ref.observeEventType(.Value, withBlock: { snapshot in

        self.base64String = snapshot.value as! NSString
        let decodedData = NSData(base64EncodedString: self.base64String as String, options: NSDataBase64DecodingOptions())
        //Next line is giving the error
        var decodedImage = UIImage(data: decodedData!)

        self.image.image = decodedImage
        }, withCancelBlock: { error in
            print(error.description)
        })

}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}
}

The error says: "fatal error: unexpectedly found nil while unwrapping an Optional value"; decodedData is nil. Could someone explain what is going wrong.

sai2555
  • 43
  • 1
  • 5
  • 1
    try NSDataBase64DecodingOptions.IgnoreUnknownCharacters – Jay Nov 11 '15 at 17:49
  • I had the same problem and I fixed it using the comment from @jay. You should write an answer so the user that asked it can mark it as a solution. The only problem that remains is that the decoded image is rotated 90 degrees, from horizontal to vertical. Could I also fix this? – eeschimosu Dec 01 '15 at 15:33
  • @eeschimosu Done - converted to an answer. As far as the rotating goes.... it shouldn't be so something in the code itself is causing that. I added some code to my answer as a use case. – Jay Dec 02 '15 at 13:56

2 Answers2

6

Instead of

let decodedData = NSData(base64EncodedString: self.base64String as String, 
                                     options: NSDataBase64DecodingOptions())

try adding IgnoreUnknownCharacters

NSDataBase64DecodingOptions.IgnoreUnknownCharacters

Use Example: Encode a jpg, store and read from firebase

encode and write our favorite starship

    if let image = NSImage(named:"Enterprise.jpeg") {
    let imageData = image.TIFFRepresentation
    let base64String = imageData!.base64EncodedStringWithOptions(.Encoding64CharacterLineLength)
    let imageRef = myRootRef.childByAppendingPath("image_path")
    imageRef.setValue(base64String)

read and decode

       imageRef.observeEventType(.Value, withBlock: { snapshot in

            let base64EncodedString = snapshot.value
            let imageData = NSData(base64EncodedString: base64EncodedString as! String, 
                           options: NSDataBase64DecodingOptions.IgnoreUnknownCharacters)
            let decodedImage = NSImage(data:imageData!)
            self.myImageView.image = decodedImage

            }, withCancelBlock: { error in
                print(error.description)
        })

EDIT 2019_05_17

Update to Swift 5 and Firebase 6

func writeImage() {
    if let image = NSImage(named:"Enterprise.jpg") {
        let imageData = image.tiffRepresentation
        if let base64String = imageData?.base64EncodedString() {
            let imageRef = self.ref.child("image_path")
            imageRef.setValue(base64String)
        }
    }
}

func readImage() {
    let imageRef = self.ref.child("image_path")
    imageRef.observeSingleEvent(of: .value, with: { snapshot in
        let base64EncodedString = snapshot.value as! String
        let imageData = Data(base64Encoded: base64EncodedString, options: Data.Base64DecodingOptions.ignoreUnknownCharacters)!
        let decodedImage = NSImage(data: imageData)
        self.myImageView.image = decodedImage
    })
}
Jay
  • 34,438
  • 18
  • 52
  • 81
  • This works perfect. The only problem is that encoding and decoding takes a lot of time and freezes the app. – eeschimosu Dec 03 '15 at 22:05
  • Firebase uses Asynchronous calls so it shouldn't be freezing the app during the writing or reading of the data. That being said, in my example code, the imageRef.setValue should probably be done in a block so you would know when it has completed the write cycle. On the other hand the reading and decoding is already in a block so it should only decode as fast as the data is returned from Firebase. With properly structured code it would only encode/decode when its ready so freezing shouldn't be an issue. Maybe another question with some example code would be in order? – Jay Dec 04 '15 at 00:36
5

Firebase Engineer here:

I highly recommend using the new Firebase Storage API for uploading images to Firebase. It's simple to use, low cost, and backed by Google Cloud Storage for huge scale.

You can upload from NSData or an NSURL pointing to a local file (I'll show NSData, but the principle is the same):

// Data in memory
let data: NSData = ...

// Create a reference to the file you want to upload
let riversRef = storageRef.child("images/rivers.jpg")

// Upload the file to the path "images/rivers.jpg"
let uploadTask = riversRef.putData(data, metadata: nil) { metadata, error in
  if (error != nil) {
    // Uh-oh, an error occurred!
  } else {
    // Metadata contains file metadata such as size, content-type, and download URL.
    let downloadURL = metadata!.downloadURL
    // This can be stored in the Firebase Realtime Database
    // It can also be used by image loading libraries like SDWebImage
  }
}

You can even pause and resume uploads, and you can easily monitor uploads for progress:

// Upload data
let uploadTask = storageRef.putData(...)

// Add a progress observer to an upload task
uploadTask.observeStatus(.Progress) { snapshot in
  // Upload reported progress
  if let progress = snapshot.progress {
    let percentComplete = 100.0 * Double(progress.completedUnitCount) / Double(progress.totalUnitCount)
  }
}
Mike McDonald
  • 15,609
  • 2
  • 46
  • 49
  • 1
    Hi Mike. Im using Firebase currently. How do I grab a Image from my projects Firebase Storage bucket and display it in a UIImageView? Please note that i manually uploaded all images through the Firebase Console. Thanks! – brkr Jun 09 '16 at 23:46
  • 2
    @brkr, you'll create a reference to it (in the file viewer in the console, you'll see a URL in the side panel when you click on an image, it's got a gs://bucket/path/to/your/image URL), which you can then use to download the data. Check out the download docs for more info: https://firebase.google.com/docs/storage/ios/download-files – Mike McDonald Jun 16 '16 at 04:32
  • Awesome! What do you think is the best way to save the images for the user end? Im building a game with 200 different types of gun/gun skins. For new users all their guns will be locked with a default placeholder. But as they unlock them I would need to update the images and data displayed. Should I save to memory or locally? The images are small like 70KB each. – brkr Jun 16 '16 at 17:47
  • 1
    There are a few ways of doing this: 1) keep them in the resource bundle (which is great if you're ok with shipping 200 * 70kB = 14MB of content in your app), 2) store them in a static asset service like Firebase Hosting (files will be public), or 3) store them in Firebase Storage. With 2 and 3, you can use something like SDWebImage to perform downloads and use placeholders, and the difference here is going to be cost and security (do you care that all files are public), while 1 is a tradeoff of download size. – Mike McDonald Jun 16 '16 at 20:37