Categories
Current Events Tampa Bay Uncategorized

Saving data in iOS apps / How do you actually write an app?, part 2

In the last installment, we laid out the foundation for a simple “notepad”-style app where the user can type in some text, save it with the press of a “Save” button, and then retrieve the saved text of by pressing the “Load” button.

The recap

Here’s what we did in the previous article: we built a simple app with only three controls…

  1. A text view, where the user enters and edits text,
  2. A Save button, which will eventually save the text currently in the text view, and
  3. A Load button, which will eventually retrieve the saved text and put it into the text view.

We applied constraints to the control and wired them up with an outlet and actions as shown below:

Click the diagram to see it at full size.

At the end of the last article, the Save and Load buttons didn’t save or load — they simply changed the contents of the text view. Clicking the Save button changes the contents of the text view to this:

Here’s the code for the Save button action:

@IBAction func saveButtonPressed(_ sender: UIButton) {
  userText.text = "You pressed the 'Save' button!"
}

Clicking the Load button changes the contents of the text view to this…

…and here’s the code for its action:

@IBAction func loadButtonPressed(_ sender: UIButton) {
  userText.text = "Ah, the 'Load' button. Nice."
}

In this article, we’ll make the Save and Load buttons do what they’re supposed to do: save and load the user’s text.

But before we do that, we need to look at accessing files in iOS.

The iOS filesystem

An iOS app’s access to its iDevice’s filesystem isn’t like the access a desktop application have to its computer’s filesystem. Every iOS app is cordoned off to its own space in the filesystem that only it can access: its sandbox. For two apps — we’ll call them App A and App B — App A’s filesystem access is limited to the App A sandbox, and App B’s is limited to the App B sandbox.

I’ve taken the filesystem sandbox diagram from Apple’s File System Programming Guide and added a few details to it:

For our notepad app, we’re going to store the data that the user types inside a directory in the Data container, which is the part of the sandbox specifically for storing user- and application-related data.

The data container contains a number of subdirectories, one of which is the Documents directory, the designated directory for files that the user will create, import, delete or edit. As an added bonus, unless you specify otherwise, files in this directory are backed up by default.

What we want to do now

We want to change the code in our app’s saveButtonPressed and loadButtonPressed methods so that they do the following:

  • saveButtonPressed: Take the contents of the userText text view and save them in the Documents directory in a file named savefile.txt.
  • loadButtonPressed: Take the contents of the file named savefile.txt in the Documents directory and put them into the userText text view.

Getting our hands on the Documents directory

We’ll store and retrieve our notepad app’s data to and from a file in the Documents directory, which we’ll access with the help of…

  • The FileManager.default object, which is a one-stop object for working with the file system that should work for most common file system operations, including reading and writing files, and
  • FileManager’s url method, which we use to access specific directories in the sandbox. If we pass it a parameter that we want the user’s Documents directory for the app, we get an URL struct representing the location of that directory.

Here’s code that gets the Documents directory for the current app and stores it in a constant named documentsDirectoryURL (don’t bother entering this right now):

let documentsDirectoryURL = try! FileManager.default.url(for: .documentDirectory,
                                                         in: .userDomainMask,
                                                         appropriateFor: nil,
                                                         create: false)

What powers this code is the url(for:in:appropriateFor:create:) method, which can locates and returns the URL for a directory in a given domain. It can even create a directory. It has these parameters:

  • for: The directory to search for, which we specify with a value of the enum FileManager.SearchPathDirectory. In this case, we’re looking for the Documents directory, so we’ll use the value FileManager.SearchPathDirectory.documentDirectory, or .documentDirectory for short.
  • in: Where to look for the directory, which we specify with a value of the enum FileManager.SearchPathDomainMask. In this case, we’re searching the user’s filespace, so we’ll use the value FileManager.SearchPathDomainMask.userDomainMask, or .userDomainMask for short.
  • appropriateFor: This is often used to specify a temporary directory to be created, which we’re not doing here. As a result, we’re simply setting this to nil, which says “No, we’re not creating a temporary directory”.
  • create: A boolean value that specifies if the directory should be created if it doesn’t already exist. In this case, we don’t want to do that, so we’ll set this value to false.

