SPEAKER: Let's take a look
at how you can save data when you're making iOS apps. One of the easiest ways
to save data in an iOS app is to use something called SQLite. Now, SQLite, is basically
a simple SQL database where you can write queries
from your Swift code and persist that data to disk. So when your user opens your app,
does some stuff, closes it out-- when they come back, they can
look at that data that they saved. So to start, let's look at
just a few different queries that you'll probably write while you're
writing some SQLite in your apps. The first query is a CREATE query
that's used to create tables. So here we're going to
create a simple users table-- just has two columns. One is an id. And this is just a
unique id for each user. You can see here that
the type is an INTEGER. We're using it as our PRIMARY KEY. So this is sort of a unique key
that's used to identify each row and it's AUTOINCREMENT. And that means that each time we
insert a new row into this table we're automatically going
to create a new id that's one more than the id before it. Our other field in this table-- It's just a text field. We called it, name. So we can give our users a name. And we can put in any amount
of text we want there. And then, finally, the
start of this query just says, want to create a new
table, but if a table already exists then don't worry about it,
we're not going to do anything. And we're going to
call that table, users. So that's how we can create a
table pretty simply in SQLite. The next query we're going to look
at is inserting data into the table. To insert a new row we're going to start
our query with, INSERT INTO, followed by the name of our table. So that's, users. And then, a list of the columns
we want to supply values for. So here, we're just going
to supply one column, and that column is called, name. Then we'll say, VALUES,
and then, followed by the values we're going to insert. So here, we're inserting one new row. And for the name column, we're
inserting a value of, 'Tommy' After we insert data into
the table, we probably want to read that data back somehow. So this is what a
SELECT query looks like. We're going to start by saying, SELECT. Our * says, just give me
all of the columns you have. We'll say, FROM users, which is the
table we want to read data from. And then, we'll have a WHERE clause
that filters down those rows. So this says, only give me the rows
where the name column is = to the text, 'Tommy' After that, we have an UPDATE query. So we want to start our
query by saying, UPDATE. Then we have the name of
the table, which is, users. Then we're going to say, SET, and
say our column name, which is, name. And let's say a value
of 'Tommy M' and then we want to filter down
to some list of rows. We'll say, WHERE name = 'Tommy'
So this query says, for every row where the name column
is exactly the string, 'Tommy' I want to change that and
update it to instead be, 'Tommy M' And so these queries are fairly simple. And you'll often find that the
queries you're doing in your app are also pretty simple
because you're usually just sort of storing values
and reading them back. And oftentimes, you're not doing
more complicated SQL operations, which is nice. So let's now take a look at an
example of how we can call those SQL queries from our iOS app. And the app that we're going to
be making here is a Notes App. So this is an app that's pretty--
it's already built into iOS. It's pretty simple. But you'll be able to create notes. You know, write down some note,
it'll be saved to a SQLite database, and then you can browse
and read them later. So let's jump in. First, we'll open up Xcode. As always, we're going to start
by creating a new Xcode project-- just a regular single view app
and we'll call our project, Notes. Just save it on the desktop
as we've been doing. And by now we're pretty familiar
with the code that's generated. So let's first open up
that storyboard and think about what we want our app to do. So the first view in our app
is going to be a TableView. That makes sense
because each row is just going to represent a different note. And then, we're also going to have
another ViewController in the app and that's going to be the
actual NoteView itself. So it'll be basically a big editable
text field where you can type in a note and then when you go
back to that TableView we'll make sure to
save that note to Disc. So a lot of this should
feel pretty familiar. So let's jump in. First thing we want to do is
change to a TableViewController just like we did before. So let's just delete that. Come up to our top right. Create a TableViewController. We know that we want to have some
kind of Navigation Controller. So, again, we can just embed
in a Navigation Controller. By the way, you can also just add a
Navigation Controller from this View. It will actually automatically
create a TableView for you as well. So just like before, let's set
some properties on that cell. We're going to say it's same-- that basic style we had before. Let's call it a, Note Cell. Set our accessory to a Disclosure
Indicator just like we had before. And now let's give this
a title, which is, Notes. So that's a start
that's our first screen. And those are all the Views
for that first ViewController. Next, let's create our model. So let's create a new file-- just an empty Swift file that
we can use for our Notes. So let's just call it, note.swift. Now maybe to start, let's just define
a struct where we can have a Note. And we can have a couple
different fields in that Note. First, we're going to have an id. It's going to be an integer, sort of
just like the queries that we just saw. And then, there our other field can
just be the contents of that note. And we'll just use a string for that. So that's our struct. Let's come back to our ViewController. So just like we did before, let's
make this, TableViewController. We're going to store, in memory,
a list of all of our notes so that we can display
them in the table. So let's do that. Let's say that we have some Notes. It's a list of [Note]
and it's initially empty because we haven't loaded anything. Now remember those three methods we have
to implement on a TableViewController. The first is numberOfSections. It's just 1. We're not worried about
sections right now. Second one is the numberOfRowsInSection. Now, just like before, the
number of rows in our Table is just going to be the
size of this notes array. And basically, as the user is
adding notes and saving them, we're just going to continually
update this notes array. So finally, the third method we have to
implement is the cellForRow indexPath. And recall that all
we're doing here is we're grabbing that cell from
that pool, we're going to change some properties on that cell
based on the data, and then return it. So remember that to grab a cell all we
say is TableView, dequeReuseableCell. We had a note an
identifier of, "NoteCell". And we can pass in the indexPath. We can say we want the Tax
Label to just be our notes. Remember, we're using indexPath.row
because we're using rows and not worried about
sections right now. And then, we can just set it
to be the contents of that note and return to cell. So this is all really similar to
what we saw in our Pokedex App because it's just sort of a
standard simple TableView. So let's just run this just to
make sure that everything displays and nothing looks off. So here's our app in the simulator. But you'll notice that
it's totally blank. So something happened here. So let's see what our console says. This is pretty helpful error message. It says, failed to instantiate
the DefaultViewController. Perhaps the entry point is not set. Now, remember what we
did last time, after we created that new ViewController
in our storyboard? We made sure that we set that is
initial ViewController to be true. So if we come back over to
our Navigation Controller open up the right hand panel, and make
sure we have is initial ViewController, checked. Now after we've done that, we can
rerun our app, and there we go. So this is a pretty skeleton
version of our notes. And so now what we need
to do is actually write the code to create, read, and
update notes for the user. So let's start by creating
a new class that's going to have methods for different
operations on our Notes database. So let's jump back to
our model file here. And in addition to having a struct
representing an individual note, I'm going to create this new class
that I'm going to call, Note Manager. And this is basically a class, which
as it sounds like, for managing notes. So this is going to handle
connecting to the database, creating notes, getting all
the notes, and updating a note. So those are basically
the methods that we're going to want to have in this class. Let's start by writing a method
to connect to a database. With SQLite 3, your database is
just a file on the user's phone. So you can think about this as
just opening up a file on Disk and then using that as your database. So let's start by creating
a method called, Connect. And let's get a path to some
file on the user's phone. So let's create a variable
called, databaseURL. We're going to use a built
in iOS class here called, FileManager default, and URL. So there's a few
different parameters here that basically allow you to specify
where you want this file to be stored. So this first one says, what
directories do we want to use? So we want to just use
the User directory. Wouldn't really make sense to put
this in Music or Photos or something. And so this directory is
basically some space for your app to store some user specific files. Next is just some more search path. So we just want to use
the userDomainMask. Again, we're just putting everything
in sort of the users path. These next few parameters--
don't worry too much about. We can just say, no,
here, that doesn't matter. And this last parameter says,
if this file doesn't exist, do you want to create it? Which we do. So that's going to give us a path
to some folder on the user's phone. But what we really want
is a path to a file. So to do that, we're going to
say appendingPathComponent. And then we're just going to
call this file, "notes.sqlite3". So what this kind of long
method is really doing is it's just a way to get a path
to somewhere on the device where it's safe for you to save,
read, and write, files. So you'll notice here that the compiler
says a couple of things are wrong here. And the main one is that
we need to add a try-catch. So, again, something could
go wrong in this place. There could be a reason that you can't
save files-- you don't have access to somewhere in the user's device. So if that happens it's
going to throw an exception. So we just need to handle that. So just like we did before, we're
going to put this inside of a do block. And then, going to catch the error. And then we'll just print out,
"Could not create database." Again, if this were
a real app you'd want to display some sort of
error message to the user but we're not worried about that. And then lastly, we just want to
put a try in front of this call. And so now, our build succeeded. Now that we have a
path to a file on Disk, we now want to establish a
connection to that database. And open up that file. So to do that, we want to have inside
of this class, a handle to a database. Or just some sort of
reference to one database. You don't have to keep connecting
over and over again since connecting can be kind of slow. So we're going to create a
new variable called database. And the type of this
variable is, opaquePointer. So this basically says that
this is a pointer to something, but we know that this is going to
be a reference to our database. So with that, we can start
calling some SQLite3 functions. So before we do, we just have to
make sure that we import SQLite3. And now, after we have that line, we
have access to all of those functions. So let's start. So the first function we want
to call is, sqlite3_open. And this is going to
take two parameters. It's going to take a
file that we want to open up, and then, as its
second parameter, we're going to say where we want to
establish a connection to the database. So the first thing is just going to
be that file name we just created. And then, the second thing
is going to be our database. So a couple of things here-- so first,
we want to change this URL to a path. So all we have to do is say URL.path. And then, second, is we want to give
it a reference to that database. So this is basically
the same as the pointers are in C. We want to give
a pointer to this function so that it knows where
to open the database. So if we just put an ampersand
here, that's going to say, all right, here's the address
of where I want you to store this connection to the database. Now, with SQLite3 functions, they
can return a few different values. So we want to at least want to make sure
that they're returning what we expect and not airing out. So to do that, we're just going
to put it inside of an if. So we're going to say, if the open
was successful-- so that's SQLITE_OK. So that means we're good to go. If not, again, you want
to handle this somehow. But we'll just print
out, "Could not connect." OK. So now that we've opened the database,
the last thing that we want to do is create a Table. So we're going to use one of
those Create Table statements that we looked at earlier. So let's say a SQLite3,
we're going to use, exec. So this is basically saying I want to
execute some query on the database. The first thing we need is a
pointer to the database to use. So we're going to use that
database pointer we created. Next, is going to be the SQL to execute. So here, I'm just going to
create a table I'm going to say, "CREATE TABLE IF NOT EXISTS" notes. And we're just going to have
one column in our table here. We're going to call it, Contents. And it's going to be, TEXT. Now, with SQLite3, it's actually
going to automatically create an id column for you. And that column is going to be that
Integer AUTO_INCREMENT and it's going to be called, rowid. So normally, I'd create
my own id column here, but because I know SQLite3
is going to do that for me, I'm not worried about it. So last, there's a few
other things here-- you don't have to worry about these. They're just sort of different options
that we're not going to end up using. So we can just pass nil to all of them. So let's just make sure that this works. So again, let's just put this in an, if. Make sure that it's OK. If it's not, you can print out,
could "Could not create table." OK. So that's it for our Connect Method. So now, we basically just got a path
to a database file in the user's phone, we've opened it up, and we've made sure
that the table that we need to exist, exists. Next, let's write a function
for creating a new note. So to do that, we want to make sure that
we're using this same database pointer. Because if we're not, we need to
reconnect over and over again. That's going to be slow. So let's create this function here. We're going to call it, create. So the first thing we want
to do is call, connect. So this makes sure that
there's a database pointer. But we only need to connect once,
so we can do something simple here, which is say that if database
isn't nil, then we're done. So this just says if we've
already connected to the database, don't do this over and over again. So any of our other database functions
can just call, connect, and not worry about it because we know that,
connect, isn't going to redo something if it's already been done. After we connect to the database
there is a three step process to executing queries. We're going to prepare the query. Then we're going to execute the query. And then we're going to finalize it. So the first thing we
want to do is prepare it. So to do that, we're going to create
a new variable called, statement. It's going to have
that same type before. It's basically just a pointer
to somewhere in memory. And now we're going to
say, sqlite3_prepare. You notice there's a few
different prepare functions? We're just going to use v2. That's kind of the standard one. And so, looking at these arguments,
looks like the first argument is a handle to that database. So we can safely use this here because
we know that we've called, connect. Next is the SQL that we'd like to run. So we're going to write
an INSERT statement. So we're going to say,
INSERT INTO notes. The only column we care
about is, contents. And then, we'll supply values
of, let's just say, "New note". And so this means that every
time we create a new note, we're just giving it some
default text called, "New note". These other things here-- we don't
have to worry too much about. Can just pass a -1 here. We're not worried about what that is. We can pass our statement. And then, we can just pass nil. We're not really worried
about that -1 or nil. So now, just like we
did before, we just want to make sure that this function
doesn't return any errors. So we'll surround it with an, if. And say that, if it is-- OK, and we can move on. And let's just do a build--
just a sanity check. Looks like we have an error here. OK. So it looks like, here, we forgot
that we're passing a pointer. So just pass in the ampersand
to just get the address of that so that the function
can use that pointer. OK. So now we have a prepared statement. And now our statement
is ready to execute. And the way to do that is by calling
this function called sqlite3_step. So we're going to say, if sqlite3_step-- and here, it just takes one argument
and that's actually just the statement. And let's just make sure that
this is OK, then we're good. So before we forget, let's just
add a couple print statements. Again, you really want
to handle these errors. But we're just going
to print them for now. So you can say, "Could not insert note." And we want to say here, something
like, "Could not create query." So for example, if there's a syntax
error in your query, one of these might trigger. Now the last thing we want to do
is just finalize that statement. And once you finalize
the statement, it's basically going to do some cleanup work. And it means you can't
use the statement again. To do that it's also pretty simple. I'm just going to
finalize the statement. So now, I notice that the structure
of this is a little weird. So let's just kind of change
up how our logic works here. So, actually, the first thing
is we want to change this to-- SQLITE_DONE. And let's just kind of
flip the logic here. So we'll say that if this
didn't work, then let's print, "Could not insert note." And then, let's similarly
say, if this didn't work, then we can say, "Could
not create query." And, you know, we can
just sort of return-- and this is kind of nice
because you're going to avoid having some crazy
number of nested indents. So we can get rid of that and that. OK. So as one less thing-- often when you're inserting
a new value, you immediately want to access the id of the
row that you just inserted. So to do that, let's make our create
function actually return an int. And the value we want to return is
going to be sqlite3_last_insert_rowid. And we can just sort of
pass in that same database. And so this is nice
because now we're saying, well, we just created a new row. And maybe you immediately want to
update it or do something with it. So this can tell you that id. But, again, we have this error here. All we have to do is
wrap this in an int. You'll notice here that this function
is returning a different type of number. Really easy to just cast to an
int by surrounding it with int. And then, let's say, you
know, -1, here, would indicate that the note couldn't be created. So now let's just do a quick
build, make sure we're good to go. And we are. OK. So now that we've written a
method to create new notes, let's write our next method, which
is to get all of the contents from the database. So let's say we want to have a
function called, getAallNotes. We know that this is going
to return a list of notes. So just like before, we want to
start by connecting to the database. And remember, if it's already
connected, this function isn't going to do anything. Next, let's create a statement,
just like we did before. And prepare it. So we'll say, let's prepare. We'll use the database. And this time, the statement
that we want to use is a SELECT. So we're going to say "SELECT-- we'll use that automatically
created id, that rowid, as well as, the contents FROM notes. So this will just grab everything in
the database so you can access it. Just like before, don't
worry about what this is. We'll give it a pointer to that
statement so it knows what to prepare, and then we don't need this either. And then again, we just want to make
sure that if this is not SQLITE_OK. Basically, if some error occurred, you
can print, "Error creating select." And will return some empty array. Next, just like we did
before, is we want to call, sqlite3_step to actually
execute the query. Now, last time, because we were just
running one insert we could just call, sqlite3_step, once. But this time, we want
to run it for every time there's a row available to read. So to do that, we're going
to use a, while, loop. So we can say, while sqlite3_step
of our statement is = row. This means we have a row,
and we've accessed it, we want to convert that
to our note object. So first, let's just create a new
variable where we can store our result. So this is what we're ultimately
going to return from the function. It's going to be a list of Note and
it's initially going to be empty. So now each time that we do
have a row from the database, we're just going to add that onto this
result. So we can say, result.append. And what this is going to do is
add on a new element to our array. So these arrays don't
have to be a fixed size. As we saw, we can sort of change
them, and add and remove things. So this is just going to add
an element to that array. And we want to add a Note. And just like before, we have
this nice constructor that was generated for us by the struct. And so now, we want to access the rowid
for id, and the string for contents. So again, we're going to use
some SQLite3 functions here. So we're going to say,
sqlite3_column_int. And so what this is
saying is it's saying I want to get the value
at some column, and I know that it's going to be an
integer, so give me back an int. And that works out because
our struct takes an int. So again, we're just going to use
that same statement as before. And now we're going to take
a 0 indexed list of columns. So the first column in my select is
rowid, so we're going to pass a 0 here. So that's our id. Next, we want to grab a string. So we're going to say
similarly, sqlite3_column_text. Same thing as before, (statement) and
now our contents is the first column. So now, one last step is
we want to take this string and just convert it to
a native Swift string. So all we want to do is say, string-- and what's coming back
now is called a cString. And so what's nice is that
string has this constructor that just takes a cString and converts
into a regular Swift string. It's sort of this lower
level API, similar to how we're using pointers and strings and c. So this is just an easy
way to convert them. And then last, we have the compiler,
hopefully telling us to just make sure we wrap this in an
int, which we've done. So now, if we build this, as a compiler
is reminding us, the last thing to do is just return that result.
But before that, just make sure we call finalize on the statement just
to do any cleanup behind the scenes. So now, if we run,
build, we're good to go. So every time you're
writing these SQLite3 queries you're basically going
to be following the same pattern. You're going to take a statement,
you're going to prepare it, then you're going to execute
that statement-- either in a loop or just once. Then you're going to
grab-- if it's selectory-- grab some of those column values. And then, lastly, finalize it. So that sort of pattern is
going to apply no matter what you're doing with SQLite3 on iOS. So now, we've got enough to
populate our TableView because we can start creating
notes, and then, we can fill them up-- or display them in the
TableView with this getAllNotes method. So let's start doing that. The first thing you want to do is to
create a button to create new notes. Because right now, there's no
way in the UI to add a new note. So we're going to use
a BarButtonItem again. So just come back here, BarButtonItem-- add that up here in the top right. And now, we can take advantage of
some built in system icons in iOS. So if you come over here to System
Item, rather than being Custom. If you just set it to Add you
get this nice looking + for free. So no need to download images from
the internet or something like that. So that's done. And now, let's add an Action. So notice here, we're out of our model. We're back in our Controller. And we're going to create an Action. And let's call it, createNote. And now, we want to create a note. So the first thing we
want to do is somehow get an instance of that Note Manager. Right now we have a
bunch of methods, but we don't have any Note Managers created. But remember we talked about how we want
to save this connection to the database since connecting can
be kind of expensive? So what we want to do is
actually create an instance of Note Manager inside of itself. And if you're familiar with
Object Oriented Programming this is something that's
called a singleton, which means that we have this
class but there there's only ever going to be one instance of it. And we've actually
already seen singletons. This, for example, is one. File Manager is a class. And by saying, default,
we're saying give me that one instance of that class. So we're basically going to
follow the same pattern here. To do that, we're going to say,
static let, and let's just call ours, default. Or let's call it, main. You can call it whenever you want. And then we're going to set
that equal to a Note Manager. So it's kind of this cool thing where a
Note Manager has a reference to itself. And by saying, static,
that basically enables you to access this property
without an instance. Which makes sense, because
if you had an instance, you wouldn't need another instance. And so, similarly, how you could
say, FileManager.default here, we're going to be able to
say NoteManager.main here. One other thing you might want to
do if you're writing singletons, is just to make it so that no one
else can instantiate your class. So to do that, you can create that
init method, but mark it as private. By prefixing this with,
private, we're basically saying, nobody else can call this. So even if you try to
instantiate a NoteManager, the compiler is going to
tell you that you can't. This is just kind of a
nice thing to, you know, if you're giving an API to
somebody else, or even just working with somebody, to sort of
enforce and remind them that they should be using
this singleton instance and not trying to create
NoteManagers themselves. OK. Great. So now, we have an easy way
of accessing the NoteManager, we can just say NoteManager.main.create. And that's going to create a new note. And it's going to give
us back an id, which we're not too worried about right now. So we can just say, let_ = that. We're never going to use the
value of that underscore. So now, what we've done is we've
inserted something into the database and now what we need to do is reload
the TableView just like we did before. So let's write a method
that reloads the TableView. And this method needs to do two things. The first thing it needs
to do is say, give me all of the notes that are currently
in the database so I can display them, and then, let me tell
the TableView that it needs to reload its data because you've
changed that underlying data structure. So let's do both of those things. Recall that we had this
property called, Notes. And this is going to hold all of
the notes that the user has created. And let's set that =
NoteManager.main.getAllNotes. Make sense? That's just going to return a list of
notes, and so we want to save that. After that we want to call
self.tableView.reloadData just like before. You'll notice that we didn't need this
dispatch.main.async thing this time. The reason is that we're not inside
of a background task at this point. Everything happening here
is in the foreground, and so there's no need to sort
of jump back into the foreground because we're already there. So after we call createNote,
we just want to call reload. And that's going to
reload the entire table. Didn't end up using viewDidLoad here
so let's just remove that for now. So one last thing to do
before we can run this app is remember to hook up this IB action. So let's jump back to our storyboard. Let's select the TableVIewController. And first, let's make sure that it's
using the Class that we specified. So let's change the Class to
ViewController, which is the Class that we were just writing. Then, let's Control, Click, and Drag,
from the + over to the ViewController, and there's our Sent Action, createNote. OK. So let's run our app
and see what happens. So here's our empty
TableView just like before. Let's try pressing the +. OK. So that's not great. Looks like we've got
some error messages. And these are error
messages that we wrote. So that's helpful. So let's just jump back into our model. And-- ah-- so this was a-- I was wrong before. You want to save this in
the Document Directory. So this is just basically
a directory where apps do have permission to read and write. What was happening is we're
trying to save it somewhere where we didn't actually
have permission to do that. So let's try again. Let's rerun this. And, OK. So if you click +, there we go. So now we have added a new note. So what we've just done there is we've
inserted a note into the database. We've notified our TableView
that we need to reload. And then, we reloaded all of
the notes back from the database so that the TableView is displaying
the most up to date versions of all the notes. So let's just sanity check to
make sure that these notes are sort of behaving as we'd expect. So let's stop the app. And now, when I rerun it,
I should see the note again because that means that
we're saving it to Disk. So let's click, Run. OK. And that's interesting. It looks like we don't see the note. So now, let's see. It doesn't look like there's
anything wrong in our model because none of these
print statements triggered. So maybe it's in the Controller. Let's jump back to the Controller and-- a-ha. So what's happening is we
have this reload method but we're never loading
it for the first time. So when that view is
created, we want to make sure that we load the notes from
memory right when that app starts up. So right now what's happening is
it's starting up, it's saying, well, my notes are set to an empty array, ,
OK, I guess there's nothing to display. And so let's change that. So let's say, on viewDidLoad-- make sure recall the super method. And then let's just say, reload. And so what this is saying is
when the app first starts up, fetch all the notes from the database,
tell the TableView it's time to update. So now let's run this. And there we go. Just as we expected. There's our note. If we stop this and we create it-- stop this and rerun it,
rather, there's our note. So there's one last
piece of the puzzle here, which is creating a ViewController
to display and allow the user to edit the contents of their note. And again, some of this
should be pretty familiar. So let's first jump
back to the storyboard to create a new ViewController. Add it there. Remember we want to add a Segue here. So we're going to Control, Click. Add that ShowSeque. Give it a name. So we'll just call it
something like, NoteSeque. Let's create a new SWIFT file. And let's call it, NoteViewController. OK. Here's our NoteViewController. We're going to say, NoteViewController
extends UIViewController. Make sure we've imported UIKit. Everything builds. That's great. Come back to the storyboard and make
sure that in the Identity Inspector, we have set this to NoteVIewController. OK. So now, let's add a
TextView that will display all of the contents of this note. So here, let's type in
TextView, drag this over. And so now what we want to
do, is we want this TextView to fill the entire screen. Much like it does in the iOS app. And so, to do that, we're going to
use something called, Constraints. So if you come down here
in the bottom right, you can see a few of
these little buttons. And this third one here
says, Add New Constraint. So let's click that. And we get these this little pop over. And what we want to
do is you want to say, let's set the top of this view
to be the top of the iPhone. The bottom of this view to
be the bottom of the iPhone. And same for the left and right. So to do that we can just constrain
all four of these things to its parent. So when we say, Add Constraints, we
can see these sort of little lines that indicate that they're going to
sort of stick to the edges there. So now we can just sort of drag this
over, drag this over, and we're good. So we can get rid of this
sort of default text. It was kind of silly. Get rid of that. And then we'll also make sure that
our TextView is editable, which it is. And so now, our View,
should all be connected. So let's jump back to the
model and write the last query that we'll need to write, which
is to save a note to a database. So we're back in our
NoteManager here and we're going to create a new
method called, save. This method is going
to take one parameter and it's going to be a note
that we'd like to save. So we're going to say, Note, here. And it's going to follow that
same pattern that we saw before. We're going to call, connect. That's going to establish a
connection to the database. We're going to create a statement. It's in the OpaquePointer. We're going to try
preparing a statement. So, sqlite3_prepare. It's going to take a database. And now we're going to
write an UPDATE statement. So we're going to say, UPDATE notes. The only thing we're willing
to set is the contents. So we're going to say, SET contents. Now we're going to add
a question mark here. We'll come back to that later. WHERE realid = ? As always, we don't worry about this. This is a pointer to our statement. This we don't need. And so, make sure that this is OK. It's error messages helped us
last time, so I'll do it again. OK. So now what we want to do is we
want to bind data to this query. So these question marks here are just a
nice way of passing data into a query. You could use something
like string interpolation. But it's kind of insecure and you're
opening yourself up to someone sort of doing things with your app
that you might not want them to. And basically, open to a
single injection attack. And so we want to instead use
parameter binding to safely pass data into this query. So the way to bind data to a query
is by using the SQLite3 Bind Methods. So you can say, sqlite3_bind_text,
followed by that data type. So we're going to say,
use the statement. The next parameter to
this function is going to be the Index of the question
mark you want to update. So what's kind of annoying about
this is a one indexed list where the other 1 was a 0 index list. So to bind to the first
question mark, you actually want to say 1, which is
really easy to forget. But hopefully, this error
messages will help you out. So then we'll bind that. The next thing is going to
be the value we want to bind. And similarly, to hear how we
wanted to change a regular Swift String into this cString-- we're going to do the same thing here. So the way to do that is to first
create an NSString from a regular Swift String. So we're going to say,
note.contents here. And then from there,
you're going to call this other method called utf8String. And you don't really have to know
the details of what's going on here. But basically, you're
just converting formats-- converting the data between
a Swift friendly format. And then the SQLite3 friendly format. Same as always, we're not worried
about these last two parameters. So that's binding the first parameter. Now let's bind the second parameter. So let's bind the integer. This is a statement. Again, we're using a 2 here
because it's a 1 index list. And now we want to set note.id. Let's build just to make sure. Looks like instead of an
int, we need to use an int32. Easy to fix. Just surround your int with that. Now that you've bound the parameters
to this query you want to execute it. So remember to do that, we're
going to use the step function, pass in that statement. And then we want to make
sure that it's done. If it's not then we'll say,
"Error running update." Finally, we just want to finalize
that statement to do that cleanup. OK. And so now, what this
was going to do, is you can pass it in an
instance of a note and we're going to write those values to Disk. Using an update statement so you can
change the value of a note that's already exists. So that's it for our model. We've written the three model
methods we needed to write. We can create Notes, we can get
Notes, and then we can save Notes. So the last thing to do is wire
all of this up to our Views through our Controllers. So we created that
segue in the storyboard and so now we want to
utilize that segue to pass the Note from this list to
the other ViewController. So remember that method was
called, prepare for segue. Let autocomplete do the work. We want to check in on that identifier. That identifier we
called it, a NoteSeque. That's true. Just like last time, we want to
cast that DestinationViewController to an instance of our
NoteViewController. So let's do that. If let destination = segue.destination
as NoteViewController. And so now, here is where
we can pass the note. So let's jump to this ViewController,
add a parameter for that note. Make sure that exists. While we're here, let's
also define an outlet because we know we're going
to need to connect that later. So let's say there's an IB
outlet for the textView. Now from our ViewController
we can say, destination.note. Just like before, we're
going to use notes. And the index we're going to use is
tableView.indexPathForSelectedRow. And we just need the row. So that's going to pass over the note
data to our second ViewController. OK. So we've passed the note. So let's jump back to
our NoteViewController. Let's fix this typo. So now, let's bind that text that we
received from the note to our textView. So we're going to say viewDidLoad. Call, super. Then textView.text = note.contents. Now, this just says, whenever the
View is loaded let's set the contents. Now, the other thing we
want to do is save the note when the user exits the View. So to do that, we're going to use
another iOS method that's called for us called, viewWillDisappear. And so this is going to be
called automatically right when the user hits that Back Button
and before the View has disappeared. So just like before, we're
going to call, super, pass in that animated parameter. And then we're going to say,
NoteManager, just like before, main. And now we're just going to save
the note that we currently have. So let's build. Looks good. Now, lastly, let's not forget
to connect this outlet. So come back over to main. We're going to select
our NoteVIewController and then we're going to Drag and
Connect the TextView property. And now, one last thing
we need to do is make sure that we update that first ViewController
every time the user comes back to it. So before, we used
viewDidLoad but this is only going to be called once, which is the
first time that the VIewController is created. But when the users jumping back and
forth between these ViewControllers, every time you come
back to the first one, you want to reload the data to
make sure that it's updated. So rather than using viewDidLoad
we're going to use viewWillAppear. So it's sort of the opposite
of viewWillDisappear. But every time this View comes into
focus, and into the foreground, this method is going to be called. So let's change our super.viewDidLoad
to super.viewWillAppear to pass along that animated parameter. And now we can try running
our app and see what happens. So here's the app in the simulator. From before, we had that new
note and we persisted that. So that's going to be around. So let's click that. Great. Looks like we've passed the
data from that first note into the second ViewController. Now let's try typing. Save this is a new note. Click back. Doesn't look like it worked. So even though we change
the text when we came back, looks like the note that was
displayed was this new note. So let's see what happened. Let's jump into our
NoteViewController here and take a look at what we're doing. Looks like when the View loads
we're setting the text = contents. That looks right. Then when the View disappears,
looks like we're saving the note. But-- ah-- we never
changed that note object. So what's happening is when the second
ViewController receives that note object, it's just going to display it. And then no matter what the user typed
in, just save it right back to Disk without being changed. So what we want to do is
actually change this note object. So we want to set its contents to be
equal to the contents of that TextView. So we'll say, note.contents
= textView.text. Now, if we build here-- I think this is the first time we've
seen this but we notice in the struck that we created let variables,
which means they're immutable. So here the compiler is telling me
that I declared contents as a let in mutable variable. Instead, I want to declare it
as a mutable variable with var. So I'll come back to my struct, change
let to var and try rerunning again. Here we go. So let's go into our new note,
change this to this is my new note. Backup. And it looks like we're working. So just to make sure let's create
a new note by tapping on the +. Let's save this one as Note 2. And there we go. It looks like everything worked. So there's our fully
functional Notes App, using SQLite to save
data on the user's phone.