Our next story is "I want to select the type of snippet I am creating". The tasks are:
SnippetData
model to allow for Text and Photo typesFor this feature, we're going to expand on our SnippetData
model to include the ability to have a type using an enum. Then we're going to create and present an alert controller to the user that allows them to choose a type. Finally, we're going to update our view controller to respond to the different options the user can select, and create the correct type of data.
Current task: Append the SnippetData
model to allow for Text and Photo types.
When we created our SnippetData
model, it was a very simple Swift struct that didn't hold any data. Now we want to add a type property to the struct so that it knows what kind of data it is holding. We're going to create an enum to describe the possible types that can exist for our SnippetData
, and use a String
as a backing type for our enum. This means that our enum will be built on top of String
values. Let's add this enum to our SnippetData.swift
file:
enum SnippetType: String { case text = "Text" case photo = "Photo" }
At the top, we declared a new enum called SnippetType
, and then said that it is built on top of the String
class. This syntax is similar to the way we use inheritance in Swift, which makes sense, since, in a way, we are inheriting and writing on top of a string. Inside the enum, we define two possible cases, Text
and Photo
. We then assign a String
value to our cases, which is the backing (or raw) value of that case.
Then, inside our SnippetData
struct, we add a new property to hold our new SnippetType
information, and update the initializer to take an argument for the type:
struct SnippetData { let type: SnippetType init ( snippetType: SnippetType ) { type = snippetType print ("(type.rawValue) snippet created") } }
At the top of our struct, we declare type
with the let
keyword, which means it is a constant. This makes sense because, once we initialize a snippet, its type should never change. Then, in our init
function, we updated it to accept a parameter for our SnippetType
, and then we assign the parameter value to our type
constant.
Finally, you'll see that we changed our print()
function. We replaced the word new
with the expression (type.rawValue)
. If you remember from earlier, the ()
syntax allows us to splice data values into a string. In this case, we are inserting the rawValue
property of our type
enum. Remember how, when we created our SnippetType
enum, we assigned each case a backing value? That is the rawValue
we're using here. So if we create a new SnippetData
with the type SnippetType.Text
, then our type.rawValue
will equal the Text
string we assigned in our enum, and it will print out Text snippet created
when it is initialized.
Now our SnippetData
model has the capability to support multiple types. Let's enable our user to select which type they want to make.
Current task: Create an alert controller that allows the user to select the type of snippet.
In our basic pre-visualization of the app, we saw that we were using an action sheet with a few options that let the user select the type of snippet they were creating. Now we're going to walk through the process of creating an action sheet like that using the UIAlertController
.
Since we want to give the user this option after pressing the new button, we're going to delete the code we have in our createNewSnippet()
function, and replace it with some new code that presents an action sheet. Let's take a look:
@IBAction func createNewSnippet(_ sender: AnyObject) { let alert = UIAlertController(title: "Select a snippet type", message: nil, preferredStyle: .actionSheet) let textAction = UIAlertAction(title: "Text", style: .default) { (alert: UIAlertAction!) -> Void in self.data.append(SnippetData(snippetType: .text)) } let photoAction = UIAlertAction(title: "Photo", style: .default) { (alert: UIAlertAction!) -> Void in self.data.append(SnippetData(snippetType: .photo)) } let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) alert.addAction(textAction) alert.addAction(photoAction) alert.addAction(cancelAction) present(alert, animated: true, completion:nil) }
First, we create a new UIAlertController
, and initialize it with a title and a preferred style of .actionSheet
. The title property is what goes at the top of the action sheet and describes what the alert is for, while the message property is for giving more detail. Since our alert is pretty simple, we don't need both, so we set the message to nil.
Next, we create new UIAlertActions
. These are going to be the actual options that the user can select from the action sheet. The first two UIAlertActions
are very similar, since they are both used to create a new Text snippet and Photo snippet, respectively.
When creating a UIAlertAction
, we need to pass in three things: a title, a button style, and a completion handler. The title is the text on the button, the style is the formatting of the button, and the completion handler is the code that runs when the button is pressed.
let textAction = UIAlertAction(title: "Text", style: .default) { (alert: UIAlertAction!) -> Void in self.data.append(SnippetData(snippetType: .text)) }
For our text/photo actions, we pass in the title, set the style to .default
, and then use what's called a trailing closure tacked on to the end. Inside the completion handler closure are two parts: the definition of the parameters being passed into the closure, and the body of the closure.
The first part is the (alert: UIAlertAction!) -> Void in
line, which tells the closure that we are passing in a parameter named alert
that is the type of an explicitly unwrapped optional UIAlertAction
, and returns Void
(nothing).
In the body of our completion handler we have the following line:
self.data.append(SnippetData(snippetType: .text))
Here we are initializing and appending a new SnippetData
struct to our data array. We use the new initializer we wrote for our SnippetData
struct, and let it know that its snippet type is .text
. In our photo action completion handler, we'd write .photo
.
Also notice that we have to write self.
at the beginning of the line. In a closure, we need to be explicit about the scope, so by letting the closure know exactly what we're talking about with self.data
, it can capture the scope of the function and use it later on whenever the completion handler is run.
Further down, you'll see our cancelAction
is a little different. Since our cancel button doesn't really do anything, we can pass in nil
for the completion handler, instead of using a trailing closure like the other actions.
Finally, we add all of our UIAlertActions
to our UIAlertController
by writing alert.addAction( UIAlertAction )
for each action. Then we present our UIActionController
like this:
presentViewController(alert, animated: true, completion:nil)
This presents our alert controller to the user.
And that's it! Build and run the project on the simulator, and try out our new type selection. In the debug area, you'll see that the console now says Text snippet created
and Photo snippet created
, depending on which button you press. Before we move on to the next user story, remember to commit your changes to the local Git repository now that we've finished a story and the project is stable.
3.19.211.134