Skip to content

Commit

Permalink
docs: update README (#153)
Browse files Browse the repository at this point in the history
  • Loading branch information
grdsdev authored Nov 8, 2023
1 parent 70dc479 commit fddfd36
Showing 1 changed file with 25 additions and 311 deletions.
336 changes: 25 additions & 311 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,19 @@

Supabase client for swift. Mirrors the design of [supabase-js](https://github.com/supabase/supabase-js/blob/master/README.md).

## Contents
- [Installation](#installation)
- [Usage](#usage)
- [Login Implementation](#login-implementation)
- [Social Login Implementation](#social-login-implementation)
- [Setup Callback URL](#setup-callback-url)
- [Google Sign In](#google-sign-in)
- [Apple Sign In](#apple-sign-in)
- [Other Social Logins](#other-social-logins)
- [Basic CRUD Implementation](#basic-crud-implementation)
- [Insert Data](#insert-data)
- [Select Data](#select-data)
- [Contributing](#contributing)
- [Sponsors](#sponsors)
* Documentation: [https://supabase.com/docs/reference/swift/introduction](https://supabase.com/docs/reference/swift/introduction)

---

## Installation
## Usage

Swift Package Manager:
Install the library using the Swift Package Manager.

Add the following lines to your `Package.swift` file:
```swift
let package = Package(
...
dependencies: [
...
.package(name: "Supabase", url: "https://github.com/supabase/supabase-swift.git", branch: "master"), // Add the package
.package(name: "Supabase", url: "https://github.com/supabase/supabase-swift.git", from: "1.0.0"), // Add the package
],
targets: [
.target(
Expand All @@ -44,306 +29,35 @@ let package = Package(

If you're using Xcode, [use this guide](https://developer.apple.com/documentation/swift_packages/adding_package_dependencies_to_your_app) to add `supabase-swift` to your project. Use `https://github.com/supabase/supabase-swift.git` for the url when Xcode asks.

## Usage
If you don't want the full Supabase environment, you can also add individual packages, such as `Functions`, `GoTrue`, `Realtime`, `Storage`, or `PostgREST`.

To make requests to the `Supabase` database, you will need to initialize a `SupabaseClient` object:
Then you're able to import the package and establish the connection with the database.

```swift
let client = SupabaseClient(supabaseURL: "{ Supabase URL }", supabaseKey: "{ Supabase anonymous Key }")
/// Create a single supabase client for interacting with your database
let client = SupabaseClient(supabaseURL: URL(string: "https://xyzcompany.supabase.co")!, supabaseKey: "public-anon-key")
```

## Login Implementation

Inside the `SupabaseClient` instance created before, you can find an `auth` property of type `GoTrueClient`. You can use it to perform sign in and sign up requests.

- Here's how to sign up with an email and password and get the signed in user `Session` info:
### Initialize with custom options

```swift
Task {
do {
try await client.auth.signUp(email: email, password: password)
let session = try await client.auth.session
print("### Session Info: \(session)")
} catch {
print("### Sign Up Error: \(error)")
}
}
```

If you wish to add metadata for the user, you can pass it as part of the `data` parameter, just be sure to `import GoTrue` first to use the User metadata values.

```swift
Task {
do {
try await client.auth.signUp(
email: email,
password: password,
data: [
"name": .string("John Doe"),
"age": .number(25),
"some_boolean_parameter": .bool(true)
]
)

let session = try await client.auth.session
print("### Session Info: \(session)")
} catch {
print("### Sign Up Error: \(error)")
}
}
```

- For existing users, here's how to log in with an email and password and get the logged in user `Session` info:

```swift
Task {
do {
try await client.auth.signIn(email: email, password: password)
let session = try await client.auth.session
print("### Session Info: \(session)")
} catch {
print("### Sign Up Error: \(error)")
}
}
```

## Social Login Implementation

### Setup Callback URL

We need to first set up the callback URL for all Social Logins inside the app.

- Setup the callback `URL` on `Info.plist`:

```xml
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLSchemes</key>
<array>
<string>app.yourapp://login-callback</string>
</array>
</dict>
</array>
```

- Add this callback `URL` on `Supabase` under `Authentication -> URL Configuration -> Redirect URLs`.

### Google Sign In

- Setup Google Auth as per [Supabase's Documentation](https://supabase.com/docs/guides/auth/social-login/auth-google).
- Note: For iOS we still need to use Google Consent Form for Web.
- Import `SafariServices` in your `ViewController` and create a `SFSafariViewController` instance:

```swift
import SafariServices

var safariVC: SFSafariViewController?
```

- Get the `URL` for Google Sign in from `Supabase` and load it on `SFSafariViewController`.
- Pass the previous callback `URL` you set up in the `redirecTo` parameter:

```swift
Task {
do {
let url = try await client.auth.getOAuthSignInURL(provider: Provider.google, redirectTo: URL(string: {Your Callback URL})!)
safariVC = SFSafariViewController(url: url as URL)
self.present(safariVC!, animated: true, completion: nil)
} catch {
print("### Google Sign in Error: \(error)")
}
}
```

- Handle the callback `URL` on `SceneDelegate` (for older projects, you can use `AppDelegate` if `SceneDelegate` is not present).
- Post a `NotificationCenter` call to let the `ViewController` know the callback has been fired and pass the `URL` received. This `URL` will be used to get the user session.

```swift
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
if let url = URLContexts.first?.url as? URL {
if url.host == "login-callback" {
let urlDict: [String: URL] = ["url": url]
NotificationCenter.default.post(name: Notification.Name("OAuthCallBack"), object: nil, userInfo: urlDict)
}
}
}
```

- In your `ViewController`, observe for the `Notification` and handle it minimizing the `SFSafariViewController` and getting the session:

```swift
NotificationCenter.default.addObserver(
self,
selector: #selector(self.oAuthCallback(_:)),
name: NSNotification.Name(rawValue: "OAuthCallBack"),
object: nil)

@objc func oAuthCallback(_ notification: NSNotification){
guard let url = notification.userInfo?["url"] as? URL else { return }
Task {
do {
let session = try await SupaBaseAuth().client.session(from: url)
print("### Session Info: \(session)")
} catch {
print("### oAuthCallback error: \(error)")
}
}
safariVC?.dismiss(animated: true)
}
```

### Apple Sign In

- Setup Apple Auth as per [Supabase's Documentation](https://supabase.com/docs/guides/auth/social-login/auth-apple).
- For Sign in with Apple follow the above as per Google Sign In and just replace the provider.
- Once the user moves to the `SFSafariViewController`, an Apple native pop-up will slide up to continue with the sign in.

```swift
Task {
do {
let url = try await client.auth.getOAuthSignInURL(provider: **Provider.apple**, redirectTo: URL(string: {Your Callback URL})!)
safariVC = SFSafariViewController(url: url as URL)
self.present(safariVC!, animated: true, completion: nil)
} catch {
print("### Apple Sign in Error: \(error)")
}
}
```

### Other Social Logins

- If using a WebViews, other social logins will be similar to above. Just follow the [Supabase's Documentation](https://supabase.com/docs/guides/auth/) for their setup.

## Basic CRUD Implementation

First, import and initialize `SupabaseClient`, as explained in "Usage" section.

### Insert Data

- You can either use `Codable` or `Encodable` and `Decodable` protocols for the model's struct. However without either, you will get an error saying `Cannot convert value of type 'Void' to specified type 'InsertModel'` when trying to cast the response to your model.
- Create a model which follows your table's data structure:


```swift
struct InsertModel: Encodable, Decodable {
let id: Int? // you can choose to omit this depending on how you've setup your table
let title: String?
let desc: String?
}

let insertData = InsertModel(title: "Test", desc: "Test Desc")
let query = client.database
.from("{ Your Table Name }")
.insert(values: insertData,
returning: .representation) // you will need to add this to return the added data
.select(columns: "id") // specifiy which column names to be returned. Leave it empty for all columns
.single() // specify you want to return a single value.

Task {
do {
let response: [InsertModel] = try await query.execute().value
print("### Returned: \(response)")
} catch {
print("### Insert Error: \(error)")
}
}
```

### Select Data

- Using the same model as before:

```swift
let insertData = InsertModel(title: "Test", desc: "Test Desc")
let query = client.database
.from("{ Your Table Name }")
.select() // keep it empty for all, else specify returned data
.match(query: ["title" : insertData.title, "desc": insertData.desc])
.single()

Task {
do {
let response: [InsertModel] = try await query.execute().value
print("### Returned: \(response)")
} catch {
print("### Select Error: \(error)")
}
}
```

### Update Data

- Using the same model as before:

```swift
// Assuming the record above was inserted with id 1
let updateData = InsertModel(id: 1, title: "Test Edited", desc: "Test Desc Edited")
let query = client.database
.from("{ Your Table Name }")
.update(values: updateData,
returning: .representation) // you will need to add this to return the updated data
.select(columns: "id") // specifiy which column names to be returned. Leave it empty for all columns
.single() // specify you want to return a single value.

Task {
do {
let response: [InsertModel] = try await query.execute().value
print("### Returned: \(response)")
} catch {
print("### Update Error: \(error)")
}
}
```

### Delete Data

```swift
let query = client.database
.from("{ Your Table Name }")
.delete(returning: .representation) // you will need to add this to return the deleted data
.match(
query: ["id" : 1] // assuming the record above was inserted with id 1
// You can add additional conditions here
)
.select() // specifiy which column names to be returned. Leave it empty for all columns
.single()

Task {
do {
let response: [InsertModel] = try await query.execute().value
print("### Returned: \(response)")
} catch {
print("### Delete Error: \(error)")
}
}
```

## Postgres Functions

- Unlike the JavaScript library, you can't use the `rpc` method on the `SupabaseClient`, instead you need to use the `rpc` method on the `PostgresClient`:

```swift
struct YourFunctionNameParams: Codable {
let param1: String
let param2: String
}

let query = client.database.rpc(
fn: "your_function_name",
params: YourFunctionNameParams(param1: "param1", param2: "param2")
let client = SupabaseClient(
supabaseURL: URL(string: "https://xyzcompany.supabase.co")!,
supabaseKey: "public-anon-key",
options: SupabaseClientOptions(
db: .init(
schema: "public"
),
auth: .init(
storage: MyCustomLocalStorage(),
flowType: .pkce
),
global: .init(
headers: ["x-my-custom-header": "my-app-name"],
session: URLSession.myCustomSession
)
)
)
// Like in Supabase-js, you can use the `.single` method to return a single value.

Task {
do {
let response: [DataModel] = try await query.execute().value // Where DataModel is the model of the data returned by the function
print("### Returned: \(response)")
} catch {
print("### RPC Error: \(error)")
}
}
```

## Contributing
Expand Down

0 comments on commit fddfd36

Please sign in to comment.