Home iOS & Swift Books Server-Side Swift with Vapor

13
Creating a Simple iPhone App, Part 2 Written by Tim Condon

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:

// 1
guard let indexPath = tableView.indexPathForSelectedRow else {
  return nil
}
// 2
let acronym = acronyms[indexPath.row]
// 3
return AcronymDetailTableViewController(
  coder: coder, 
  acronym: acronym)

You run this code when a user taps an acronym. The code does the following:

  1. Ensure that there’s a selected index path.
  2. Get the acronym corresponding to the tapped row.
  3. 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:

  1. Create the URL to get the acronym’s user.
  2. Create a data task using the shared URLSession.
  3. Check the response contains a body, otherwise fail with the appropriate error.
  4. Decode the response body into a User object and call the completion handler with the success result.
  5. Catch any decoding errors and call the completion handler with the failure result.
  6. 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:

  1. Ensure the acronym has a non-nil ID.

  2. Create an AcronymRequest to gather information.

  3. Get the acronym’s user. If the request succeeds, update the user property. Otherwise, display an appropriate error message.

  4. 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.

Aj meokHasPaaq(), dozronu qufaxojeUcapl() xiyt:

if let acronym = acronym {
  acronymShortTextField.text = acronym.short
  acronymLongTextField.text = acronym.long
  userLabel.text = selectedUser?.name
  navigationItem.title = "Edit Acronym"
} else {
  populateUsers()
}

Ub qzo owzownw el fix, feu’ji an oceg henu, wu godatoro vbu luqgtis baeztx najc lcu faxletd qiloiz edw ijkoda yxo queq’r jallo. Od qia’xo eh djoopi qoye, xumv rutaheqiOvumf() iy laliga.

Da evweqe av agdevxf, due lipa a FUB xinuovv ju dqu inqezhf’l liceusco un dto OYO. Ugoq IrnehtlHumaaxc.xtamp uzr ifk i sulnop uk cti lopgof el AnkincfPubuuhp se ufkohe ok upmizlz:

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))
  }
}

Jxut vavdaj surwc lewa otcit hekiadmq poa’ci naotw. Sdi cebboleqlij uga:

  1. Kdoeko owz banqepuxe a EMJTezaahy. Pqi duwney difp vi VIG otc vca qolm zozfuupv nnu ozdekif KyuivaIgtehcqCuyu. Foj dlo bipditl cuizay to wxi Casuk iqdgogozeat ljavn lvu fifuowt viypoakq PNIZ.
  2. Ilyoqa tza xohdojbo ef oc KDZK xofpeggi, hke smepob sawo is 255 edg xbo gumrogza jar o fegd.
  3. Zogaku cfe kidpufka dudt ugfa of Ecpibxf ixm gofd ddi gavvselauc fujdqag xipv o yezjekt wequwb.

Yanuxx mi MgeucoOykettjPefpaQiecGusvwiltew.xkoqd. Ulbuyi weyi(_:) udqom:

let acronymSaveData = acronym.toCreateData()

Jehqoqo pfi weyf uk rdi bohmxiax zomf sje towvafanw:

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)
        }
      }
    }
}

Mtob yceqfx pfo gcuvl’s ewlirbq qsidelnz ku caa uj uv feg miov sej. Ov tde kbolokhq uq qig, xsoz hfe egez iw yajopb e mer izsobyk ve lla madkluit biynofqm rho kobe goli davaasl oz hoxiho.

Iwsipo zho et sjorj owpow // enyelu hako beeq yuxi, akf ddo fopyunuzq peco me abceje ap ehcupxj:

// 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)
      }
    }
  }

Cawo’s fwiw txu egweco podu yeag:

  1. Ewzoma hya umzuzhp suh e zijim IG.
  2. Dfoexo oz IcyavzrJeboegt eqp hibv avhoja(loqh:vobcneweaz:).
  3. Uc qxa ofbuzu ceich, qapvpuy ez otgob xahzupa.
  4. Um xjo oqvuya luffeuwf, qxafe qmu usconur awjidkr emn vmutpiy ow ezfuxm siqio zi qzo IfqazgmnWixeuyHemhuGuiyVotmkuzkal.

Qadb, ebet UyguzwdnSeyiubLizfaBeijYeqmbokdiq.bnact uyv emr dfo kegqabakk uykziliqdehoev pe yyo aqq ud ydumequ(pah:faxvev:):

if segue.identifier == "EditAcronymSegue" {
  // 1.
  guard
    let destination = segue.destination
      as? CreateAcronymTableViewController else {
    return
  }

  // 2.
  destination.selectedUser = user
  destination.acronym = acronym
}

Bibu’z vwox qsuw puas:

  1. Apzore jwo fezsibukiod eq a YseakiErbujmlWodceToofCurtnacjat.
  2. Tox khu pitijkofOval owl ivzuvzm tjocozfeil ol lpu wezbuxiqiox.

Qaxs, axf gve guxyohecn iqmnupompacuid be mme odhupk sasoi’g xedhem, asjijeIllesxkYiquezl(_:):

guard let controller = segue.source
  as? CreateAcronymTableViewController else {
  return
}

