Moya Tutorial for iOS: Getting Started
Moya is a networking library inspired by the concept of encapsulating network requests in type-safe way, typically using enumerations, that provides confidence when working with your network layer. Become a networking superhero with Moya! By Shai Mishali.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Moya Tutorial for iOS: Getting Started
30 mins
- Getting Started
- Moya: What Is It?
- What Is Moya?
- How Is Moya Related to Alamofire?
- Moya’s Building Blocks
- Marvel API – The API of Heroes
- Creating Your First Moya Target
- Authorizing Requests in Marvel’s API
- Using Your Target
- Imgur – Sharing With Friends!
- Creating the Imgur Target
- Wrapping Up CardViewController
- Taking Moya to the Next Level
- Where to Go From Here?
Imgur – Sharing With Friends!
For this, you’ll create another Moya target named Imgur
that will let you interact with two different endpoints for image handling: one for uploading and one for deleting.
Similar to the Marvel API, you’ll need to sign up for a free account with Imgur.
After that, you’ll need to create an Imgur Application. You may use any fake URL for the callback, as you won’t be using OAuth here. You can also simply choose **OAuth 2 authorization without a callback URL**.
Once you submit the form, Imgur will present you with your new Imgur Client ID and Client secret. Save these for the next step.
Creating the Imgur Target
Right-click the ComicCards/Network folder and select New File… Then create a new Swift file and name it Imgur.swift.
Add the following code to define the Imgur endpoints that you’ll implement and use:
import UIKit
import Moya
public enum Imgur {
// 1
static private let clientId = "YOUR CLIENT ID"
// 2
case upload(UIImage)
case delete(String)
}
Similar to the Marvel API, you:
- Store your Imgur Client ID in
clientId
. Make sure to replace this with the Client ID generated in the previous step (you don’t need the secret). - Define the two endpoints that you’ll be using:
upload
, used to upload an image, anddelete
, which takes a hash for a previously uploaded image and deletes it from Imgur. These are represented in the Imgur API as POST /image and DELETE /image/{imageDeleteHash}.
Next, you’ll conform to TargetType
. Add the following code right below your new enum
:
extension Imgur: TargetType {
// 1
public var baseURL: URL {
return URL(string: "https://api.imgur.com/3")!
}
// 2
public var path: String {
switch self {
case .upload: return "/image"
case .delete(let deletehash): return "/image/\(deletehash)"
}
}
// 3
public var method: Moya.Method {
switch self {
case .upload: return .post
case .delete: return .delete
}
}
// 4
public var sampleData: Data {
return Data()
}
// 5
public var task: Task {
switch self {
case .upload(let image):
let imageData = image.jpegData(compressionQuality: 1.0)!
return .uploadMultipart([MultipartFormData(provider: .data(imageData),
name: "image",
fileName: "card.jpg",
mimeType: "image/jpg")])
case .delete:
return .requestPlain
}
}
// 6
public var headers: [String: String]? {
return [
"Authorization": "Client-ID \(Imgur.clientId)",
"Content-Type": "application/json"
]
}
// 7
public var validationType: ValidationType {
return .successCodes
}
}
This should look familiar to you by now. Let’s go through the seven protocol properties of the new Imgur
target.
To upload a file, you’ll use the .uploadMultipart
task type, which takes an array of MultipartFormData
structs. You then create an instance of MultipartFormData
with the appropriate image data, field name, file name and image mime type.
- The base URL for the Imgur API is set to
https://api.imgur.com/3
. - You return the appropriate endpoint
path
based on the case./image
for.upload
, and/image/{deletehash}
for.delete
. - The
method
differs based on the case as well:.post
for.upload
and.delete
for.delete
. - Just like before, you return an empty
Data
struct forsampleData
. - The
task
is where things get interesting. You return a differentTask
for every endpoint. The.delete
case doesn’t require any parameters or content since it’s a simpleDELETE
request, but the.upload
case needs some more work.To upload a file, you’ll use the
.uploadMultipart
task type, which takes an array ofMultipartFormData
structs. You then create an instance ofMultipartFormData
with the appropriate image data, field name, file name and image mime type. - Like the Marvel API, the
headers
property returns aContent-Type: application/json
header, and an additional header. The Imgur API uses Header authorization, so you’ll need to provide your Client ID in the header of every request, in the form ofAuthorization: Client-ID (YOUR CLIENT ID)
. - The
.validationType
is the same as before — valid for any status codes between 200 and 299.
Your Imgur
target is done! This concludes the Moya-related code for the ComicCards app. Kudos to you!
The final step is completing CardViewController
to have it use your newly created Moya target.
Wrapping Up CardViewController
Go back to CardViewController.swift and add the following lines at the beginning of your CardViewController
class, below the comic
property:
private let provider = MoyaProvider<Imgur>()
private var uploadResult: UploadResult?
Like before, you create a MoyaProvider
instance, this time with the Imgur
target. You also define uploadResult
— an optional UploadResult
property you’ll use to store the result of an upload, which you’ll need when deleting an image.
You have two methods to implement: uploadCard()
and deleteCard()
.
At the end of uploadCard()
, append the following code:
// 1
let card = snapCard()
// 2
provider.request(.upload(card),
// 3
callbackQueue: DispatchQueue.main,
progress: { [weak self] progress in
// 4
self?.progressBar.setProgress(Float(progress.progress), animated: true)
},
completion: { [weak self] response in
guard let self = self else { return }
// 5
UIView.animate(withDuration: 0.15) {
self.viewUpload.alpha = 0.0
self.btnShare.alpha = 0.0
}
// 6
switch response {
case .success(let result):
do {
let upload = try result.map(ImgurResponse<UploadResult>.self)
self.uploadResult = upload.data
self.btnDelete.alpha = 1.0
self.presentShare(image: card, url: upload.data.link)
} catch {
self.presentError()
}
case .failure:
self.presentError()
}
})
This big chunk of code definitely needs some explanation, but worry not — most of it should be relatively familiar.
You’ll use this property later when finishing up the deleteCard()
method. After storing the upload result, you trigger the presentShare
method which will present a proper share alert with the URL to the uploaded image, and the image itself. A failure will trigger the presentError()
method.
- You use a helper method called
snapCard()
to generate aUIImage
from the presented card on screen. - Like with the Marvel API, you use your provider to invoke the
upload
endpoint with an associated value of the card image. -
callbackQueue
allows providing a queue on which you’ll receive upload progress updates in the next callback. You provide the mainDispatchQueue
to ensure progress updates happen on the main thread. - You define a progress closure, which will be invoked as your image is uploaded to Imgur. This sets the progress bar’s progress and will be invoked on the main
DispatchQueue
provided incallbackQueue
. - When the request completes, you fade out the upload view and the share button.
- As before, you handle the
success
andfailure
options of the result. If successful, you try to map the response to anImgurResponse
and then store the mapped response in the instance property you defined before.You’ll use this property later when finishing up the
deleteCard()
method. After storing the upload result, you trigger thepresentShare
method which will present a proper share alert with the URL to the uploaded image, and the image itself. A failure will trigger thepresentError()
method.
And for your final piece of code for the day: Add the following code inside deleteCard()
:
// 1
guard let uploadResult = uploadResult else { return }
btnDelete.isEnabled = false
// 2
provider.request(.delete(uploadResult.deletehash)) { [weak self] response in
guard let self = self else { return }
let message: String
// 3
switch response {
case .success:
message = "Deleted successfully!"
self.btnDelete.alpha = 0.0
case .failure:
message = "Failed deleting card! Try again later."
self.btnDelete.isEnabled = true
}
let alert = UIAlertController(title: message, message: nil, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Done", style: .cancel))
self.present(alert, animated: true, completion: nil)
}
This method is rather simple and works as follows:
- You make sure the
uploadResult
is available and disable the delete button so the user doesn’t tap it again. - You use the Imgur provider to invoke the
delete
endpoint with the associated value of the upload result’sdeletehash
. This hash uniquely identifies the uploaded image. - In case of a successful or failed deletion, you show an appropriate message.
That is it! Build and run your app one final time. Select a comic and share your image to Imgur. After you’re done with it, you can tap the Delete from Imgur button to remove it.
uploadResult
will be cleared and the deletehash
will be lost. Persisting the hash for any generated images over different sessions is a nice challenge you might want to tackle :].