30 Days of SwiftUI - Day 19: Mastering State, Persistence, and Deletion in SwiftUI
Hello Swift developers! Today, we're building a mini expense tracking app, iExpense, and diving deep into essential SwiftUI concepts like state management, data persistence, and list manipulation. Let's get started!
State Management Demystified: Using @State
with Classes
While @State
is typically used with structs, it can also be used with classes. However, it's essential to understand the implications of reference types.
Example: @State
with a Class (Conceptual)
import SwiftUI
class MyData: ObservableObject {
@Published var count = 0
}
struct ClassStateView: View {
@StateObject private var data = MyData() // Use @StateObject for classes
var body: some View {
Text("Count: \(data.count)")
Button("Increment") {
data.count += 1
}
}
}
@StateObject
: Is used for classes that conform toObservableObject
, ensure the class is created only once.@Published
: Notifies views of changes.
State Sharing Simplified: Sharing SwiftUI State with @Observable
@Observable
allows sharing state between views.
Example: Sharing State with @Observable
import SwiftUI
@Observable
class Expenses {
var items = [ExpenseItem]()
}
struct ExpenseItem: Identifiable, Codable {
var id = UUID()
let name: String
let type: String
let amount: Double
}
struct ExpensesView: View {
@State private var expenses = Expenses()
var body: some View {
List {
ForEach(expenses.items) { item in
Text("\(item.name) - \(item.amount)")
}
}
}
}
@Observable
: Makes the class observable.ExpenseItem
: Conforms toIdentifiable
andCodable
.
Visibility Control: Showing and Hiding Views
We'll use state to control the visibility of views, such as an "add expense" form.
Example: Showing and Hiding a View
import SwiftUI
struct ToggleView: View {
@State private var showDetails = false
var body: some View {
VStack {
Button("Toggle Details") {
showDetails.toggle()
}
if showDetails {
Text("Detailed Information")
}
}
}
}
Visual Representation:
- A button toggles the visibility of "Detailed Information" text.
Data Removal: Deleting Items Using onDelete()
We'll allow users to delete expenses from the list.
Example: Deleting Items from a List
import SwiftUI
struct DeleteListView: View {
@State private var items = ["Item 1", "Item 2", "Item 3"]
var body: some View {
List {
ForEach(items, id: \.self) { item in
Text(item)
}
.onDelete(perform: deleteItems)
}
}
func deleteItems(at offsets: IndexSet) {
items.remove(atOffsets: offsets)
}
}
Visual Representation:
- A list with the ability to swipe and delete items.
User Preferences: Storing User Settings with UserDefaults
We'll use UserDefaults
to store user preferences, such as default settings.
Example: Storing User Settings
import SwiftUI
struct UserDefaultsView: View {
@State private var username = ""
var body: some View {
TextField("Username", text: $username)
.onDisappear {
UserDefaults.standard.set(username, forKey: "username")
}
.onAppear {
username = UserDefaults.standard.string(forKey: "username") ?? ""
}
.padding()
}
}
UserDefaults.standard.set(value, forKey:)
: Stores data.UserDefaults.standard.string(forKey:)
: Retrieves data.
Data Archival: Archiving Swift Objects with Codable
We'll use Codable
to archive and unarchive expense items.
Example: Codable Struct
struct ExpenseItem: Identifiable, Codable {
var id = UUID()
let name: String
let type: String
let amount: Double
}
Codable
: Enables encoding and decoding of objects.
Interactive Lists: Building a List We Can Delete From
Combining ForEach
and onDelete()
, we'll create an interactive list.
Example: Deletable List with Codable Items
import SwiftUI
struct CodableListView: View {
@State private var expenses = [ExpenseItem]()
var body: some View {
List {
ForEach(expenses) { item in
Text("\(item.name) - \(item.amount)")
}
.onDelete(perform: deleteItems)
}
}
func deleteItems(at offsets: IndexSet) {
expenses.remove(atOffsets: offsets)
}
}
Unique Identifiers: Working with Identifiable
Items in SwiftUI
Identifiable
ensures each item in a list has a unique ID.
Example: Identifiable Struct
struct IdentifiableItem: Identifiable {
let id = UUID()
let name: String
}
State Propagation: Sharing an Observed Object with a New View
We'll share the Expenses
object with a new view for adding expenses.
Example: Sharing Observed Object
import SwiftUI
struct AddExpenseView: View {
@Binding var expenses: Expenses
var body: some View {
Button("Add Expense") {
let newItem = ExpenseItem(name: "New Item", type: "Personal", amount: 10.0)
expenses.items.append(newItem)
}
}
}
struct ExpensesView2: View {
@State private var expenses = Expenses()
@State private var showAddExpense = false
var body: some View {
VStack {
List {
ForEach(expenses.items) { item in
Text("\(item.name) - \(item.amount)")
}
.onDelete(perform: deleteItems)
}
Button("Add Item") {
showAddExpense = true
}
}
.sheet(isPresented: $showAddExpense) {
AddExpenseView(expenses: $expenses)
}
}
func deleteItems(at offsets: IndexSet) {
expenses.items.remove(atOffsets: offsets)
}
}
Persistent Data: Making Changes Permanent with UserDefaults
We'll use UserDefaults
to save and load expense data.
Example: Saving and Loading Codable Data
import SwiftUI
struct PersistentListView: View {
@State private var expenses = [ExpenseItem]()
var body: some View {
// ... list and delete code ...
.onAppear(perform: loadData)
.onDisappear(perform: saveData)
}
func loadData() {
if let data = UserDefaults.standard.data(forKey: "Expenses") {
if let decoded = try? JSONDecoder().decode([ExpenseItem].self, from: data) {
expenses = decoded
}
}
}
func saveData() {
if let encoded = try? JSONEncoder().encode(expenses) {
UserDefaults.standard.set(encoded, forKey: "Expenses")
}
}
}
🔥 Conclusion
Day 19 has been a deep dive into building a functional iExpense app. We've covered state management, data persistence, and list manipulation. Keep building, and you'll be creating more robust and user-friendly apps!
Follow me on Linkedin: igatitech 🚀🚀🚀
Comments
Post a Comment