Upload images with swift


#1

I’m working on the first application using Fabric. I’ve added in the user login

Twitter.sharedInstance().loginWithCompletion{ (session: TWTRSession!..)}

I see that we have authToken and authTokenSecret as properties to the session. It appears that these are the OAuth values.
I see that the /statuses/update_with_media.json is deprecated so I’m looking at the upload.twitter.com/1.1/media/upload.json. My first attempts return authentication errors

Optional({“errors”:[{“message”:“Bad Authentication data”,“code”:215}]})

I’ve done many unsuccessful searches for uploading to twitter using swift. Can anyone point me to an example of how to post multipart form data in Swift (or Objective-C) to upload.json. If I use NSMutableURLRequest then how are the credentials added, or should I use Twitter.sharedInstance().APIClient.URLRequestWithMethod(…) to generate the request?

TIA
Mike


#2

The docs suggest that URLRequestWithMethod... returns a signed url request so that should take care of your authentication.

From there, you can make a request with NSURLSession and dataTaskWithRequest. Make sure to call resume on the task after you create it and setup your completion handler.


#3

Thanks for getting me in the right direction. I thought that I would need an NSMutableURLRequest so that I could add the image data. I took the NSURLRequest from URLRequestWithMethod and cast it into a new NSMutableURLRequest, added the image data and sent it off, and then got a different error:

{code:“38”, message: “Media parameter is missing”}

So instead of adding the image data after getting the request from the framework I just decided to send it to the framework in the parameters to be included in the request. That worked. Here’s a complete sample:

func uploadThis(sender: UIButton)
{
	let strUploadUrl = "https://upload.twitter.com/1.1/media/upload.json"
	let strStatusUrl = "https://api.twitter.com/1.1/statuses/update.json"
	UIApplication.sharedApplication().networkActivityIndicatorVisible = true
	var twAPIClient = Twitter.sharedInstance().APIClient
	var error: NSError?
	var parameters:Dictionary = Dictionary<String, String>()
	// get image from bundle
	var imageData : NSData = UIImagePNGRepresentation(UIImage(named: "SampleImage.png"))
	// get anim gif from bundle
	let path = NSBundle.mainBundle().pathForResource("SampleAnim", ofType: "GIF")
	var animData : NSData = NSData(contentsOfFile: path!)!
	// use either of these, media in parameters is put into request
	// parameters["media"] = imageData.base64EncodedStringWithOptions(nil)
	parameters["media"] = animData.base64EncodedStringWithOptions(nil)
	var twUploadRequest = twAPIClient.URLRequestWithMethod("POST", URL: strUploadUrl, parameters: parameters, error: &error)
	if twUploadRequest != nil {
		twAPIClient.sendTwitterRequest(twUploadRequest) {
			(uploadResponse, uploadResultData, uploadConnectionError) -> Void in
			if (uploadConnectionError == nil) {
				// using SwiftyJSON to parse result
				let json = JSON(data: uploadResultData!)
				// check for media id in result
				if (json["media_id_string"].string != nil) {
					println("result = \(json)")
					// post a status with link to media
					parameters = Dictionary<String, String>()
					parameters["status"] = "Hey look at this"
					parameters["media_ids"] = json["media_id_string"].string!
					var twStatusRequest = twAPIClient.URLRequestWithMethod("POST", URL: strStatusUrl, parameters: parameters, error: &error)
					if (twStatusRequest != nil)
					{
						twAPIClient.sendTwitterRequest(twStatusRequest) { (statusResponse, statusData, statusConnectionError) -> Void in
							if (statusConnectionError != nil) {
								println("Error posting status \(statusConnectionError)")
							}
						} // completion
					} else {
						println("Error creating status request \(error)")
					}
				} else {
					println("Media_id not found in result = \(json)")
				}
			} else {
				println("Error uploading image \(uploadConnectionError)")
			}
		} // completion
	} else {
		println("Error creating upload request \(error)")
	}
	UIApplication.sharedApplication().networkActivityIndicatorVisible = false
}

Naturally you’ll want to do something better than println the errors and you might want to break out the image and the status into separate operations but this works.

Mike


#4

Any idea how this can be done on android?


#5

BUMP!
OK Replying to my own message here because what worked in December is no longer working. I installed an update to the TwitterKit when the notification came through in the Fabric UI. That was about a month ago I suppose. I wasn’t working on this part of the app at the time so it went un-noticed until today. This same piece of code that was my solution then returns BAD REQUEST now in response to the upload of the media:

twAPIClient.URLRequestWithMethod(“POST”, URL: strUploadUrl, parameters: parameters, error: &error)

I tried changing it to “media_data” as some suggested but then I got ‘missing required parameter “media”…’ I’m trying the simplest case, a single PNG file base64 encoded string… just like it is here. The login is taken care of in a separate function and I do already have a valid session at this point. I’ve checked Fabric.io and I have read+write selected for this app.

Anyone have a different solution to posting images with tweet?

TIA
Mike


#6

Sorry for the lightweight response as I’ve not had time to try this for myself yet - but - have you taken a look at the Cannonball sample app? https://github.com/twitterdev/cannonball-ios

Written in Swift and does image uploading although I’m not familiar with the method it uses. @romainhuet may be able to chip in with more information if he has time!


