Featuring: The Fart Button Universe Expanded 🌌💨


Welcome back, brave developer! 🧙‍♂️✨ If you thought saving farts was magical, get ready — today, we're supercharging the Fart Button App with relationships, advanced fetch requests, and performance tips.

We're building not just a fart database — but a whole fart multiverse. 🌌💥


📚 What You'll Learn

  • How to model relationships in Core Data
  • How to write advanced FetchRequests
  • Performance tips to keep your app speedy 💨💨

🛠 New Plan: Fart Categories

Our new app idea:

  • Different types of farts belong to Categories.
  • A Category (e.g., "Epic", "Squeaky", "Stealth Mode") has many Farts.
  • Each Fart belongs to one Category.

In Core Data lingo: One-to-Many Relationship.


💾 Updating the Data Model

Open Model.xcdatamodeld:

  1. Add a new Entity ➡️ Name it Category.
  2. Add attributes to Category:
    • name: String
  3. Update Fart:
    • Add a Relationship:
      • Name: category
      • Destination: Category
      • Type: To One
  4. In Category, add a Relationship:
    • Name: farts
    • Destination: Fart
    • Type: To Many
  5. Set inverse relationships:
    • category ⬅️➡️ farts

Congrats! You just taught Core Data that farts are social beings. 🎓


🖥 Updated Fart Button UI

import SwiftUI
import CoreData

struct ContentView: View {
    @Environment(\.managedObjectContext) private var viewContext

    @FetchRequest(
        entity: Category.entity(),
        sortDescriptors: [NSSortDescriptor(keyPath: \Category.name, ascending: true)]
    ) private var categories: FetchedResults<Category>

    var body: some View {
        NavigationView {
            List {
                ForEach(categories) { category in
                    Section(header: Text(category.name ?? "Unknown Category")) {
                        ForEach(category.fartsArray) { fart in
                            Text(fart.soundName ?? "Mystery Fart")
                        }
                    }
                }
            }
            .navigationTitle("💨 Fart Archive 💨")
            .toolbar {
                ToolbarItem(placement: .navigationBarTrailing) {
                    Button(action: addSampleData) {
                        Label("Add Sample", systemImage: "plus")
                    }
                }
            }
        }
    }

    private func addSampleData() {
        let newCategory = Category(context: viewContext)
        newCategory.name = "Epic Farts"

        let fart1 = Fart(context: viewContext)
        fart1.soundName = "MegaBlast.mp3"
        fart1.timestamp = Date()
        fart1.category = newCategory

        let fart2 = Fart(context: viewContext)
        fart2.soundName = "AtomicFart.mp3"
        fart2.timestamp = Date()
        fart2.category = newCategory

        do {
            try viewContext.save()
        } catch {
            print("😱 Failed to save sample data: \(error.localizedDescription)")
        }
    }
}

extension Category {
    var fartsArray: [Fart] {
        let set = farts as? Set<Fart> ?? []
        return set.sorted { $0.timestamp ?? Date() < $1.timestamp ?? Date() }
    }
}

Boom. You now have categorized farts. Elite developer status unlocked. 🏆


🎯 Advanced Fetching: Only Epic Farts, Please

Want to get only farts from "Epic Farts" category? 🔥

func fetchEpicFarts() -> [Fart] {
    let request: NSFetchRequest<Fart> = Fart.fetchRequest()
    request.predicate = NSPredicate(format: "category.name == %@", "Epic Farts")
    request.sortDescriptors = [NSSortDescriptor(keyPath: \Fart.timestamp, ascending: true)]

    do {
        return try viewContext.fetch(request)
    } catch {
        print("😱 Error fetching epic farts: \(error.localizedDescription)")
        return []
    }
}

With this, your app filters the legendary explosions from the mundane ones. 🎺💥


⚡️ Core Data Performance Tips

  • Batch Size: Large datasets? Use fetchBatchSize to avoid loading too much into memory.
request.fetchBatchSize = 20
  • Faulting: Core Data lazy-loads objects — don’t worry if you see a "fault" — it's good for performance!

  • Background Contexts: For heavy operations (e.g., importing thousands of farts), use a background context so your UI doesn't freeze. 🧊

  • Indexes: Set Indexes on frequently queried fields like timestamp or soundName!


🧹 Cleaning Up: Deleting a Category

Want to delete a Category (and maybe all its farts)?

func deleteCategory(_ category: Category) {
    viewContext.delete(category)

    do {
        try viewContext.save()
    } catch {
        print("😱 Failed to delete category: \(error.localizedDescription)")
    }
}

Easy peasy. (And surprisingly sad if you're attached to your farts.) 😢


🎇 Final Challenge: Stretch Goals

  • Create Custom Category Icons (💥 vs 🥷)
  • Allow renaming Categories
  • Sort Categories based on number of farts (MOST FARTS WINS! 🏆)
  • Add audio playback when tapping a fart 🎧

Congratulations, Core Data Wizard! 🧙‍♀️💨

You just leveled up your Fart Button App into a relational database masterpiece. Your ancestors would be proud.

Until next time — stay classy, and keep coding! 💨👑