12 Prompts for Swift and iOS: SwiftUI, UIKit, Core Data

Why You Need These Prompts for iOS Development

Building iOS apps with Swift requires mastering multiple frameworks — SwiftUI for modern declarative UIs, UIKit for imperative legacy interfaces, and Core Data for persistent local storage. Each framework has its own learning curve, and finding the right code snippet or architectural pattern can slow down even experienced developers. This collection of 12 ready-to-use prompts covers the most common tasks across these three pillars of iOS development. Each prompt is designed to be copied directly into an AI assistant (like ChatGPT or Claude) to generate precise, production-ready code. You'll get not just the prompt, but also a usage example and explanation of what it does. Whether you're fixing a navigation bug in UIKit, optimizing a Core Data fetch, or adding animations in SwiftUI, these prompts will save you hours of trial and error.

SwiftUI Prompts: Building Declarative Interfaces

SwiftUI is Apple's modern framework for building user interfaces across all platforms. Its declarative syntax lets you describe what the UI should do, and the framework handles the rest. Below are four prompts that address the most common SwiftUI challenges: navigation, state management, custom layouts, and animations.

Prompt 1: NavigationStack with Deep Linking

Prompt:
"Generate a SwiftUI view that uses NavigationStack with a deep link handler. The app should have a list of items (e.g., 'Item 1', 'Item 2') stored in an array. Tapping an item navigates to a detail view. Implement a function handleDeepLink(url:) that parses a custom URL scheme like myapp://item/2 and programmatically navigates to the correct detail view. Use SwiftUI's .navigationDestination modifier. Include comments explaining each step."

Usage Example:

struct ContentView: View {
    let items = ["Item 1", "Item 2", "Item 3"]
    @State private var selectedItem: String?

    var body: some View {
        NavigationStack {
            List(items, id: \.self) { item in
                NavigationLink(item, value: item)
            }
            .navigationDestination(for: String.self) { item in
                DetailView(item: item)
            }
            .onOpenURL { url in
                handleDeepLink(url: url)
            }
        }
    }

    func handleDeepLink(url: URL) {
        guard url.scheme == "myapp",
              url.host == "item",
              let index = Int(url.pathComponents.last ?? ""),
              index < items.count else { return }
        selectedItem = items[index]
    }
}

Explanation: This prompt helps you implement deep linking in a SwiftUI app. It shows how to parse a custom URL scheme and use NavigationStack with value-based navigation. The example uses @State to track the selected item and onOpenURL to handle incoming links.

Prompt 2: Custom ViewModifier for Text Styles

Prompt:
"Create a reusable SwiftUI ViewModifier called HeadlineStyle that applies a custom font (system font, size 24, bold), a foreground color of .primary, and a bottom padding of 8 points. Then apply this modifier to a Text view in a sample ContentView. Additionally, create an extension on View so you can call .headlineStyle() directly."

Usage Example:

struct HeadlineStyle: ViewModifier {
    func body(content: Content) -> some View {
        content
            .font(.system(size: 24, weight: .bold))
            .foregroundColor(.primary)
            .padding(.bottom, 8)
    }
}

extension View {
    func headlineStyle() -> some View {
        self.modifier(HeadlineStyle())
    }
}

struct ContentView: View {
    var body: some View {
        Text("Welcome to Our App")
            .headlineStyle()
    }
}

Explanation: This prompt teaches you to create reusable UI components in SwiftUI using ViewModifier. It keeps your code DRY and makes it easy to apply consistent styling across your app.

Prompt 3: Async Image Loading with Progress

Prompt:
"Write a SwiftUI view that loads an image from a remote URL asynchronously, shows a ProgressView while loading, and displays a system image (like 'photo') if the load fails. Use SwiftUI's AsyncImage with a custom placeholder and error handling. The image should be resizable, scaled to fit, and have a frame of 300x200 with a corner radius of 12."

Usage Example:

struct RemoteImageView: View {
    let url = URL(string: "https://example.com/image.jpg")!

    var body: some View {
        AsyncImage(url: url) { phase in
            switch phase {
            case .empty:
                ProgressView()
                    .frame(width: 300, height: 200)
            case .success(let image):
                image
                    .resizable()
                    .scaledToFit()
                    .frame(width: 300, height: 200)
                    .cornerRadius(12)
            case .failure:
                Image(systemName: "photo")
                    .frame(width: 300, height: 200)
            @unknown default:
                EmptyView()
            }
        }
    }
}

Explanation: Handling remote images is common in iOS apps. This prompt demonstrates AsyncImage with proper state handling for loading, success, and failure scenarios.

Prompt 4: Custom Animations with withAnimation

Prompt:
"Create a SwiftUI view with a circle that moves from the top-left corner to the bottom-right corner when a button is tapped. Use withAnimation with a spring animation (response: 0.5, dampingFraction: 0.6). The circle should have a color gradient and a shadow. Also include a toggle button that resets the position with a different animation (easeInOut, duration: 1)."