Now that we have the URL for the documents directory, we now need an URL for the file where we’ll save and retrieve our notepad data from.

Get the URL for the file we want to save our data in, savefile.txt

Once we have the Documents directory, we can create an URL that refers to a file in that directory by using the URL struct’s URL(fileURLWithPath:relativeTo:)method and store it in saveFileURL (again, don’t bother entering this right now; we’ll do some coding very soon):

let saveFileURL = URL(fileURLWithPath: "savefile.txt",
                      relativeTo: documentsDirectoryURL)

The method has these parameters:

  • fileURLWithPath: The name of the file path.
  • relativeTo: The base URL for the file path. Note that we’re using the documentsDirectoryURL constant that we created in the previous section.

Now let’s enter some code — a computed property that gives us the URL for the file where we want to save data, saveFileURL. Put this inside the ViewController class, outside any methods:

private var saveURL: URL {
  let documentsDirectoryURL = try! FileManager.default.url(for: FileManager.SearchPathDirectory.documentDirectory,
                                                           in: .userDomainMask,
                                                           appropriateFor: nil,
                                                           create: false)
  return URL(fileURLWithPath: "savefile.txt",
             relativeTo: documentsDirectoryURL)
}

With this property, we can now make the Save and Load buttons do what they’re supposed to.

Making the Save button save notepad data

Change the saveButtonClicked(_:) method so that it looks like this:

@IBAction func saveButtonClicked(_ sender: Any) {
  try! userText.text.write(to: saveURL,
                           atomically: true,
                           encoding: .utf8)
}

In our reformulated method, we take the text property of the userText text area — an instance of String that contains the text inside the text area — and use String’s write(to:atomically:encoding:) method to write the text property’s contents to file referenced by our saveURL computer property. Here’s what the other parameters are for:

  • atomically: A boolean, that if set to true, has the write(to:atomically:encoding:) method first write the string to a temporary file, and then once done, rename the temporary file to its final name. This ensures that if the iPhone or iPad crashes during the write process, you won’t end up with a corrupted file.
  • encoding: The way in which the string we’re saving should be encoded, which we specify with a value of the enum String.Encoding. We want to save the string in the preferred string format of the web, UTF-8, so we’ll use the value String.Encoding.utf8, or .utf8 for short.

The Save button now does what it’s supposed to do: save the contents of the text area to a save file.

Making the Load button load saved data

Change the loadButtonClicked(_:) method so that it looks like this:

@IBAction func loadButtonClicked(_ sender: Any) {
  do {
    try userText.text = String(contentsOf: saveURL)
  } catch {
    userText.text = "Encountered an error while trying to load saved data."
  }
}

In our reformulated method, we take the text property of the userText text area — an instance of String that contains the text inside the text area — and use String’s init(contentsOf:) method to fill it with the contents of the file referenced by our saveURL computer property. The Load button now does what it’s supposed to do: load the contents of the save file into a text area.

Take a look at your view controller code

The code in ViewController.swift should look like this now:

import UIKit

class ViewController: UIViewController {

  @IBOutlet weak var userText: UITextView!


  // View controller events
  // ======================

  override func viewDidLoad() {
    super.viewDidLoad()
  }

  override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
  }


  // Save and load button events
  // ===========================

  @IBAction func saveButtonClicked(_ sender: Any) {
    try! userText.text.write(to: saveURL,
                             atomically: true,
                             encoding: String.Encoding.utf8)
  }

  @IBAction func loadButtonClicked(_ sender: Any) {
    do {
      try userText.text = String(contentsOf: saveURL)
    } catch {
      userText.text = "Encountered an error while trying to load saved data."
    }
  }

  private var saveURL: URL {
    let documentsDirectoryURL = try! FileManager.default.url(
      for: FileManager.SearchPathDirectory.documentDirectory,
      in: .userDomainMask,
      appropriateFor: nil,
      create: false)
    return URL(fileURLWithPath: "savefile.txt",
               relativeTo: documentsDirectoryURL)
  }

}

If your code looks like this, it’s ready to run.


If you like, you can download the finished project here [37K .zip].

Run the app!

