Once upon a time I was given the task of sending an image captured on IOS to our Rails back-end server and at first I figured: “Wow this can’t be such a big deal, I know how to take an image and I know how to make a request, so why would this be any different from a normal request? Why would an image be special?“.
Well, it turns out I was kind of wrong. The thing about sending a picture is that you are actually not sending a picture. Well, you are, but it is not formatted as an actual picture. Yes, I was confused about it as well, but that’s how computers work. Crazy, right? First you have to encode the picture into computer-readable code, then you send that code to your server, then you decode that code into a picture again and Voila there is your picture – that’s all, simple as that. It sounds easy and at the end of the day it actually is. I will show you some code and if you want to test it out, I’ve also prepared some convenient templates that you can pull from my repo to immediately put into use:
So lets start:
First we do some safe unwrapping so we avoid possible crashes in our app. In case UIImageView doesn’t have an image, then we return the data for the specified image in PNG format (imageData variable), and the last thing we do is encode imageData to that code so that computers can understand, just as I said earlier. We do that inside of the encodedImageData variable. It’s pretty straightforward:
if let capturePhotoImage = self.capturePhotoView.image {
if let imageData = UIImagePNGRepresentation(capturePhotoImage) {
let encodedImageData = imageData.base64EncodedString(options: NSData.Base64EncodingOptions(rawValue: 0))
}
}
Then we add a simple asynchronous post request with our encodedImageData as a parameter and we should decode that back to our beautiful image.
if let capturePhotoImage = self.capturePhotoView.image {
if let imageData = UIImagePNGRepresentation(capturePhotoImage) {
let encodedImageData = imageData.base64EncodedString(options: NSData.Base64EncodingOptions(rawValue: 0))
var request = URLRequest(url: URL(string: "http://192.168.1.12:3000/create-image")!)
request.httpMethod = "POST"
let postString = "image=(encodedImageData)"
request.httpBody = postString.data(using: .utf8)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else {
print("error=(error)")
return
}
if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 { // check for http errors
print("statusCode should be 200, but is (httpStatus.statusCode)")
print("response = (response)")
}
let responseString = String(data: data, encoding: .utf8)
print("responseString = (responseString)")
}
task.resume()
}
}
And now for the server side. To be perfectly honest with you guys, this is the part where I encountered problems. It turned out that every decoded image was black. This kept troubling me for a couple of days. I kept eyeballing the project (eyeballing technique - when the developer is very desperate and is searching for a possible typo and suddenly realizes he hates computers) and then I realized there was an empty space where there should have been a ‘+’.
So first we create a new StringIO instance from the decoded image params (you can see here we’re replacing the empty whitespace with the useful + sign), with the class we are creating class from image variable, with class_eval we are adding methods to image, and then giving our image name and content type. Finally we are create our image and save it to our database.
def create_image
image = StringIO.new(Base64.decode64(params[:image].tr(' ', '+')))
image.class.class_eval { attr_accessor :original_filename, :content_type }
image.original_filename = SecureRandom.hex + '.png'
image.content_type = 'image/png'
create_image = Image.new(image: image)
create_image.save!
end