2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

えーえすAdvent Calendar 2024

Day 24

SwiftUI×Supabaseでアプリを作ろう

Last updated at Posted at 2024-12-23

はじめに

最近、FirebaseのStorageが有料化 したりなど、個人開発でFirebaseを使うのがだんだんと躊躇われるようになってきました。

そこで、次世代Firebaseとも呼び声の高いSupabaseを使って、iPhoneアプリを作っていきます。

Supabaseとは?

Supabaseは、オープンソースのBackend as a Service(BaaS)で、PostgreSQLを基盤としたリアルタイムデータベース、認証機能、ストレージ、サーバーレス関数などを提供します。これにより、開発者は複雑なバックエンドの構築を最小限に抑えつつ、高速かつ安全にアプリケーションを開発できます。また、リアルタイム同期やロールベースのアクセス制御などの高度な機能が特徴で、Firebaseの代替として注目されています。

Supabaseでプロジェクトを作成しよう

アカウントを作成すると、New projectから新しいプロジェクトを作成できます。

image.png

作成するとこんな感じ。

次に左のメニューからSQL Editorを開きます。

image.png

ここにSQLを書くことで、テーブルを作成できます。

今回はサンプルとして、usersテーブルとscoresテーブルを作り、1対多の関係を持たせます。

create table public.users (
  id uuid default gen_random_uuid() primary key,
  name text not null,
  created_at timestamp with time zone default now()
);
create table public.scores (
  id uuid default gen_random_uuid() primary key,
  user_id uuid references public.users (id) on delete cascade not null,
  value int not null,
  created_at timestamp with time zone default now()
);

実行が成功すると画面下部に「Success. No rows returned」と表示されます。

スクリーンショット 2024-12-23 19.35.51.png

次に、それぞれのテーブルにテストデータを追加しましょう。

-- Insert test data into the 'users' table
INSERT INTO users (id, name, created_at)
VALUES
    ('11111111-1111-1111-1111-111111111111', 'Alice', NOW()),
    ('22222222-2222-2222-2222-222222222222', 'Bob', NOW()),
    ('33333333-3333-3333-3333-333333333333', 'Charlie', NOW());

-- Insert test data into the 'scores' table
INSERT INTO scores (id, user_id, value, created_at)
VALUES
    ('44444444-4444-4444-4444-444444444444', '11111111-1111-1111-1111-111111111111', 100, NOW()),
    ('55555555-5555-5555-5555-555555555555', '11111111-1111-1111-1111-111111111111', 150, NOW()),
    ('66666666-6666-6666-6666-666666666666', '22222222-2222-2222-2222-222222222222', 200, NOW()),
    ('77777777-7777-7777-7777-777777777777', '33333333-3333-3333-3333-333333333333', 50, NOW()),
    ('88888888-8888-8888-8888-888888888888', '33333333-3333-3333-3333-333333333333', 75, NOW());

左のメニューからDatabase > Schema Visualizerを選ぶと、現在のテーブルの関係が見られます。

スクリーンショット 2024-12-23 19.37.09.png

また、Table Editorから現在のデータ一覧が確認できます。

image.png

Supabaseをアプリに組み込もう

次に、Supabaseを用いてアプリを作成していきます。

Swift Package ManagerでSupabaseを追加

まずはSwift Package ManagerでSupabaseを追加します。

スクリーンショット 2024-12-23 20.29.33.png

ここで、import Supabaseがうまく反応しない場合は、ライブラリとしてSupabaseが認識されていない場合があります。

スクリーンショット 2024-12-23 20.11.43.png

Frameworks, Libraries, and Embedded Contentの+ボタンから、Supabaseを追加します。

スクリーンショット 2024-12-23 20.11.53.png

コードを書く

次にコードを書いていきます。

Models.swift
// Models.swift
import Foundation

struct User: Identifiable, Decodable {
    let id: UUID
    let name: String
    let created_at: Date
}

struct Score: Identifiable, Decodable {
    let id: UUID
    let user_id: UUID
    let value: Int
    let created_at: Date
}
SupabaseManager
import Foundation
import Supabase

class SupabaseManager {
    static let shared = SupabaseManager()
    
    let client: SupabaseClient
    
    private init() {
        // Supabase URLとAnon Keyで置き換え
        let url = URL(string: "https://your url.supabase.co")!
        let key = "Your API KEY"
        client = SupabaseClient(supabaseURL: url, supabaseKey: key)
    }
}

ここで、Supabase URLとAnon Keyは画面右下のProject Setting > APIから確認できます。

スクリーンショット 2024-12-23 20.33.57.png

ContentView.swift
// ContentView.swift
import SwiftUI

struct ContentView: View {
    @StateObject private var viewModel = UserListViewModel()
    
    var body: some View {
        NavigationView {
            List(viewModel.users) { user in
                NavigationLink(destination: ScoreListView(user: user)) {
                    Text(user.name)
                }
            }
            .navigationTitle("Users")
            .task {
                await viewModel.fetchUsers()
            }
        }
    }
}
UserListViewModel.swift
// UserListViewModel.swift
import Foundation
import Supabase

@MainActor
class UserListViewModel: ObservableObject {
    @Published var users: [User] = []
    @Published var error: String?
    
    func fetchUsers() async {
        do {
            let response: PostgrestResponse<[User]> = try await SupabaseManager.shared.client
                .from("users")
                .select()
                .execute()
            
            users = response.value
        } catch {
            self.error = error.localizedDescription
        }
    }
}
ScoreListView.swift
// ScoreListView.swift
import SwiftUI

struct ScoreListView: View {
    let user: User
    @StateObject private var viewModel = ScoreListViewModel()
    
    var body: some View {
        List(viewModel.scores) { score in
            HStack {
                Text("Score: \(score.value)")
                Spacer()
                Text("\(score.created_at, formatter: dateFormatter)")
                    .font(.caption)
                    .foregroundColor(.gray)
            }
        }
        .navigationTitle("\(user.name)'s Scores")
        .task {
            await viewModel.fetchScores(for: user.id)
        }
    }
}

let dateFormatter: DateFormatter = {
    let formatter = DateFormatter()
    formatter.dateStyle = .short
    formatter.timeStyle = .short
    return formatter
}()
ScoreListViewModel.swift
// ScoreListViewModel.swift
import Foundation
import Supabase

@MainActor
class ScoreListViewModel: ObservableObject {
    @Published var scores: [Score] = []
    @Published var error: String?
    
    func fetchScores(for userId: UUID) async {
        do {
            let response: PostgrestResponse<[Score]> = try await SupabaseManager.shared.client
                .from("scores")
                .select()
                .eq("user_id", value: userId)
                .execute()
            
            scores = response.value
        } catch {
            self.error = error.localizedDescription
        }
    }
}

これで実行すると、ちゃんとデータを取得することができます。

simulator_screenshot_E561FDE5-ECC4-40D7-8775-C3FEE4AC1853.png

まとめ

GraphQLなどもサポートされていてかなり使い勝手が良い印象を受けるので、これからも使い倒していきたいなと思います!皆さんも使っていきましょう!

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?