In this comprehensive guide, we’ll walk through creating a simple yet functional bookmarking app using SwiftUI for the user interface and Core Data for persistent storage. Bookmarking apps allow users to save links, articles, and other useful information for later access, making them a handy tool for managing online content.
Why SwiftUI and Core Data?
- SwiftUI: Provides a declarative and modern approach to building UI, making development faster and more intuitive.
- Core Data: Offers a robust framework for managing the data model, ensuring data persistence and efficient storage.
Project Setup
Start by creating a new Xcode project:
- Open Xcode and select “Create a new Xcode project.”
- Choose the “App” template under the iOS tab.
- Enter a product name (e.g., “Bookmarker”), select SwiftUI for the interface, and check the “Use Core Data” box.
- Save the project in your desired location.
Defining the Core Data Entity
First, define the data model in Core Data. In Xcode, open the .xcdatamodeld
file. Click “Add Entity” and name it Bookmark
.
- Add attributes to the
Bookmark
entity:title
: String (Represents the title of the bookmark)url
: String (Represents the URL of the bookmark)timestamp
: Date (Represents when the bookmark was created)
- Ensure that
url
andtitle
are set to be non-optional and are indexed.timestamp
will also be non-optional.
After defining the entity, Xcode will generate a managed object subclass automatically (if you select ‘Codegen’ to ‘Class Definition’). This will be available to your code automatically.
Creating the SwiftUI Views
1. BookmarkListView
The main view that displays the list of bookmarks.
import SwiftUI
import CoreData
struct BookmarkListView: View {
@Environment(\\.managedObjectContext) private var viewContext
@FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \\Bookmark.timestamp, ascending: false)],
animation: .default)
private var bookmarks: FetchedResults
@State private var isAddingBookmark = false
var body: some View {
NavigationView {
List {
ForEach(bookmarks) { bookmark in
NavigationLink(destination: BookmarkDetailView(bookmark: bookmark)) {
VStack(alignment: .leading) {
Text(bookmark.title ?? "Untitled")
.font(.headline)
Text(bookmark.url ?? "No URL")
.font(.subheadline)
.foregroundColor(.gray)
}
}
}
.onDelete(perform: deleteBookmarks)
}
.navigationTitle("Bookmarks")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
EditButton()
}
ToolbarItem {
Button(action: { isAddingBookmark = true }) {
Label("Add Bookmark", systemImage: "plus")
}
}
}
.sheet(isPresented: $isAddingBookmark) {
AddBookmarkView()
}
}
}
private func deleteBookmarks(offsets: IndexSet) {
withAnimation {
offsets.map { bookmarks[$0] }.forEach(viewContext.delete)
do {
try viewContext.save()
} catch {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
}
}
2. AddBookmarkView
A sheet view to add a new bookmark.
import SwiftUI
import CoreData
struct AddBookmarkView: View {
@Environment(\\.managedObjectContext) private var viewContext
@Environment(\\.dismiss) var dismiss
@State private var title: String = ""
@State private var url: String = ""
var body: some View {
NavigationView {
Form {
Section(header: Text("Bookmark Details")) {
TextField("Title", text: $title)
TextField("URL", text: $url)
.keyboardType(.URL)
}
}
.navigationTitle("Add Bookmark")
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Cancel") {
dismiss()
}
}
ToolbarItem(placement: .confirmationAction) {
Button("Save") {
addBookmark()
}
.disabled(title.isEmpty || url.isEmpty)
}
}
}
}
private func addBookmark() {
withAnimation {
let newBookmark = Bookmark(context: viewContext)
newBookmark.timestamp = Date()
newBookmark.title = title
newBookmark.url = url
do {
try viewContext.save()
dismiss()
} catch {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
}
}
3. BookmarkDetailView
A detailed view for displaying bookmark information. Users can tap on URL to launch it in the default browser of iOS. This can be accomplished with a custom function.
import SwiftUI
struct BookmarkDetailView: View {
let bookmark: Bookmark
var body: some View {
VStack(alignment: .leading) {
Text(bookmark.title ?? "Untitled")
.font(.largeTitle)
.padding()
Text("URL: \(bookmark.url ?? "No URL")")
.padding()
.onTapGesture {
openURL(urlString: bookmark.url)
}
Spacer()
}
.navigationTitle("Bookmark Detail")
}
// Function to Open URL
private func openURL(urlString: String?) {
guard let urlString = urlString, let url = URL(string: urlString) else {
return
}
if UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url)
}
}
}
Integrate Core Data
The ContentView
or main App file needs to inject the environment for CoreData.
import SwiftUI
@main
struct BookmarkerApp: App {
let persistenceController = PersistenceController.shared
var body: some Scene {
WindowGroup {
BookmarkListView()
.environment(\\.managedObjectContext, persistenceController.container.viewContext)
}
}
}
Running the App
Run the app in the Xcode simulator or on a physical device.
- The BookmarkListView should display any saved bookmarks or a list which is initially empty.
- Tap the “+” button to open the AddBookmarkView and add a new bookmark.
- Newly added bookmarks should appear in the BookmarkListView.
- You can also tap into a list and tap the link to launch that link in the browser
Additional Features
- Searching: Implement a search feature using
@State
and filtering logic within theBookmarkListView
. - Categorizing Bookmarks: Add categories to your data model to allow organization.
- iCloud Syncing: Integrate CloudKit to sync bookmarks across devices.
- User Authentication: Include user authentication for personal bookmark management.
Conclusion
This guide covered building a bookmarking app using SwiftUI for the UI and Core Data for data persistence. Leveraging the capabilities of both technologies ensures an efficient and scalable development process. Experimenting with additional features enhances the utility and functionality of the app, making it a valuable tool for users to manage their online bookmarks.