If you’ve followed the steps in this article and the previous one, the app should work as expected. You should be able to:

  1. Type into the text view.
  2. Save the contents of the text view by pressing the Save button.
  3. Change the contents of the text view.
  4. Restore the contents of the text view to the saved text by pressing the Load button.

Let’s change up the controls whose value we’re saving (or: “What if we changed the text view to a slider?”)

The project that we just covered was the one that I used in the last Tampa iOS Meetup. At the end of the session, one of the attendees asked an interesting question:

“What if we changed the text view to something else? Maybe a slider?”

“Why don’t we give it a try right now?” I replied. “Let’s change the app so that the Save button saves the current position of the slider, and the Load button restores the slider to the saved position.”

I opened Main.storyboard, deleted the text view, and then dragged a slider into its place.

With the slider still selected, I set its constraints as shown above.

Then, I created an outlet for the slider. Using the Assistant Editor, I:

  1. Deleted the old text view outlet,
  2. Control-dragged from the slider into ViewController.swift, and
  3. Gave the outlet the name userValue.

It’s now time to change the code behind the Save and Load buttons.

Making the Save button save the slider position

Here’s the revised saveButtonClicked(_:) method:

@IBAction func saveButtonClicked(_ sender: Any) {
  let sliderValue = String(userValue.value)
  print("Saving slider value: \(sliderValue)")
  try! sliderValue.write(to: saveURL,
                         atomically: true,
                         encoding: String.Encoding.utf8)
}

Here’s what’s happening inside this method:

  1. We convert the slider’s value property — by default, a Float whose value can range from 0 (all the way to the left) to 1 (all the way to the right) — and convert it to into a String.
  2. For debugging’s sake, we print the String-ified slider value.
  3. We take the String-ified slider value and use String’s write(to:atomically:encoding:) method to write its value to savefile.txt.

Making the Load button restore the slider position

Here’s the revised loadButtonClicked(_:) method:

@IBAction func loadButtonClicked(_ sender: Any) {
  do {
    let sliderValue = try String(contentsOf: saveURL)
    print("Loading slider value: \(sliderValue)")
    userValue.value = Float(sliderValue)!
  } catch {
    print("Encountered an error while trying to load saved data.")
  }
}

Here’s what’s happening inside this method:

  1. We take the contents of the file referenced by our saveURL computer property and put it inside the constant sliderValue.
  2. For debugging’s sake, we print sliderValue.
  3. We convert sliderValue into a Float and use that value to set the position of the slider.

Once again, take a look at your view controller code

The code in ViewController.swift should look like this now:

import UIKit

class ViewController: UIViewController {

  @IBOutlet weak var userValue: UISlider!

  // View controller events
  // ======================

  override func viewDidLoad() {
    super.viewDidLoad()
  }

  override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
  }


  // Save and load button events
  // ===========================

  @IBAction func saveButtonClicked(_ sender: Any) {
    let sliderValue = String(userValue.value)
    print("Saving slider value: \(sliderValue)")
    try! sliderValue.write(to: saveURL,
                           atomically: true,
                           encoding: String.Encoding.utf8)
  }

  @IBAction func loadButtonClicked(_ sender: Any) {
    do {
      let sliderValue = try String(contentsOf: saveURL)
      print("Loading slider value: \(sliderValue)")
      userValue.value = Float(sliderValue)!
    } catch {
      print("Encountered an error while trying to load saved data.")
    }
  }

  private var saveURL: URL {
    let documentsDirectoryURL = try! FileManager.default.url(
      for: FileManager.SearchPathDirectory.documentDirectory,
      in: .userDomainMask,
      appropriateFor: nil,
      create: false)
    return URL(fileURLWithPath: "savefile.txt",
               relativeTo: documentsDirectoryURL)
  }

}

If your code looks like this, it’s ready to run.

If you like, you can download the finished project here [37K .zip].

What we’ve done so far

In this exercise, we have, over two articles:

  • Created a single-view app and added controls to the app’s single view.
  • Constrained the controls so that they stay in the proper place, regardless of screen size and orientation.
  • Connected the controls to the underlying view controller code using outlets and actions.
  • Added code to use the buttons’ actions to respond to being pressed, and to use the text view’s outlet to change its contents.
  • Learned about the iOS filesystem.
  • Learned how to access the Documents directory in an app’s sandbox.
  • Changed the code behind the app’s buttons so that the Save button saves the contents of the text view, and the Load button loads the text view with the saved data.
  • Changed the control from a text view to a slider, and made the modifications to the Save and Load button code to save and load the position of the slider.

