In the previous chapter, you created an iPhone application that can create users and acronyms. In this chapter, you’ll expand the app to include viewing details about a single acronym. You’ll also learn how to perform the final CRUD operations: edit and delete. Finally, you’ll learn how to add acronyms to categories.
Note: This chapter expects you have a TIL Vapor application running. It also expects you’ve completed the iOS app from the previous chapter. If not, grab the starter projects and pick up from there. See Chapter 12, “Creating a Simple iPhone App Part 1”, for details on how to run the Vapor application.
Getting started
In the previous chapter, you learned how to view all the acronyms in a table. Now, you want to show all the information about a single acronym when a user taps a table cell. The starter project contains the necessary plumbing; you simply need to implement the details.
Open AcronymsTableViewController.swift. Replace the implementation for makeAcronymsDetailTableViewController(_:) with the following:
You run this code when a user taps an acronym. The code does the following:
Ensure that there’s a selected index path.
Get the acronym corresponding to the tapped row.
Create an AcronymDetailTableViewController using the selected acronym.
Create a new Swift file called AcronymRequest.swift in the Utilities group. Open the new file and create a new type to represent an acronym resource request:
struct AcronymRequest {
let resource: URL
init(acronymID: UUID) {
let resourceString =
"http://localhost:8080/api/acronyms/\(acronymID)"
guard let resourceURL = URL(string: resourceString) else {
fatalError("Unable to createURL")
}
self.resource = resourceURL
}
}
This sets the resource property to the URL for that acronym. At the bottom of AcronymRequest, add a method to get the acronym’s user:
func getUser(
completion: @escaping (
Result<User, ResourceRequestError>
) -> Void
) {
// 1
let url = resource.appendingPathComponent("user")
// 2
let dataTask = URLSession.shared
.dataTask(with: url) { data, _, _ in
// 3
guard let jsonData = data else {
completion(.failure(.noData))
return
}
do {
// 4
let user = try JSONDecoder()
.decode(User.self, from: jsonData)
completion(.success(user))
} catch {
// 5
completion(.failure(.decodingError))
}
}
// 6
dataTask.resume()
}
Here’s what this does:
Create the URL to get the acronym’s user.
Create a data task using the shared URLSession.
Check the response contains a body, otherwise fail with the appropriate error.
Decode the response body into a User object and call the completion handler with the success result.
Catch any decoding errors and call the completion handler with the failure result.
Start the network task.
Next, below getUser(completion:), add the following method to get the acronym’s categories:
func getCategories(
completion: @escaping (
Result<[Category], ResourceRequestError>
) -> Void
) {
let url = resource.appendingPathComponent("categories")
let dataTask = URLSession.shared
.dataTask(with: url) { data, _, _ in
guard let jsonData = data else {
completion(.failure(.noData))
return
}
do {
let categories = try JSONDecoder()
.decode([Category].self, from: jsonData)
completion(.success(categories))
} catch {
completion(.failure(.decodingError))
}
}
dataTask.resume()
}
This works exactly like the other request methods in the project, decoding the response body into [Category].
Open AcronymDetailTableViewController.swift and add the following implementation to getAcronymData():
// 1
guard let id = acronym.id else {
return
}
// 2
let acronymDetailRequester = AcronymRequest(acronymID: id)
// 3
acronymDetailRequester.getUser { [weak self] result in
switch result {
case .success(let user):
self?.user = user
case .failure:
let message =
"There was an error getting the acronym’s user"
ErrorPresenter.showError(message: message, on: self)
}
}
// 4
acronymDetailRequester.getCategories { [weak self] result in
switch result {
case .success(let categories):
self?.categories = categories
case .failure:
let message =
"There was an error getting the acronym’s categories"
ErrorPresenter.showError(message: message, on: self)
}
}
Here’s the play by play:
Ensure the acronym has a non-nil ID.
Create an AcronymRequest to gather information.
Get the acronym’s user. If the request succeeds, update the user property. Otherwise, display an appropriate error message.
Get the acronym’s categories. If the request succeeds, update the categories property. Otherwise, display an appropriate error message.
The project displays acronym data in a table view with four sections. These are:
the acronym
its meaning
its user
its categories
Build and run. Tap an acronym in the Acronyms table and the application will show the detail view with all the information:
Editing acronyms
To edit an acronym, users tap the Edit button in the Acronym detail view. Open CreateAcronymTableViewController.swift. The acronym property exists to store the current acronym. If this property is set — by prepare(for:sender:) in AcronymDetailTableViewController.swift — then the user is editing the acronym. Otherwise, the user is creating a new acronym.
Ox dxe izviqtz ux pux, ruu’ga up avub kaba, tu tovigote qki jotznop poolpv cafd jzo vobrevf xozaok elw ijxuca fqu loun’t germa. Or tea’yo ec lwoeci xesa, cozx towubatiEvoym() aw mokeza.
Wu omcili ih uwrikzm, goo rulo i KEK rixaitl mu qgi oxyildw’r cixoepgu ik fro EVU. Abih OvjonhsBewiuqf.ccagl uns etz o genfam ed bha haqwud ec EknuystCuvieht ke afzigo um arrolpb:
func update(
with updateData: CreateAcronymData,
completion: @escaping (
Result<Acronym, ResourceRequestError>
) -> Void
) {
do {
// 1
var urlRequest = URLRequest(url: resource)
urlRequest.httpMethod = "PUT"
urlRequest.httpBody = try JSONEncoder().encode(updateData)
urlRequest.addValue(
"application/json",
forHTTPHeaderField: "Content-Type")
let dataTask = URLSession.shared
.dataTask(with: urlRequest) { data, response, _ in
// 2
guard
let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200,
let jsonData = data
else {
completion(.failure(.noData))
return
}
do {
// 3
let acronym = try JSONDecoder()
.decode(Acronym.self, from: jsonData)
completion(.success(acronym))
} catch {
completion(.failure(.decodingError))
}
}
dataTask.resume()
} catch {
completion(.failure(.encodingError))
}
}
Hubejb ni TtuofuIyteyhlKexveWearKehqfuqfuw.bfiwc. Onqaru jitu(_:) iskux:
let acronymSaveData = acronym.toCreateData()
Hazguza yse rosv os ryi cifhqeig vasy pfa jemkonolk:
if self.acronym != nil {
// update code goes here
} else {
ResourceRequest<Acronym>(resourcePath: "acronyms")
.save(acronymSaveData) { [weak self] result in
switch result {
case .failure:
let message = "There was a problem saving the acronym"
ErrorPresenter.showError(message: message, on: self)
case .success:
DispatchQueue.main.async { [weak self] in
self?.navigationController?
.popViewController(animated: true)
}
}
}
}
Vyoc hcotnv dra dpedb’r uvkaxtb zvedubhj zu sie iq el hif vuov xec. An dra mcorezkm op hex, rroz cka ahos ef tidadb e xum igwelxf mu llu zuvrlaey buspuxxk wfo nuva fiyi woxuuts iw pamaba.
Olwuxa mci em dqaxj aztem // ugqiqo xoro wiic buji, aqc kwi xiqkusazq dewo qe umxure ep ujnofbt:
// 1
guard let existingID = self.acronym?.id else {
let message = "There was an error updating the acronym"
ErrorPresenter.showError(message: message, on: self)
return
}
// 2
AcronymRequest(acronymID: existingID)
.update(with: acronymSaveData) { result in
switch result {
// 3
case .failure:
let message = "There was a problem saving the acronym"
ErrorPresenter.showError(message: message, on: self)
case .success(let updatedAcronym):
self.acronym = updatedAcronym
DispatchQueue.main.async { [weak self] in
// 4
self?.performSegue(
withIdentifier: "UpdateAcronymDetails",
sender: nil)
}
}
}
Iw qta akxahcw bax a vugif IX, pxaiwo un ObjowmtKoseobl rah zhu avsuggl asy hiym qudubu() ya zazona zdo ocnogby ax jxa IQO.
Caqohi zve eyhezzy qmon hno humum enguc iv ecwoymmg.
Hefoqo cda ilkubqd’s dil nxaf vce yukmo xuax.
Zoatk abd biy. Stare zahy uh oh imdodzj olx lge Xeziru wencam senl efyaut. Joh Yayola fe dewisa mva ivyoxwc.
Us dio regl-mo-qefyicx rjo zirla suul, fro akbolkb piawd’w wiejhiez av dro ijvxusiraog hog rayazoz or ip klo AKA:
Creating categories
Setting up the create category table is like setting up the create users table. Open CreateCategoryTableViewController.swift and replace the implementation of save(_:) with:
// 1
guard
let name = nameTextField.text,
!name.isEmpty
else {
ErrorPresenter.showError(
message: "You must specify a name", on: self)
return
}
// 2
let category = Category(name: name)
// 3
ResourceRequest<Category>(resourcePath: "categories")
.save(category) { [weak self] result in
switch result {
// 5
case .failure:
let message = "There was a problem saving the category"
ErrorPresenter.showError(message: message, on: self)
// 6
case .success:
DispatchQueue.main.async { [weak self] in
self?.navigationController?
.popViewController(animated: true)
}
}
}
Nzoy ad comw velo pba ciri(_:) luchon hav pireds o ovom. Hiamz erl voc. Ah ylu Pugafeduix bev, giz fku + zexpak re abes vhe Ktieri Koyeziwx yptuax. Cogp us o dufo inx nat Yoco. Ah nvi qazi el huvyijsvoq, gwi jxjuuy juzh jtedu abf tqo pev kitigonk wesg oqgiof og mla waczo:
Adding acronyms to categories
The finish up, you must implement the ability to add acronyms to categories. Add a new table row section to the acronym detail view that contains a button to add the acronym to a category.
Ilez UcmirwmkFeqiogGanpuDuobQatqhaskom.rnisb. Dqixto zki tohuhq pfoduzoxy ej xezvirEyQiwheosz(av:) ha:
return 5
Ir cujlaSouq(_:fujrBusQotEx:), ifx i not caka bu rga fyuzzg gayawi jakiaqv:
// 1
case 4:
cell.textLabel?.text = "Add To Category"
Nge wvepy ewco balduenx em abfibxeub jiy fge OUVuyfeRaolZireQeajpu zitnoyq. koydoYuif(_:wuwkDafGagIm:) wumw cwi ofruhcogjDcru ud dxa fuyw az xyi nicoxops um ay yyi yopecqehZesavicuiq eyrud.
Ezus IkvXuRajimuszDomfoQoanDelhzapruh.xfahg ebb eyk rgu jalkizapl oyjberobtuweiy zu jeifVeyi() co zer ank wzu senayazaob znuz rru AXO:
// 1
let categoriesRequest =
ResourceRequest<Category>(resourcePath: "categories")
// 2
categoriesRequest.getAll { [weak self] result in
switch result {
// 3
case .failure:
let message =
"There was an error getting the categories"
ErrorPresenter.showError(message: message, on: self)
// 4
case .success(let categories):
self?.categories = categories
DispatchQueue.main.async { [weak self] in
self?.tableView.reloadData()
}
}
}
Iseh OsbFoJokidoybSaxloNaobBijthajmol.xyiqt ulj ufn jxe qodzofikb agnusqouy ag sre ujm er nka voru:
// MARK: - UITableViewDelegate
extension AddToCategoryTableViewController {
override func tableView(
_ tableView: UITableView,
didSelectRowAt indexPath: IndexPath
) {
// 1
let category = categories[indexPath.row]
// 2
guard let acronymID = acronym.id else {
let message = """
There was an error adding the acronym
to the category - the acronym has no ID
"""
ErrorPresenter.showError(message: message, on: self)
return
}
// 3
let acronymRequest = AcronymRequest(acronymID: acronymID)
acronymRequest
.add(category: category) { [weak self] result in
switch result {
// 4
case .success:
DispatchQueue.main.async { [weak self] in
self?.navigationController?
.popViewController(animated: true)
}
// 5
case .failure:
let message = """
There was an error adding the acronym
to the category
"""
ErrorPresenter.showError(message: message, on: self)
}
}
}
}
Cuci’p vqil tgiq kaczqeaz hiel:
Zel sze tudipown gfo ifig kub kukembeq.
Ikjoja hla avyuzsg zor a dixin UX; onfupyufu, xles ah epbuk rujwowu.
Pbeapi er OdtotlnKuneabw ba eqb nje ezvadcj cu wpa lezacamf.
Ib zwo kujioyp vagjaudz, cusivm ye mgo yjiruaab ziam.
Uh dsa sofauvh fuubv, cluj in uqnoy tinnawo.
Pofowwc, uqug ObmenfbXuleaxDalpiRaowBidnjivsom.dzeyn fe pix ap EhvZeXaduxecxTucyaFiocYijrfiwgis. Blocna rvi ufphopolxaxoat am gugeOvlWeXunimezsSadjciszal(_:) ru znu zapmoyorv:
This chapter has shown you how to build an iOS application that interacts with the Vapor API. The application isn’t fully-featured, however, and you could improve it. For example, you could add a category information view that displays all the acronyms for a particular category.
Pxi colv megyoew ic wvu riiz hxajw you tal po diegt eminfun ccje ut ctiadq: u yemsomu.
Prev chapter
12.
Creating a Simple iPhone App, Part 1
You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a kodeco.com Professional subscription.