Implementing External Links and Local Data Persistence in SwiftUI using FileManager and CoreData
This chapter demonstrates how to open external web links in the iOS browser using SwiftUI's Link view, and provides a comprehensive guide to local data persistence by employing FileManager with property list files and the CoreData framework for storing, loading, editing, and deleting model data within a SwiftUI app.
In the previous chapters the basic functionality of the Linkworld app was built and the declarative SwiftUI syntax was introduced; this chapter continues by showing how to open external web links in the iOS browser and how to persist data locally.
To open an external link SwiftUI provides the Link view, which launches the system browser. A button is created and added to the navigation bar:
// 打开浏览器按钮
func openWebBtn() -> some View {
Image(systemName: \"network\")
.font(.system(size: 17))
.foregroundColor(.blue)
}The button is inserted with:
.navigationBarItems(leading: backBtn(), trailing: openWebBtn())The link itself is written as:
Link(destination: URL(string: \"https://\"+indexURL)!) {
Image(systemName: \"network\")
.font(.system(size: 17))
.foregroundColor(.blue)
}For local storage the FileManager API is used. The following helper returns the app’s documents directory:
// 获取设备上的文档目录路径
func documentsDirectory() -> URL {
FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
}A path to a plist file is then built:
// 获取plist数据文件的路径
func dataFilePath() -> URL {
documentsDirectory().appendingPathComponent(\"Linkworld.plist\")
}Saving data uses PropertyListEncoder :
//写入本地数据
func saveItems() {
let encoder = PropertyListEncoder()
do {
let data = try encoder.encode(models)
try data.write(to: dataFilePath(), options: .Data.WritingOptions.atomic)
} catch {
print(\"错误信息: \\(error.localizedDescription)\")
}
}Loading data uses PropertyListDecoder and checks for the file’s existence:
// 加载本地数据
func loadItems() {
let path = dataFilePath()
if let data = try? Data(contentsOf: path) {
let decoder = PropertyListDecoder()
do {
models = try decoder.decode([Model].self, from: data)
} catch {
print(\"错误提示: \\(error.localizedDescription)\")
}
}
}The initializer of the view model simply calls loadItems() to populate the in‑memory collection when the app starts.
For a more robust solution the chapter introduces CoreData, the most widely used persistence framework on iOS. A CoreData model file is created with an entity named Model containing the attributes id , platformIcon , title , platformName and indexURL . The code generation is set to Manual/None.
A singleton persistence controller is defined:
import CoreData
struct Persistence {
static let shared = Persistence()
let container: NSPersistentContainer
init(inMemory: Bool = false) {
container = NSPersistentContainer(name: \"CoreData\")
if inMemory {
container.persistentStoreDescriptions.first?.url = URL(fileURLWithPath: \"/dev/null\")
}
container.loadPersistentStores { description, error in
if let error = error {
fatalError(\"Error: \\(error.localizedDescription)\")
}
}
}
}The app entry point injects the managed object context into the environment:
import SwiftUI
import CoreData
@main
struct LinkworldApp: App {
let persistenceController = Persistence.shared
var body: some Scene {
WindowGroup {
ContentView() .environment(.managedObjectContext, persistenceController.container.viewContext)
}
}
}The Model class mirrors the CoreData entity:
import CoreData
import Foundation
import SwiftUI
public class Model: NSManagedObject, Identifiable {
@NSManaged public var id: UUID
@NSManaged public var platformIcon: String
@NSManaged public var title: String
@NSManaged public var platformName: String
@NSManaged public var indexURL: String
enum CodingKeys: String, CodingKey {
case platformIcon, title, platformName, indexURL
}
}Data is fetched in views with @FetchRequest :
@FetchRequest(entity: Model.entity(),
sortDescriptors: [NSSortDescriptor(keyPath: \Model.title, ascending: false)])
var models: FetchedResults<Model>Creating a new record in NewView uses the injected context:
@Environment(.managedObjectContext) var context
let newItem = Model(context: context)
newItem.id = UUID()
newItem.platformIcon = platformIcon
newItem.title = title
newItem.platformName = platformName
newItem.indexURL = indexURL
do {
try context.save()
} catch {
print(error)
}Editing follows a similar pattern, locating the record by id and updating its fields before saving:
@Environment(.managedObjectContext) var context
@FetchRequest(entity: Model.entity(),
sortDescriptors: [NSSortDescriptor(keyPath: \Model.id, ascending: false)])
var models: FetchedResults<Model>
if let editItem = models.first(where: { $0.id == model.id }) {
editItem.platformIcon = model.platformIcon
editItem.title = model.title
editItem.platformName = model.platformName
editItem.indexURL = model.indexURL
do {
try context.save()
self.presentationMode.wrappedValue.dismiss()
} catch {
let nsError = error as NSError
fatalError(\"Unresolved error \\(nsError), \\(nsError.userInfo)\")
}
}Deletion is performed by finding the matching Model instance, calling context.delete , and saving the context:
@FetchRequest(entity: Model.entity(),
sortDescriptors: [NSSortDescriptor(keyPath: \Model.id, ascending: false)])
var models: FetchedResults<Model>
if let deleteItem = models.first(where: { $0.id == model.id }) {
context.delete(deleteItem)
do {
try context.save()
} catch {
let nsError = error as NSError
fatalError(\"Unresolved error \\(nsError), \\(nsError.userInfo)\")
}
}A preview configuration creates an in‑memory Persistence instance and inserts sample data so that SwiftUI previews can display realistic content.
The chapter concludes by recommending CoreData for persistence because of its scalability and iCloud integration potential, and notes that the current implementation places CRUD logic directly in views, encouraging future refactoring into a proper MVVM architecture.
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.