What now?

If you’re in the Tampa Bay area, come to Tampa iOS Meetup next Tuesday, May 22nd, when I’ll walk you through the process of building a “to-do list” app, and in the process cover:

  • Tables, which is how you present lists in iOS,
  • the Codable protocol, another way to save data in iOS,
  • and finally (and most importantly), getting into the mindset of applying what you know to actually write apps.

After the meetup, I’ll write it up here. Stay tuned!

Other articles in this series

Categories
Current Events Tampa Bay Uncategorized

What’s happening in the Tampa Bay tech/entrepreneur scene (Week of Monday, May 14, 2018)

Every week, I compile a list of events for developers, technologists, tech entrepreneurs, and nerds in and around the Tampa Bay area. We’ve got a lot of events going on this week, and here they are!

Monday, May 14

Tuesday, May 15

Wednesday, May 16

Thursday, May 17

Friday, May 18

Saturday, May 19

Sunday, May 20

Categories
Uncategorized

My reaction to Facebook’s plans to launch their own cryptocurrency

“Facebook reportedly plans to launch its own cryptocurrency,” reads The Verge’s headline, followed by the subhead “With an eye towards payments on the social media platform”. My immediate reaction is best summed up with the picture below:

Categories
Uncategorized

Get my “Getting Started with ARKit” tutorial video and code from RWDevCon 2018 for FREE!

If you missed my two-hour tutorial session on ARKit programming at RWDevCon 2018, Getting Started with ARKit, you’re in luck — you can get the video of the session, along with the slides, step-by-step instructions, and starter and finished code for free!

RWDevCon is an annual conference organized by the people behind RayWenderlich.com, the premier site for mobile app development tutorials. Over three days in early April 2018, the conference featured:

  • 4 intensive half-day hands-on workshops, including my in-depth ARKit session,
  • 18 hands-on tutorials, including my “Getting Started with ARKit” tutorial included in the free bundle,
  • Some very moving inspiration talks, and
  • A number of great parties, including one where I got to join the band, James Dempsey and the Breakpoints (who are playing later this year at WWDC)!

My free tutorial, Getting Started with ARKit

Go and download the materials from my tutorial, where you’ll enjoy all sorts of interesting stuff, including gratuitous meme abuse…

…the secret history of augmented reality, including what could be the first literary reference to what would eventually become AR glasses (and written by the author of The Wizard of Oz)…

…what I consider to be some of the best diagrams explaining key ARKit concepts anywhere…

…and a complete walk through the process of building a couple of apps that are both fun to use and show you how to write your own AR apps:

  1. Happy AR Painter, which you can think of as a less expensive version of Google’s Pixel Brush, but with your iPhone or iPad, and
  2. Raykea, my tribute to IKEA Place, which lets you see what stuff from my pretend semi-disposable funny-named furniture catalog would look like in your room.

The RWDevCon 2018 Vault

Part of what you get for RWDevCon’s price of admission is the RWDevCon 2018 Vault, which is the next-best thing to being there. It includes:

  • Four intensive, half-day workshop videos (including my in-depth ARKit session, where I walk you through building four ARKit apps)
  • 18 hands-on tutorial session videos (which includes Getting Started with ARKit)
  • Over 500 MB of complete sample project code for all tutorial sessions and workshops
  • Over 500 pages of conference instructional workbooks in PDF format

If you didn’t attend RWDevCon 2018 but still want the bundle, you can buy it. Better yet, for a limited time, you can buy it for half price — $99.99 instead of $199.99! (And in case you were wondering, I don’t make any money from sales of the Vault.)

Feeling lucky?

RayWenderlich.com is giving away three copies of the RWDevCon 2018 Vault to three lucky people! Enter the draw by going to this article on their site and leave a comment. On Friday, May 18, they’ll pick three random commenters and give them a free copy of the Vault.

Categories
Current Events Tampa Bay Uncategorized