Usage Example:

struct AnimatedCircleView: View {
    @State private var offset = CGSize.zero

    var body: some View {
        VStack {
            Circle()
                .fill(LinearGradient(colors: [.blue, .purple], startPoint: .top, endPoint: .bottom))
                .frame(width: 80, height: 80)
                .shadow(radius: 10)
                .offset(offset)

            Button("Move") {
                withAnimation(.spring(response: 0.5, dampingFraction: 0.6)) {
                    offset = CGSize(width: 100, height: 100)
                }
            }

            Button("Reset") {
                withAnimation(.easeInOut(duration: 1)) {
                    offset = .zero
                }
            }
        }
    }
}

Explanation: SwiftUI animations are powerful but can be tricky. This prompt shows how to use withAnimation with different animation types and control timing precisely.

UIKit Prompts: Powerful Imperative Controls

UIKit remains essential for complex UI interactions, custom drawing, and maintaining legacy codebases. These prompts cover common UIKit tasks: table views, collection views, Auto Layout, and gesture recognizers.

Prompt 5: UITableView with Diffable Data Source

Prompt:
"Write a UIViewController with a UITableView that uses a modern diffable data source (UITableViewDiffableDataSource). The table should display a list of contact names (strings). Include a button that adds a new random name to the list and another button that removes the first item. Use a NSDiffableDataSourceSnapshot to animate updates. The cell style should be .default with a reuse identifier 'Cell'."

Usage Example:

class ContactsViewController: UIViewController {
    var tableView: UITableView!
    var dataSource: UITableViewDiffableDataSource<Int, String>!

    override func viewDidLoad() {
        super.viewDidLoad()
        tableView = UITableView(frame: view.bounds)
        view.addSubview(tableView)
        dataSource = UITableViewDiffableDataSource<Int, String>(tableView: tableView) { tableView, indexPath, contact in
            let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
            cell.textLabel?.text = contact
            return cell
        }
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
        applySnapshot(with: ["Alice", "Bob"])
    }

    func applySnapshot(with items: [String]) {
        var snapshot = NSDiffableDataSourceSnapshot<Int, String>()
        snapshot.appendSections([0])
        snapshot.appendItems(items)
        dataSource.apply(snapshot, animatingDifferences: true)
    }

    @objc func addContact() {
        let newName = ["Charlie", "Diana", "Eve"].randomElement()!
        var snapshot = dataSource.snapshot()
        snapshot.appendItems([newName])
        dataSource.apply(snapshot, animatingDifferences: true)
    }

    @objc func removeFirst() {
        var snapshot = dataSource.snapshot()
        guard let item = snapshot.itemIdentifiers.first else { return }
        snapshot.deleteItems([item])
        dataSource.apply(snapshot, animatingDifferences: true)
    }
}

Explanation: Diffable data sources simplify table view updates and eliminate common errors like index mismatches. This prompt shows how to set one up and animate changes.

Prompt 6: UICollectionView with Compositional Layout

Prompt:
"Create a UICollectionView in a UIViewController that uses a compositional layout with two sections: a horizontal scrolling section for featured items (width: 0.9 of container, height: 200) and a vertical grid section for regular items (3 columns, 150 height). Use a simple cell with a label. Register the cell class and populate with sample data (array of strings)."

Usage Example:

class FeaturedCollectionViewController: UIViewController, UICollectionViewDataSource {
    var collectionView: UICollectionView!
    let featuredItems = ["Featured 1", "Featured 2"]
    let regularItems = ["Item 1", "Item 2", "Item 3", "Item 4", "Item 5"]

    override func viewDidLoad() {
        super.viewDidLoad()
        let layout = UICollectionViewCompositionalLayout { sectionIndex, _ in
            if sectionIndex == 0 {
                let item = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.9), heightDimension: .fractionalHeight(1)))
                let group = NSCollectionLayoutGroup.horizontal(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.9), heightDimension: .absolute(200)), subitems: [item])
                return NSCollectionLayoutSection(group: group)
            } else {
                let item = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1/3), heightDimension: .absolute(150)))
                let group = NSCollectionLayoutGroup.horizontal(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .absolute(150)), subitems: [item])
                return NSCollectionLayoutSection(group: group)
            }
        }
        collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout)
        collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "cell")
        collectionView.dataSource = self
        view.addSubview(collectionView)
    }

    func numberOfSections(in collectionView: UICollectionView) -> Int { 2 }

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        section == 0 ? featuredItems.count : regularItems.count
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
        cell.backgroundColor = .lightGray
        let label = UILabel(frame: cell.bounds)
        label.text = indexPath.section == 0 ? featuredItems[indexPath.row] : regularItems[indexPath.row]
        cell.addSubview(label)
        return cell
    }
}

