- Görkem Güray/
- 100 Günde SwiftUI Notları/
- 36.Gün - SwiftUI: @Observable, onDelete(), UserDefaults, @AppStrorage, Codable/
36.Gün - SwiftUI: @Observable, onDelete(), UserDefaults, @AppStrorage, Codable
Table of Contents
Bu bölümde UserDefaults
, @Observable
, sheet()
ve onDelete()
gibi konular üzerinde çalışacağız. Ayrıca bu bölümde oluşturacağımız uygulamamızın adı iExpense olacak. Bu uygulama ile birden fazla ekran sahip, kullanıcı verilerini yükleyen ve kaydeden daha karmaşık uygulamalara giriş yapacağız. Bu uygulama ile birlikte;
- İkinci bir ekranı sunup kapatacağız.
- Listeden satır sileceğiz.
- Kullanıcının verilerini kaydedip yükleyeceğiz.
Sınıflarla @State Kullanımı #
SwiftUI’nin @State
property wrapper’ı, geçerli view için yerel basit veriler için tasarlanmıştır, ancak verileri paylaşmak istediğimizde bazı önemli ekstra adımlar atmamız gerekir.
Bunu kodla açıklayalım, kullanıcının adını ve soyadını saklamak için bir struct oluşturalım;
struct User {
var firstName = "Bilbo"
var lastName = "Baggins"
}
Artık bunu bir SwiftUI view’da @State
property oluşturarak ve $user.firstName
ve $user.lastName
gibi şeylerle kullanabiliriz.
struct ContentView: View {
@State private var user = User()
var body: some View {
VStack {
Text("Your name is \(user.firstName) \(user.lastName).")
TextField("First name", text: $user.firstName)
TextField("Last name", text: $user.lastName)
}
}
}
Hepsi çalışıyor: SwiftUI, bir nesnenin tüm verilerimizi içerdiğini anlayacak kadar akıllıdır ve herhangi bir değer değiştiğinde kullanıcı arayüzünü güncelleyecektir. Sahne arkasında olan şey, struct’ın içindeki bir değer her değiştiğinde tüm struct’ın değişmesidir. Ad ve soyad için bir harf yazdığımızda her seferinde struct’ın değişmesi anlamına gelir. Bu kulağa savurganlık gibi gelebilir, ancak aslında son derece hızlıdır.
Daha önce sınıflar ve struct’lar arasındaki farklara bakmıştık ve iki önemli fark vardı. Birincisi, struct’ların her zaman benzersiz sahipleri vardır, oysa sınıflarda birden fazla şey aynı değere işaret edebilir. İkincisi, sınıfların property’lerini değiştiren methodların mutating
keyword’üne ihtiyaç duymamasıdır, çünkü constant sınıfların property’lerini değiştirebiliriz.
Pratikte bunun anlamı, iki SwiftUI view varsa ve her ikisine de çalışmak için aynı struct’ı gönderirsek, aslında her biri bu struct’ın benzersiz bir kopyasına sahip olur; biri değiştirirse diğeri bu değişikliği göremez. Öte yandan, bir sınıfın instance’ını oluşturur ve bunu her iki view’a da gönderirsek, değişiklikleri paylaşacaklardır.
SwiftUI geliştiricileri için bunun anlamı, birden fazla view arasında veri paylaşmak istiyorsak (yani iki veya daha fazla view’ın aynı veriye işaret etmesini ve böylece biri değiştiğinde hepsinin bu değişiklikleri almasını istiyorsak) struct yerine sınıf kullanmamız gerektiğidir.
Bu sebeple User
bir sınıf olarak değiştirelim. Bundan;
struct User {
Buna;
class User {
Uygulamayı tekrar çalıştırdığımızda artık beklediğimiz gibi çalışmadığını göreceğiz. Eskisi gibi TexField’lara yazabiliyoruz fakat yukarıdaki Text view değişmiyor.
@State
kullandığımızda, SwiftUI’den bir property’yi değişiklikler için izlemesini isteriz. Yani, bir string’i değiştirirsek, bir Boolean’ı çevirirsek, bir array’e ekleme yaparsak vb. property değişmiştir ve SwiftUI view’ın body property’sini yeniden çağıracaktır.
User
bir struct olduğunda, bu struct’ın bir property’sini her değiştirdiğimizde Swift aslında struct’ın yeni bir örneğini oluşturuyordu. @State
bu değişikliği fark edebiliyor ve view’ı otomatik olarak yeniden yüklüyordu. Artık bir sınıfımız olduğuna göre, bu davranış artık gerçekleşmiyor. Çünkü Swift değeri doğrudan değiştirebilir.
Property’leri değiştiren struct methodları için mutating
keyword’ünü nasıl kullanmak zorunda kaldığımızı hatırlıyor musunuz? Bunun nedeni, struct property’lerini değişken olarak yaratırsak ancak yapının kendisi sabitse, property’leri değiştiremeyiz. Swift’in bir property değiştiğinde tüm struct’ı yok edip yeniden yaratabilmesi gerekir ve bu constant struct’lar için mümkün değildir. Sınıfların mutating
keyword’üne ihtiyacı yoktur, çünkü sınıf instance’ı constant olarak işaretlenmiş olsa bile Swift değişken özelliklerini değiştirebilir.
İşin püf noktası şu; User artık bir sınıf olduğu için property’nin kendisi değişmiyor, bu nedenle @State
hiçbir şey fark etmiyor ve view’ı yeniden yükleyemiyor. Evet, sınıfın içindeki değerler değişiyor, ancak @State bunları izlemiyor, bu nedenle olan şey sınıfımızın içindeki değerlerin değiştirilmesi ancak view’ın bu değişikliği yansıtacak şekilde yeniden yüklenmemesidir.
Bu sorunu küçük bir değişiklik ile çözebiliriz. Sınıftan önce @Observable
satırını ekleyelim;
@Observable
class User {
Ve artık kodumuz tekrar çalışacak.
SwiftUI State’i @Observable ile Paylaşma #
Bir struct ile @State
kullanırsak, SwiftUI view değer değiştiğinde otomatik olarak güncellenir. Ancak bir sınıf ile @State
kullanırsak ve SwiftUI’nin değişiklikleri izlemesini istiyorsak bu sınıfı @Observable
ile işaretlememiz gerekir.
Neler olup bittiğini anlamak için bu koda daha ayrıntılı bir şekilde göz atalım;
@Observable
class User {
var firstName = "Bilbo"
var lastName = "Baggins"
}
Bu, iki string değişkeni olan bir sınıftır, ancak @Observable
ile başlar. Bu SwiftUI’ye sınıf içindeki her bir property’yi değişikliklere karşı izlemesini ve bir property değiştiğinde ona bağlı olan tüm view’ları yeniden yüklemesini söyler.
Biraz daha derine inelim ve yeni bir şey import
edelim.
import Observation
Bu @Observable
satırı bir makrodur. Observation
’ı içeri aktardıktan sonra, @Observable
’a sağ tıklayarak expand macro ‘ya tıklayarak makro kodunu genişleterek inceleyebiliriz. Burada değinilecek 3 nokta var;
- İki property
@ObservationTracked
olarak işaretlenmiştir. Bu da Swift ve SwiftUI’nin bunları değişiklikler için izlediği anlamına gelir. - Eğer
@ObservationTracked
üzerine sağ tıklarsanız bu makroyu da genişletebilirsiniz. Bu makro herhangi bir property okunduğunda veya yazıldığında izleme işine sahiptir, böylece SwiftUI yalnızca, kesinlikle yenilenmesi gereken view’ları güncelleyebilir. - Sınıfımız
Observable
protokolüne uygun hale getirilmiştir. Bu önemlidir, çünkü SwiftUI’nin bazı bölümleri bunun “bu sınıf değişiklikler için izlenebilir” anlamına gelmesini bekler.
Bunların üçü de önemlidir, ancak ağır işi yapan ortadakidir. iOS, @Observed
nesnesinden property’leri okuyan her SwiftUI view’ını takip eder böylece bir property değiştiğinde diğerlerini değiştirmeden bırakırken ona bağlı olan tüm view’ları akıllıca güncelleyebilir.
Struct ile çalışırken, @State
property wrapper bir değeri canlı tutar ve ayrıca değişiklikleri izler. Öte yandan, sınıflarla çalışırken, @State
sadece nesneyi canlı tutmak için vardır, yani tüm değişiklik izleme ve görünümü güncelleme @Observable
tarafından halledilir.
View’ları Gösterme ve Gizleme #
SwiftUI’de view’ları göstermenin birkaç yolu vardır ve en temel olanlardan biri sheet ‘tir. Sheet mevcut view’ın üstüne sunulan yeni bir view’dır. iOS’ta bu otomatik olarak bize mevcut view’ın biraz uzağa kaydığı ve yeni view’ın üstte hareket ettiği kart benzeri bir sunum verir.
Sheet, mySheet.present()
veya benzeri bir kodla doğrudan sunulmadıkları için uyarılar gibi çalışır. Bir sheet’in hangi koşullar altında gösterilmesi gerektiğini tanımlarız ve bu koşullar doğru veya yanlış olduğunda sheet sunulur veya kapatılır.
Bir sheet kullanarak bir view’ı diğerinden gösteren basit bir örnekle başlayalım. İlk olarak, bir sheet’in içinde göstermek istediğimiz view’ı aşağıdaki gibi oluşturuyoruz;
struct SecondView: View {
var body: some View {
Text("Second View")
}
}
Bu view’ın hiçbir özel yanı yoktur. Bir sheet üzerinde gösterileceğini bilmez, bilmesi de gerekmez.
Ardından, ikinci view’ı gösterecek olan ilk view’ımızı oluşturuyoruz;
struct ContentView: View {
var body: some View {
Button("Show Sheet") {
// show the sheet
}
}
}
Bu işlemi gerçekleştirmek için dört adım gerekiyor ve bunları ayrı ayrı ele alacağız
İlk olarak sayfanın gösterilip gösterilmediğini takip etmek için bazı state’lere ihtiyacımız var. Tıpkı uyarılarda olduğu gibi, bu da basit bir Boolean olabilir, bu sebeple aşağıdaki property’yi ContentView
’e ekleyelim;
@State private var showingSheet = false
İkinci olarak, butonumuza dokunulduğunda bunu değiştirmemiz gerekiyor, bu nedenle // show the sheet
yorumunu değişitirelim;
showingSheet.toggle()
Üçüncü olarak, sheet’i view hiyerarşimizde bir yere eklememiz gerekir. Hatırlarsanız, state property’e two-way binding ile isPresented
kullanarak uyarıları göstermiştik ve burada da neredeyse aynı şeyi kullanıyoruz : sheet(ispresented:)
sheet()
tıpkı alert()
gibi bir modifier’dır, bu nedenle bu modifier’ı butona ekleyelim;
.sheet(isPresented: $showingSheet) {
// contents of the sheet
}
Dördüncü olarak, sayfada gerçekte ne olması gerektiğine karar vermemiz gerekir. Bizim durumumuzda, tam olarak ne istediğimizi zaten biliyoruz: SecondView
’in bir instance’ını oluşturmak ve göstermek istiyoruz.
Dolayısıyla, tamalanmış ContentView
struct şu şekilde görünmelidir;
struct ContentView: View {
@State private var showingSheet = false
var body: some View {
Button("Show Sheet") {
showingSheet.toggle()
}
.sheet(isPresented: $showingSheet) {
SecondView()
}
}
}
Uygulamayı çalıştırırsanız, ikinci view’ın alttan yukarıya doğru kaymasını sağlamak için butona dokunabileceğinizi ve ardından kapatmak için aşağı sürükleyebileceğinizi göreceksiniz.
Bunun gibi bir view oluşturduğunuzda, çalışması için gereken parametreleri iletebilirsiniz. Örneğin, SecondView
’e görüntüleyebileceği bir ad gönderilmesini isteyebiliriz, bunun gibi;
struct SecondView: View {
let name: String
var body: some View {
Text("Hello, \(name)!")
}
}
Ve şimdi sheet’de sadece SeconView()
kullanmak yeterli değil, gösterilecek bir ad string de eklememiz gerekiyor. Örneğin Twitter kullanıcı adımı şu şekilde girebiliriz.
.sheet(isPresented: $showingSheet) {
SecondView(name: "@grkmgry")
}
Şimdi sheet “Hello, @grkmgry” gösterecektir.
Swift burada bizim adımıza bir sürü iş yapıyor. SecondView
’in bir name property’si olduğunu söyler söylemez, Swift, SecondView()
’in tüm instance’ları SecondView(name: “some name”)
olana kadar kodumuzun oluşturulmamasını sağladı, bu da bir dizi olası hatayı ortadan kaldırıyor.
Bir view’ın kendini nasıl kapatacağını inceleyelim.
Farklı bir view’ı kapatmak için başka bir property wrapper’a ihtiyacımız var. Bu da @Environment
olarak adlandırılıyor ve bize dışarıdan sağlanan değerleri depolayan property’ler oluşturmamıza olanak tanıyor. Kullanıcı aydınlık modda mı yoksa karanlık modda mı? Daha küçük veya daha büyük yazı tipleri istediler mi? Hangi saat dilimindeler? Tüm bunlar ve daha fazlası ortamdan gelen değerlerdir ve bu örnekte ortamdan view’ı kapatmasını isteyeceğiz.
Denemek için bu property’yi SecondView
’a ekleyelim, bu property ortamdaki (environment) bir değere dayalı olarak dismiss
adlı bir property oluşturur.
@Environment(\.dismiss) var dismiss
SecondView
’deki text view’ı bu buton ile değiştirelim;
Button("Dismiss") {
dismiss()
}
Artık ikinci sayfadaki buton ile sayfayı kapatabiliriz.
onDelete() Kullanarak Öğeleri Silme #
SwiftUI, nesnelerin bir collection’dan nasıl silinceğini kontrol etmek için bize onDelete()
modifier’ını verir. Pratikte, bu neredeyse sadece List
ve ForEach
ile kullanılır: ForEach
kullanarak gösterilen satırların bir listesini oluştururuz, ardından onDelete()
işlevini bu ForEach
’e ekleriz, böylece kullanıcı istemediği satırları kaldırabilir.
Bu, SwiftUI’nin bizim adımıza çok fazla iş yaptığı bir başka yerdir, ancak göreceğiniz gibi birkaç ilginç tuhaflığı vardır.
İlk olarak, üzerinde çalışabileceğimiz bir instance oluşturalım: sayıları gösteren bir liste ve düğmeye her dokunduğumuzda yeni bir sayı beliriyor.
struct ContentView: View {
@State private var numbers = [Int]()
@State private var currentNumber = 1
var body: some View {
VStack {
List {
ForEach(numbers, id: \.self) {
Text("Row \($0)")
}
}
Button("Add Number") {
numbers.append(currentNumber)
currentNumber += 1
}
}
}
}
Şimdi, ForEach
’e gerek olmadığını düşünebilirsiniz liste tamamen dinamik satırlardan oluşuyor, bu yüzden bunun yerine aşağıdakini yazabiliriz,
List(numbers, id: \.self) {
Text("Row \($0)")
}
Bu da işe yarar, ancak işte ilk tuhaflığımız: onDelete()
modifier yalnızca ForEach
üzerinde mevcuttur, bu nedenle kullanıcıların bir listeden öğeleri silmesini istiyorsak, öğeleri ForEach içine koymalıyız. Bu yalnızca dinamik satırlara sahip olduğumzu zamanlar için az miktarda ekstra kod anlamına gelir, ancak diğer taraftan yalnızca bazı satırların silinebileceği listeler oluşturmanın daha kolay olduğu anlamına gelir.
onDelete()
‘in çalışmasını sağlamak için IndexSet
türünde tek bir parametre alacak bir method uygulamamız gerekir. Bu, sıralanmış olması dışında bir tamsayılar kümesine benzer ve bize ForEach
içindeki kaldırılması gereken tüm öğelerin konumlarını söyler.
Bunu aşağıdaki gibi onDelete()
methoduna bir closure yazarak halledebiliriz.
ForEach(numbers, id: \.self) {
Text("Row \($0)")
}
.onDelete{ indexSet in
numbers.remove(atOffsets: indexSet)
}
İşte sonuç artık listeden element silebiliyoruz.
Navigation Bar’a kullanıcıların birkaç satırı daha kolay silmelerini sağlayan bir Edit/Done butonu da ekleyebiliriz.
VStack
’i bir NavigationStack
ile sararak ardından aşağıdaki modifier’ı VStack
’e ekleyelim;
.toolbar {
EditButton()
}
Kullanıcı Ayarlarının UserDefaults ile Saklanması #
Çoğu kullanıcı, uygulamaların daha özelleştirilmiş deneyimler yaratabilmeleri için verilerini depolamalarını bekler ve bu nedenle iOS’un kullanıcı verilerini okumak ve yazmak için bize çeşitli yollar sunması şaşırtıcı değildir.
Az miktarda veri depolamanın yaygın bir yolu UserDefaults
olarak adlandırılır ve basit kullanıcı tercihleri için harikadır. “Az bir miktar” için belirli bir sayı yoktur, ancak UserDefaults
’da sakladığınız her şey uygulamanız başlatıldığında otomatik olarak yüklenecektir. Dolayısıyla burada çok fazla veri saklarsak uygulamamızın başlatılması yavaşlayacaktır. En azından bir fikir vermesi için, UserDefaults
’da 512KB’den fazlasını saklamamalıyız.
UserDefaults
, kullanıcının uygulamayı en son ne zaman başlattığı, en son hangi haberi okuduğu veya pasif olarak toplanan diğer bilgiler gibi şeyleri saklamak için mükemmeldir. Daha da iyisi, SwiftUI genellikle UserDefaults
’u @AppStorage
adı verilen güzel ve basit bir property wrapper içine sarabilir.
Burada, dokunma sayısını gösteren ve butona her dokunulduğunda bu sayıyı arttıran bir view vardır.
struct ContentView: View {
@State private var tapCount = 0
var body: some View {
Button("Tap count: \(tapCount)") {
tapCount += 1
}
}
}
Kullanıcının yaptığı dokunma sayısını kaydetmek istiyoruz, böylece gelecekte uygulamaya geri döndüklerinde kaldıkları yerden devam edebilirler.
Bunu gerçekleştirmek için, butonumuzun closure’u içinde UserDefaults
’ı yazmamız gerekir. Yani, tapCount += 1
satırından sonra bunu ekleyelim;
UserDefaults.standard.set(tapCount, forKey: "Tap")
Sadece bu tek kod satırda üç şeyin işlediğini görebilirsiniz:
UserDefaults.standart
kullanmamız gerekiyor. Bu, uygulamamıza eklenmiş olan yerleşikUserDefaults
instance’tır, ancak daha gelişmiş uygulamalarda kendi instance’ımızı oluşturabiliriz. Örneğin User Defaults’ları bir kaç uygulama uzantısında paylaşmak istiyorsanız, kendiUserDefaults
instance’ını oluşturabilirsiniz.- Tamsayılar, Booleanlar, Array’ler ve daha fazlası gibi her türlü veriyi kabul eden tek bir
set()
methodu vardır. - Bu veriye bir string adı ekleriz, bizim durumumuzda bu “Tap” anahtarıdır (key). Bu anahtar, tıpkı normal Swift stringleri gibi büyük/küçük harf duyarlıdır ve önemlidir. Çünkü verileri
UserDefaults
’tan geri okumak için aynı anahtarı kullanmamız gerekir.
tapCount
0 olarak başlatmak yerine, değeri UserDefaults
’dan şu şekilde geri okumasını sağlamalıyız;
@State private var tapCount = UserDefaults.standard.integer(forKey: "Tap")
Uygulamayı çalıştırıp deneyelim, butona dokundukça sayı artacak, uygulamayı yeniden çalıştırdığımızda değer sıfırlanmayacak kaldığı yerden devam edecek.
Bu kodda göremediğimiz iki önemli şey var. Birincisi “Tap” anahtarı ayarlanmamışsa ne olur? Uygulama ilk kez çalıştırıldığında durum böyle olcaktır, ancak az önce gördüğünüz gibi sorunsuz çalışır, anahtar bulunamazsa sadece 0 geri gönderir.
Bazen 0 gibi varsayılan bir değere sahip olmak yardımcı olur, ancak diğer zamanlarda kafa karıştırıcı olabilir. Örneğin boolean(forkey:)
istediğimiz anahtarı bulamazsa false değerini alırız, ancak bu false değeri istediğimiz değer midir, yoksa hiçbir değer olmadığı anlamına mı gelir?
İkinci olarak, iOS’un verilerinizi kalıcı depolama alanına yazması biraz zaman alır. Arka arkaya birkaç değişiklik yapılması ihtimaline karşın, güncellemeleri hemen yazmazlar, bunun yerine bir süre bekledikten sonra tüm değişiklikleri bir kerede yazarlar. Ne kadar zaman olduğunu bilmiyoruz ama birkaç saniye yeterli olacaktır.
Bunun bir sonucu olarak, butona dokunup uygulamayı Xcode’dan hızlıca yeniden başlatırsanız, en son dokunma sayınızın hemen kaydedilmediğini göreceksiniz. Eskiden güncellemeleri hemen yazamaya zorlamanın bir yolu vardı, ancak bu noktada değersizdir, çünkü kullanıcı bir seçim yaptıktan sonra uygulamamızı sonladırma sürecini hemen başlatsa bile, varsayılan verilerimiz hemen yazılır, böylece hiçbir şey kaybolmaz.
Şimdi, SwiftUI’nin UserDefaults
etrafında bir @AppStorage
property wrapper bunun gibi basit durumlarda gerçekten yardımcı oluyor. Yaptığı şey, UserDefaults
’u tamamen görmezden gelmemize ve @State
yerine @AppStorage
kullanmamıza izin vermektir.
struct ContentView: View {
@AppStorage("tapCount") private var tapCount = 0
var body: some View {
Button("Tap count: \(tapCount)") {
tapCount += 1
}
}
}
Burada dikkat çekeceğimiz üç husus var;
UserDefaults
sistemine erişimimiz@AppStorage
property wrapper aracılığıyla gerçekleşir. Bu@State
gibi çalışır: değer değiştiğinde kullanıcı arayüzümüzün yeni verileri yansıtması içinbody
property’yi yeniden çağırır.- Verileri depolamak istediğimiz
UserDefaults
anahtarı olan bir string ekliyoruz. Burada “tapCount” kullandık, ancak herhangi bir şey olabilir. - Property’nin geri kalanı varsayılan 0 değerinin sağlanması da dahil olmak üzere normal bir şekilde bildirilir.
UserDefaults
içinde kaydedilmiş mevcut bir değer yoksa bu değer kullanılacaktır.
Açıkçası @AppStorage
kullanmak UserDefaults
kullanmaktan daha kolay. İki yerine bir satır kod ve ayrıca her seferinde anahtar adını tekrarlamak zorunda olmadığımız anlamına geliyor. Ancak şu anda en azından @AppStorage
, Swift struct gibi karmaşık nesnelerin depolanmasını kolaylaştırmıyor.
Önemli : App Store’a bir uygulama gönderdiğimizde Apple, neden UserDefaults
kullanarak veri yüklediğimizi ve kaydettiğimizi onlara bildirmemizi ister. Bu durum @AppStorage
property wrapper için de geçerlidir.
Swift Nesnelerini Codable ile Arşivleme #
@AppStorage
, tamsayılar ve Boolean gibi basit verileri saklamak için harikadır, ancak karmaşık veriler söz konusu olduğunda biraz daha fazla iş yapmamız gerekir. İşte bu noktada @AppStorage
property wrapper yerine doğrudan UserDefaults
’un kendisini kullanmamız gerekir.
Basit bir User
veri yapısı ile başlayalım.
struct User {
let firstName: String
let lastName: String
}
Bu veri yapısının iki string’i vardır. Bu gibi verilerle çalışırken, Swift bize Codable
adında harika bir protokol sunar. Özellikle verileri arşivlemek ve arşivden çıkarmak için bir protokol, bu da “nesneleri düz metne dönüştürmek ve tekrar geri almak” demenin süslü bir yoludur.
Gelecek projelerde Codable
’a daha fazla bakacağız, ancak şimdilik mümkün olduğunca basit tutacağız. Özel bir türü arşivlemek istiyoruz, böylece onu UserDefaults
’a koyabiliriz ve ihtiyacımız olduğunda geri alabiliriz.
Yalnızca basit property’lere sahip bir türle (string, integer, boolean, string array vb.) çalışırken arşivleme ve arşivden çıkarmayı desteklemek için yapmamız gereken tek şey Codable
ı aşağıdaki şekilde eklemektir.
struct User: Codable {
let firstName: String
let lastName: String
}
Swift, gerektiğinde User
instance’larını bizim için arşivleyecek ve arşivden çıkaracak bazı kodları otomatik olarak oluşturacaktır, ancak yine de Swift’e ne zaman arşivleyeceğini ve verilerle ne yapacağını söylememiz gerekir.
Sürecin bu kısmı JSONEncoder
adı verilen yeni bir tür tarafından desteklenmektedir. Görevi, Codable
’a uyan bir şeyi almak ve bu nesneyi JavaScript Object Notation (JSON) olarak geri göndermektir.
Codable
protokolü JSON kullanmamızı gerektirmez ve aslında başka formatlar da mevcuttur, ancak en yaygın olanı budur. Bu örnekte, aslında ne tür bir veri kullanıldığını umursamıyoruz, çünkü bunlar sadece UserDefaults
içinde saklanacak.
user
verilerimizi JSON verilerine dönüştürmek için, bir JSONEncoder üzerinde encode()
methodunu çağırmamız gerekir. Bu hata verebilir, bu nedenle hataları düzgün bir şekilde ele almak için try
veya try?
ile çağrılmalıdır. Örneğin, bir User
instance saklamak için bunun gibi bir property’imiz olsaydı;
@State private var user = User(firstName: "Taylor", lastName: "Swift")
Daha sonra kullanıcıyı arşivleyen bir buton oluşturabilir ve bunu UserDefaults
’a şu şekilde kaydedebiliriz;
Button("Save User") {
let encoder = JSONEncoder()
if let data = try? encoder.encode(user) {
UserDefaults.standard.set(data, forKey: "UserData")
}
}
Bu, @AppStorage
üzerinden gitmek yerine doğrudan UserDefaults
’a erişir, çünkü @AppStorage
property wrapper burada çalışmaz.
Bu data
sabiti Data
olarak adlandırılan yeni bir veri türüdür String, image, zip dosyaları gibi aklınıza gelebilecek her türlü veriyi saklamak için tasarlanmıştır. Ancak burada önemsediğimiz tek şey, doğrudan UserDefaults
içine yazabileceğimiz veri türlerinden olmasıdır.
Veriyi geri okumak istediğimizde yani JSON verimiz olduğunda ve bunu Swift Codable
türlerine çevirmek istediğimizde, JSONEncoder
yerine JSONDecoder
kullanmalıyız, ancak süreç hemen hemen aynıdır.
Bu yazıyı İngilizce olarak da okuyabilirsiniz.
You can also read this article in English.