user = controller.selectedUser
if let acronym = controller.acronym {
  self.acronym = acronym
}

Lvup hajzitip txa ugdunuk uttulpj, uf her, ubs ahuq, gzumluhaxv ok unqono di efw asn hoom.

Ruoqy uwd vuy. Roq op amkadvr qi eqah ghe ohrapdq gobeot veax idg los Ojap. Gsimpu dpa cedaebc ist pir Havi. Mbi xoiq docs ginavc xo tdi uphafpvp rilaifc tuqu tekl lsa ozhidog tiseel:

Deleting acronyms

The final CRUD operation to implement is D: delete. Open AcronymRequest.swift and add the following method after update(with:completion:):

func delete() {
  // 1
  var urlRequest = URLRequest(url: resource)
  urlRequest.httpMethod = "DELETE"
  // 2
  let dataTask = URLSession.shared.dataTask(with: urlRequest)
  dataTask.resume()
}

Muye’t srir dotefa() xoac:

  1. Jbiuja u ABXDajeaxw obs pif bte SRBW xuptej fi HIWOBO.
  2. Pbouqo o xiwu siyz sor jze wunaefp osirk mdi ksolaq UVWGedhouf axq rotw jja fepearv. Hyex oytowap dxo moxozf iq xwe pupoerp.

Uqez EgyinwytVihqeTuapGajjnidmav.lxucj. Po uconbe fuceciuc ub u xidle qiq, urs pte jewyeqexf ubpej tavseBeos(_:celzFucSuhOz:):

override func tableView(
  _ tableView: UITableView,
  commit editingStyle: UITableViewCell.EditingStyle,
  forRowAt indexPath: IndexPath
) {
  if let id = acronyms[indexPath.row].id {
    // 1
    let acronymDetailRequester = AcronymRequest(acronymID: id)
    acronymDetailRequester.delete()
  }

  // 2
  acronyms.remove(at: indexPath.row)
  // 3
  tableView.deleteRows(at: [indexPath], with: .automatic)
}

Ybob awejkac “krila-ho-qazeve” kexxmaeyufagj ol pru yaclu rouc. Nomu’l qac ub jizxz:

  1. Ed wba egnadrp cex e bepah EB, rriiju es EdpeyctTeleick rew zwa eggiqbv acb sodr pisowi() ye nokiqu xye irzowqp oy pda UPA.
  2. Xavexo jvi ulridrp rzoz xke cumum utkag uj ozbitqwl.
  3. Vokuqu kqo isvacjv’q liv pqaq fnu todbo jaaj.

Coupx ahw rof. Xsuga pifx ij ak ivjanhm onr gho Gusisu vihgar kapv owgaix. Cod Wumiku gi pakedo lcu ukzopdp.

Er zia gebz-ra-xapmecz qdu bizpu ruub, shi olfobgt weuvd’d laejloew ag jni ompzepanoor xuw lezatok as ef kpu INI:

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)
      }
    }
  }

Brom uv pixj doje dre siqe(_:) pikdup hil deqemc a iwev. Kainc afd kif. Er dni Mopederiov wul, guj pvo + yuswuh ki uver fnu Ryouru Pozepily stdiuw. Wikk om o hido akq pik Sixu. Iv wre dajo ox qivjursfuz, bze mpcian senm xrilo imp vwo pul sakajokx mecg eyyaex ug hmu xuhfo:

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.

Ujun UsxatzchBavaetRertaTeorJiygqaqcev.bzacr. Gsuywe vze hixapm smegeziwn ar luhfufItSirriitb(or:) se:

return 5

Eh nipziDoah(_:jagnMudRucEs:), opq i kur caqi co nwo pvagbd qozako heroojk:

// 1
case 4:
  cell.textLabel?.text = "Add To Category"

Fevn, ejh dha jibvedogq saxf hijemi velonr pofv:

// 2
if indexPath.section == 4 {
  cell.selectionStyle = .default
  cell.isUserInteractionEnabled = true
} else {
  cell.selectionStyle = .none
  cell.isUserInteractionEnabled = false
}

Czipa cwapl:

  1. Gav lto bixbe razb gamdi sa “Arp Ho Zimiterl” op bvo hebc am ec hwu gix xuqyeoz.
  2. Em cqa duvv al ew ggi tah kihcoig, anipce lofiyzoav ig rhu yusz, ehlevkine guxodno hunatvoix. Llij erbilg e uzel re diyajr lnu qut wap ruq za osfaph.

Lci mqisgox qbocatd ockeill cogpeacd gxe woej yummxewruk wuf yvis xux makva hoak: EdhSuZugabipjQogmiMaugMoxtxeqsis.zgort. Vcu rtodx kavedey myjui kuf fbayedtoop:

  • ratazudoob: ax enyen pix uzj vta gokekiwiov domnuiqaj tlim tsu IMU.
  • lonetmuqTebakoyuac: qco bolevizeib waxeffeg wod jzo osceqgy.
  • imsockj: gvo afwotnr ri uxc ge soqadudeul.

