SwiftData
Herkese merhabalar bu yazımda sizlere WWDC23 ile hayatımıza giren SwiftUI ile entegre olarak calışan persistance frameworku SwiftData’ dan bahsetmek istiyorum.
Şu an beta sürümünde olan SwiftData, Core Data’nın persistance teknolojisini eşzamanlılık yetenekleriyle birlikte kullanmamıza olanak veriyor. Veri modelleme ve yönetim frameworku olarak tasarlanan SwiftData daha önce kullandığımız yöntemlere göre gerçekten çok kullanışlı ve basit.
Bu frameworku örnek proje üzerinde inceleyelim, futbol takımlarının olduğu bir listeye yeni takım ekleme ve silme yada güncelleme ile SwiftData frameworkunun daha iyi anlaşılacağını düşünüyorum.
Uygulamanın bitmiş halinin videosu aşağıdaki gibi burada SwiftData frameworku ile verileri viewler arasında yönetiyoruz ve lokalde saklıyoruz.
Verileri yönetmek icin kullandığımız ObservableObject property wrapperi ile olusturduğumuz Team classı aşağıdaki gibiydi.
import Foundation
class Team: ObservableObject {
@Published var teamName: String
@Published var countryName: String
@Published var company: String
var establishedDate: Date
init(teamName: String, countryName: String, establishedDate: Date, company: String) {
self.teamName = teamName
self.countryName = countryName
self.establishedDate = establishedDate
self.company = company
}
}
SwiftData ile verileri yönetmek ve UI akışları için kullandığımız ObservableObject property wrapperi ve Published computed property kullanmamıza gerek kalmıyor. Aşağıda SwiftData ile oluşturduğumuz Team classı hem verilerimizi diğer viewlerde kullanmamıza ve yönetmemize olanak tanıyor hem de persistance kabiliyeti ile verilerimizi cok basit sekilde lokalde saklamamıza olanak tanıyor.
import Foundation
import SwiftData
@Model
class Team {
var teamName: String
var countryName: String
var company: String
var establishedDate: Date
init(teamName: String, countryName: String, establishedDate: Date, company: String) {
self.teamName = teamName
self.countryName = countryName
self.establishedDate = establishedDate
self.company = company
}
}
SwiftData frameworkunu import ediyoruz @Model ile ObservableObject propert wrapper otomatik olarak classimıza tanımlanmış oluyor.
Fakat bazı değişiklikler ve eklemelere ihtiyacımız var EditTeamView içerisinde Team classı ObservableObject ile sarmalamıştık fakat biz onun yerine @Model ekledik edit yaptığımız viewde team objesine artık ObservedObject tanımlayamayız.
import SwiftUI
struct EditTeamView: View {
@ObservedObject var team: Team ->Warning<!Generic struct 'ObservedObject' requires that 'Team' conform to 'ObservableObject'>
@Environment(\.dismiss) private var dismiss
var body: some View {
Form {
Section {
TextField("Team name", text: $team.teamName)
TextField("Country name", text: $team.countryName)
}
Section {
TextField("Company", text: $team.company)
}
}
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button("Done") {
dismiss()
}
.bold()
}
}
.navigationBarTitle("Edit Team", displayMode: .inline)
}
}
ObservedObject property wrapper i yerine yeni olan @Bindable propert wrapper ini ekliyoruz.
import SwiftUI
struct EditTeamView: View {
@Bindable var team: Team
@Environment(\.dismiss) private var dismiss
var body: some View {
Form {
Section {
TextField("Team name", text: $team.teamName)
TextField("Country name", text: $team.countryName)
}
Section {
TextField("Company", text: $team.company)
}
}
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button("Done") {
dismiss()
}
.bold()
}
}
.navigationBarTitle("Edit Team", displayMode: .inline)
}
}
import SwiftUI
struct TeamRowView: View {
//@ObservedObject var team: Team (bunun yerıne Bindable çünkü Team classı Observable degıl @Model)
@Bindable var team: Team
var body: some View {
HStack {
Text(team.initials)
.font(.system(size: 20, weight: .semibold))
.foregroundColor(.white)
.frame(width: 48, height: 48)
.background(Color(.systemGray4))
.clipShape(Circle())
VStack(alignment: .leading, spacing: 4) {
Text(team.fullname)
Text(team.company)
.font(.footnote)
}
}
}
}
ObservedObject yerine verilerimizi yonetmek ve kaydetmek icin @Bindable ekledik.
Team classi datamızı localde kaydetmek ve veri akışını diğer viewlerde sorunsuz kullanmak icin veriyi kapsayıcı modelContainer kullandık. Uygulama açılırken modelContainer içerisindeki Team classı verilerini uygulamaya senkronize şekilde aktarır.
import SwiftUI
import SwiftData
@main
struct SwiftDataContactsApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}.modelContainer(for: Team.self)
}
}
Yeni takim olusturdugumuz view icerisinde daha oncesinde kullandigimiz @binding property si yerine modelContext kullanmamiz gerekiyor.
Binding ile bağladığımız yeni team objesini kullanarak oluşturduğumuz yeni takım ContentView içerisindeki List’ te gözüküyordu fakat biz verimizi SwiftData frameworku kullanarak saklamak istiyoruz. Save işlemi icin modelContext.insert() yeni team objesini locale kaydediyoruz.
import SwiftUI
struct CreateTeamView: View {
@Environment(\.dismiss) private var dismiss
//@Binding var contacts: [Team]
@Environment(\.modelContext) private var modelContext
@State private var firstName = ""
@State private var lastName = ""
@State private var company = ""
var body: some View {
NavigationStack {
Form {
Section {
TextField("Team name", text: $firstName)
TextField("Country name", text: $lastName)
}
Section {
TextField("Company", text: $company)
}
}
.navigationTitle("New Team")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .topBarLeading) {
Button("Cancel") {
dismiss()
}
}
ToolbarItem(placement: .topBarTrailing) {
Button("Done") {
withAnimation {
let newContact = Team(
teamName: firstName,
countryName: lastName,
establishedDate: Date(),
company: company
)
// contacts.append(newContact)
modelContext.insert(newContact)
dismiss()
}
}
.bold()
}
}
}
}
}
ContentView içerisinde team listesi edit ve add işlemlerini yaptığımız aksiyonlardan oluşuyor. Uygulama açıldığında ya da guncelleme silme ekleme yaptigimizda yeni veriyi fetch islemi yapan Query i ekliyoruz.
import SwiftUI
import SwiftData
struct ContentView: View {
@State private var showCreateContactView = false
@Query private var teams : [Team]
var body: some View {
NavigationStack {
List {
ForEach(teams) { contact in
NavigationLink(value: contact) {
TeamRowView(team: contact)
}
.swipeActions {
Button {
withAnimation {
// TODO: Delete contact
}
} label: {
Image(systemName: "trash.fill")
}
.tint(.red)
}
}
}
.navigationDestination(for: Team.self, destination: { person in
EditTeamView(team: person)
})
.sheet(isPresented: $showCreateContactView, content: {
CreateTeamView()
})
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button {
showCreateContactView.toggle()
} label: {
Image(systemName: "plus")
}
}
}
.navigationBarTitle("Contacts")
}
}
}
Query ile fetchlenen veriyi silme işlemi için ContentView e modelContext ekleyip kaydedilen veriyi silme güncelleme işlemi yapabiliriz.
import SwiftUI
import SwiftData
struct ContentView: View {
@State private var showCreateContactView = false
@Query private var teams : [Team]
@Environment(\.modelContext) private var modelContext
var body: some View {
NavigationStack {
List {
ForEach(teams) { team in
NavigationLink(value: team) {
TeamRowView(team: team)
}
.swipeActions {
Button {
withAnimation {
// Delete Team
modelContext.delete(team)
}
} label: {
Image(systemName: "trash.fill")
}
.tint(.red)
}
}
}
.navigationDestination(for: Team.self, destination: { person in
EditTeamView(team: person)
})
.sheet(isPresented: $showCreateContactView, content: {
CreateTeamView()
})
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button {
showCreateContactView.toggle()
} label: {
Image(systemName: "plus")
}
}
}
.navigationBarTitle("Contacts")
}
}
}
Bugün Beta sürümü yayımlanan SwiftData frameworkunu anlatmaya çalıştım umarım faydalı olmustur. Herkese sağlıklı keyifli günler dilerim.