macOS14(2024)以降Observableマクロが使える。2026時点で導入から3年目を迎える。移行ガイドはXcode Documentationをsample code: Observableとすればヒットするのだが、Xcodeを起動せずとも移行の要点を把握できる様に、移行ガイド記載のコードの断片をここに記載する。
Migrating from the Observable Object protocol to the Observable macro: Apple Developer Documentation
// BEFORE
import SwiftUI
class Library: ObservableObject {
@Published var books: [Book] = [Book(), Book(), Book()]
}
class Book: ObservableObject, Identifiable {
@Published var title = "Sample Book Title"
let id = UUID() // A unique identifier that never changes.
}
note: Xcode Documentationでは2つ目のLibraryにはObservableObjectプロトコルへの準拠の記載が無いが誤記だと思われる。
SwiftUIに監視させるにはその型(class)にマクロを書く
// AFTER
import SwiftUI
@Observable class Library {
var books: [Book] = [Book(), Book(), Book()]
}
@Observable class Book: Identifiable {
var title = "Sample Book Title"
let id = UUID() // A unique identifier that never changes.
}
Appを構成するWindow全体で値を共有したい場合、@StateObjectプロパティラッパーを使用し、Windowへの注入にはenvironmentObjectモディファイアを使用
// BEFORE
@main
struct BookReaderApp: App {
@StateObject private var library = Library()
var body: some Scene {
WindowGroup {
LibraryView()
.environmentObject(library)
}
}
}
あれこれ異なる
// AFTER
@main
struct BookReaderApp: App {
@State private var library = Library()
var body: some Scene {
WindowGroup {
LibraryView()
.environment(library)
}
}
}
Windowに注入された変数の受け取り
// BEFORE
struct LibraryView: View {
@EnvironmentObject var library: Library
var body: some View {
List(library.books) { book in
BookView(book: book)
}
}
}
あれこれ異なる
// AFTER
struct LibraryView: View {
@Environment(Library.self) private var library
var body: some View {
List(library.books) { book in
BookView(book: book)
}
}
}
監視されたオブジェクトをWindowで利用。book変数はBookView生成時に引数に与える
// BEFORE
struct BookView: View {
@ObservedObject var book: Book
@State private var isEditorPresented = false
var body: some View {
HStack {
Text(book.title)
Spacer()
Button("Edit") {
isEditorPresented = true
}
}
.sheet(isPresented: $isEditorPresented) {
BookEditView(book: book)
}
}
}
@ObservedObjectプロパティラッパーは不要
// AFTER
struct BookView: View {
var book: Book
@State private var isEditorPresented = false
var body: some View {
HStack {
Text(book.title)
Spacer()
Button("Edit") {
isEditorPresented = true
}
}
.sheet(isPresented: $isEditorPresented) {
BookEditView(book: book)
}
}
}
上記コードのBookEditView生成時に引数で与えられたbook変数にはプロパティラッパーが必要
// BEFORE
struct BookEditView: View {
@ObservedObject var book: Book
@Environment(\.dismiss) private var dismiss
var body: some View {
VStack() {
TextField("Title", text: $book.title)
.textFieldStyle(.roundedBorder)
.onSubmit {
dismiss()
}
Button("Close") {
dismiss()
}
.buttonStyle(.borderedProminent)
}
.padding()
}
}
子Windowであれば@Bindableプロパティラッパーを使う
// AFTER
struct BookEditView: View {
@Bindable var book: Book
@Environment(\.dismiss) private var dismiss
var body: some View {
VStack() {
TextField("Title", text: $book.title)
.textFieldStyle(.roundedBorder)
.onSubmit {
dismiss()
}
Button("Close") {
dismiss()
}
.buttonStyle(.borderedProminent)
}
.padding()
}
}