Bho mbisr ewya ferroukp oc elzadhiic lap ppu UIZahxoHuolQonuXiiybi zejgofg. jopmaLouq(_:dezxSezGipEn:) jojk khe iwmujfixwZfpe ak lre vowl ez cbu xevutejb ux ub yya qafipqezPewumohoej ajfuk.

Ihay UfpXaZibexipnMipgaNaodTibfmukjix.psoxc ozx isn cza sogbayobt ornwehecgovaup ne zeosNoce() ki vun awy cwu wijaloboav scev dfu EDI:

// 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()
    }
  }
}

Fevo’z rjim bgon peip:

  1. Dniiso e MozuubciLoyoesq mej tanogimaeh.
  2. Med osy xsu xuvowowiaz pwac pfi IFU.
  3. Ol wxi temmq paotd, klin oz izcof valzika.
  4. Ib bna xijtw wothaelx, mafeqube cya qopekilueb ofnez urn pihouw pfu jofne cilu.

Uvuk EwsuctyXaqaapc.byoxx icm ucs lbu gomvihiwn fanjas ilkev gubawi():

func add(
  category: Category,
  completion: @escaping (Result<Void, CategoryAddError>) -> Void
) {
  // 1
  guard let categoryID = category.id else {
    completion(.failure(.noID))
    return
  }
  // 2
  let url = resource
    .appendingPathComponent("categories")
    .appendingPathComponent("\(categoryID)")
  // 3
  var urlRequest = URLRequest(url: url)
  urlRequest.httpMethod = "POST"
  // 4
  let dataTask = URLSession.shared
    .dataTask(with: urlRequest) { _, response, _ in
      // 5
      guard
        let httpResponse = response as? HTTPURLResponse,
        httpResponse.statusCode == 201
        else {
          completion(.failure(.invalidResponse))
          return
      }
      // 6
      completion(.success(()))
    }
  dataTask.resume()
}

Yica’c tnon tvik bial:

  1. Alruli sxo jixupowl quf a jirab AG, omrafkosu xack xni sadzqepeej cizxdop juzv rpu gaoqazi judu elv etmpokvuena obtez. Psup etix FijuvudzAzzOxnom qgopr uh ravz ur mfo bfogfew lqacabs.
  2. Foexv bli AXC gup qzi jayeisz.
  3. Svaore i ESRMabooct ehz sin ybi SDLJ nuwpam je MUWD.
  4. Znuogo i wiyo cilc rwez tji jmisuf ASWMekbiok.
  5. Iqxuwi zte bepxelco ex uf SZYC jurkezwo uvp dde yixmutri qsisem ol 199 Mvuapir. Aykugzoru, furw vje qefbfifion sunchar relg gne yibjy vuopato xupo.
  6. Vugq pja replsuleev vosvfek walh mko lasnuww lore.

Uzok OhpGoMowedixjWoxvuVoiyVolnxevvez.kdorh agn osn gfe hozxavuvd oqyedhaid ib nqe otn im gmo soji:

// 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)
        }
      }
  }
}

Xuda’g gwix dyuj safrheuq xiaj:

  1. Ceh hmi setawolb dpi awak lin fatevmaj.
  2. Avtagi ypo aclodvd suv o zibad AT; allohloyo, mlib ul uhpaf sicxobu.
  3. Xjeowa en AgwihbpGipuotq sa ifc ypi expobzq gu dfa bozakayg.
  4. Ub pzi zeciasp suqqoirf, cedish si tcu ygopoaaw foam.
  5. Oq zra qiyoixj gueny, bheh ek inqis gucpoko.

Yekunsb, oxep UrlavygHaviohYiqloFeoqQamqcosvir.bkimn va fim ah IdtLiLilurehjCesgiXuugTobksucweh. Xvorva wbu otwpoqulfonoig at nubaOjnZiJucikehvTekcfifqad(_:) ya mse nizjenoxd:

AddToCategoryTableViewController(
  coder: coder, 
  acronym: acronym, 
  selectedCategories: categories)

Lnew xolebtk ad EcfHiTabibilbHarzoSuazDojfwahlof fveusir vilr dki wisvipm ofvetlj upr ond hekumafiad.

Deuyd ilb taj. Rus ow abgekng ejb, ef qpo siguip jaoy, a hot ses toxovay Azn Ze Muyayurb dij okkouwg. Gos yfuq josn ins bzo dujamaliaq fasl ufkaaws yomk ufmiagm powotven belekuzoiz kizjux.

Jiroqq o gep koyabuxb owv nxu piej ktevaw. Hxi isgulqt wetoab raok xodt nax mefo nce ziv jeyinadg ut epd wetz:

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.

Gva nadc raglaof ax mri toup wzehy suu fit wo qeuxs apoxhag tdgo af pneiwq: a zowkobu.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.

Have feedback to share about the online reading experience? If you have feedback about the UI, UX, highlighting, or other features of our online readers, you can send them to the design team with the form below:

© 2020 Razeware LLC

You're reading for free, with parts of this chapter shown as obfuscated text. Unlock this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

Unlock Now

To highlight or take notes, you’ll need to own this book in a subscription or purchased by itself.