How to Build a SwiftUI App and Integrate It with Supabase

In this article, we will create an iOS app which is integrated with Supabase backend for authentication. We will use SwiftUI to create the app's UI. The full source code for this project can be found here.

\ The tutorial will be divided into following sections:

  1. Setting up Supabase Project (install Supabase iOS SDK)
  2. Designing the UI of the app using SwiftUI (Login, Registration, Home screens)
  3. Registration with Supabase authentication / saving user data in database
  4. Login with Supabase auth / retrieving user data from database

\

1.Setting up Supabase Project (install Supabase iOS SDK)

Getting started with Supabase is extremely easily as compared to AWS or even Firebase. Simply head over to Supabase.io and sign up using Github. That's it. You will see your Supabase console as well as all the private keys, and URLs you will need for your project. To install Supabase iOS SDK, follow these three easy steps:

  1. Open Xcode and create your project
  2. then, Go to File -> Add Packages
  3. Enter this URL: <https://github.com/supabase/supabase-swift.git> and install Supabase package

\

2. Designing the UI of the app using SwiftUI

Now lets set up the UI. The UI for this project is extremely simple. We will have three SwiftUI files

  1. ContentView where we will implement Sign Up view
  2. SignIn view
  3. HomeView

\ This is the UI for `ContentView`:

```javascript struct ContentView: View { @State var email: String = "" @State var password: String = "" @State var name: String = ""

@State var presentHomeView: Bool = false
@State var user = User()

NavigationView { ZStack { VStack(spacing: 20) { Text("Sign Up") .font(.largeTitle) .bold() .padding(.bottom, 24) TextField("Name", text: $name) TextField("Email", text: $email) SecureField("Password", text: $password) Button { self.signUp(email: self.email, password: self.password, name: self.name) } label: {

                   Text(&quot;Sign Up&quot;)
                       .padding()
                       .background(Color.blue.opacity(0.3))
                       .cornerRadius(5)
               }.padding(.top, 20)


               HStack {
                   Text(&quot;Already Registered&quot;)
                   NavigationLink(&quot;Log In&quot;, destination: SignInView())
               }


           Spacer()
           }
           .padding(.horizontal)
           .padding(.top, 64)

       }.fullScreenCover(isPresented: self.$presentHomeView) {
           HomeView(user: self.$user)
       }

   }

} ```

\ This will be the `SignInView`:

```javascript struct SignInView: View { @State var presentHomeView: Bool = false @State var email = "" @State var password = "" @State var user = User() var body: some View { ZStack { VStack(spacing: 20) { Text("Log In") .font(.largeTitle) .bold() .padding(.bottom, 24) TextField("Email", text: $email) SecureField("Password", text: $password) Button { self.signIn(email: self.email, password: self.password) } label: {

               Text(&quot;Log In&quot;)
                   .padding()
                   .background(Color.blue.opacity(0.3))
                   .cornerRadius(5)
           }.padding(.top, 20)
       Spacer()
       }
       .padding(.horizontal)
       .padding(.top, 64)

   }
   .fullScreenCover(isPresented: self.$presentHomeView) {
       HomeView(user: $user)
   }

} } ```

\ This is the code for `HomeView`:

```javascript struct HomeView: View { @Environment(\.presentationMode) var presentationMode @Binding var user: User

var body: some View {
    VStack {
        Text(&quot;Hello,&#x5C;(user.name ?? &quot;&quot;)&quot;)
        Button {
            self.signOut()
        } label: {

            Text(&quot;Sign Out&quot;)
                .padding()
                .background(Color.blue.opacity(0.3))
                .cornerRadius(5)
        }.padding(.top, 20)
    }
}

} ```

\ Please add `import Supabase` in all three files.

3. Registration with Supabase auth:

Whenever we need to call supabase - during our authentication procedures or when we are accessing database, we will first create a Supabase client object that requires two items both of which we can get from the Supabase console.

So to make it easier for ourselves, create a new swift file and name it API. Create a class API and add the following code:

\ ```javascript import Foundation import Supabase class API { static var supabaseServiceKey = "get the key from Supabase console" static var supabaseURL = "get the URL from Supabase console"

static var supabase = SupabaseClient(supabaseUrl: API.supabaseURL, supabaseKey: API.supabaseServiceKey)

} ```

\ You can get these URL and keys by heading over to "API" section in Supabase console and copying the following keys: Supabase URL and Service Key. Now whenever we need to use the supabase client, we can just call do `API.supabase`.

