All right. 1
So, so far we've looked at all four of the CRUD capabilities that Core Data offers. 2
So we've looked at creating, reading, updating, and destroying data using the context, and once we're happy 3
with our changes, then we've committed those changes to our persistent container. 4
So, in this lesson, I want to talk about how you can query items using Core Data. 5
So in order to do this, we're going to give our app a little bit more functionality, and the functionality 6
that we want is having search capability, and adding that to our table view. 7
So I want to be able to type in part of or completely a to-do list item. I want our table view to update 8
to show me all of the items that are related with that searched him. 9
So for example, I could have something like, you know, buy milk and buy eggs. 10
and I just want to search for "buy" to see what I need to get from the supermarket. 11
So in order to do that, we're going to go into our trusted object library and we're going to search for 12
a search bar. A little bit circular there. 13
But the one that we want is the Search Bar, not the Search Bar and Search Display controller because 14
we're going to implement a really simple search bar. 15
So I'm going to drag it on to our table view 16
and I'm going to place it just below the navigation bar, 17
so above the table view. 18
So the next thing is to set our view controller as the delegate for the search bar, so that whenever there's 19
any changes inside the search bar like, say, when somebody hits the search button, then this is the class 20
that's going to be informed, 21
and we're going to deal with whatever they type in there inside our TodoListViewController. 22
So I'm going to say UISearchBarDelegate. 23
Now, previously, every single time we've declared our view controller as a delegate of something. We also have 24
to make an IBOutlet for our search bar. And then inside viewDidLoad, say something like searchBar.delegate 25
= self. 26
So we're familiar with these two steps, adding the protocol in the class declaration, and also setting 27
the outlet's delegate as self. 28
Now, this is optional, so you can either do it in code or 29
the alternative is you can simply hit control, drag, and point to this little yellow icon which represents 30
the view controller. 31
And if you let go, you see that there is a delegate for this outlet that you can set as the view controller 32
which, remember, the yellow icon represents. 33
So make sure that you actually originat it from the search bar, rather than from somewhere else like, 34
say, the table view, right? 35
Because in this case, it's got a data source and delegate, and you can see that they're already set up 36
to be 37
our 38
View Controller class. 39
So it might even be easier going from the document outline and dragging it to the yellow icon, and then 40
hitting delegate. 41
So the next step if we go back into our TodoListViewController is we need to add some delegate methods 42
in order to make the UISearchBarDelegate work, right? 43
So what we can do is right at the bottom below loadItems, we can add the delegate method and it's called 44
a searchBar. 45
Usually, so for a lot of these methods, as long as you're aware they exist, you can pretty much guess what you 46
should start typing in order to get this list of suggestions come up. And you can scroll through them 47
looking at the information texts as to what each one does 48
until you find the one that you want which is, in my case, searchBarSearchButtonClicked. 49
So this tells a delegate that the search button was tapped and that is the point where we want to reload 50
our table view using the text that the user has inputted. 51
So let's hit enter and insert that delegate function in there. 52
Now, some of you might wonder if we have so many of these delegates saying, you know, we have 53
UISearchBarDelegate, then at a later stage, we decide to add like a picker view, UIPickerViewDelegate, then we 54
ddecide to have one which is UIImagePickerControllerDelegate. 55
If we have all of these stacking up because we need all of them in our app inside the same view controller, 56
inside the same screen, then it's going to start getting pretty cramped up here. 57
And you've also got different delegate methods that relate to each of these bits of functionality, 58
for example, the SearchBar or the PickerView, ImagePickerController. 59
And should they really all be crammed into the same class? 60
I mean, it seems like it's going to get a little bit tight on space, right? 61
Well, the alternative that we can use that you're going to see in a lot of projects that you download from 62
GitHub, or out there in the wild, is this is the closing brace for our class and everything inside is our 63
TodoListViewController class. 64
Now, if we go outside of the last curly brace and we create an extension, then we can extend the functionality 65
of our TodoListViewController, and we can set it to be a UISearchBarDelegate. 66
So the advantage of this is that we can split up the functionality of our view controller and we can have 67
specific parts that are responsible for specific things. 68
So for example, this extension extends our base view controller and gives it the extra power or the extra 69
functionality of being able to handle search bar methods. 70
So the other advantage is that when you look up here, you can see that we've got the base class here 71
and we've got our extension down here. 72
And one of the downsides of MVC or the model-view-controller pattern that Apple is such a big fan of 73
is that your C starts getting really, really big, and it basically becomes like tiny "M," tiny "V," and massive 74
"C." You often see these gigantic view controllers, especially when you look at Apple's example projects, 75
and they just start getting really, really big. 76
So it's really important that you modularize and try to split up the functionality as much as you can, 77
not just for code organization, which is handy, but also so that when you come to debug it, you can narrow 78
down on which section you need to look at, instead of going through the entire page, right? 79
So for example, if we had issues with our search bar, then we can navigate straight down to our extension 80
that deals with all of the search bar functionality, and we can read only, you know, maybe 10 lines, rather 81
than 100 lines of the page. 82
So, so far we've been using these MARK comments in order to be able to look at our view controller and 83
have this quick breakdown of what are the different sections, and what are the methods in each section. 84
But now we have this extension, and even though the extension, unfortunately, doesn't allow you to rename 85
it, you can still put a MARK above it so that you can see it as a separate section. 86
So we'll call this "Search bar methods." And when we go into our file now, we can see our main class, 87
as well as our extension class which is responsible for the search bar methods. 88
So this is a very Swifty kind of pattern and you're going to see this pretty much everywhere. And usually 89
as a rule of thumb, whenever I want to create a new protocol here in the class declaration line, 90
I tend to create a new extension and group the protocol methods together, like we're going to do now. 91
So inside our TodoListViewController extension where we are extending the functionality with a search 92
bar and some search bar methods, we've got this delegate method which is going to be triggered once the 93
user presses the search button on the search bar. 94
So this is a good point for us to query our database and try to get back the results that the user is 95
searching for. 96
So as always, in order to read from the context, we always have to create a request and we have to declare 97
the request data type. 98
So NSFetchRequest and it's going to return an array of items, and we're going to set this to equal 99
Item.fetchRequest. 100
Now, the next step is we have to specify what is going to be our filter, right? What is going to be our 101
query? 102
And we do that using something called an NSPredicate. 103
But before we do that, let's first have a look at what is the data that we get back when the user clicks 104
on the search button in the search bar. 105
So let's go ahead and just print out the searchBar.text, and let's go ahead and run our app. 106
All right. 107
So the first thing that we see is that we now have a search bar. 108
Yay! That was pretty easy, right? 109
And the first thing I'm going to do is I'm going to hit command K or go to Hardware, and keyboard, and 110
Toggle Software Keyboard. So command K does exactly the same thing and it basically pops up and pops 111
down the keyboard on screen, and then I'm going to, for example, type "Save" in order to pull up all the 112
items that contain the words "Save," either in their title, and then I'm going to hit search. And this is 113
the point where our searchBarSearchButtonClicked delegate method gets fired and it runs everything 114
that's inside. 115
And so our print statement prints out what the searchBar.text! at this point is, and it's the word that 116
we entered which is "Save." 117
Okay, so that worked pretty nicely. 118
It's always a good idea to check along the way to make sure that all your presumptions are right and all 119
your setup, especially with these UIElements have not gone awry. 120
So we now have this request and we need to modify it so that we tag on a query that specifies what we 121
want to get back from the database. 122
So in order to query objects using Core Data, we need to use something called an NSPredicate. 123
So we can say, for example, let predicate = NSPredicate and we're going to initialize it using 124
a format. 125
So the format, as you can see from the placeholder, takes a string and the string is a little bit scary 126
because you're now typing in free text. And in our case, what we would do is we would say "title," 127
we're going to look at the title attribute of each of our items in our itemArray, 128
and we're going to check that it CONTAINS a value. 129
So this is the argument that we're going to substitute into this percentage, "at" sign. 130
And I'm going to talk about why all of this looks so different from what we've been doing in Swift in just 131
a moment, but let's just set it up first. And the argument is, of course, what we printed out in our print 132
statement which is the searchBar.text. 133
And so when we hit the search button, then whatever we have entered inside the searcBar at that time 134
point is going to be passed in to this method into this part, and replacing this percentage, "at" sign. 135
So then our query becomes "For all the items in the item array, look for the ones where the title of the 136
item contains this text." 137
So this is pretty unfamiliar if you have only been working with Swift. 138
This part might look quite familiar to you if you've ever used or looked at Objective-C code. 139
And the reason is because NSPredicate is basically a foundation class that specifies how data should 140
be fetched or filtered. It's essentially a query language which is like some sort of weird lovechild 141
between the SQL WHERE clause and a rejects or regular expression. It's meant to be similar to natural 142
language like how you would speak in English. 143
But there's a whole bunch of modifiers and logical conditions that you have access to that you can put 144
inside this format string. 145
So let's take a look at those. 146
So a really good resource is actually on the Realm blog, and even though we're not using Realm yet, we're 147
actually still using Core Data. 148
When you query items inside Realm, they also use the same thing which is NSPredicate. 149
So they've made this beautiful PDF that is, essentially, NSPredicate Cheatsheet which you can download 150
from this link here which I'll include in the resources for this lesson. 151
But, essentially, you can have a look at how the string is structured, 152
right? 153
So remember that the percentage "at" sign substitutes any sort of argument that you want to pass in, and 154
you can use most of your basic comparisons like left hand is equal to right hand, or left hand is greater 155
than or equal to the right hand, as well as everything that we've been using so far. 156
But it's also got some of these special words in all caps. 157
For example, the word like "BETWEEN," right? The left hand expression is BETWEEN or equal to the right hand 158
expression. 159
And you can compound your predicates together 160
by using the "AND," "OR," and "NOT." So you can say that the age is equal to 40 AND the price is greater than 67. 161
I don't know in which case you love to look for something that has age over 40 and price over 162
67. Well, maybe wines, I guess. That works. 163
I don't know why I was thinking about humans or pets. 164
That's really bizarre. Anyways-- 165
So it's also got things like string comparison operators which is what we use which is the "CONTAINS" 166
because our title is a string and we want to check to make sure that the titles of each of those items 167
contains, i.e., it can partly contain the text that we're searching for, because we're not going to enter 168
the exact word that we're looking for. 169
If we're looking to try and pull up the items like "buy milk," and "buy bread," then it's not very useful 170
just searching for "buy milk," 171
so that "buy milk" pops up, right? 172
We want to look at all the things that contains the word "buy." 173
Now, if you wanted an exact match, then you might use something like "MATCHES," right? so that it exactly 174
equals the right-hand side. 175
So when you're dealing with strings, then these are some good ones to work with. 176
For all the other data types, then usually you'd be using these basic comparatives when you're working 177
with numbers especially. 178
So this is a brilliant Cheatsheet. 179
Have a look at it. Study it 180
if you're going to be querying a database. And you can do a lot of really sophisticated stuff with it 181
and it's still relatively short and easily readable. 182
And they've also got some tips, tricks, and examples which work with Core Data as well. 183
It's basically how you search your databases using Realm or using Core Data. 184
And another good article is this one on 185
NSPredicate written by Mattt Thompson who was one of the creators of Alamofire that we've been 186
using so far. 187
So a really good article on this 188
if you want to read out more on it. 189
And one of the things you need to remember about string comparisons using NSPredicate, as you'll 190
find in this article, is that string comparisons are by default case and diacritic sensitive. 191
So case, obviously, means upper-lower case, and diacritic refers to these little marks above your letters. 192
So, for example, in French, you have the "circonflexe" and the "grave" accents, or in German, you have the "umlaut." 193
So these are the diacritics. And you know for us to implement search, do we really want it to be case-sensitive 194
and diacritic-sensitive? 195
Probably not. 196
So you can modify that by just adding the square brackets and including "c" and "d." So "c" stands for case 197
and "d" stands for diacritic, and it makes it, basically, insensitive to these two things. 198
So let's go back to our code and modify it with our new knowledge. So we can add it after the "CONTAINS" 199
And we're just going to add "CD" to it. 200
So, now we have structured our query and we're going to add our query to our request, so we can say 201
request.predicate = predicate. 202
All right, so now that we've created our predicate which specifies how we want to query our database, 203
the next thing that we can do is we can actually sort the data that we get back from the database in 204
any order of our choice. 205
So we can create a sortDescriptor that is a NSSortDescriptor, and we can say that we want to sort 206
using the key that's the titles of each of the Items, and we can sort it in alphabetical order by saying 207
ascending equals true. 208
So, now we can add the sortDescriptor to our request by saying request.sortDescriptor, so it's really 209
important that you notice here that this property is actually plural and that's because it expects an 210
array of NSSortDescriptors. 211
But in our case, we actually just want to specify a single sort rule. 212
So we're going to say sortDescriptors equals an array of sortDescriptors, but it only contains a single 213
item and that's a perfectly valid code. 214
So, now all we have to do is to run our request and fetch the results, right? 215
So if you have a look in our loadItems method, you can see that we've got a block of code that does 216
exactly that. 217
So we can copy and paste it over here so that we can try using our context to fetch these results from 218
our persistent store 219
that satisfy the rules that we specified for our request which is, namely, the title should contain 220
whatever is in the searchBar, and the results should come back with their titles assorted in ascending 221
alphabetical order. 222
So then we assigned the results of that fetch to our itemArray and we now need to reload our 223
tableView so that we retrigger the cellForRowAt indexPath methods and we update our tableView with the 224
current itemArray which contains the results of the search. 225
So, now as I always say, whenever you find yourself copying and pasting large chunks of code, then it's 226
probably a good trigger for you to think about whether if you can refactor your code so that it's more 227
succinct 228
and you're not repeating yourself, but instead keeping all code dry. 229
But before you refactor your code, you should always make sure that you test your assumptions and make 230
sure that the current code works. 231
So let's do that now. Let's run our app and see if it works. 232
All right. 233
So I've added a whole bunch of items that all contain the word "Save." 234
So there's four items that contain a capitalized Save. 235
There's one lowercase save and this one save with a diacritic. 236
So if we go into our search bar and search for the word "Save" with a capital "S," we're going to look inside 237
all of these items and look for the ones that contain the word "Save," 238
and our query is case and diacritic insensitive. 239
So let's go ahead and hit search. 240
Which, of course, triggers the searchBarSearchButtonClicked delegate method and executes all of this 241
code. 242
And when we get back, there's all of the items that contain the word "Save" and it's case and diacritic insensitive. 243
So, now that we've proven that our code works, then it's a good time to refactor our code. 244
So one of the first things is that we probably don't need this constant, 245
right? 246
We can simply replace it with request.predicate 247
and we can delete this line. So two become one. And we can replace this let sortDescriptor with 248
request.sortDescriptor. 249
Now, before we can delete this line, we have to realize that sortDescriptors expects an array of 250
NSSortDescriptors. 251
So we have to add these square brackets around this line on the right-hand side in order to make it equivalent 252
to this. 253
And now we can finally delete that line. 254
So, now we've gone down from four to two. 255
And finally, as I said before, we've got this do-catch block that's repeated inside our loadItems method. 256
So I want to be able to call loadItem, rather than having to write out all of this code again. 257
So what I can do is I can comment out this code and I can modify this function to take a parameter that 258
is the request. 259
So this is going to be of data type NSFetchRequest and it's going to return an array of Items. 260
So, now instead of repeating this do-catch block, I can simply say loadItems and the request I want to 261
make is this modified request which contains the query and the sort descriptors. 262
So I can say loadItems with request and the request is this one. 263
So the only problem here is that loadItems(request request) doesn't really make a lot of sense in English, 264
right? 265
And what you notice with Swift is that it's really pro-natural language. 266
So you want to have your method calls sound almost like they make sense in English. 267
So having "request: request" is kind of confusing and doesn't make a lot of sense. 268
So we can modify this parameter to also have an external parameter. 269
So this is the external parameter and this is the internal parameter. 270
So the internal parameter is going to be used inside this block of code and the external one is the one 271
that we're going to use when we call the function. 272
So now that reads a little bit better, 273
loadItems(with: request) 274
Right? Now, what's happening is we're creating a new request, 275
then we modify it with our query and our sortDescriptor, 276
and then we pass that request into our loadItems function over here as NSFetchRequest. 277
And then we perform that request by saying context.fetch. So we can now delete this do-catch block 278
and we can also delete tableView.reload data because it's already inside loadItems, and we now have a 279
shorter and much sweeter block of code. 280
Now, because we're passing in the request, and then we're creating a new constant code request, 281
it's a little bit confusing 282
which one is used here. 283
And in fact, we don't really need this line if we're going to pass in the request. 284
We don't need to reinitialize a new request, 285
right? Now, there's just one problem, and Xcode is actually notifying you of it using this little error here, 286
and it's saying right at the beginning when we perform viewDiLoad, we also called loadItems, and this 287
is where we want to load up all of the to-do list items from our persistent container. 288
And at this point, we're not really interested in querying it or sorting it. We just want every single 289
to-do list item so that we can show it in our table view. 290
But now this loadItems method actually expects a parameter. 291
So what can we do? 292
Well, we could maybe copy this line of code and paste it up here, so we can say let request equals just 293
fetch, all of the items from the persistent store, and we can now pass it in as a parameter, 294
say loadItems with request. But this is not really ideal and it's not all that succinct either. 295
So we can actually shorten this code even more by providing our parameter with a default value. 296
So we can say that the request is of type and as fetch request which should fetch an array of items, 297
that's the data type, and if when we call this function and we don't provide a value for the request, 298
then we can have a default value that simply Item.fetchRrequest. 299
So, now we can delete this line of code and we can be safe in knowing that when we call loadItems with 300
request and we provide an argument, then that's the one that we're going to fetch from our context. 301
But if we call on loadItems without a NSFetchRequest Item, then we can simply use the default value 302
which is Item.FetchRequest. 303
And what this allows us to do isup here when we call loadItems, we can simply call it as we did before 304
without giving it any parameters, and we can also delete this line of code. 305
So this method is a little bit complex because we have an external and an internal parameter, but, also, 306
it has a default value in cases where we call loadItems without giving it any parameters. 307
So it's a good idea to have a play around maybe in playgrounds at creating methods that have internal 308
and external parameters just to see how it works, and also to create methods that have default values, 309
so you can see how you can call them and how you can create them. 310
Now, there's just one problem with our search bar and it's the fact that we can actually get back to 311
our original table view. 312
What do we do once we're satisfied with searching for the word "Save"? 313
And we actually want to go back to seeing all of the to-do list items. 314
It doesn't help if we try and click the cross and delete all the search, we can't get back, right? 315
And that's because we haven't written any code to do that yet. 316
So that's what we're going to do in the next lesson. 317
We're going to address that bug and we're going to fix our table view so that we can return to our original 318
state. 319
So for all of that and more, I'll see you on the next lesson.