(Translated by https://www.hiragana.jp/)
GitHub - briannadoubt/PocketBase: A PocketBase client for iOS, macOS, watchOS, tvOS, and visionOS
Skip to content

A PocketBase client for iOS, macOS, watchOS, tvOS, and visionOS

License

Notifications You must be signed in to change notification settings

briannadoubt/PocketBase

Repository files navigation

PocketBase

Run Tests

A pure Swift client for interfacing with a PocketBase instance.

Getting Started

Development Environment

Easiest way to get started with PocketBase for Swift is to run an instance inside of a Docker container.

Run the following commands to start PocketBase locally:

git clone https://github.com/briannadoubt/PocketBase.git
cd PocketBase
docker compose up

You should then see something like:

Starting pocketbase ... done
Attaching to pocketbase
pocketbase    | > Server started at: http://0.0.0.0:8090
pocketbase    |   - REST API: http://0.0.0.0:8090/api/
pocketbase    |   - Admin UI: http://0.0.0.0:8090/_/

Now you're ready to incorporate the library.

The Codes

First, be sure to import the right things. This should be all the dependencies required to build an app with PocketBase:

import PocketBase // <~ Exposes the core `PocketBase` object. Imports `Foundation`.
import PocketBaseUI // <~ Exposes the various SwiftUI helpers surrounding the `PocketBase` instance.
import SwiftUI

To setup a pocketbase instance on your app, use the Environment. PocketBase will default to localhost if no instance is defined here.

To set up a custom url, use a similar pattern, but just pass a URL:

@main
struct CatApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        #if DEBUG
        .pocketbase(.localhost) // <~ optional
        #else
        .pocketbase(url: URL(string: "https://production.myFancyApp.com/")!)
        #endif
    }
}

Then, if you want to support authentication, you'll need to create an AuthCollection. This object should match the field schema shape of your authentication collection defined in your PocketBase admin console:

@AuthCollection("users") // <~ Define auth collection model to enable authentication.
struct User {
    var name: String = ""
}

Now that our app is set up with a user schema to authenticate with, let's make that happen in our App:

@main
struct CatApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .authenticated { username, email in // <~ Attach a default authentication flow to get started.
                    User(username: username, email: email) // <~ Provide a default instance of your user. 
                }
        }
        .pocketbase(.localhost)
    }
}

To provide a custom auth flow, use a different overload:

@main
struct CatApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .authenticated(as: User.self) {
                    ProgressView("Loading...")
                } signedOut: { collection, authState in
                    CustomLoginScreen(
                        collection: collection,
                        authState: authState
                    )
                }
        }
        .pocketbase(.localhost)
    }
}

struct CustomLoginScreen: View {
    @Environment(\.pocketbase) private var pocketbase // <~ get the `PocketBase` instance from the environment to make mutations
    
    var collection: RecordCollection<User>
    @Binding var authState: AuthState
    
    var body: some View {
        // All your fancy styling here
    
        SignUpButton(
            { username, password in
                User(username: username, password: password)
            },
            collection: collection,
            authState: $authState,
            strategy: .identity(
                "meowface",
                password: "Test1234"
            )
        )
    }
}

To log a user out, just call .logout() on the relevant RecordCollection<T>:

struct LogoutButton: View {
    @Environment(\.pocketbase) private var pocketbase
    var body: some View {
        Button("Logout") {
            pocketbase.collection(User.self).logout()       
        }
    }   
}

Now, users can download records in other collections. So let's define another one. Just like the AuthCollection, this object should match the field schema shape of your base collection defined in your PocketBase admin console:

@BaseCollection("rawrs") // <- Define a base collection type
struct Rawr {
    var field: String = ""
}

Awesome. Now that we have a type, we can query for them. There are two options in this realm: StaticQuery and RealtimeQuery.

StaticQuery is a simple propertyWrapper that pages results and stores them in-memory. It can be used like so:

struct StaticRawrs: View {
    @StaticQuery private var rawrs: [Rawr]
    var body: some View {
        List(rawrs) { rawr in
            Text(rawr.field)
        }
        .task {
            await $rawrs.load()
        }
        .refreshable {
            await $rawrs.load()
        }
    }
}

RealtimeQuery is a bit fancier, and enables realtime updates to the data as it changes on the server. It can be used in a very similar way:

struct RealtimeRawrs: View {
    @RealtimeQuery private var rawrs: [Rawr]
    var body: some View {
        List(rawrs) { rawr in
            Text(rawr.field)
        }
        .task {
            await $rawrs.start()
        }
        .refreshable {
            await $rawrs.start()
        }
    }
}

Or, if you want to handle state on your own, you can hook into the async events stream:

let pocketbase = PocketBase()
var events: [RecordEvent<Rawr>] = []
let stream = try await pocketbase.collection(Rawr.self).events()
for await event in stream {
    let record = event.record
    switch event.action {
    case .create:
        // Do
    case .update:
        // Yo
    case .delete:
        // Thang
    }
}
// etc.

Any other data mutations can be made with the RecordCollection<T> that is generated with PocketBase().collection(Rawr.self):

let pocketbase = PocketBase()
let collection = pocketbase.collection(Rawr.self)
let new = Rawr(field: "meow")
let created = try await collection.create(new)
let results = try await collection.list()
let record = try await collection.view(id: created.id)
guard var first = results.items.first else { return }
first.field = "updated value"
let updated = try await collection.update(first)
try await collection.delete(updated)

About

A PocketBase client for iOS, macOS, watchOS, tvOS, and visionOS

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages