// User.swift
import Foundation
import SwiftData
@Model
class User: Codable, Identifiable, Hashable {
var id: String
var isActive: Bool
var name: String
var age: Int
var company: String
var email: String
var address: String
var about: String
var registered: Date = Date()
var joinedDate: String {
registered.formatted(date: .numeric, time: .omitted)
}
var tags: [String]
var friends: [Friend]
enum CodingKeys: String, CodingKey {
case id = "id"
case isActive = "isActive"
case name = "name"
case age = "age"
case company = "company"
case email = "email"
case address = "address"
case about = "about"
case friends = "friends"
case tags = "tags"
case registered = "registered"
}
required init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decode(String.self, forKey: .id)
self.isActive = try container.decode(Bool.self, forKey: .isActive)
self.name = try container.decode(String.self, forKey: .name)
self.age = try container.decode(Int.self, forKey: .age)
self.company = try container.decode(String.self, forKey: .company)
self.email = try container.decode(String.self, forKey: .email)
self.address = try container.decode(String.self, forKey: .address)
self.about = try container.decode(String.self, forKey: .about)
self.registered = try container.decode(Date.self, forKey: .registered)
self.tags = try container.decode([String].self, forKey: .tags)
self.friends = try container.decode([Friend].self, forKey: .friends)
}
func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.id, forKey: .id)
try container.encode(self.isActive, forKey: .isActive)
try container.encode(self.name, forKey: .name)
try container.encode(self.age, forKey: .age)
try container.encode(self.company, forKey: .company)
try container.encode(self.email, forKey: .email)
try container.encode(self.address, forKey: .address)
try container.encode(self.about, forKey: .about)
try container.encode(self.registered, forKey: .registered)
try container.encode(self.tags, forKey: .tags)
try container.encode(self.friends, forKey: .friends)
}
}
// Friend.swift
import Foundation
import SwiftData
@Model
class Friend: Codable, Identifiable, Hashable {
var id: String
var name: String
var owner: User?
enum CodingKeys: String, CodingKey {
case id
case name
}
required init(from decoder:Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decode(String.self, forKey: .id)
self.name = try container.decode(String.self, forKey: .name)
}
func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.id, forKey: .id)
try container.encode(self.name, forKey: .name)
}
}
var owner: User? . If there is a User model, we want it to be an owner of Friend.
required keyword.
.modelContainer(for: User.self) to the FriendFaceApp file along the import SwiftData. When run on first launch of the app it creates the database using the models listed. On next launches it loads the database and information in it.
users property is empty, this ensures we get the data at the first opening of the app (if user has internet connection), after that they can be offline and still access all of the data.
@Query var users: [User] we are loading all the users from the SwiftData to this screen (ContentView).
@Environment(\.modelContext) var modelContext allows us to now manipulate the SwiftData, write to it.
// at the top of ContentView I added
@State private var readUsers: [User] = [User]()
@Query var users: [User]
@Environment(\.modelContext) var modelContext
// load function that now saves!
func loadData() async {
if users.isEmpty {
// get the URL
guard let url = URL(string: "https://www.hackingwithswift.com/samples/friendface.json") else {
print("Invalid URL")
return
}
// fetch the data
do {
let (data, _) = try await URLSession.shared.data(from: url)
// decode data if fetched correctly
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
if let decodedUsers = try? decoder.decode([User].self, from: data) {
readUsers = decodedUsers
// loading the users to memory
for user in readUsers {
modelContext.insert(user)
}
}
} catch {
print("Invalid data")
}
}
}
// in ContentView
.toolbar {
Menu("Filter", systemImage: "arrow.up.arrow.down") {
Text("Filter by activity:")
Picker("Filter", selection: $userActive) {
Text("All")
.tag("All")
Text("Active")
.tag("true")
Text("Inactive")
.tag("false")
}
Text("Order by:")
Picker("Sort order", selection: $sortOrder) {
Text("Join Date")
.tag([SortDescriptor(\User.registered)])
Text("Name")
.tag([SortDescriptor(\User.name)])
Text("Age")
.tag([SortDescriptor(\User.age)])
}
}
}
//in UsersView
init(userActive: String, sortOrder: [SortDescriptor]) {
_users = Query(filter: #Predicate {user in
if userActive == "All" {
return true
} else if userActive == "true" && user.isActive == true {
return true
} else if userActive == "false" && user.isActive == false {
return true
}
else {
return false
}
}, sort: sortOrder
)}
@Query var users: [User] earlier.
.tag to update the values based on users choice. It’s a very simple and elegant way of adding this functionality, I think it’s beautiful.
.onAppear{} As soon as the UserView loads, we can see their friends.
func checkFriends() {
for friend in user.friends {
for user in users {
if friend.id == user.id {
userFriends.append(user)
}
}
}
}
@State private var path = [User]().
.navigationDestination when moving to UserView.
.navigationDestination(for: User.self) { user in
UserView(user: user)
}
@Binding var path: [User] in UserView and pass it. I updated the code to the below one.
.navigationDestination(for: User.self) { user in
UserView(user: user, path: $path)
}
.toolbar {
Button("Home", systemImage: "house.fill") {
path.removeAll()
}
}