#7

The Cannonball sample uses the twitter composer window and we want to use our own window and send the tweet in the background. I researched and found the recommended method was the upload.twitter.com/1.1/media/upload.json.

Mike


#8

OK so it turned out to be user error, as it often is. We took this code and snapped it into a new app and, while this code was the same, the data I was passing in was different. Rather than getting the image data within the function I was passing in the NSData from locally cached files, and that’s where the issue was.

If you’re posting an image then this is needed (given an NSURL of a PNG file called imgURL)

let fileData = NSData(contentsOfFile: imgURL.path!)
let image = UIImage(data: fileData!)
let imageData : NSData = UIImagePNGRepresentation(image)
parameters["media"] = imageData.base64EncodedStringWithOptions(nil)

To upload an animated gif you use NSData right from the file (given an NSURL of an anim gif file called animURL)

var animData : NSData = NSData(contentsOfFile: animURL.path!)!
parameters["media"] = animData.base64EncodedStringWithOptions(nil)

So if you’re posting an image then encode the image, if it’s an animation encode it right from the contents of the file.

Mike


#9

Hi Mike,
I’ve tried to use the similar code for posting gif animation to twitter, however, I’m facing weird error:

fatal error: unexpectedly found nil while unwrapping an Optional value

in the part of the code:

        let twUploadRequest = twAPIClient.URLRequestWithMethod("POST", URL: strUploadUrl, parameters: parameters, error: &clientError)

that is strange, I printed all options to make sure they are not nil , except clientError, that is supposed to be nil

Have you faced the problems like that?


#10

In my case the err value wasn’t nil it was a pointer to a var marked optional. Try this…

var clientError: NSError?
let twUploadRequest = twAPIClient.URLRequestWithMethod("POST", URL: strUploadUrl, parameters: parameters, error: &clientError)

HTH,
Mike


#11

I have declared clientError, just like you did it, so that is not the source of the problem.
I noticed in debugger, that twAPIClient, or Twitter.sharedInstance().APIClient is nil, which is strange.
I found some working twitter kit project at github - FabricTest and there Twitter.sharedInstance().APIClient is not nil, so there smh wrong at this stage. On the other hand in this case there must have been some errer, connected with the declaration of Twitter.sharedInstance().APIClient but there isn’t since twitter kit is successfully added to the project.

Anton


#12

Sorry it took so long to get back to you. I got swamped for a while.

There are a couple of things needed before posting… in the AppDelegate didFinishLaunchingWithOptions you need this

Fabric.with([Twitter()])

Note that’s a Twitter class instance being constructed and passed in to the Fabric class function “with”. Fabric no doubt takes this instance and stores it for later and this is likely where the APIClient is obtained.

Twitter.sharedInstance().logInWithCompletion { (session: TWTRSession!, error: NSError!) in
    if session != nil {
        self.mSession = session
    } else {
        // report error
    }

In order for that to work you have to have twitter credentials setup in the phone settings. There is also a guest login but you can’t post as a guest of course.

Let me know if this takes care of it.

Mike


#13

Hi I checked all these things and it still didn’t work. What seemed strange, as I’ve noticed before, Twitter.sharedInstance().APIClient returned nil, smh was wrong with the library, despite there were no errors about that during compilation. After reinstalling the library for another project all worked well.
thank you

Anton


#14

Hi Mike !

I’ve just noticed, that this library uses its own authorization page for twitter account (one can find it out if deletes all twitter accounts in settings). It is smth like “authorize created by fabric for mylogin-project project-work-name on iOS to use your account?” . This looks weird, is there any way to turn it off and just redirect user to settings as usual ?

Anton


#15

I believe that issue is on my list for next week. There’s supposed to be a way to change what is presented on that dialog that’s created by Fabric, but when I followed those directions I didn’t see what I expected to see. I also found another function called “loginWithViewController” suggesting that we can present whatever view we want for the login credentials.
As to your question specifically, turning off the dialog and redirecting them to settings, I don’t believe we can ‘redirect’. So far the only thing I’ve seen other apps do is present a screen with animated or step-by-step directions for the user to make the settings changes.
I didn’t find out about this for quite some time because I only had one account, the account was already set up in the settings, and I had granted the app access to the account when it first asked. Anyway, like I said, I’ll be looking at this closer next week and I’ll come back and post what I find.

Mike


#16

I know this thread is kind of old but I am having trouble with this. It works perfectly for images but for videos I can’t get it to work.

I tried changing this line of code
var imageData : NSData = UIImageJPEGRepresentation(self.image!, 0.5)
parameters[“media”] = imageData.base64EncodedStringWithOptions(nil)
to this

parameters[“media”] = self.video!.base64EncodedStringWithOptions(nil) //self.video is NSData

but i get 400 bad request back from twitter… what am I doing wrong here?


#17

Sorry it took a while to get back to you. One of the things I found that was important was how the NSData was created. For a movie I had to use animated gif and use

var animData = NSData(contentsOfFile: path)! // path is the path to a local file
mediaParams["media"] = animData.base64EncodedStringWithOptions(nil)

If I used a .mov type file it failed, if I created the NSData using anything but NSData(contentsOfFile:) then it failed.

HTH,
Mike


#18