\ Now let's go ahead and implement the logic for signing up users. First create a simple User struct:

```javascript struct User: Codable { var id: Int? var userID : String? var name: String? var email: String? } ```

\ Create a new function - we will call it `signUp` and it will take `email`, `password` and `name` string:

```javascript func signUp(email: String, password: String, name: String) { API.supabase.auth.signUp(email: email, password: password) { result in switch result { case let .success(session): print(session) guard let safeUser = session.user else { return } let user = User(userID: safeUser.id,name: name,email: safeUser.email) self.user = user self.addUserToDatabase(user: user) self.presentHomeView = true self.email = "" self.password = "" case let .failure(error): print(error.localizedDescription) } } } ```

\ Here we are calling the `API.supabase.auth.signUp` method and passing in the `email` and `password`. If the sign up is successful, we will get `result` which contains the newly created user information. This information will contain user's email, and `userID`. This is what we will user to query the user in the database later on. We will create a simple `user` object and we will construct the object by passing in the `userID` (which we get from the `result` object), email and name. We will then pass it to the `@State user` property of type `User`. Then we will pass this `user` object to a subroutine call `addUserToDatabase`. Let's implement this subroutine next:

\ ```javascript func addUserToDatabase(user: User) { do { let jsonData: Data = try JSONEncoder().encode(user)

    API.supabase.database.from(&quot;User&quot;).insert(values: jsonData).execute { result in
        switch result {
        case let .success(response):
            print(response)
            print(&quot;Success&quot;)
        case let .failure(error):
            print(error.localizedDescription)
        }
    }

} catch {
    print(error.localizedDescription)
}

} ```

\ In this subroutine, we will first encode the `user` object to `jsonData`. Then we will use the same SupabaseClient object and this time use Supabase database. You can easily create a new table in your Supabase console, here is how:

  1. Head over to `Table Editor` (situated on the left side panel)
  2. Click on `New table`
  3. Give the new table a name (we named our table as "User"), and add columns. Keep id as it is, and add other columns - (we will add userID of type text/string, name of type text/string and email of type text/string).

\ Going back to the code, we are basically saying to the database, to insert the jsonData in the "User" table. That's it, so now when a user signs up, we are also storing the new user's details in database.

4. Log In using Supabase:

Create the following subroutine in `SignInView`:

```javascript func signIn(email: String, password: String) { API.supabase.auth.signIn(email: email, password: password) { result in switch result { case let .success(session): print(session) guard let safeUser = session.user else { return } self.fetchUserDetails(userID: safeUser.id) { user in self.user = user self.presentHomeView = true }

            self.email = &quot;&quot;
            self.password = &quot;&quot;
        case let .failure(error):
            print(error.localizedDescription)
        }
    }
}

```

\ We are pretty much just using the `signIn` method provided to us by the supabase SDK and using the `email` and `password` which we will get from the user. If the log in is successful, we will grab the `userID` from the result and pass it to `fetchUserDetails` subroutine. In this subroutine we will fetch all the data of the user from the database and create the User object. Here is how:

```javascript func fetchUserDetails(userID: String, completion: @escaping (_ user: User) -> ()) { API.supabase.database.from("User").select().eq(column: "userID", value: userID).execute { result in switch result { case let .success(response): print(response) do { let user = try response.decoded(to: [User].self) completion(user[0]) print(user[0]) } catch { print(String(describing: error)) } case let .failure(error): print(error.localizedDescription) } } } ```

\ So this time we are querying the database and saying to retrieve the entire row in the "User" table for "UserID" that matches the userID we passed to the subroutine. The response we will get from this is JSON data. We will decode it to a `User` array. However, we know that there will be just one item in that array so we can easily grab the first element from the array and pass it to the completion handler.

\ Back in `signIn` routine, we are calling the `fetchUserDetails` subroutine which gives us the `user` item, which we assign to `@state user` property and also toggle the boolean to show the `HomeView` modally (fullScreenCover). The HomeView takes in the user object.

5. HomeView and SignOut:

So now we have the last view, the `HomeView`. The `HomeView` will grab this `User` object and show the name of the user. We will also add a SignOut button, which when pressed invokes the following subroutine:

```javascript func signOut(){ API.supabase.auth.signOut { result in print("Signed Out") presentationMode.wrappedValue.dismiss() } } ```

\