SDK Setup Guides
...
Tutorials in Swift
SwiftUI: Task App Quickstart
this quickstart provides step by step instructions for using swiftui to create a task app in xcode, which consists of the following high level steps /#creating a new app in xcode /#configuring ditto /#showing the list of tasks /#editing tasks prequisites ditto account and access credentials ios 13 (or later) macos 11 (or later) https //apps apple com/us/app/xcode/id497799835 15 (or later) for instructions on creating your account and obtaining your credentials, see docid\ jqjyl9gbvsgi9vlw3ywvc creating a new app in xcode click file , and then select new project in the choose a template for your new project modal, select app and then click next in the choose options for your new project modal, enter the following information as appropriate and then click next the following are merely suggestions; enter any information in the form that you desire the following steps provide suggested values for the form; however you can enter any information you desire for product name , type "tasks" for team , select dittolive incorporated for organization identifier , type "live ditto" for interface , select swiftui for life cycle , select swift ui app for language , select swift configuring ditto add ditto to tasksapp swift when xcode generated your project, there should be a file called tasksapp swift first import ditto with import dittoswift construct an instance of ditto with an online playground identity using the app id of the app that you just created on the portal we are using an onlineplayground setup, which should suffice for this tutorial however, you should never deploy this to a production environment like the apple app store we will call startsync as soon as the app's contentview appears add two @state variables to capture if ditto startsync throws an error one variable will be @state var ispresentingalert = false and the other is a @state var errormessage = "" add an onappear function and give it a playground token look for "your app id here" and insert your valid token you can get a token from our https //portal ditto live if the startsync() fails, we will set ispresentingalert = true and set the errormessage to the error's localizeddescription we will then present a alert if ispresentingalert is true notice that we will pass a @state variable as a binding type, which is why we denoted $ispresentingalert prefixed with a $ to learn more about swiftui's binding types like @state https //developer apple com/tutorials/app dev training/managing data flow between views import swiftui // 1 import dittoswift @main struct tasksapp app { // 2 var ditto = ditto(identity onlineplayground(appid "your app id here", token "your token here")) // 3 @state var ispresentingalert = false @state var errormessage = "" var body some scene { windowgroup { taskslistscreen(ditto ditto) // 4 onappear(perform { do { try ditto startsync() } catch (let err){ ispresentingalert = true errormessage = err localizeddescription } }) // 5 alert(ispresented $ispresentingalert) { alert(title text("uh oh"), message text("there was an error trying to start the sync here's the error \\(errormessage) ditto will continue working as a local database "), dismissbutton default(text("got it!"))) } } } } create a task struct ditto is a document database, which represents all of its rows in the database a json like structure in this tutorial, we will define each task like so { " id" "123abc", "body" "get milk", "iscompleted" true } these task documents will all be in the "tasks" collection we will be referencing this collection throughout this tutorial with let taskscollection = ditto store\["tasks"] ditto documents have a flexible structure oftentimes, in strongly typed languages like swift, we will create a data structure give more definition to the app create a new swift file called task swift in your project add import dittoswift to the top of the file add the matching variables let id string , let body string , and let iscompleted bool to the struct we will use this to match the document values to to the struct add an init constructor to task that takes in a dittodocument in the init constructor, parse out the document's keys with ditto's type safe value accessors this will safely map all the document's values to the struct's variables that we created in step 2 add a second init constructor to task that just takes a string and a bool add an extension task identifiable right below the task struct definition and implement var id string to return the id key we add the identifiable protocol to assist swiftui's list view and foreach component in later sections collection views in swiftui require knowing if a view is unique to prevent wasteful redraws while it may seem confusing, we are only allowing the protocol to read the id that we added in step 2 task swift // 1 import dittoswift struct task { // 2 let id string let body string let iscompleted bool // 3 init(document dittodocument) { // 4 id = document\[" id"] stringvalue body = document\["body"] stringvalue iscompleted = document\["iscompleted"] boolvalue } // 5 init(body string, iscompleted bool) { self id = uuid() uuidstring self body = body self iscompleted = iscompleted } } // 6 extension task identifiable { var id string { return id } } this data class takes a dittodocument and safely parses out the values into native swift types we also added constructor that allows us to preview data without requiring ditto so now in our application if we want an array of tasks , \[task] , we can write the following code let tasks \[task] = ditto store\["tasks"] find("!isdeleted") exec() map({ task(document $0) }) once we set up our user interface, you'll notice that reading these values becomes a bit easier with this added structure create a taskslistscreen view when we generated the project, xcode created a default contentview , which need to swap out for a better starter view let's create a view called taskslistscreen which will show the list of the views create a new swiftui view view by clicking file > new > swiftui view https //docs ditto live/ios/tutorial/swift/configure ditto#2 5 create a taskslistscreen view name it "taskslistscreen" at the top of the new taskslistscreen add ditto with import dittoswift at the top of the file create a constructor and a variable to pass var ditto ditto replace the body with navigationview with a single list child we will fill out the contents of the list in the next section we've also added a couple of decorative navigation elements which we will hook up later this includes a navigation title, navigationtitle which shows the name of the app, a navigation plus button in navigationbaritems and a sheet that we will use navigate to an editscreen we will create the editscreen later taskslistscreen swift import swiftui // 3 import dittoswift struct taskslistscreen view { // 4 let ditto ditto init(ditto ditto) { self ditto = ditto } var body some view { // 5 navigationview { list { } navigationtitle("tasks swiftui") navigationbaritems(trailing button(action { }, label { image(systemname "plus") })) sheet(ispresented constant(false), content { }) } } } showing the list of tasks in the last part of the tutorial we referenced a class called taskslistscreen this screen will show a list\<task> using a jetpack compose column create a taskrow views each row of the tasks will be represented by a swiftui view called taskrow which takes in a task and two callbacks which we will use later if the task iscompleted is true , we will show a filled circle icon and a strikethrough style for the body if the task iscompleted is false , we will show an open circle icon if the user taps the icon , we will call a ontoggle (( task task) > void)? , we will reverse the iscompleted from true to false or false to true if the user taps the text , we will call a onclickbody (( task task) > void)? we will use this to navigate an editscreen (we will create this later) for brevity, we will skip discussions on styling as it's best to see the code snippet below we've also included a taskrow previews that allows you to see the end result with some test data quickly taskrow\ swift import swiftui struct taskrow view { let task task var ontoggle (( task task) > void)? var onclickbody (( task task) > void)? var body some view { hstack { // 2 image(systemname task iscompleted ? "circle fill" "circle") renderingmode( template) foregroundcolor( accentcolor) ontapgesture { ontoggle?(task) } if task iscompleted { text(task body) // 2 strikethrough() ontapgesture { onclickbody?(task) } } else { // 3 text(task body) ontapgesture { onclickbody?(task) } } } } } struct taskrow previews previewprovider { static var previews some view { list { taskrow(task task(body "get milk", iscompleted true)) taskrow(task task(body "do homework", iscompleted false)) taskrow(task task(body "take out trash", iscompleted true)) } } } create a taskslistscreenviewmodel in the world of swiftui, the most important design pattern is the mvvm, which stands for model view viewmodel mvvm strives to separate all data manipulation (model and viewmodel) and data presentation (ui or view) into distinct areas of concern when it comes to ditto, we recommend that you never include references to edit ditto in view\ body all interactions with ditto for upsert , update , find , remove and observe should be within a viewmodel the view should only render data from observable variables from the viewmodel and only the viewmodel should make direct edits to these variables typically we create a viewmodel per screen or per page of an application for the taskslistscreen we need some functionality like showing a realtime list of task objects triggering an intention to edit a task triggering an intention to create a task clicking an icon to toggle the icon from true to false or false to true in swiftui we create a view model by inheriting the observableobject the observableobject allows swiftui to watch changes to certain variables to trigger view updates intelligently to learn more about observableobject we recommend this excellent https //www hackingwithswift com/quick start/swiftui/how to use observedobject to manage state from external objects create a file called taskslistscreenviewmodel swift in your project add an init constructor to pass in a ditto ditto instance and store it in a local variable create two @published variables for tasks and i spresentingeditscreen @published variables are special variables of an observableobject if these variables change, swiftui will update the view accordingly any variables that are not decorated with @published can change but will be ignored by swiftui we also add a normal variable, private(set) var tasktoedit task? = nil when a user is attempting to edit a task, we need to tell the view model which task the user would like to edit this does not need to trigger a view reload, so it's a simple variable here's where the magic happens as soon as the taskslistscreenviewmodel is initialized, we need to observe all the tasks by creating a live query to prevent the livequery from being prematurely deallocated, we store it as a variable in the observe callback, we convert all the documents into task objects and set it to the @published tasks variable every time to observe fires, swiftui will pick up the changes and tell the view to render the list of tasks we will add an eviction call to the initializer that will remove any deleted documents from the collection add a function called toggle() when a user clicks on a task's image icon, we need to trigger reversing the iscompleted state in the function body we add a standard call to find the task by its id and attempt to mutate the iscompleted property add a function called clickedbody when the user taps the taskrow 's text field, we need to store that task and change the ispresentingeditscreen to true this will give us enough information to present a sheet in the taskslistscreenviewmodel to feed to the editscreen in the previous setup of the taskslistscreen , we added a navigationbaritem with a plus icon when the user clicks this button we need to tell the view model that it should show the editscreen so we've set the ispresentingeditscreen property to true however, because we are attempting to create a task , we need to set the tasktoedit to nil because we don't yet have a task taskslistscreenviewmodel swift class taskslistscreenviewmodel observableobject { // 3 // highlight start @published var tasks = \[task]\() @published var ispresentingeditscreen bool = false // highlight end // 4 // highlight next line private(set) var tasktoedit task? = nil let ditto ditto // 5 // highlight start var livequery dittolivequery? var subscription dittosubscription? init(ditto ditto) { self ditto = ditto self subscription = ditto store\["tasks"] find("!isdeleted") subscribe() self livequery = ditto store\["tasks"] find("!isdeleted") observelocal(eventhandler { docs, in self tasks = docs map({ task(document $0) }) }) //6 ditto store\["tasks"] find("isdeleted == true") evict() } // highlight end // 7 // highlight start func toggle(task task) { self ditto store\["tasks"] findbyid(task id) update { mutabledoc in guard let mutabledoc = mutabledoc else { return } mutabledoc\["iscompleted"] set(!mutabledoc\["iscompleted"] boolvalue) } } // highlight end // 8 // highlight start func clickedbody(task task) { tasktoedit = task ispresentingeditscreen = true } // highlight end // 9 // highlight start func clickedplus() { tasktoedit = nil ispresentingeditscreen = true } // highlight end } render taskrow in a foreach within the taskslistscreen now we need to update our taskslistscreen to properly bind any callbacks, events, and data to the taskslistscreenviewmodel back in the taskslistscreen view, we need to construct our taskslistscreenviewmodel and store it as an @observedobject this @observedobject tells the view to watch for specific changes in the viewmodel variable we will need to store our ditto object to pass to the editscreen later in our body variable, find the list and add foreach(viewmodel tasks) { task in taskrow(task task, ontoggle { task in viewmodel toggle(task task) }, onclickbody { task in viewmodel clickedbody(task task) } ) } this will tell the list to iterate over all the viewmodel tasks and render a taskrow in each of the taskrow children, we need to bind the ontoggle and onclick callbacks to the viewmodel methods bind the plus button to the viewmodel clickedplus event now we need to present a sheet which will activate based on the $viewmodel ispresentingeditscreen variable notice how we added the $ before viewmodel sheet can edit the ispresentingeditscreen once it's dismissed, so we need to treat the variable as a bidirectional binding we've also included a taskslistscreen previews so that you can add some test data and see the result in a live view taskslistscreen swift struct taskslistscreen view { // 2 // highlight next line let ditto ditto // 1 // highlight start @observedobject var viewmodel taskslistscreenviewmodel init(ditto ditto) { self ditto = ditto self viewmodel = taskslistscreenviewmodel(ditto ditto) } // highlight end var body some view { navigationview { list { // 3 // highlight start foreach(viewmodel tasks) { task in taskrow(task task, ontoggle { task in viewmodel toggle(task task) }, onclickbody { task in viewmodel clickedbody(task task) } ) } // highlight end } navigationtitle("tasks swiftui") navigationbaritems(trailing button(action { // 4 // highlight next line viewmodel clickedplus() }, label { image(systemname "plus") })) // 5 // highlight start sheet(ispresented $viewmodel ispresentingeditscreen, content { editscreen(ditto ditto, task viewmodel tasktoedit) }) // highlight end } } } // 6 // highlight start struct taskslistscreen previews previewprovider { static var previews some view { taskslistscreen(ditto ditto()) } } // highlight end notice that we do not have to manipulate the tasks value calling update on ditto will automatically fire the livequery to update the tasks you can always trust the livequery to immediately update the @published var tasks there is no reason to poll or force reload ditto will automatically handle the state changes and swiftui will pick these changes up automatically editing tasks our final screen will be the editscreen and its viewmodel the editscreen will be in charge of 3 functions editing an existing creating a deleting an existing creating the editscreenviewmodel like before, we need to create an editscreenviewmodel for the editscreen since we've already gone over the concepts of mvvm, we will go a bit faster the editscreenviewmodel needs to be initialized with ditto and an optional task task? value if the task value is nil we need to set the candelete variable to false this means that the user is attempting create a new task we will use this value to show a delete button in the editscreen later we will store the id string? from the task parameter and use it later in the save() function we need two @published variables to bind to a textfield and toggle swiftui views for the task's iscompleted and body values if the task == nil , we will set some default values like an empty string and a false iscompleted value when the user wants to click a save button , we need to save() and handle either an upsert or update function appropriately if the local id variable is nil , we assume the user is attempting to create a task and will call ditto's upsert function otherwise, we will attempt to update an existing task with a known id finally if a delete button is clicked, we attempt to find the document and call remove editscreenviewmodel swift import swiftui import dittoswift class editscreenviewmodel observableobject { @published var candelete bool = false // 2 // highlight start @published var body string = "" @published var iscompleted bool = false // highlight end // 1 // highlight start private let id string? private let ditto ditto init(ditto ditto, task task?) { self id = task? id self ditto = ditto candelete = task != nil body = task? body ?? "" iscompleted = task? iscompleted ?? false } // highlight end // 3 // highlight start func save() { if let id = id { // the user is attempting to update ditto store\["tasks"] findbyid( id) update({ mutabledoc in mutabledoc?\["iscompleted"] set(self iscompleted) mutabledoc?\["body"] set(self body) }) } else { // the user is attempting to upsert try! ditto store\["tasks"] upsert(\[ "body" body, "iscompleted" iscompleted, "isdeleted" false ]) } } // highlight end // 4 // highlight start func delete() { guard let id = id else { return } ditto store\["tasks"] findbyid( id) update { doc in doc?\["isdeleted"] set(true) } } // highlight end } create the editscreen like the taskslistscreen swift in the previous section, we will create an editscreen swift this screen will use swiftui's form and section wrapper an textfield which we use to edit the task body a switch which is used to edit the task iscompleted a button for saving a task a button for deleting a task in the editscreen we need to add a @environment(\\ presentationmode) private var presentationmode in swiftui views house some environment variables because the taskslistscreen presened the editscreen as a sheet , we need a way to dismiss the current screen if the user taps any of the buttons to learn more about environment , https //developer apple com/documentation/swiftui/environment to dismiss the current screen we can call self presentationmode wrappedvalue dismiss() like before, store the editscreenviewmodel as an observedobject pass the task task? and the ditto instance to properly initialize the editscreenviewmodel now the viewmodel should know if the user is attempting a creation or update flow we now can bind the textfield for the $viewmodel body and toggle to the $viewmodel iscompleted notice the $ , this allows swiftui fields to bi directionally edit these @published values and trigger efficient view reloading bind the save button's action handler to the viewmodel save() function and dismiss the view whenever the user clicks the save button, they will save the current data and return back to the taskslistscreen if the viewmodel candelete is true , we can show a delete button notice how we don't need the $ since we are only reading the value once moreover, we do not need to tell swiftui to re render on candelete since it will never change during the editscreen 's life cycle bind the delete button's action to the viewmodel delete() function and dismiss the view finally we add a editscreen previews so that you can easily watch the view's final rendering as you develop editscreen swift struct editscreen view { // 1 // highlight next line @environment(\\ presentationmode) private var presentationmode // 2 // highlight start @observedobject var viewmodel editscreenviewmodel init(ditto ditto, task task?) { viewmodel = editscreenviewmodel(ditto ditto, task task) } // highlight end var body some view { navigationview { form { section { // 3 // highlight start textfield("body", text $viewmodel body) toggle("is completed", ison $viewmodel iscompleted) // highlight end } section { button(action { // 4 // highlight start viewmodel save() self presentationmode wrappedvalue dismiss() // highlight end }, label { text(viewmodel candelete ? "save" "create") }) } // 5 // highlight next line if viewmodel candelete { section { button(action { // 6 // highlight start viewmodel delete() self presentationmode wrappedvalue dismiss() // highlight end }, label { text("delete") foregroundcolor( red) }) } } } navigationtitle(viewmodel candelete ? "edit task" "create task") navigationbaritems(trailing button(action { self presentationmode wrappedvalue dismiss() }, label { text("cancel") })) } } } // 7 // highlight start struct editscreen previews previewprovider { static var previews some view { editscreen(ditto ditto(), task task(body "get milk", iscompleted true)) } } // highlight end run the app! https //docs ditto live/ios/tutorial/swift/edit screen#4 4 run the app congratulations you have successfully created a task app using ditto!