What’s happening in the Tampa Bay tech/entrepreneur scene (Week of Monday, May 7, 2018)

Every week, I compile a list of events for developers, technologists, tech entrepreneurs, and nerds in and around the Tampa Bay area. We’ve got a lot of events going on this week, and here they are!

Monday, May 7

Tuesday, May 8

Wednesday, May 9

Thursday, May 10

Friday, May 11

Saturday, May 12

Sunday, May 13

Categories
Uncategorized

From the April 24, 2018 Tampa iOS Meetup: Saving data in iOS apps / How do you actually write an app?

I’ve been doing a number of programming presentations lately: my monthly Tampa iOS Meetup gatherings, my recent ARKit workshop and tutorial at RWDevCon, an intro to Android programming with Kotlin at DevFest Florida, and an ARKit session at Tampa CodeCamp. At each of these gatherings, I’ve had post-presentation Q&A sessions, and without fail, I’m asked a question along these lines:

“I’ve taken some programming courses, I’ve followed some coding tutorials, and I’ve gone through some development books. But when I set out to write an app, I have no idea how to begin. How do you actually write an app?

On this blog, over the next few months, I’ll try to answer that question indirectly — by example. As my first example, I’ll take the app that I and the attendees of the most recent Tampa iOS Meetup built as a group.

That meetup was the first in a series on saving data in iOS apps, and focused on writing an app that demonstrated saving and retrieving data to the file space reserved for the app by iOS. The app was a simple notepad app that had just three controls:

  1. A text area where the user can enter notes,
  2. A Save button, which would save whatever text was in the text area for later retrieval, and
  3. A Load button, which would retrieve previously saved text and put it into the text area.

Here’s a quick hand-drawn “wireframe” for the app:

 

Let’s get started! Open Xcode and start with a new project (FileNewProject…), create a Single View App, and find some place to save it. Once that’s done, we can get to work.

Add controls to the app

Open Main.storyboard and drag a Text View and two Buttons — with one’s Title property set to Save, and the other’s set to Load — onto the view as shown in the screenshot below:

Click the screenshot to see it at full size.

Apply constraints to the controls

With those controls in place, we want to apply some constraints to them so that we get the following effect:

  • We want the text view to take up most of the screen, with its top, left, and right edges of the text view sticking close to the top, left and right edges of the screen, regardless of screen size and orientation.
  • We want the Save button to stick to the lower left-hand corner of the screen, regardless of screen size and orientation.
  • We want the Load button to stick to the lower right-hand corner of the screen, regardless of screen size and orientation.

We’ll apply the constraints to each of these controls, clicking the button, and setting the constraints using the pop-up that appears:

Click the screenshot to see it at full size.

  • For the Text View, select it, click the  button, and set the following constraints:
    • Top: 0 pixels
    • Left: 16 pixels
    • Right: 16 pixels
    • Check the Constrain to margins checkbox
  • For the Save Button, select it, click the  button, and set the following constraints:
    • Top: 20 pixels
    • Left: 16 pixels
    • Bottom: 20 pixels
    • Check the Constrain to margins checkbox
  • For the Load Button, select it, click the  button, and set the following constraints:
    • Top: 20 pixels
    • Right: 16 pixels
    • Bottom: 20 pixels
    • Check the Constrain to margins checkbox

Connect the text view to the code using an outlet

Open the Assistant Editor — do it with the button. Xcode should show you two things now:

  • The view that you were just editing, and
  • ViewController.swift

It’s time to create an outlet for the text view, a way to refer to it in code. Select it in Main.storyboard, and then control-drag from it into an empty line near the top of the ViewController class, as shown in the screenshot below:

Click the screenshot to see it at full size.

Release the mouse or trackpad button, and you’ll see a pop-up appear:

Click the screenshot to see it at full size.

Adjust the settings in the pop-up so that you create an outlet for the text view named userText. Use the settings, as shown in the screenshot above:

  • Connection: Outlet
  • Name: userText
  • Type: UITextView
  • Storage: Weak

Then click the Connect button to make the connection. You should now see this line added to the ViewController class:

@IBOutlet weak var userText: UITextView!