Explanation: Compositional layouts enable complex, adaptive layouts without manual frame calculations. This prompt builds a multi-section collection view similar to the App Store.

Prompt 7: Auto Layout with Anchors

Prompt:
"Write a UIViewController that adds a UIView (red, corner radius 10) and a UIButton ('Tap Me') using Auto Layout anchors. The red view should be centered horizontally and vertically, with width 200 and height 100. The button should be placed 20 points below the red view, centered horizontally. Do not use storyboards or frame-based layout."

Usage Example:

class AutoLayoutViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        let redView = UIView()
        redView.backgroundColor = .red
        redView.layer.cornerRadius = 10
        redView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(redView)

        let button = UIButton(type: .system)
        button.setTitle("Tap Me", for: .normal)
        button.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(button)

        NSLayoutConstraint.activate([
            redView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            redView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
            redView.widthAnchor.constraint(equalToConstant: 200),
            redView.heightAnchor.constraint(equalToConstant: 100),
            button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            button.topAnchor.constraint(equalTo: redView.bottomAnchor, constant: 20)
        ])
    }
}

Explanation: Auto Layout with anchors is the standard way to position UI elements programmatically. This prompt shows the correct pattern with translatesAutoresizingMaskIntoConstraints = false.

Prompt 8: UISwipeGestureRecognizer on a View

Prompt:
"Create a UIView (blue, 100x100) that responds to a right swipe gesture by changing its background color to green and displaying a temporary label 'Swiped!' that fades out after 1 second. Use UISwipeGestureRecognizer with direction .right. The view should be placed in the center of the screen using Auto Layout."

Usage Example:

class SwipeViewController: UIViewController {
    let swipeView = UIView()

    override func viewDidLoad() {
        super.viewDidLoad()
        swipeView.backgroundColor = .blue
        swipeView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(swipeView)

        NSLayoutConstraint.activate([
            swipeView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            swipeView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
            swipeView.widthAnchor.constraint(equalToConstant: 100),
            swipeView.heightAnchor.constraint(equalToConstant: 100)
        ])

        let swipe = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipe))
        swipe.direction = .right
        swipeView.addGestureRecognizer(swipe)
    }

    @objc func handleSwipe() {
        swipeView.backgroundColor = .green
        let label = UILabel(frame: swipeView.bounds)
        label.text = "Swiped!"
        label.textAlignment = .center
        swipeView.addSubview(label)
        UIView.animate(withDuration: 0.5, delay: 1, options: .curveEaseOut) {
            label.alpha = 0
        } completion: { _ in
            label.removeFromSuperview()
        }
    }
}

Explanation: Gesture recognizers add interactivity to UIKit views. This prompt demonstrates swipe detection and a simple animation.

Core Data Prompts: Persistent Local Storage

Core Data is Apple's object graph and persistence framework, ideal for offline-first apps. These prompts cover setup, fetching, relationships, and background contexts.

Prompt 9: Core Data Stack Setup with NSPersistentContainer

Prompt:
"Generate a Core Data stack using NSPersistentContainer in a Swift file called PersistenceController. The stack should include a shared singleton instance for production and a preview instance for SwiftUI previews that uses an in-memory store. The container should load persistent stores with error handling (print error and crash in debug). Include a save method that checks for changes."

Usage Example:

struct PersistenceController {
    static let shared = PersistenceController()

    static var preview: PersistenceController = {
        let controller = PersistenceController(inMemory: true)
        let viewContext = controller.container.viewContext
        for i in 0..<10 {
            let item = Item(context: viewContext)
            item.name = "Sample \(i)"
        }
        try? viewContext.save()
        return controller
    }()

    let container: NSPersistentContainer

    init(inMemory: Bool = false) {
        container = NSPersistentContainer(name: "YourModelName")
        if inMemory {
            container.persistentStoreDescriptions.first?.url = URL(fileURLWithPath: "/dev/null")
        }
        container.loadPersistentStores { _, error in
            if let error = error as NSError? {
                fatalError("Unresolved error \(error), \(error.userInfo)")
            }
        }
        container.viewContext.automaticallyMergesChangesFromParent = true
    }

    func save() {
        let context = container.viewContext
        if context.hasChanges {
            do {
                try context.save()
            } catch {
                let nserror = error as NSError
                fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
            }
        }
    }
}

Explanation: A proper Core Data stack is foundational. This prompt provides a production-ready PersistenceController with preview support and save error handling.

Prompt 10: Fetch Request with Predicate and Sort

Prompt:
"Write a Core Data fetch request that retrieves all 'Person' entities with age greater than 18, sorted by lastName ascending, then firstName ascending. Use NSFetchRequest with a predicate and NSSortDescriptor. Return the results as an array of Person objects. Include a usage example in a function that prints the full names."

Usage Example:

