The specific usage is similar to that provided by pocketbase.
- List/Search
- View
- Create
- Update
- Delete
- Realtime
- Auth
- File upload
Demo App Design Reference Youtuber Kavsoft's SwiftUI Social Media App
demo_video.mp4
Make sure to enable PocketBase first, please refer to pocketbase for specific usage methods
- Insert url of this SPM in your XCode Project with
File → Add Package → Copy Dependency
. - Because of EventSource, you can only choose to use branch main at present.
https://github.com/zz129869523/PocketBase
- Import the framework:
import PocketBase
let client = PocketBase<User>() // default is http://0.0.0.0:8090
let client = PocketBase<User>(host: "https://your_domain")
If you want to customize the default authStore(User), you can extend it and pass a new custom struct to the client:
struct CustomUser: AuthModel { // implement AuthModel
var id: String?
var collectionId: String?
var collectionName: String?
var created: String?
var updated: String?
var username: String?
var verified: Bool?
var emailVisibility: Bool?
var email: String?
var name: String?
var avatar: String?
var deactivation: Bool?
}
let client = PocketBase<CustomUser>()
print(client.authStore.model?.deactivation ?? false)
@main
struct ChartTestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(PocketBase<User>()) // <-- 1
}
}
}
struct ContentView: View {
@EnvironmentObject var client: PocketBase<User> // <-- 2
var body: some View {
// ...
}
}
or
struct ContentView: View {
let client = PocketBase<User>() // choose one of three
@StateObject var client = PocketBase<User>() // choose one of three
@ObservedObject var client = PocketBase<User>() // choose one of three
var body: some View {
// ...
}
}
struct Post: Codable, Identifiable { // or implement BaseModel
var id: String?
var title: String
}
extension Post {
init(dictionary: [String: Any]) throws {
self = try JSONDecoder().decode(Self.self, from: JSONSerialization.data(withJSONObject: dictionary))
}
}
Task {
// getList
let dic = await client.collection("posts").getList() // return type of [String : Any]?
if let dic {
let err = try? ErrorResponse(dictionary: dic)
// Handle errors such as status code 400 404
print(err?.code)
print(err?.message)
if err == nil, let result: ListResult<Post> = try? Post(dictionary: dic) {
print(result.items)
}
}
// or
let posts: ListResult<Post>? = await client.collection("posts").getList()
if let listResult: ListResult<Post> = await client.collection("posts").getList() {
self.posts = listResult.items
}
// getFullList
let posts: [Post] = await client.collection("posts").getFullList(batch: 200, filter: "created<'2022-12-26 11:10:08'", sort: "-created")
// getFirstListItem
let post: Post? = await client.collection("posts").getFirstListItem()
if let post: Post = await client.collection("posts").getFirstListItem() {
// ...
}
}
Task {
let post: Post? = await client.collection("posts").getOne(id: "RECORD_ID")
}
Task {
let post: Post? = await client.collection("posts").create(["title": "hello pocketbase"])
let post: Post? = await client.collection("posts").create(Post(title: "hello pocketbase"))
// If you wanna upload file you should add MultipartFormData in your sturct.
// And use File struct.
// Like:
struct PostRequest: Codable, Identifiable, MultipartFormData { // <- Add MultipartFormData
var id: String?
var title: String
var image: File // <- This
var imageOptional: File? // Optional
var images: [File] // <- Or like this
var imagesOptional: [File]? // Optional
}
let file = File(mimeType: "image/jpeg", filename: "img1", data: photoDataOrOtherData)
// Upload post with image file
let post: Post? = await client.collection("posts").create(PostRequest(title: "hello pocketbase", image: file, images: [file, file, file]))
// Download URL is http://127.0.0.1:8090/api/files/COLLECTION_ID_OR_NAME/RECORD_ID/FILENAME
// returns something like:
// http://127.0.0.1:8090/api/files/posts/amqb484dme8ujz4/img1_52iWbGinWd.jpg
if let record: [String: Any] = await client.collection("posts").getOne(id: "amqb484dme8ujz4") {
let filename = record["image"] as! String
print(client.getFileUrl(record, filename))
}
// or struct Post implement BaseModel
struct Post: Codable, BaseModel { // <- BaseModel is required
var id: String?
var collectionId: String?
var collectionName: String?
var created: String?
var updated: String?
var title: String
var image: String
var imageOptional: String?
var images: [String]
var imagesOptional: [String]?
}
// returns something like:
// http://127.0.0.1:8090/api/files/posts/amqb484dme8ujz4/img1_52iWbGinWd.jpg?thumb=200x200
if let record: Post = await client.collection("posts").getOne(id: "amqb484dme8ujz4") {
let filename = record.image
print(client.getFileUrl(record, filename, query: ["thumb": "200x200"]))
}
}
Task {
let post: Post? = await client.collection("posts").update("RECORD_ID", body: ["title": "hello pocketbase"])
let post: Post? = await client.collection("posts").update("RECORD_ID", body: Post(title: "hello pocketbase"))
}
Task {
let dic = await client.collection("posts").delete("RECORD_ID")
}
// Subscribe to changes only in the specified record
client.collection("posts").subscribe("RECORD_ID") { dict in
print(dict)
}
// Subscribe to changes in any posts record
client.collection("posts").subscribe("*") { dict in
if let result: Event<Post> = try? Utils.dictionaryToStruct(dictionary: dict ?? [:]) { // Optional: Event<Type> Can use
switch result.action {
case .create:
self.posts.append(result.record)
case .update:
if let row = self.posts.firstIndex(where: { $0.id == result.record.id }) {
self.posts[row] = result.record
}
case .delete:
self.posts = self.posts.filter { $0.id != result.record.id }
}
}
}
// Unsubscribe
client.collection("posts").unsubscribe("RECORD_ID") // remove all "RECORD_ID" subscriptions
client.collection("posts").unsubscribe("*") // remove all "*" topic subscriptions
client.collection("posts").unsubscribe() // remove all subscriptions in the collection
Task {
// authWithPassword
let _ = await client.collection("users").authWithPassword("pocketbase@test.email", "12345678")
let response: AuthResponse<User>? = await client.collection("users").authWithPassword("pocketbase@test.email", "12345678")
print(response?.token)
print(response?.record)
print(client.authStore.isValid)
print(client.authStore.token)
print(client.authStore.model.id)
// "logout" the last authenticated account
client.authStore.clear()
// authWithOAuth2
let _ = await client.collection("users").authWithOAuth2(.google, code: "CODE", codeVerifier: "VERIFIER", redirectUrl: "REDIRECT_URL")
// authRefresh
let _ = await client.collection("users").authRefresh()
// requestVerification
let _ = await client.collection("users").requestVerification("email")
// requestPasswordReset
let _ = await client.collection("users").requestPasswordReset("email")
// requestEmailChange
let _ = await client.collection("users").requestEmailChange("email")
// listAuthMethods
let authMethods: AuthMethods? = await client.collection("users").listAuthMethods()
print(authMethods?.authProviders)
// listExternalAuths
let authMethod: [AuthMethod] = await client.collection("users").listExternalAuths("id")
print(authMethod)
// unlinkExternalAuth
let _ = await client.collection("users").unlinkExternalAuth("id", provider: .google)
}
print(client.authStore.isValid)
print(client.authStore.token)
print(client.authStore.model.id)
// "logout" the last authenticated account
client.authStore.clear()
// MARK: - List/Search
func getList(page: Int, perPage: Int, filter: String, sort: String, expand: String) async -> [String: Any]?
func getList<R: Codable>(page: Int, perPage: Int, filter: String, sort: String, expand: String) async -> ListResult<R>?
func getFullList<R: Codable>(batch: Int, filter: String, sort: String, expand: String) async -> [R]
func getFirstListItem<R: Codable>(filter: String, sort: String, expand: String) async -> R?
// MARK: - View
func getOne(id: String, expand: String) async -> [String: Any]?
func getOne<R: Codable>(id: String, expand: String) async -> R?
// MARK: - Create
func create<BodyType: Codable>(_ body: BodyType) async -> [String: Any]?
func create<BodyType: Codable & MultipartFormData>(_ body: BodyType) async -> [String: Any]?
func create<BodyType: Codable, R: Codable>(_ body: BodyType) async -> R?
func create<BodyType: Codable & MultipartFormData, R: Codable>(_ body: BodyType) async -> R?
// MARK: - Update
func update<BodyType: Codable>(_ id: String, body: BodyType, expand: String) async -> [String: Any]?
func update<BodyType: Codable, R: Codable>(_ id: String, body: BodyType, expand: String) async -> R?
// MARK: - Delete
func delete(_ id: String) async -> [String: Any]?
// MARK: - Realtime
func subscribe(_ recordId: String, completion: @escaping ([String: Any]?) -> Void)
func unsubscribe(_ recordId: String)
// MARK: - Auth
func authWithPassword(_ identity: String, _ password: String, _ expand: String) async -> [String: Any]?
func authWithPassword<UserModel: AuthModel>(_ identity: String, _ password: String) async -> AuthResponse<UserModel>?
func authWithOAuth2(_ provider: OAuthProvider, code: String, codeVerifier: String, redirectUrl: String, createData: [String: String], expand: String) async -> [String: Any]?
func authWithOAuth2<UserModel: AuthModel>(_ provider: OAuthProvider, code: String, codeVerifier: String, redirectUrl: String, createData: [String: String], expand: String) async -> AuthResponse<UserModel>?
func authRefresh(expand: String) async -> [String: Any]?
func authRefresh<UserModel: AuthModel>(expand: String) async -> AuthResponse<UserModel>?
func requestVerification(_ email: String) async -> [String: Any]?
func requestPasswordReset(_ email: String) async -> [String: Any]?
func requestEmailChange(_ email: String) async -> [String: Any]?
func listAuthMethods() async -> [String: Any]?
func listAuthMethods() async -> AuthMethods?
func listExternalAuths(_ id: String) async -> [String: Any]?
func listExternalAuths(_ id: String) async -> [AuthMethod]
func unlinkExternalAuth(_ id: String, provider: OAuthProvider) async -> [String: Any]?