Open15

備忘録

平成n年生まれ@iOSエンジニア志望平成n年生まれ@iOSエンジニア志望

overlay & background


Circleは高さを持たない点に注意する。

Text("Sample")
    .overlay {
        Circle()
            .foregroundStyle(Color(uiColor: .systemGray6))
            .scaledToFill()
    }
Divider()
    .padding(.vertical)
Text("Sample")
    .background {
        Circle()
            .foregroundStyle(Color(uiColor: .systemGray6))
            .scaledToFill()
    }
平成n年生まれ@iOSエンジニア志望平成n年生まれ@iOSエンジニア志望

よく使うボタンの装飾

paddingが二つあるのは少々気持ち悪いが、サイズに合わせ自動的に調整してくれる。RoundedRectangleをclipShapeの引数に持たせたいが、CGFloatかCGSizeを指定する必要があるので、Capsuleで妥協。colorInvertを用いることでダークモードにも対応できる。

Text("Sample")
    .foregroundStyle(.primary)
    .colorInvert()
    .font(.title)
    .fontWeight(.semibold)
    .padding()
    .padding(.horizontal)
    .background(.blue)
    .clipShape(Capsule())
平成n年生まれ@iOSエンジニア志望平成n年生まれ@iOSエンジニア志望

ContentUnavailableView

https://developer.apple.com/documentation/swiftui/contentunavailableview

struct ContentView: View {
    var body: some View {
        ContentUnavailableView("該当者なし", systemImage: "person.slash", description: Text("条件を変更し再度お試しください。"))

        ContentUnavailableView.search

        ContentUnavailableView.search(text: "apple")

        ContentUnavailableView(label: {
            Label("No Mail", systemImage: "tray.fill")
        }, description: {
            Text("New mails you receive will appear here.")
        }, actions: {
            Button(action: {}) {
                Text("Refresh")
            }
        })
    }
}
平成n年生まれ@iOSエンジニア志望平成n年生まれ@iOSエンジニア志望

後日検証

/// SwiftDataのプレビューに関して
/// すでに保存されているものしか引数に取れない疑惑。

import SwiftUI
import SwiftData

@Model final class User {
    let name: String
    init(name: String) { self.name = name }
}

struct SampleView: View {
    let user: User

    var body: some View {
        Text(user.name)
    }
}

///// NGパターン
//#Preview {
//    SampleView(user: .init(name: "Naruto"))
//}

/// OKパターン
#Preview {
    let config = ModelConfiguration(isStoredInMemoryOnly: true)
    let container = try! ModelContainer(for: User.self, configurations: config)
    let naruto = User(name: "Naruto")
    container.mainContext.insert(naruto)

    return SampleView(user: naruto)
        .modelContainer(container)
}

///// それとSwiftDataのモデルはどのような場合でもイニシャライザは必要っぽい。エラーメッセージは以下の通り。
///// @Model requires an initializer be provided for 'User'
//@Model final class User {
//    let name: String = "Naruto"
//}
平成n年生まれ@iOSエンジニア志望平成n年生まれ@iOSエンジニア志望

なぜかsharedModelContainerをAppのところで作ってるとOK。
ContentViewに何もつけてないけど問題ない。

import SwiftUI
import SwiftData

@main
struct SwiftData_240906App: App {
    var sharedModelContainer: ModelContainer = {
        let schema = Schema([
            User.self,
        ])
        let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)

        do {
            return try ModelContainer(for: schema, configurations: [modelConfiguration])
        } catch {
            fatalError("Could not create ModelContainer: \(error)")
        }
    }()

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}
import SwiftUI
import SwiftData

@Model final class User {
    let name: String
    init(name: String) { self.name = name }
}
struct SampleView: View {
    let user: User

    var body: some View {
        Text(user.name)
    }
}

/// OK
#Preview {
    SampleView(user: .init(name: "Naruto"))
        .modelContainer(for: User.self, inMemory: true)
}

そしてAppファイルにあれこれ書かない場合、以下のようにするとOK。

#Preview {
    let config = ModelConfiguration(isStoredInMemoryOnly: true)
    let container = try! ModelContainer(for: User.self, configurations: config)

    return SampleView(user: .init(name: "Naruto"))
        .modelContainer(for: User.self, inMemory: true)
}
平成n年生まれ@iOSエンジニア志望平成n年生まれ@iOSエンジニア志望

ざっくりFirebaseAuth

import Foundation
import FirebaseAuth

@MainActor
final class AccountManager: ObservableObject {

    @Published private(set) var currentUser: FirebaseAuth.User? {
        didSet { print("currentUserが\(String(describing: oldValue))から\(String(describing: currentUser))に変更されました。\n")}
    }

    @Published private var auth: FirebaseAuth.Auth! {
        didSet { print("authが\(String(describing: oldValue))から\(String(describing: auth))に変更されました。\n") }
    }

    var isReady: Bool { auth != nil }

    private var authStateDidChangeListenerHandle: AuthStateDidChangeListenerHandle!

    init() {
        authStateDidChangeListenerHandle = Auth.auth().addStateDidChangeListener { [weak self] auth, currentUser in
            if self?.auth != auth { self?.auth = auth }
            if self?.currentUser != currentUser { self?.currentUser = currentUser }
        }
    }

    deinit {
        if let authStateDidChangeListenerHandle = authStateDidChangeListenerHandle {
            Auth.auth().removeStateDidChangeListener(authStateDidChangeListenerHandle)
        }
    }

    func createAccount(email: String, password: String) async throws {
        try await auth.createUser(withEmail: email, password: password)
    }

    func deleteAccount(email: String, password: String) async throws {
        let credential = EmailAuthProvider.credential(withEmail: email, password: password)
        try await auth.currentUser?.reauthenticate(with: credential)
        try await auth.currentUser?.delete()
    }

    func signIn(email: String, password: String) async throws {
        try await auth.signIn(withEmail: email, password: password)
    }

    func signOut() throws {
        try auth.signOut()
    }

}

/// Auth.auth().createUserで新規ユーザーを作成した場合、Auth.auth().currentUserは新しいユーザーに置き換わる。
///
/// Authの初期化が完了する前にAuth.auth().currentUserを使うと、ログインしていたとしてもnilが返ってくる。
/// → addStateChangeListenerを使うとこの問題解決できる。
///
/// ログインしている状態でアプリを削除し、その後にアプリを再インストールした場合、なぜかログインされた状態から始まる。
/// → ログイン情報はキーチェーンに保存されてるらしい。
///
/// Firebase Consoleの方からログイン中のアカウントを削除しても、端末に何かしらの通信がいくわけではない模様。
///
/// Firebaseコンソールでユーザーをブロック、もしくは削除したときの情報の反映はどんな感じでやればいい?