Ana içeriğe geç
  1. 100 Günde SwiftUI Notları/

36.Gün - SwiftUI: @Observable, onDelete(), UserDefaults, @AppStrorage, Codable

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;

  1. İki property @ObservationTracked olarak işaretlenmiştir. Bu da Swift ve SwiftUI’nin bunları değişiklikler için izlediği anlamına gelir.
  2. 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.
  3. 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.

SwiftUI Remove Item From List

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()
}

SwiftUI List Edit Button

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:

  1. UserDefaults.standart kullanmamız gerekiyor. Bu, uygulamamıza eklenmiş olan yerleşik UserDefaults 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, kendi UserDefaults instance’ını oluşturabilirsiniz.
  2. Tamsayılar, Booleanlar, Array’ler ve daha fazlası gibi her türlü veriyi kabul eden tek bir set() methodu vardır.
  3. 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;

  1. 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çin body property’yi yeniden çağırır.
  2. Verileri depolamak istediğimiz UserDefaults anahtarı olan bir string ekliyoruz. Burada “tapCount” kullandık, ancak herhangi bir şey olabilir.
  3. 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.

Bu yazı, SwiftUI Day 36 adresinde bulunan yazılardan kendim için aldığım notları içermektedir. Orjinal dersi takip etmek için lütfen bağlantıya tıklayın.