🌇

WebフロントエンドエンジニアがSwiftUIに入門した話

2023/04/23に公開

いつもはNuxtやNextを使って開発していますが、Web以外の技術も触ってみたいなってということで、SwiftUIとFirebaseを使って、Todoアプリを作ってみました。

GitHub: https://github.com/AFURO-1130/swift-todo-app

環境

  • Swift 5.6.1
  • cocoapods 1.12.0
  • xcode 13.4.1

xcodeのインストールとプロジェクトの作成

ここは他の人の記事がたくさんあるので、そちらを参照してください。
https://qiita.com/niwasawa/items/15694e9ee5e8a9ec6e84

Hello Worldまでできたら完璧です。

Firebaseとの連携

Firebaseとの連携の流れは、4つ手順があります。

  1. FirebaseコンソールからGoogleService-info.plistを取得
    2.
    3. ContentView.swiftと同じ階層にファイルを読み込ませてください
    4. もし、ダイアログが出てきたら、下記の画像のようにチェックしてください

    5.

  2. FirebaseのSDKインストール

    1. 自分はcocoapodsを使っているので、cocoapodsのインストール
    2. pod initをして、Podfileを作成
    Podfile
    pod 'FirebaseUI'
    pod 'Firebase'
    // 上記の二行を追加
    

  3. エントリーファイルに初期化コードを書く

    プロジェクト名App.swift
    import SwiftUI  //追加
    import Firebase  //追加
    // ここから
    class AppDelegate: NSObject, UIApplicationDelegate{
        func application(_ application: UIApplication,
                            didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
                FirebaseApp.configure()
                return true
            }
    }
    // ここまで追加
    
    @main
    struct tutorials_iOSApp: App {
        @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate // 追加
        var body: some Scene {
            WindowGroup {
                ContentView()
            }
        }
    }
    
  4. 使いたいところでモジュールをインポートする

    contentView.swift
    import Firebase
    
    // Firestoreを呼び出す時
    FireStore.firestore()
    

あとは下記をコピペしたら、とりあえず動くものは完成します!

contentView.swift
//
//  ContentView.swift
//  Shared
//
//  Created by 金城翔太郎 on 2023/04/16.
//

import SwiftUI
import Firebase

// todoの型を作成
struct Todo: Identifiable {
    let id: String
    let title: String
    var isEditing: Bool
}
struct TodoListView: View{
    var todo: Todo
    var body: some View{
        Text(todo.title)
    }
}


struct ContentView: View {
    @State var todos:[Todo] = [
    ];
    @State private var inputTitle = ""
    @State private var newTitle = ""
    var body: some View {
        VStack{
            Spacer().frame(height: 100)
            TextField("todoのタイトルを入力してください。", text: $inputTitle).textFieldStyle(RoundedBorderTextFieldStyle())
                .padding()
            // 入力してもらったものを表示する
            Button("タスクを追加する"){
                if inputTitle.isEmpty{
                    return
                }
                else{
                    addTodos(title: inputTitle)
                    getTodoTasks()
                }
            }
            
            List(todos){
                todo in
                HStack(){
                    if todo.isEditing {
                        TextField("タイトル", text: $newTitle)
                    } else{
                        Text(todo.title)
                    }
                    Spacer()
                    if todo.isEditing {
                        Button(action:{
                            if newTitle.isEmpty {
                                print("空ですよ")
                            } else {
                                updateTodo(task: todo, newTitle: newTitle)
                            }
                        }) {
                            Text("保存")
                        }
                        Spacer()
                    } else {
                        Button(action:{
                            // 編集を押したら走る関数
                            updateItem(id: todo.id)
                        }) {
                            Text("編集")
                        }
                        
                        Button(action: {
                            // Firestoreからデータを削除する関数を呼び出す
                            deleteTodo(task: todo)
                        }, label: {
                            Text("削除").foregroundColor(.red)
                        }).buttonStyle(PlainButtonStyle())
                    }
                }
                }
            }
            .onAppear(){
                getTodoTasks()
            }
           
        }

    func updateItem(id: String){
        if let index = todos.firstIndex(where: { $0.id == id }) {
            todos[index].isEditing.toggle()
        }
        
    }
    func updateTodo(task:Todo, newTitle:String){
        let db = Firestore.firestore();
        db.collection("todos").document(task.id).updateData([
            "title": newTitle,
        ]) {
            err in
                if let err = err {
                    print("更新に失敗しました: \(err)")
                } else {
                    print("更新に成功しました")
                    getTodoTasks()
                }
        }
    }
    func deleteTodo(task: Todo){
        let db = Firestore.firestore();
        db.collection("todos").document(task.id).delete() {
            err in if let err = err{
                print("削除に失敗しました \(err)")
            }
            else{
                print("削除に成功しました。")
                self.todos = self.todos.filter { $0.id != task.id }
            }
        }
    }
    
    func addTodos(title: String){
        let db = Firestore.firestore()
        db.collection("todos").addDocument(data: ["title": title, "isDone": false])
        { err in
            if let err = err {
                print("タスクの追加に失敗しました: \(err)")
            } else {
                print("タスクの追加に成功しました")
            }
        }
        inputTitle = ""
    }
    
    func getTodoTasks(){
        let db = Firestore.firestore();
        db.collection("todos").getDocuments { snapshot, error in
                        if let error = error {
                            print(error.localizedDescription)
                            return
                        }
                        
                        guard let documents = snapshot?.documents else {
                            return
                        }
                        
                        todos = documents.map { document in
                            let data = document.data()
                            let id = document.documentID
                            let title = data["title"] as? String ?? ""
                            return Todo(id: id, title: title, isEditing: false)
                        }
                    }
        
    }
    
    

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            ContentView()
        }
    }
}
}

webはこういう時に使い方だが、swiftだったらこう書く

  1. 非同期処理
    2. async/awaitクロージャーを使って、非同期処理を実装する。
  2. 画面がマウントされたときのイベント
    4. .onAppear()を使って、UIコンポーネントがマウントされた時に、処理ができます。
  3. TSとかのtypeinterface
    6. structを使って、型を書いていきます。

参考資料

Discussion