Hey, guys. In this lesson, I've got three goals that I want to achieve before the lesson ends. And they're 1
all to do with creating a better user experience and also creating a better user interface. 2
Let me show you what we can achieve. On the left is the current version of our app where everything goes 3
in looking exactly the same because we're using the same reusable cell. And it looks like as if I'm just 4
being ghosted because I'm just sending lots of messages as myself, never getting a reply. 5
But on the right here in the final version of the app, I should be able to see a difference between the 6
messages that I've sent which are marked as Me, and the messages that my recipient has sent which is 7
marked as You. 8
So, now I'm logged in as a different user. And if I was to write and hit send, 9
you can see that this comes out completely different from this one. 10
The second thing I want to show you how to do is to be able to scroll to the very bottom of the table 11
view. So that way 12
If we had a whole bunch of messages, let's just go ahead and add a few more--not sure why Sheffield 13
came up right now, but let's just go ahead and add some more messages, and you can see already the messages 14
are no longer showing up in the current window of my table view. And if I scroll down, I'll be able to 15
see them, 16
but every time I send a new message, I have to scroll down some more. 17
So this is not a great user experience 18
if I have to scroll down every time to see my latest message. And it also means that when I log in to 19
my app, if there's more messages, then there are in the table view, it shouldn't really show the top one, 20
right? 21
It should show the one right at the bottom which is what happened most recently. 22
So that's the second thing that we're going to be tackling here, 23
this way of automatically scrolling to the very bottom of the table view. And the last thing I want to 24
show you how to do is to customize the navigation bar, so that we have a different background color, 25
a different font size for our title, a different color for the logout button 26
and the title text, as well as removing the navigation bar from the welcome screen, because it looks a 27
bit unnecessary here, 28
right? 29
Those are the three things that we're going to be tackling. 30
So let's get on with the first one. 31
How can we differentiate our sender from our recipient? 32
Effectively, what we're going to do is in our ChatViewController at the place where we load up our 33
cells for the tableView, we're going to check to see if the sender of the message that's going into 34
the cell is the same as the user that's currently logged in. 35
And if so, then that means this current message that's being loaded up, this current cell, belongs to the 36
current user, in which case it will show this particular style of cell. 37
But if the user of the message is not the same as the current user, then we're going to show this style 38
of message. 39
So how can we achieve this? 40
Well, one way would be to have two different message cells, but we can also work with a single message 41
cell and just style it differently depending on the sender. 42
And that's the way we're going to do it. 43
Let's go into our MessageCell.xib file, and make some small modifications. 44
I'm going to go ahead and copy this right imagery, and go ahead and paste it inside here. 45
Now, as soon as I hit paste or command V, it usually paste it right next to the previous one. But what I 46
want to do is I want to actually drag it, so that it comes to the very front of our horizontal stack. 47
So you should see a line right at the beginning which means that it's being put at the very left part 48
of the stack view. 49
And in terms of the hierarchy in the document outline, this is what you should see, the MeAvatar, the Message 50
Bubble, and the Right Image View. 51
So this left one shouldn't have the Me image. 52
Instead, if we click on the Image property, we're going to change it to the YouAvatar which is another 53
image that we included in the 54
Assets.xcassets right here. 55
So, now that we've got two different types of bubbles. What we want to be able to do is to hide one of 56
these, 57
if the message came from myself or if the message came from my recipient. Let's go ahead and link up 58
that image view by opening up our assistance. And we've really got our Right Image View, 59
so let's go ahead and just call this one the leftImageView, and hit connect. 60
So, now we've got both of these image views linked up to our MessageCell class, 61
we'll be able to manipulate the MessageCell inside our 62
ChatViewController. 63
So now let's head into our ChatViewController and inside our TableView cellForRowAt indexPath 64
method, 65
let's go ahead and modify our cell depending on whether if the current message that is being displayed 66
into the cell is from the current logged in user or from somebody else. 67
And to do that let's go ahead and create a new constant code message, and we're gonna set that to the 68
current message at indexPath row from the messages array. 69
So this first part before the .body. 70
Just as a quick reminder, this method tableView cellForRoawAt gets called as many times as there are 71
cells. 72
So in our case, we're saying create the number of cells that match the number of messages, and for each 73
of those cells, call this method to figure out what needs to be displayed in it. 74
In our case, we're going to be using a message object to populate the cell. So we can now change 75
the cell.label.text to message.body. 76
And if we want to get a hold of the sender property of the message, then we can just happen to message.sender, 77
and we can use an "if" statement to check if this is equal to, so two equals, the current logged 78
in user. 79
So we'll use the Auth module, Auth.auth() currentUser.email. 80
So remember, this is the same thing that we used when we created each of the messages. 81
So when we said, "let message.sender equals the current user?.email, and then put that messageSender into 82
the senderField of our new document which gets saved in our messages collection. And that messages collection 83
is what we're retrieving and putting into the messages array. And then for each of the rows in our table, 84
we're getting hold of the message and we're checking to see if its sender is the same as the current 85
logged in user. 86
So that means that all of these messages that match this criteria are the ones that the current logged 87
in user sent. 88
So that means that the cell that they should see is the MeAvatar and this particular message, 89
and then we have to hide this 90
YouAvatar, so the one on the left, the one that's linked to the leftImageView. 91
Let's go back to our ChatViewController and inside this "if" statement, 92
so this means "This is a message from the current user," and in this case, we're still gonna be creating 93
a MessageCell from the messageCell class and the MessageCell.xib, but we're going to modify it slightly 94
differently. 95
So let's go ahead and move these two lines of code to above where we actually make the adjustments. 96
So right here. 97
So, now that we've added the message into the cell, we're ready to go ahead and customize it. 98
So the first thing we gonna do is gonna say cell.lleftImageView.isHidden is set to true, and 99
we're going to make sure that the rightImageView is not hidden, 100
so let's change that property to false. 101
So, now we're displaying the rightImageView, but we're hiding the leftImageView, and we're also going 102
to set the cell's background color to make it look a little bit different from the messages that we're 103
getting from other senders. 104
So, we're gonna say, cell.messageBubble which is that UI View that is this purple one right here. 105
We're going to set it to have a different background color which is going to be equal to a UIColor that is 106
named based on one of our constants. 107
So remember that in order to use our custom colors that we create in our Assets.xcassets like 108
this BrandLightPurple, we have to use that method UIColor named, and instead of using the 109
BrandLightPurple string in here, I'm gonna use the same one that I've already got in my constants right here. 110
So it's gonna be under the structure K. Inside K, 111
we've got a structure called BrandColors. 112
And then inside that, we've got a property called lightPurple. 113
So let's go back here and tap into the K constant and then we're going to go to brand colors, and then 114
we're gonna go for light purple like that. 115
That way we won't make any typos here and we'll be able to get the light purple set as the background 116
color. 117
Now, in addition to setting the selves' background color, we're going to also change the labels text color. 118
So let's go ahead and change that property. And you guessed it, we're going to use the same method 119
UIColor named, and in this case, though, it's going to be the purple color that we're going to use 120
as the text color. 121
So K.BrandColors.purple, so that way we have a light purple background with a purple text. 122
And in the case where this is a message from another sender, well, in that case, 123
so the "else" statement is going to have these cell's leftImageView.isHidden set as false, 124
and the cell's rightImageView.is hidden set as true. And then we're going to basically switch up these two 125
things, so that instead of having a background color of light purple, it's going to have a background 126
color purple and the text is going to be light purple instead. Let's go ahead and run our app and see what 127
this actually gives us. 128
Now, when we log in and we take a look at our table view, you can see that for all the messages that I've 129
sent from the same username, so as myself, they have the same appearance. But if I go ahead and log out 130
and log in with the other account that I've signed up for, 131
so if you haven't got one, then go ahead and register a new account. 132
So this is a@b.com 133
We're now getting all the cells formatted as if the message came from somebody else. 134
So let's say I go ahead and write a new message, 135
"I am a@b.com," and hit send. 136
Notice how we now have completely different formats for this type of message versus the message that sent 137
from this user 1@2.com. 138
So, now let's hit send and scroll to the bottom. 139
You can see that we've now formatted ourselves to be able to differentiate between the messages that 140
the user sent and the ones that they received. 141
Now, this is a good point to mention that if you had formatted your cells to be rounded for the previous 142
dimension, so previously in our MessageCell.xib, 143
we only had this message bubble and the rightImageView. 144
But now in addition to that, we've also got the leftImageView. 145
So given that our Stack View has 20 points of space set between all the elements, we've now changed the 146
dimensions of the cell. 147
So if you're wondering why it is that your message bubbles no longer look right or the same way that 148
you had before, 149
well, it's because of this line of code. 150
So if you've changed this to divided by 4 or, say, another number, you'll have to change it to divided 151
by 5 152
if you want this perfectly rounded corner like I've got here. 153
If you wanted to have it square or any other different shape, then feel free to keep it the way that 154
you've got it. 155
This is just the quick tip in case you're wondering why it is the shape has changed. So let's tackle 156
the next problem. 157
Notice how every time I send a new message, it gets put right at the bottom 158
and we have to scroll manually to be able to see it which is quite painful, 159
so let's try and change that. In our Chart View Controller, 160
the point where we can do this is inside our loadMessages method because it's here where we have our 161
Snapshot Listener added to our collection. 162
And every time there's a change in our database, we trigger this closure which empties out our messages 163
array and adds all of the new messages back in to that messages array. 164
So at this time point is where we call reload data to trigger these two delegate methods and put the 165
cells into our table view. 166
Now, if we wanted to scroll to the very bottom of the table view every time there's a new message added, 167
then all we have to do is to call a method which is self.tableView.scrollToRow. 168
Now, this particular method expects a IndexPath provided, 169
so we're going to have to create that as a constant as well. 170
So let's call it indexPath and I'm going to initialize a IndexPath object, and I'm going to choose 171
the initializer that asks for a row number. 172
This is basically saying scrollToRow. And in this line we're creating which row we want to scroll to. 173
We want to scroll to the very end of our messages array. So we can simply write messages.count. 174
But this is not enough because let's do the math. 175
Let's say that we had an array with two items, right, just "A" and "B." 176
Well, in this case, the array.count is going to be equal to 2. 177
There's just two items. 178
But if we tried to get a hold of the last item in the array by tapping into the item at index 2, then that's 179
going to be 0, 1, 2. 180
So it's going to be out of bounds. 181
So in addition to writing messages.count here, we have to subtract 1 to be able to get a hold of 182
the last item in our messages array. 183
And we also have to add the "self" keyword because we are inside a closure. 184
The next thing it asked for is the section number and we're going to set that as 0 because we actually 185
only have one section. 186
Now, if we were inside, say, these settings page of our iPhone, then you'll notice that it has lots of different 187
sections. 188
Here's section zero, section 1, section 2. 189
But we've only got one, so we can set that as the first section. 190
So, now that we've created this IndexPath object, we can now use it inside here to specify which row 191
we want our table view to scroll to. 192
And then the position for the table view is to position that row at the top, and then the animated property, 193
you can either set to true, which is going to give you an animation of that scrolling to this row, 194
or you can set it to false if you just wanted to go straight there without the animation. 195
Let me demonstrate so you can see what's the difference. Let's log in and you'll see that it scrolled, 196
right? 197
It just did that animation to go to the very bottom of our table view and show the latest message which 198
is the effect that we wanted. 199
If I change that to false, however, you'll see that once the screen loads up, it goes straight to the bottom 200
of the table view 201
without that animation. 202
So I'm going to leave this up to you depending on whichever way you prefer your app to be, 203
then you can set that property however you want, 204
true or false. But we've now solved this problem because every time I write a new message now, 205
if I hit send, 206
you can see that it pops up right at the bottom, 207
so I don't have to manually scroll to see it. But there is something that we can still improve here, though, 208
because when I create a new message and I hit send, the old message doesn't disappear from my text field. 209
So I have to manually delete it in order to write a new message which is a bit painful, 210
right? 211
And I want to pose this as a challenge to you. Can you modify the code, so that when the send button is 212
pressed, the old text is deleted. 213
I'll give you a few seconds to pause the video before I show you the solution 214
Ready? 215
Here's the solution. 216
The key is locating the time point in our code when we send a new message. 217
And that time point is the IBAction sendPressed. 218
So all we need to do here is add a little bit of code. 219
If the message was successfully saved, then we can go ahead and happen to our text field, 220
our messageTextField and set its text property to an empty string. 221
This way the text field will empty out whenever we're done with sending the message. 222
And because we're inside a closure and we're trying to update the user interface, remember that we should 223
tap into the DispatchQueue.main.async method so that this actually happens on the main 224
thread, rather than on a background thread, which is where the code enclosures tend to take place. 225
And that's the solution to the challenge. 226
Now, let's run our app and see this behavior showing up. let's go ahead and write a message and hit send. 227
And now that message goes in and our text field clears up. 228
Perfect. 229
Now all that's left to do is the last thing that we mentioned which is to format our navigation bar. 230
The first thing I said we wanted to do was to change its color and change some of the properties. 231
Now, to change this particular nav bar, 232
you don't actually change it in this view controller, you change it in the navigation controller. So selecting 233
this navigation bar from the navigation controller, which controls all of these screens, then we can go 234
into the attributes inspector and change the Bar Tint color. 235
So let's go ahead and select our BrandBlue to go in there and you can see that that color now goes 236
in all the screens where it's being displayed. 237
And we're also going to change the Title Color. So that will change the text that's going into the navigation 238
bars 239
as the title. So at the moment, we're setting that title right here in our viewDidLoad where we said 240
title = K.appName which, of course, corresponds to our constant app name which is this right here. 241
An alternative way of setting the title is to click on this navigation item and put it right here 242
where it'll show up. 243
So depending on whichever one you want to use, you can set it either in the Interface Builder or as 244
code inside the Chat View Controller. 245
Now, let's go back to our navigation bar and change the title color to white and change the font size 246
to System fonts. And Blacks are really, really bold, so much bolder than bold, and change the size to 25 247
to make it a lot bigger, so that it shows up. 248
So, now let's go ahead and give it a spin. And once we go to the Log In page, you can see that there is 249
this clear difference between the navigation bar and this blue background screen. 250
So if you want this to look identical, then the way to address that is by selecting the Navigation Bar 251
on the Navigation Controller, and then changing that Translucent property to false. 252
So, now let me run that app again. 253
And on our Log In screen, you can see that those two colors now match perfectly. 254
Now, the final thing I want to change is to make that Back button stand out a little bit more, because 255
at the moment, it's blue on blue because we've changed the color of the navigation bar. So, again, selecting 256
the navigation bar from the navigation controller. 257
Let's go ahead and change the tint color to white. 258
That way all of the bar buttons will turn into a white colored button item like so. 259
Now, all that we have left to do is the last thing which is, how can we disable this navigation bar 260
so that it doesn't show up on some of the screens? 261
For example, it makes sense for it to be here because we might want to press back, but on the welcome screen, it looks 262
kind of ugly just to have it hanging around not doing anything. 263
Let's go into that WelcomeViewController. 264
And in order to disable that navigation bar, the property that we can set is code "i is Navigation Bar Hidden." 265
But the time point where we want to hide it is at the point where the viewWillAppear. 266
So this is just before the view shows up on screen. And it's at this point in time where we're going 267
to tap into the navigationController.isNavigationBarHidden and we're going to set it to true, 268
so that we hide it for our WelcomeViewController. 269
But notice how if I run the app as it is right now while I've hidden the navigation bar for the welcome screen, 270
if I go into the log in or the register screens, I've now lost my navigation bar. It's still hidden. 271
So in addition to hiding it, I also have to unhide it. 272
And the time point where I want to do that is when we're about to go onto the next screen. And we can 273
override a method called viewWillDisappear. 274
So just before the view disappears and the next screen shows up, we tap into a navigation controller 275
and reset the isNavigationBarHidden property to false. This way just before the view shows up, 276
we set that navigation bar to hidden. 277
But as soon as I press log in and my welcome view controller disappears, my navigation bar becomes visible 278
again. 279
So that way we only get it on the screen where we want it and we disable it for the screen 280
when we don't. Now, the final thing to note is 281
something that won't affect how it behaves. But whenever you're overriding one of these lifecycle methods 282
from the view controller, such as viewWillAppear, viewWillDisappear, and viewDidLoad, 283
you should always code the super method like how we have here with viewDidLoad. 284
So this is something that it's best to get into the habit of doing because it's good practice. And in 285
fact, when you read the documentation for one of these methods, let's take a look at the one 286
for viewWillAppear, 287
you can see that down here in the Discussion, it tells you that if you override this method, like what 288
we have done here, and we're providing our own custom functionality, you must call super 289
at some point in your implementation. So that just means that the parent, the UIViewController, which 290
our class is inheriting from, gets an opportunity to run its own code inside its own viewWillAppear, 291
and then afterwards, we can run our code which has our custom functionality. 292
So in order to do this, we write super.viewWillAppear, and then we pass in the same value of animated, 293
as we have here from our inputs. And we do the same thing for our viewWillDisappear. 294
And this, even though you'll find that when you run the app, doesn't actually change anything at all. 295
But it's really a good practice to get into the habit of always calling super whenever you override any 296
function from the superclass. In the next lesson, 297
we're gonna be looking in more detail at each of these lifecycle methods that come from the UIViewController 298
and we're going to see when they get called, and how they allow us to tap into certain time 299
points during the life of our view controller, and to be able to perform things, such as updating the 300
UI or triggering animations, or starting timers, et cetera. 301
So for all of that and more, I'll see you there.