VNUHCM
Bài trước Bài kế tiếp
  • Nội dung học
  • Trợ giúp
    Bạn có thắc mắc khi đang học?
    Hướng dẫn cách học Những câu hỏi thường gặp Email cho giáo vụ
    • Tiếng Việt
    • English
    • Thông tin Thành viên
    • Khoá học đăng ký
    • Đăng xuất
  • Cohota
  • HƯỚNG DẪN HỌC TẬP

  • Xem chi tiết >>
    Bạn đã hoàn thành 0% khoá học
  • HƯỚNG DẪN SINH VIÊN ĐĂNG NHẬP HỆ THỐNG
    • Hướng dẫn đăng nhập
    • Hướng dẫn vào khóa học
  • Introduction
    • Welcome
  • Unit 1: Values
    • Introduction - Unit 1: Values
    • Get Started With Values
    • Play with Values
    • Playground Basics
    • Naming and Identifiers
    • Simulation
    • Strings
    • Constants and Variables
    • Word Games
    • Build a PhotoFrame App
    • Design for People
  • Episode 1: The TV Club
    • Introduction - Episode 1: The TV Club
    • Searching for Content
    • Sharing Personal Information
    • Ordering Online
    • Reflection: Episode 1
  • Unit 2: Algorithms
    • Introduction - Unit 2: Algorithms
    • Get Started with Algorithms
    • Play with Programs
    • Functions
    • Types
    • Parameters and Results
    • Making Decisions
    • BoogieBot
    • Data Visualization
    • Build a QuestionBot App
    • Design an Experience
  • Episode 2: The Viewing Party
    • Introduction - Episode 2: The Viewing Party
    • Accessing the Show
    • Streaming on the Network
    • Reflection: Episode 2
  • Unit 3: Organizing Data
    • Introduction - Unit 3: Organizing Data
    • Get Started with Organizing Data
    • Play with Complex Data
    • Instances, Methods, and Properties
    • Arrays and Loops
    • Structures
    • Enums and Switch
    • Testing Code
    • Processing Data
    • Pixel Art
    • Password Security
    • Visualization Revisited
    • Build a BouncyBall App
    • Design a Prototype
  • Episode 3: Sharing Photos
    • Introduction - Episode 3: Sharing Photos
    • Capturing Images
    • Posting on Social Media
    • Reflection: Episode 3
  • Unit 4: Building Apps
    • Introduction - Unit 4: Building Apps
    • Get Started with App Development
    • Play with App Components
    • Color Picker
    • ChatBot
    • Rock, Paper, Scissors
    • MemeMaker
    • Build an ElementQuiz App
    • Design for Impact
  • Appendix
    • Episode Technical Concepts
    • Glossary
Tổng quan điểm khóa học
Đánh giá

Tiến độ
Tên tiêu chí Trọng số (%) Điểm Tiến độ (%)
Unit 4: Building Apps

ChatBot

Unit 4: Building Apps|Play

What you'll build

  • A data source object to keep track of a conversation between a user and a chatbot.

What you'll learn

  • How apps are built up from instances of different types working together.
  • How data sources are used in iOS apps.

Key vocabulary

  • Cell
  • Data source
  • Table view

Introduction

ChatBot is similar to the project you worked on in QuestionBot. Instead of asking a question, pressing a button, and getting an answer, you’ll build up a conversation, similar to a chat in the Messages app:

Screenshot of full ChatBot app showing a conversation and thinking

In the screenshot you can see the following:

  • A list of messages forming a conversation.
  • Messages entered by the user that look different from those given ​by the app.
  • A “thinking” indicator.
  • An entry area where the user can type a question.

In iOS apps, a scrolling list of items is known as a table view. Each item in the list is called a cell. (You may have heard the term “cell” when talking about spreadsheet programs.)

As with QuestionBot, much of the app is already built, including the parts that deal with showing the table view and the cells. Your job is to work on the part ​of the app that keeps track of the conversation.