With this connection, we now have a way to refer to the text view in code: userText, the name of the outlet connecting the code to the text view. We’ll use this outlet soon, but let’s connect the buttons to the code, starting with the Save button.

Connect the Save and Load buttons to the code using an action

Let’s create an action for the Save button, which is a method that gets called in response to an event raised by the button. Select it in Main.storyboard, and then control-drag from it into an empty line near the bottom of the ViewController class, as shown in the screenshot below:

Click the screenshot to see it at full size.

Release the mouse or trackpad button, and you’ll see a pop-up appear:

Click the screenshot to see it at full size.

Adjust the settings in the pop-up so that you create an action for the text view named saveButtonPressed that gets called when the button is pressed. Use the settings, as shown in the screenshot above:

  • Connection: Action
  • Object: View Controller
  • Name: saveButtonPressed
  • Type: UIButton
  • Event: Touch Up Inside
  • Arguments: Sender

Then click the Connect button to make the connection. You should now see these lines added to the ViewController class:

@IBAction func saveButtonPressed(_ sender: UIButton) {
}

We’ll fill that method with code that will execute in response to a press on the Save button soon. But first, we need to make our final connection: the one for the Load button.

Select the Load button in Main.storyboard, and then control-drag from it into an empty line near the bottom of the ViewController class, as shown in the screenshot below:

Click the screenshot to see it at full size.

Release the mouse or trackpad button, and you’ll see a pop-up appear:

Click the screenshot to see it at full size.

Adjust the settings in the pop-up so that you create an action for the text view named loadButtonPressed that gets called when the button is pressed. Use the settings, as shown in the screenshot above:

  • Connection: Action
  • Object: View Controller
  • Name: loadButtonPressed
  • Type: UIButton
  • Event: Touch Up Inside
  • Arguments: Sender

Then click the Connect button to make the connection. You should now see these lines added to the ViewController class:

@IBAction func loadButtonPressed(_ sender: UIButton) {
}

Again, we’ll fill that method with code that will execute in response to a press on the Load button.

Run the app. You should see something like this:

Since you haven’t written any code, the Save and Load buttons do nothing…yet.

Let’s make the Save and Load buttons do something when pressed

Let’s add some code to change the contents of the text view in response to presses on the Save and Load buttons:

  • Save: “You pressed the ‘Save’ button!”
  • Load: “Ah, the ‘Load’ button!”

Change the code for the saveButtonPressed and loadButtonPressed methods:

@IBAction func saveButtonPressed(_ sender: UIButton) {
  userText.text = "You pressed the 'Save' button!"
}

@IBAction func loadButtonPressed(_ sender: UIButton) {
  userText.text = "Ah, the 'Load' button. Nice."
}

Run the app again, and press the Save button. You should see this result:

Now press the Load button. You should see this:

What we’ve done so far

In this exercise, we have:

  • Created a single-view app and added controls to the app’s single view.
  • Constrained the controls so that they stay in the proper place, regardless of screen size and orientation.
  • Connected the controls to the underlying view controller code using outlets and actions.
  • Added code to use the buttons’ actions to respond to being pressed, and to use the text view’s outlet to change its contents.

What we’ll do in the next installment

In the next installment, we’ll make the buttons do what we set out to have them do:

  • The Save button will take whatever text is in the text view and save it, and
  • The Load button will take whatever text data was saved, retrieve it, and put it into the text view.

Other articles in this series

Categories
Uncategorized

Two Facebook stories, one picture of Silicon Valley in a nutshell

Image: Welcome to late-stage Silicon Valley, with two clippings: 1. A Financial Times piece on Facebook announcing its dating feature, and 2. A Motherboard piece on facebook firing an employee who allegedly used his data access to stalk women.

Click the image to see it at full size.

Welcome to late-stage Silicon Valley, folks, where Facebook’s announcement that they’re adding dating features and the revelation that they had to fire someone for using their inside access to Facebook profiles to stalk women come less than 24 hours apart.

Perhaps it’s time to have some kind of Hippocratic Oath for data or a computer/data science equivalent of a Pugwash (a series of conferences for scientists and engineers to bring their expertise, insight, and reason to threats brought about by the weaponization of science and technology).

While I’m on the topic, here’s some additional reading: