Build a SwiftUI to-do app from scratch with Realm Crash Course

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
Hi! Stephanie from Design plus Code. This week, we're going to learn how to code another application from scratch. And this time we will code a to-do application. This application will be fully functional, meaning that we can add new tasks. For example, I will need to wash the dishes as well. And I can also check the task that I completed. For example, I mopped the floor so I can check it and it will move to the bottom of the list. And not only can we check our tasks, we can also delete them. So we'll learn how to implement swipe actions, with a destructive method, which will delete our row. Before we start, if you like building SwiftUI applications from scratch with me, don't forget to subscribe and to hit the like button, because it means a lot to us. In this tutorial, we'll learn how to implement the MongoDB Realm package. This package is an alternative to CoreData and it allows us to perform CRUD actions, which means create, read, update, and delete and also to persistently cache our data, even when we're offline. Before starting to code, make sure you have at least Xcode 13 installed on your MacBook. If you have an older version of Xcode, make sure to update it, because we will use new features introduced since iOS 15 and iOS 15 is only available in Xcode 13 and higher. Now, without wasting any more time, let's start by creating our Xcode project. In the modal that appears when you launch Xcode, click on create a new Xcode project. In here, we'll need to go under the iOS tab and click on app, then click on next. We'll need to give our project a name. In my case, I will name it ToDoDemo. Select your team as well as your organization identifier. So the organization identifier is your URL, but in reverse. For example, the URL of Design+Code is designcode.io, so we will write io.designcode. Make sure the interface is SwiftUI and the language is Swift. Usually these are the default values, so you don't need to change anything. Then, click on Next. Choose where you want to save your project and click on create. The first thing I like to do when I create a new project is to change the simulator target. By default, it will either be iPod touch or your Mac, so I like to set it to iPhone 13 or iPhone 12. So I'll select iPhone 13 in this case. And we can also add an icon to our application by clicking on the assets folder. And under app icon, we will simply need to add an icon for each empty slot by respecting the size that you see. For example, here we need a 60 by 60 pixels icon and here for the app store, we'll need a 1,024 by 1,024 pixels icon. I have my folder containing all the sizes and I will simply need to drag and drop them into their specific slot. Now that we added all the icons to our application, we can start coding the UI. We'll take care of the TasksView first, which is this screen here, which shows a title as well as the task rows. So let's create a new file in our project. This file will be a SwiftUI view. And we'll name it TasksView. Then, click on create. Since we will have a few views, let's organize our files a bit and create a group called views. Then we'll drag and drop the content view as well as the tasks view under the views folder. Now in the TasksView, let's remove the text hello world. And we'll be adding a VStack instead. We'll add two modifiers to the VStack. The first one will be a frame with a maxWidth of infinity, as well as a maxHeight of infinity as well, so that it takes the entire screen. We'll also add a custom background color. So the color I will add is does yellowy orange color. And you can change it if you want by clicking on the inspectors at the top right corner of Xcode. And at the bottom, you will see the background and you can custom and change the color if you wish. Inside of the VStack, we'll add a title. So let's add a text that says My tasks. Then let's change the font of this title, to title3, and add a bit of boldness to the title. Then we'll also add some padding all around this title, as well as a frame with a maxWidth of infinity and an alignment to leading. This tasks view will contain a few rows. If you take a look at the final product, you can see that the task view will contain a few rows and each row will present a different task. Let's create this component first. So let's add a new group under the to do demo folder and we'll name it. Components. Then inside of the components folder, we'll create a new file and this file will also be a SwiftUI view. Let's name this file TaskRow, then click on create. So each task row will require two arguments. The first one will be a task, which will be a string. The second variable will be the completed status, which will be a boolean. Don't forget to pass the task, as well as the completed status to the preview, otherwise you will see an error like this and you won't be able to build your application. So the task, let's say that it's Do laundry. And the completed status will be true. Now in the body of our task row, let's remove the text Hello world and add an HStack instead, with some spacing of 20 pixels. Inside of this HStack, we'll have two elements. The first one will be an image with the systemName. The system name is a string and the string will come from SF Symbols. So let's open the SF Symbols application. And if you don't have it, simply search for SF Symbols in Google, and then you will find the link to download this application on your Mac. So let's search for circle here. And you can see that we have a simple circle, so let's use this icon when the task is not yet completed. And if we scroll down, you will see at the bottom here, we have a checkmark dot circle. So let's use this when the task is completed. To do so, instead of simply specifying one string, we'll add a ternary operator. So we'll check if the completed status is true. If it's true, we'll call the check. mark.circle icon. Otherwise we'll call the circle without any checkmark. So you can see here that since the completed status that I passed to the preview is true, you can see the checkmark. If a change it to false, we can see that we only have a circle. So let me change it back to true. And right below this image, we'll simply display the task. So let's just add a text. And the string will be the task. So you can see that we have do laundry, which is the task that we passed of this preview. And the completed status is true, so it shows a circle with a check mark inside. Let's go back to the task view. And for now, you can see that the task view is pretty empty, but don't worry, because we'll add the task rows to this view later on, after integrating Realm to our project. Now, let's take care of the add task view. Under the views folder, we'll right click and create a new file. This file will be a SwiftUI view. And let's name it AddTaskView. Then, click on create. This view is where the user will be able to add a new task to the app. If we take a look at the final product, once you click on the plus add button at the bottom of this screen, we'll see a sheet presentation and this is the AddTaskView. So back in Xcode, let's remove the text Hello world in AddTaskView and add a VStack, with an alignment to leading and some spacing of 20 pixels. We'll also need to add a few modifiers to the VStack. So let's add some padding to the top 40 pixels, as well as some horizontal padding. Next we'll also need to add a background color and we'll use the same color that we used in task view. So let's simply copy the background modifier from the task view. And then paste it in the AddTaskView file. Don't forget to resume your preview. And you can see that for now. We only see a small rectangle in our view, so let's add some content inside of it, so our background color takes the entire screen. So let's add a title and it will say Create a new task. We'll change the front of this title to title3, and also add some boldness to it. Next, let's add a frame with a maxWidth of infinity, as well as some alignment to leading. Below the title, we'll need to add a text field. As the name says, the text field is a field in which the user will be able to write some content. And this text field will require two arguments. The first one is a string, and this will be the placeholder. So we'll write Enter your task here. And the second argument is a text, which as you can see here, it's a binding string. To create the binding string, let's create a private state at the top of this structure. So we'll do at state private var title, which will be a string and by default it will be an empty string. The private keyword here means that this variable will only be accessible within this view and not outside of it. In order to add a binding, we simply needs to add the dollar sign in front and then write title. So this will be our binding state, and the value of the title will always change anytime that the user adds a character in the text field. Let's resume our preview. And you can see that right now, we have it text field at the bottom here. Let's change the style of this text field a bit, so we'll add the text field style modifier. And we'll add the rounded border value. This means that our text field will have a bit of rounded corners. Then, below the text below, we'll need to add a button. So a button has two closures. The first one is the action. And the second one will be the label. In the action, let's simply print a message to the console that says Task added!. In the label, we'll add a text that says our task. So when the user taps on this button, we'll add this task, but we'll take care of the functionality later on. Now, the textbooks a bit boring here. So let's change that. We'll add a foreground color of white. And we'll add some padding all round the button and add more horizontal padding. Then we'll add a background color. And you can select any color that you want, but I have my color, which is a dark green. And I'll also add some corner radius of 30 pixels. Right below this button, we'll add a spacer to push everything to the bottom and you can see that now our background takes our entire screen. Now let's take a look again at the final product. So we already coded the TasksView. And we also coded the AddTaskView. The last thing we need to code is the SmallAddButton here. So let's create that under the components folder. We'll right click, create a new file. It will be a SwiftUI view and we'll name this file SmallAddButton. Then click on create. Let's remove the text HelloWorld and add a ZStack instead. The ZStack means that we are working on the Z axis, so each element will be on top of one another. Let's simply add a frame with a height of 50 pixels to this ZStack. The element that we want to be on the bottom of this ZStack is a circle. So let's add a circle. And you can see that by default, the foreground color of the circle is black. Let's make sure that the width of this circle will stay at 50 pixels. Then let's change the foreground color of this circle. Let's add the foreground color modifier. And we'll take the same green as the button in our AddTaskView. So let's simply copy the color of our button's background. And then paste it in the foreground color modifier in the small add button component. Now on top of this circle, we want to show a little plus sign. So we'll add a text. And the content will be a string, and the string will be a plus sign. Then we'll change the font to title. We'll also add some fontWeight, heavy. And the foreground color of this text will be white. Now that we created all our components and views, let's add everything to ContentView. So let's go to ContentView. And instead of showing a text that says Hello world, we will add a ZStack. So let's remove the text and add a ZStack. The alignment of this ZStack will be to bottom trailing, meaning that everything will be aligned to the bottom and to the right of the screen. Let's also add a frame to our ZStack. The max west will be infinity and the max height will also be infinity. We'll also add some alignment to bottom. Now we need a background color, and we want the same background color that we added in our TasksView and AddTaskView. So let's simply go in either one of these views. And copied the background modifier. Then go back to ContentView and paste this modifier here. Now we can see our background color in the preview here, and the first thing we want to add in our ZStack is a TasksView. So let's call the TasksView. And you can see that right now, we only see My task here, but we will be adding some content later on. Right below the task view, we'll call the SmallAddButton. And let's add some padding all around it. Now, we can see our TasksView and an AddButton at the bottom right corner. However, the user has no way to access the AddTaskView. We should implement that functionality when the user taps on this SmallAddButton, so let's do it! In ContentView, let's add a state at the top. So at state private var, and we'll call it showAddTaskView and set it by default to false, because we don't want to show the AddTaskView by default. The private keyword here means that this state is only accessible inside of the ContentView, and not in any other file. Next, on tap gesture of our SmallAddButton, so let's add the on tap gesture, we want to toggle the value of the showAddTaskView. So let's call the showAddTaskView state, and then call the toggle function, which means that when a user taps on the SmallAddButton, we will toggle this value to true. Now on the ZStack, we'll add the sheet modifier and double-click on the first one, with the isPresented parameter. You can see that the isPresented accept a binding boolean value. If you remember, in our AddTaskView, in order to add a binding value, we needed to add a dollar sign in front of the variable. So we'll need to do the same in ContentView. So let's replace the binding bool by a dollar sign and then call the showAddTaskView. We don't need to do anything on dismiss of the sheet. So let's simply remove this. And for the content, you can double-click on it and add the content inside of the closure that X code will automatically add, but what I like to do is to simply remove all of this and add a closure after the closing parentheses. We need to call what we want to show on the sheet that is being presented on the screen. In our case, we want to call the AddTaskView, meaning that we will show the AddTaskView on top of the TasksView when the user taps on the SmallAddButton. So let's resume our preview. And then click on play. And if we click on the plus sign, you can see that we see a sheet presentation and we can dismiss it by dragging it down. However, we also want to dismiss the AddTaskView sheet when the user taps on the Add task button. So let's go to the AddTaskView. And at the top, we'll need to add an environment variable. So at environment. And then we need to add parentheses, added backslash, dot, dismiss var dismiss. So dismiss is a new environment variable, introduced since iOS 15, that allows us to easily dismiss a view. So right below the print Task added to the console, let's call the dismiss with parentheses at the end, so that it dismisses the sheet, once the user taps on the Add task button. Now let's go back to ContentView and resume our preview. Let's click on the SmallAddButton. And we can see our sheet presentation. We can write anything in the text field, but, for the moment, it's not adding anything to our database because we didn't implement the database yet. So let's right Do laundry, and then click on Add task and it dismisses the sheet for us, so we're good to go. We can now move on to the more complex task of adding Realm and the CRUD functionalities to our application. If you don't see the sheet when you click on this SmallAddButton, make sure you added the showAddTaskView state in ContentView, toggle its value on top gesture of the SmallAddButton and added the sheet modifier to the ZStack as well. Now, we need a way to persistently save the tasks in a database. There's a lot of alternatives out there to save data persistently in an application like CoreData from Apple, UserDefaults or AppStorage, or even cloud managed databases like Firestore, but in this tutorial, as I mentioned earlier, we will learn how to use MongoDB Realm, because it's pretty straightforward, easy to learn, and we can persist data even when we're offline. First, let's install the Realm package in our Xcode project. So click on your project name and then under project, click on your project name again. And then we'll click on the Package dependencies tab. Here, we'll click the add button. And we'll add a package to our project. You can see that I already used the Realm package, so they suggest me to install this package in this project. But for those of you who never used this package, you will need to copy this link that you can also find in the text content of the section, and then paste it in the search box here. And Xcode will find the package on GitHub for you. Now we need to set the options for this package. For the Dependency Rule option, we'll keep it to up to next major version. However, we'll change the version to at least 10.10.0, because we will use the new @Persisted property wrapper from Realm, which is only available from version 10.10.0 and higher. For the add to project option, select the project you're currently working on. So by default, this should already select the project that you're currently working on. Then click on add package. This might take a few minutes because it will fetch the package from GitHub into your project. You'll, then be able to choose which package products you wish to install in your project. We'll need both the Realm and the RealmSwift package, so check both boxes and then click on add package. Once the package is installed, you might see a few warnings here. However, we have no way to fix these warnings as they come with the Realm package. Also, they're just warnings, so we can omit them. Then we'll need to create the RealmManager. So under our ToDoDemo folder, we'll right click and create a new file. This file will be his Swift file and we'll name this file RealmManager. Inside of this file, we'll need to import RealmSwift, then let's create a class called RealmManager that will conform to the ObservableObject protocol. By conforming to this protocol, it will allow the entire application, or at least all views connected to the RealmManager class, to be notified of changes in this class. In order to use Realm, we first need to open a Realm. Think of a Realm like a box that we need to open in order to store data inside. Before opening the Realm, let's create a variable inside of the RealmManager class. It will be a private set variable var called localRealm, which will be of Realm type and it will be optional. The private set part means that we can only set this variable within the RealmManager class. And we are adding an optional here because opening a Realm might throw an error, and if we throw an error, we cannot set the value to the localRealm. Next, right below this variable, we will create a function called openRealm. Inside of this, we need to wrap everything in a do catch statement. Because, as I mentioned earlier, opening a Realm might throw an error. In the catch statement, we will print a message that says Error opening Realm and we'll print the actual error. In the do statement, we first need to create a config variable. So let config is equals to Realm dot Configuration and we'll set our schema version to one. Then, right below the config variable, we need to call Realm dot Configuration dot defaultConfiguration will be the config variable that we just created. And finally we will try Realm, so this will create a Realm and you can think of it as opening a box. So let's save this box in our localRealm variable here. Now, let's call this openRealm function on initialize of our class. So we'll add an init here and call the openRealm function. That means that every time that we will initialize this class, we will open our Realm. Next, we'll need to create a Realm schema. This is the model that each object we'll create should follow. Think of a model, like a cupcake mold and each cupcake that you make should follow the shape of the mold, so a model is pretty similar to a mold. Let's create a new file under the ToDoDemo folder. And it will be a Swift file. Let's name this file Task, because our model will be named task. In here, we will need to import RealmSwift. And Realm accepts classes as model, so we'll need to create a class called Task and it will conform to the Object and the ObjectKeyIdentifiable protocols. These two protocols are from Realm, so remember to import RealmSwift in this file. Then, we need to create a few variables inside of our class. We'll need to add the @Persisted property wrapper at the beginning, as specified in Realm's documentation. We'll mark the first variable that we create as primary key, true var id, which will be an ObjectId. This is pretty similar to the UUID that is automatically generated in SwiftUI, but since we're using Realm, we'll use the ObjectId from Realm instead. Then we also need to add two other variables. The second one is the title, which will be a string. And the last one will be a completed status and by default, we'll set it to false. If you run into an error that Persisted is not a property wrapper, or Xcode cannot find this property wrapper, upgrade your Realm package to at least version 10.10.0. To check which version you have, simply go to your project name and under package dependencies, you'll see the Realm package and the version rules. So if the package is not at least the version 10.10.0, you will need to upgrade it. You can upgrade it by double clicking on it and then changing the number here. Now back in our RealmManager, if you ever need to change the Realm schema, later on, in the config variable, we will need to pass a second argument, which will be the migration block. And this accepts a closure. In the closure, you can get the migration as well as the old schema version. And then you'll need to add an if statement. If old version is higher than one. Then, in here, you will need to add your code to update the schema. But since we don't need to update our schema, we can omit the migration block here. The migration block is required every time you update the schema, otherwise, you'll run into a crash. Now that we opened our Realm and created a schema, let's take care of the C of the CRUD actions - that is, the create action. In our RealmManager class, under the openRealm function. Let's read another one and we'll call it addTask. It will accept the task title as an argument, which will be a string. Then, inside, we need to unwrap the optional of the localRealm variable. That means that if we don't have a value assigned to the localRealm, we won't run the code inside of this if statement. We will wrap everything in a do catch statement. And in the catch, we'll print Error adding task to Realm and we'll print the actual error. In the do statement, we need to try localRealm dot write. And inside of the closure we will create a new task. So let newTask will be equals to a task and we need to pass the value variable, which will be a dictionary. So in our task, we have a title that we need to assign, as well as a completed status. The ID will be auto-generated, so we don't need to take care of it. So let's assign a title, which will be the taskTitle that we passed to this function. And we also need to assign a completed status and by default, we'll set it to false. Then, after creating our newTask variable, we'll call the localRealm dot add our new task. And finally let's print a message to the console that says Added new task to Realm and we'll print the new task. Now that we created our create function, we'll need to code the R of the CRUD actions, that is, the read action. Now, just a disclaimer, we'll create all of the CRUD functions first, and then after creating all of the functions, we'll apply the functionality to our UI. So first, at the top of our RealmManager, we'll create @Published variable private set var and it will be called tasks. It will be an aray of Task and by default, we'll set it to an empty array. The Published property wrapper here will allow us to notify our views of any changes in this class. Next, right below our add task function, let's create another one. And this function will be called getTasks. The first thing we need to do in this function is the same as in our addTask function, that means unwrapping the optional of our localRealm. So let's do if localRealm is equals to localRealm. Inside of this if statement, we will need to call our localRealm dot objects, so that means that we're getting all the objects from our localRealm. And the objects requires us to specify the type of objects that we want to fetch. So in this case, we want to fetch the task objects. Then, let's call the sorted function and you can see that it needs a key path, which is a string. So we'll sort all of our objects by the completed status, meaning that the tasks that are not completed, will go at the beginning of the array and the ones that are completed will go to the bottom of our array. Then let's save this in a variable called allTasks. And if you jump a definition for the sorted function, you can see that it returns us results. So we'll need to iterate over the all tasks results, and for each one, we'll append it to the tasks variable that we created on line 13. So before adding the task to our array, let's simply reset it to an empty array, because we will call this function in our add task, update and delete functions as well. Then we'll call allTasks dot for each. So for each task, we will call the tasks array and append the task that we're iterating through. Now that we have our getTasks function, remember to call it in the init function of our RealmManager, because we want to populate the tasks array on initialize of this manager. So let's call this function right after adding our new task in our addTask function. By doing so, we'll update the tasks array every time we add a task to our localRealm. Now let's code the updateTask function. So right below the getTasks, we'll create a new function called updateTask. And this function will accept two variables as arguments. The first one is the id, which is the ObjectId, because this is the type of variable that we set in our task class. And we also have a second argument, which is the completed state and it will be a boolean. Inside of this function again, we need to unwrap the optional. So if localRealm equals to localRealm, then we'll call the do catch statement, because writing to our localRealm might throw an error. In the catch, let's print Error updating task, and we'll print the ID, to Realm. And we'll print the actual error. Now, inside of the do, we need to find the exact task that the user wishes to update. So let's call our localRealm dot objects. And we want to fetch all the tasks. Then we will filter. And by filtering, it accepts and NS predicate. So let's create an NS predicate and we need to specify the format. The format is what variable you want to check. And we want to check the ID. So we'll write ID two equal signs, a percentage, and a at. Then we need to pass the second argument, which is the ID that we passed to this function. So this will filter all the tasks and only return us the task with the specified ID. Then let's save this in a taskToUpdate variable. And we will add a guard statement. So in the guard statement, we'll add an exclamation mark and call our taskToUpdate, isEmpty. Else, return. So that means that we want to check if the task to update is not empty, because adding an exclamation mark in front means that we want the opposite of that. Then right below that, we'll try our localRealm dot write. And we'll call our taskToUpdate and get the first one. And we'll change its completed status to the completed variable that we passed to our update function. Then right below that, we'll get our tasks. And we'll print a message to the console saying updated task with ID, and we'll print the ID. And the completed status is the completed variable that we passed to this function. Finally, let's code the last letter of the CRUD actions, which is the delete action. So we'll create a new function called deleteTask, and it will accept an ID which will also be an ObjectId type. Inside of this, we need to unwrap the localRealm optional. Then we'll add a do catch statement. In the catch, we'll print Error deleting task, with the ID, from Realm and we'll print the actual error. The delete function is pretty similar to the update function, so let's copy the first two lines and paste it inside of our deleteTask. Instead of calling this variable taskToUpdate, let's call it taskToDelete. And we'll also update the variable in our guard statement. Then right below that, we'll call try localRealm dot write. Inside of the closure. We'll call our localRealm dot delete and pass the taskToDelete to our function. Finally, we'll call the getTasks function to update the tasks array. And we'll print Deleted task with id and print the id. Great! We coded all the CRUD actions. Now it's time to connect our RealmManager to our views. First we'll need to initialize the class. So let's go to ContentView and we'll intialize our RealmManager here, as a StateObject. So right before our showAddTask state, we'll create an @StateObject var realmManager will be equals to RealmManager with parentheses at the end, so that we can initialize this class. We'll also need to add the RealmManager environment object in our two other views. So in our TasksView and our AddTaskView. So in TasksView, let's add the EnvironmentObject property wrapper var realmManager will be of RealmManager type. In order to make the preview work again, don't forget to pass the environmentObject modifier to the preview. And we'll initialize a RealmManager just for the preview. Let's do the same for the AddTaskView. So @EnvironmentObject var realmManager will be of RealmManager type. And let's not forget the preview, by adding an environmentObject modifier and initializing a RealmManager just for the preview. Now let's go back to ContentView, and we'll also need to add the environmentObject modifier when calling both of these views. So on the TasksView, we'll add the environmentObject and pass our realmManager. Let's copy this line and we'll also need to add this modifier to our AddTaskView. Now let's take care of the create and read functionalities. Let's go to our AddTaskView. In the action of our button, instead of printing task added, we will call the RealmManager dot addTask and the taskTitle will be the title state that we created on line 12. However, this will add a new task. Even if the title is an empty string. So let's add an if statement. So if title is not equals to an empty string, we will call our add task function. And we'll keep the dismiss because once we add a task, we want to dismiss the sheet presentation. Now, let's go to our TasksView and we'll display all of our tasks in a list. Right below our title, we will create a list. Since by default, you can see that the list has a white blue-ish background color, we'll need to change this background color to a clear background, so that we can see our orange yellowy background of our TasksView. So we'll call the onAppear modifier and then we'll add UITableView dot appearance dot background color will be equals to UIColor dot clear. Then we'll call the UITableViewCell dot appearance dot background color will also be equals to UIColor dot clear. Where using a list here, because we will use swipe actions later on. And swipe actions only work inside of the list. Then inside of this list, we will iterate through the tasks array of our RealmManager. We'll call the for each. So for each realmManager dot tasks, we also need to provide an ID, so we will provide the ID of the task that we're iterating through and we'll get a task in the closure. For each task we want to display a task row. Luckily, at the beginning of this tutorial, we already coded our task row. So let's simply call it inside of our for each. So we'll display a task row and we need to pass a task, which will be the task dot title. And we also need to pass a completed status, which will be task dot completed. Now we don't see anything in our view yet because we didn't add any task to our application. So let's go to ContentView. And in the preview, click on the play button and we'll be adding a few tasks to our view. So let's click on the add button here and we'll be adding a few tasks to our application. So let's say that I need to do my laundry, because I have a lot of laundry to do. Then I will also need to do some grocery shopping, as well as mop the floor. So you can see that all of our tasks are added in our TasksView, and you can see that we have a little separator between each row. We can hide it. So let's go back to TasksView. And on our for each, we'll call the listRowSeparator and set it to hidden. Now that we implemented the create and read functionalities, let's take care of the update and delete functionalities. So first we'll do the update functionality. So we want to update the status of each task on tap gesture of the row. Still in TasksView, on the task row, let's add the on tap gesture modifier. So when the user taps on this want to call the realmManager dot updateTask, and we need to specify an ID, which will be the task that we get in the closure of the for each, dot ID. And the completed status will be the opposite of the task dot completed. In other words, by adding the exclamation mark here, we're passing the opposite value of the current completed state. For example, if the completed is currently false, we'll pass true instead. And if the completed state is true, we'll pass false and so on. Now let's go back to ContentView and build our application again in the preview here. Let's say that we did our grocery shopping, so I will click on this road and you can see that the circle changed to a circle with a check mark inside and it moved to the bottom of the list. Great! We implemented the update functionality. We just have one last functionality to tackle, which is the delete action. Let's go to the TasksView. And we'll implement a swipe action, which means that the user will be able to swipe on the row, and it will delete our task. So right after the on tap gesture, we'll add another modifier, which will be the swipe actions modifier. And we can specify an edge, so we'll specify the trailing edge, which means that the user will need to swipe from right to left in order to delete the row. You can also set it to leading, which means that the user will need to swipe from left to right. The content of our swipe action will be a button. So let's add a button, with a role destructive. And the button will have a closure for an action as well as another closure for the label. Let's take care of the label first. So let's create a label, which will be a delete label and we'll use a system image from SF Symbols, which will be the trashcan. For the action of this button, we will call the realmManager dot deleteTask, and we need to specify an ID. Luckily, we already have the ID because we're iterating through the tasks array. So let's call the task dot id. Now, if we go to ContentView, and resume the preview. Let's try to delete the grocery shopping row. So I can swipe on it and click on the delete button and it will delete my row. Now, let's try to build it in the simulator and we'll try to delete a row as well in our simulator. So I already added a few tasks, so let's try to delete a row. By default with the swipe actions, we can swipe all the way to the left of the screen and it will delete the row. So let's test that out, I will swipe all the way. However, you can see that it bugged. And I ran into a crash. It says Object has been deleted or invalidated. We get this error because we're displaying the task in our task view. So that means that we're trying to access the task, even though it has been deleted, or invalidated in Realm's terms. To fix this, let's go to our TasksView and, right before, displaying our task row. We'll add an if statement. So we want to make sure that the task is not invalidated before displaying our row. So by adding the question mark, it means that we want the task to not be invalidated. Now let's build this application again in the simulator. And we'll add another task. Let's say, read a book. And let's update our do launch task. And delete it as well. And you can see that we don't run into any error anymore. Congratulations! You just built an entire To-do application from scratch. In this tutorial, we learned how to useMongoDB Realm as our database to persistently save our tasks. We also learned how to perform CRUD actions with Realm. Moreover, we covered sheet presentations, SwiftUI lists, a few modifiers, like swipe actions and on tap gesture. We learned a lot in this video, and I hope that you enjoyed coding along with me. See you in the next tutorial.
Info
Channel: DesignCode
Views: 5,336
Rating: undefined out of 5
Keywords: designcode, course for beginners, api, swiftui, ios, app dev, SwiftUI app dev, learn SwiftUI, online course, crash course, build a SwiftUI app, from scratch, to-do, app, application, mobile, crud, actions, create, read, update, delete, realm, mongodb realm, swiftui package, tutorial, step by step
Id: b6q9vKaXtoU
Channel Id: undefined
Length: 42min 39sec (2559 seconds)
Published: Mon Dec 13 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.