Part 1 - Exploring the Project

Open the project called 'ChatBot.xcodeproj' in the "ChatBot" folder of your course resources and take a look around. Make sure you can see the project navigator. If you can’t, choose View > Navigators > Show Project Navigator.

The files that make up the app are collected into groups. You’ll only be working with the files in the Model group for this project. Feel free to explore the other files, but don’t worry if you see things you don’t understand. This project has been organized so you can concentrate just on the areas you’ve been learning about so far.

Here’s a summary of what the other files in the project are for, in the order they’re shown in the project navigator:

  • UI
    • Main: The interface of the app, including the layout of the screens.
    • LaunchScreen: The screen displayed when the app is first launched (an empty white screen).
    • ThinkingCell: A specialized cell for showing the app is thinking.
    • ConversationCell: A specialized cell for showing a message in the conversation.
    • AskCell: A specialized cell for allowing the user to type in a question.
    • Assets: The asset catalog holding all of the images used in the app.
  • Controllers
    • ConversationViewController: The main view controller for the app. This class is responsible for the list view and handling updates when the user asks questions. It’s the most complicated file in the project, and contains a lot of code you haven’t learned about yet.
  • Model: Details of this group are given below.
  • Support files:
    • AppDelegate: Part of the standard app template, normally used to handle events such as the app being launched.
    • Info: Part of the standard app template, holding information about the app itself.

Inside the Model group are three Swift files:

Message

The app will show you a conversation between you and a robot. This file describes the things that can make up a conversation.

The conversation is going to hold two types of messages: questions ​(asked by the user of the app) and answers (given by the brain of the app). ​The MessageType enum holds the two possible values, .question and .answer.

The Message struct holds the information needed to make an entry in the conversation: the date, the message text, and the type of message.

To create a question, you use the Message initializer like this:

let question = Message(date: Date(), text: "Do you know the way to Cupertino?", type: .question)

This code will create a Message instance with the current date (given by Date()) and a type of .question.

You can see an example for the openingLine constant at the end of the file. This openingLine creates a standard opening line to be used anywhere in the app.

ConversationDelegate

This file, formerly known as QuestionAnswerer, may be familiar from the QuestionBot lesson. The ConversationDelegate struct is the brain of the chat and is responsible for providing answers to questions. Just as in the QuestionBot lesson, you can edit this file to give different responses. The conversation view controller owns the conversation delegate and uses it to get answers from questions the user enters.

Delegation is a common pattern for providing information to other parts of an app based on their demands. You didn't write the code to call the answerTo(question:) function. The app knows to ask your conversation delegate to provide an answer when the user poses a question. The view controller delegates the responsibility of answering questions to your code so that it doesn't have to know about the details of how questions are answered.

ConversationDataSource

The ConversationDataSource class is responsible for holding and updating the details of the current conversation. The conversation view controller owns the data source. The data source is best thought of as a lightning-quick, very attentive assistant that waits to be asked for information or to be given orders. In this case, the questions and orders are:

  • How many messages are there?
  • Add this question to the conversation.
  • Add this answer to the conversation.
  • What is message number X?

The view controller asks these questions or gives those orders based on the user’s actions in the app. This arrangement is very common when writing apps. The data source doesn’t deal with anything other than the information about the conversation. Showing things onscreen or dealing with what the user types in is handled by the view controller. As you’ve learned before, being able to focus on one job makes your code easier to write and easier to understand.

A data source is another example of a way to respond to the circumstances of your app as it runs, rather than calling your code directly. The view controller knows when to ask the data source what it needs to know, so your code is called on demand.

Currently, ConversationDataSource doesn’t do anything when given orders, and doesn’t give very helpful answers when asked for information. You’ll be working on this implementation for the rest of the lesson. There are print statements in the methods so that you can keep track of when they're being called.