func fetchAdults() -> [Person] {
    let request: NSFetchRequest<Person> = Person.fetchRequest()
    request.predicate = NSPredicate(format: "age > 18")
    request.sortDescriptors = [
        NSSortDescriptor(key: "lastName", ascending: true),
        NSSortDescriptor(key: "firstName", ascending: true)
    ]
    do {
        let results = try PersistenceController.shared.container.viewContext.fetch(request)
        return results
    } catch {
        print("Fetch failed: \(error)")
        return []
    }
}

func printAdultNames() {
    let adults = fetchAdults()
    for person in adults {
        print("\(person.firstName ?? "") \(person.lastName ?? "")")
    }
}

Explanation: Filtering and sorting are fundamental to Core Data queries. This prompt shows how to combine predicates and sort descriptors.

Prompt 11: One-to-Many Relationship (Author – Books)

Prompt:
"Define a Core Data data model with two entities: Author (attributes: name, birthDate) and Book (attributes: title, publishDate). Create a one-to-many relationship: an Author can have many Books, and a Book belongs to one Author. Provide Swift code to create a new Author, add two books to that author, and save the context. Also include a fetch request that retrieves all books by a specific author."

Usage Example:

// Create author and books
let context = PersistenceController.shared.container.viewContext
let author = Author(context: context)
author.name = "Jane Austen"
author.birthDate = Date()

let book1 = Book(context: context)
book1.title = "Pride and Prejudice"
book1.publishDate = Date()
book1.author = author

let book2 = Book(context: context)
book2.title = "Sense and Sensibility"
book2.publishDate = Date()
book2.author = author

PersistenceController.shared.save()

// Fetch books by author
func fetchBooks(by author: Author) -> [Book] {
    let request: NSFetchRequest<Book> = Book.fetchRequest()
    request.predicate = NSPredicate(format: "author == %@", author)
    do {
        return try context.fetch(request)
    } catch {
        print("Fetch failed: \(error)")
        return []
    }
}

Explanation: Relationships are core to Core Data. This prompt demonstrates creating and querying a one-to-many relationship.

Prompt 12: Background Context for Large Imports

Prompt:
"Write a function that performs a large import of 1000 JSON objects into Core Data on a background context to avoid blocking the main thread. Use performBackgroundTask from NSPersistentContainer. After the import, save the background context and notify the main context via mergeChanges(fromContextDidSave:). Include a progress callback that prints the number of imported items."

Usage Example:

func importLargeDataSet(jsonArray: [[String: Any]]) {
    let container = PersistenceController.shared.container
    container.performBackgroundTask { backgroundContext in
        var count = 0
        for json in jsonArray {
            let item = Item(context: backgroundContext)
            item.name = json["name"] as? String
            count += 1
            if count % 100 == 0 {
                print("Imported \(count) items")
            }
        }
        do {
            try backgroundContext.save()
            DispatchQueue.main.async {
                NotificationCenter.default.post(name: .NSManagedObjectContextDidSave, object: backgroundContext)
            }
        } catch {
            print("Background save failed: \(error)")
        }
    }
}

Explanation: Background contexts prevent UI freezes during heavy data operations. This prompt shows the correct pattern using performBackgroundTask.

Prompt 13: Lightweight Migration with Options

Prompt:
"Enable automatic lightweight Core Data migration when loading persistent stores. Modify the PersistenceController to add options dictionary with NSMigratePersistentStoresAutomaticallyOption and NSInferMappingModelAutomaticallyOption set to true. Explain when lightweight migration works (adding new optional attributes, adding new entities) and when it fails (renaming attributes, changing relationships)."

Usage Example:

init(inMemory: Bool = false) {
    container = NSPersistentContainer(name: "YourModelName")
    if inMemory {
        container.persistentStoreDescriptions.first?.url = URL(fileURLWithPath: "/dev/null")
    }
    if let storeDescription = container.persistentStoreDescriptions.first {
        storeDescription.setOption(true as NSNumber, forKey: NSMigratePersistentStoresAutomaticallyOption)
        storeDescription.setOption(true as NSNumber, forKey: NSInferMappingModelAutomaticallyOption)
    }
    container.loadPersistentStores { _, error in
        if let error = error as NSError? {
            fatalError("Unresolved error \(error), \(error.userInfo)")
        }
    }
}

Explanation: Lightweight migration simplifies schema changes. This prompt configures automatic migration and explains its limitations.

Conclusion

These 12 prompts cover the most common and challenging tasks in iOS development with SwiftUI, UIKit, and Core Data. By using them as starting points, you can accelerate your workflow and reduce bugs. Remember to adapt the code to your specific data models and UI requirements. For deeper dives, refer to Apple's official documentation: SwiftUI, UIKit, and Core Data. Bookmark this page and use these prompts as your personal iOS development cheat sheet.

← All posts

Comments