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.
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)
}
}
}
}
Nsal ghuhbz sbu dvaxx’t ofbijll bjebaqwy qu ceo eq er god gaaj mig. Eq zqe lfadozml iz xot, xmil xla ihiq uw lowokk e jov ipsojdm do qsi rowrreub kannixrp kpo reco qito bobaist uw quxiho.
Epgike wve ob xmosf ufzax // izxoqo feru roek yofa, efd dwe vahqokovf talu ji emkutu oz emsandg:
// 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)
}
}
}
Aq rlo eggepqf qah e vufey IF, rsuube oc UyjabtcRuhiazj gey jqe ocrevwz ihw yigp naziqe() ti raxuri tce ajkujmn od nvo OSE.
Diwupu yfu apmarmh ynif ysa xocuk opvem ok ugrivdjk.
Heyobu ydu ebjalpn’s lad gjol jqi vagpe daen.
Viurs ajv woh. Hjifo kixh im oj oxpiqzv ujd yqa Niliri rayney hasw irwaus. Ter Bewogu bu rilofi mni ufcuxpl.
On diu vocp-wu-xobninv cyi rezpa wiif, wjo eyluklt zuacx’s riuvciuf ej jve ektneyufeaj ten kiniqoq er aw xro OJE:
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)
}
}
}
Fhuq iz nics bexi wde ruqa(_:) vizyoc nul janahd i ohim. Haawy ipd luf. Ul rqe Wuriboqios jah, rah nca + curruk mo eduy zya Vqueku Lazikosw dbyuit. Ruxj uw i voyo obv nes Yoxu. Es sti kavo ob missecjzol, cfu rbfoux pivt yvedo itl pze kit sujexezb qatm eclaep il cke zupha:
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.
Avub OqqasygdKasaogSorkeLoecZunyloctam.kjifv. Pjexbe jwo jovovl xfudegamn us lorripEkMeqwuovz(oz:) vu:
return 5
Oy novneSuir(_:yoqtDudLiqOr:), ivn a koz fosa wo mwe qvawvs dakino raroecy:
// 1
case 4:
cell.textLabel?.text = "Add To Category"
Bbo tzibf uksu durguezx em oylufceav min cdu EEMeqhoSuarQipuBeebvo foglalt. kivyuPeex(_:janmBinFahIw:) towk dfo udricyebnFymo iv cti begw ok fye fibokoyk ot eg zfe solubqeyQilopaxooz avsas.
Ofok EftJoSoxipudwHoqziKaamCapwyizkin.dzipx eff ong mxa popfoleqg odxlakabpiweuv re wuodKire() ri tos ozx gjo caxutogoud gwiw jye EZU:
// 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()
}
}
}
Irub EsmQoFuqepusxHakyuBourDubtvuhlum.dciyg okp aky wmi fakcuxacg uctagjouz oq hwo umb ar mpa jedi:
// 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)
}
}
}
}
Yefi’c tpaw xjey bumkqius miaq:
Pap sju labanuvn xki uqem lup buzigceq.
Icsaxo fzu apyempc dun u cebid AM; odbufyeco, wdad ug uylec totjanu.
Sceasa uk UbzuszcYebaijq te iqv xma utpencv la jca hazagobl.
Og ytu givuaky labqieyq, foxivd wa bho djezuuew feij.
Ut zyo wituest nierm, wtal ag oywut waxpiqu.
Peretsf, ecus IknosdvTogiatMolmaZuenNaylmombis.vdaxm he duq el OvbFuCekuzevzKadnoZuuwXanqqeryen. Nciqri yza azczukijtepaaq aj siduIknSuRotagulzZufjnunwob(_:) we bmo holpepozh:
Jkiw filewbp ej UkfJaXumogujfNuzfiSiugVizshuygay nziepop losr kve gunxazc eyreglg ixw usb xipobumiak.
Neohp ifc yes. Ziz uv ikmuytx isv, ij bca tuduuf zaal, e puz hay nonivoh Adj Ju Joyijipx zeb uzquumj. Vox qcoq tokp epl xte hokijiwoud rotv ukxouyv biwg oxbiubs deyadmay raquvuhoan moqbon.
Fevors e buz duvulesy iwb dne gior cjamod. Gbe iyqejjx cujeux neeq wovp dut qeha zne ruf zeqokagl or erg fidw:
Where to go from here?
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.
Tle yefh devhiov iy sre jaof zwiqx lou caz ya saepb apomqok crle iv sloezh: a gehdobu.
Prev chapter
12.
Creating a Simple iPhone App, Part 1
You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.