This diagram shows a summary of how some of the instances in the app work together:

Information flow between objects in ChatBot app

In some kinds of iOS apps, nothing happens until an action is performed. Those user-initiated actions, like swiping or tapping, are called events. Because so much that an app does is governed by events, much of the app code you write will run when it's triggered by the user. There are two types of events that ChatBot reacts to: the user scrolling the table and the user entering questions. When those events happen, your conversation data source will be asked to perform its duties.

Part 2 - Examining the Code

Build and run the app, which will look like the screenshot shown to the left. If you check in the console in Xcode, you won’t see any of your print statements yet. That's because you've only launched the app and haven't triggered any events—so your code in ConversationDataSource hasn't run.

Empty chatbot

Enter a question. The app will think a little bit, then will go back to looking just like it did before.

Check the console again. Entering a question generated an event, which ran some code. So now you'll see two messages generated by the print statements:

  • Asked to add question: ... was printed when you first tapped the return key and asked the question.

  • Asked to add answer: ... was printed when the app was done thinking about your question and decided to give an answer.

Nothing changed on the screen, because the data source says there aren't any messages in the conversation. Your data source's current capabilities look like this:

  • How many messages are there? – None.
  • Add this question to the conversation! – I'll print to the console, but I'm not doing anything else!
  • Add this answer to the conversation! – I'll print to the console, but I'm not doing anything else!
  • What is message number X? – The answer "Hello world!"

You never saw the line Asking for message at index ... in the console, because the data source says there are no messages in the conversation. The app fills the table view based on that answer, so it doesn't ask for any messages.

Now you’ll look at each aspect of the data source in turn and explore what’s happening.

Part 3 - Fixing the Message Count

Change the messageCount property to have a value of 1 instead of 0:

let messageCount = 1

Build and run again:

Chatbot with single entry

You can see a single message in the conversation. Check in the console, and you'll see this message:

Asking for message at index 0

(Remember: in Swift, you count starting at 0.)

If you ask a question, you'll see that nothing changes, but you still see the console messages about adding question and answer messages. Now the data source's capabilities look like this:

  • How many messages are there? – One.
  • Add this question to the conversation! – I'll print to the console, but I'm not doing anything else!
  • Add this answer to the conversation! – I'll print to the console, but I'm not doing anything else!
  • What is message number X? – The answer "Hello, world!"

What should really happen is that each time a message is added to the conversation, the data source should update the number of messages so it can give the correct answer the next time it's asked.

Change the definition of the messageCount property to a var instead of a let, and set the initial value back to 0:

var messageCount = 0

Inside the add(question:) and add(answer:) methods, after the print statement, add the following line:

messageCount += 1

This increases the messageCount property each time a message is added to the conversation.

Build and run the app again, and ask a question.

Chatbot with entries matching the number of messages

You can see that the right number of messages are displayed, and in the console the data source is being asked for messages at indexes 0 (for the question) and 1 (for the answer).

Now the data source's capabilities look like this:

  • How many messages are there? – The total number of questions and answers you've given me.
  • Add this question to the conversation! – I'll print to the console, and I'll update my message count.
  • Add this answer to the conversation! – I'll print to the console, and I'll update my message count.
  • What is message number X? – The answer "Hello, world!"

This is a little better. At least the right number of entries are in the chat, ​but they’re all the same answer.

Part 4 - Working with Indexes

The app allows you to ask a question, then it thinks, and then it gives you an answer. You're not allowed to ask another question while it's thinking. Since your numbering starts at 0, you can assume that each even-numbered message in the conversation is a question and each odd-numbered message is an answer.

To show this in the app, remove the return line from messageAt(index:) and replace it with this:

if index % 2 == 0 {
    return Message(date: Date(), text: "Question \(index / 2)", type: .question)
} else {
    return Message(date: Date(), text: "Answer \(index / 2)", type: .answer)
}

This code uses the remainder (or modulo) operator, which you saw in a previous lesson, to determine if the message should be a question or an answer, then it creates and returns something appropriate.

Build and run the app, and ask a few questions. You should see the screen shown to the left.

Chatbot with alternating questions and answers

Now your app is showing the right number of messages in the conversation, the questions and answers are showing up correctly, and you’re seeing different text as you move through the conversation. But it isn’t showing the actual contents of the conversation. Now the data source's capabilities look like this:

  • How many messages are there?: –The total number of questions and answers you've given me.
  • Add this question to the conversation! – I'll print to the console, and I'll update my message count.
  • Add this answer to the conversation! – I'll print to the console, and I'll update my message count.
  • What is message number X? – If X is even, a numbered question, otherwise a numbered answer.

Part 5 - Adding Storage

The conversation data source needs a way to store the questions that the user has entered and the answers given by the brain. The method of storage must be able to:

  • Store as many messages as required.
  • Keep the messages in a specific order.
  • Give back a message at a specific index.

You've already learned about something that can handle these tasks: an array. Add the following property to hold an array of messages:

var messages = [Message]()

You've now initialized an empty array. Add this code to add(question:) to create a new message and store it in the array:

let message = Message(date: Date(), text: question, type: .question)
messages.append(message)

Add similar code to add(answer:):

let message = Message(date: Date(), text: answer, type: .answer)
messages.append(message)

Now each time the data source is asked to add a question or answer, it will create a new Message and add it to the array.

Remove the conditional statement lines after the print statement in messageAt(index:) and replace them with this:

return messages[index]

Build and run the app, and ask some questions. You’ll see your conversation history is now kept and displayed in the list. You should see a screen similar to the one shown to the left, with your questions and answers.

Congratulations! You've built a working data source. The code in this assistant performs a small set of well-defined tasks, and the rest of your app just has to put it to work. By grouping the information about the conversation history in the ConversationDataSource struct, you've created an abstraction that hides those details from the rest of the app. As you improved the capabilities of your assistant, the app's capabilities improved as well. When you structure app code in this way, you can work more efficiently when you need to enhance the app—whether it be the brains, the data management, or the UI.

Chatbot showing full conversation history

Part 6 - Refinements

There’s no longer any need for the separate messageCount, since you can just use the count property of Array to figure out how many messages there are. In fact, storing redundant information can be the source of bugs if you make changes in one place but forget to make them elsewhere. Delete the lines from add(question:) and add(answer:) where you increase the message count, then make the messageCount property a computed property:

var messageCount: Int {
   return messages.count
}

In addition to the “Ask a question...” prompt, it’s nice for the app to welcome the user and ask them to get started. To add a welcome message, find the ​line where the messages array is initialized and replace it with this:

var messages = [openingLine]

The code above creates an array with a single message in it, rather than an empty array of messages. You don’t need to change anything else, since messageCount is now a computed property which will have the right answer. (You've already benefited from eliminating a source of redundant information!)

For the last step, improve ChatBot's brain. From the project navigator, open ConversationDelegate under the Model section. Change the logic of the responseTo(question: String) implementation to return your own answers to the user's queries. You can use your code from QuestionBot—or even improve on it.

Reflection Questions

In QuestionBot, you worked on the part of the app that gave answers to questions. In this lesson, you worked on a separate part (a delegate) that also gave information back to another part of the app.

Would it have been better to have both of these jobs—delegate and data source—executed by the same instance?

What is the benefit of separating work in this way?

If the data source hadn’t already been partly written, how would you have known which methods to write?

Summary

You’ve learned how to use an array to provide ordered storage of values of the same type, and you’ve had some practice at initializing structs. You’ve seen another example of a type that exists just to provide information to another part of your app—an idea that's used often in iOS apps. In the next lesson, you’ll use all the skills at your disposal to create a game with standard iOS controls and its own small game engine.

Báo lỗi