15 Prompts for Swift and iOS Development: SwiftUI, UIKit, Core Data, Combine

15 Prompts for Swift and iOS Development: SwiftUI, UIKit, Core Data, Combine

Introduction

As an iOS developer, you’ve probably experimented with AI coding assistants like GitHub Copilot, Cursor, or ChatGPT. The magic isn’t just in the tool—it’s in the prompt. A well-crafted prompt can save you hours of boilerplate, suggest architecture patterns, or even debug a cryptic crash. This article collects 15 curated prompts across four essential iOS frameworks: SwiftUI, UIKit, Core Data, and Combine. Each prompt comes with a clear task, the exact prompt to use, and an example result (with code).

I’ve organized them into three skill levels: Basic (for beginners), Advanced (for intermediate developers), and Expert (for those tackling concurrency, migration, or performance). Whether you’re building a new feature from scratch or maintaining a legacy codebase, these prompts will help you get more out of your AI assistant.

Basic Prompts

1. Generate a SwiftUI List with Navigation

Task: Create a SwiftUI view that displays a list of items (e.g., fruits) and navigates to a detail view on tap.

Prompt:

"Generate a SwiftUI view with a List that shows an array of strings (fruits). Each row should show the fruit name. Tapping a row should navigate to a DetailView that displays the fruit name in a large title. Use NavigationStack."

Example Result (Code):

struct ContentView: View {
    let fruits = ["Apple", "Banana", "Cherry", "Date", "Elderberry"]

    var body: some View {
        NavigationStack {
            List(fruits, id: \.self) { fruit in
                NavigationLink(fruit, value: fruit)
            }
            .navigationDestination(for: String.self) { fruit in
                Text(fruit)
                    .font(.largeTitle)
            }
            .navigationTitle("Fruits")
        }
    }
}

Explanation: This prompt produces a fully functional navigation and list. The NavigationLink with a value and .navigationDestination pattern is idiomatic for modern SwiftUI. Ideal for beginners to understand data flow.

2. Create a UIKit Table View with Delegate & Data Source

Task: Build a simple UITableViewController that displays an array of names and prints the selected name when tapped.

Prompt:

"Create a UITableViewController subclass in Swift that displays an array of names (["Alice","Bob","Charlie"]). Implement UITableViewDataSource and UITableViewDelegate. When a row is tapped, print the name to the console. Use a plain UITableViewCell."

Example Result (Code):

class ContactListVC: UITableViewController {
    let names = ["Alice", "Bob", "Charlie"]

    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return names.count
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        cell.textLabel?.text = names[indexPath.row]
        return cell
    }

    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        print("Selected \(names[indexPath.row])")
        tableView.deselectRow(at: indexPath, animated: true)
    }
}

Explanation: This covers the classic UIKit pattern. It registers a cell, implements two data source methods, and handles selection. Good for understanding delegate callbacks.

3. Core Data Entity Creation and Fetch Request

Task: Define a Core Data entity Person with attributes name (String) and age (Int), then write a fetch request that returns all persons older than 18.

Prompt:

"Generate the Core Data model for a Person entity with name (String) and age (Int). Then write a function fetchAdults() that returns an array of Person objects where age > 18, sorted by name ascending. Use the container's viewContext."

Example Result (Code):

import CoreData

// Model: Person entity with attributes:
// name: String
// age: Int16

func fetchAdults(context: NSManagedObjectContext) -> [Person] {
    let request: NSFetchRequest<Person> = Person.fetchRequest()
    request.predicate = NSPredicate(format: "age > %d", 18)
    request.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)]
    do {
        return try context.fetch(request)
    } catch {
        print("Fetch failed: \(error)")
        return []
    }
}

Explanation: This demonstrates the standard Core Data fetch pattern with predicate and sort. Note the use of Int16 for age (common in Core Data). Helps beginners set up their first fetch request.

4. Combine Publisher for Text Field Input

Task: Listen to a text field’s input and print the text after a 0.5-second debounce.

Prompt:

"Write a SwiftUI view that contains a TextField. Use Combine to subscribe to changes of the text field's input. Apply a debounce of 0.5 seconds and then print the text to the console. Import Combine and use @Published."

Example Result (Code):

import SwiftUI
import Combine

class ViewModel: ObservableObject {
    @Published var text = ""
    private var cancellables = Set<AnyCancellable>()

    init() {
        $text
            .debounce(for: .seconds(0.5), scheduler: RunLoop.main)
            .sink { value in
                print("Debounced text: \(value)")
            }
            .store(in: &cancellables)
    }
}

struct ContentView: View {
    @StateObject private var vm = ViewModel()

    var body: some View {
        TextField("Type here", text: $vm.text)
            .padding()
    }
}

Explanation: Introduces @Published and debounce. This pattern is common for search-as-you-type. The prompt ensures the AI uses RunLoop.main for UI updates.

5. Simple MVVM Architecture in SwiftUI

Task: Build a counter app with a ViewModel that increments and decrements a value.

Prompt:

"Create a SwiftUI counter app using MVVM. The ViewModel should have a published `count` variable, and methods `increment()` and `decrement()`. The view displays the count and two buttons."

Example Result (Code):

import SwiftUI

class CounterViewModel: ObservableObject {
    @Published var count = 0

    func increment() { count += 1 }
    func decrement() { count -= 1 }
}

struct CounterView: View {
    @StateObject private var vm = CounterViewModel()

    var body: some View {
        VStack {
            Text("\(vm.count)")
                .font(.largeTitle)
            HStack {
                Button("-") { vm.decrement() }
                Button("+") { vm.increment() }
            }
        }
    }
}

Explanation: A classic MVVM example. Demonstrates @StateObject, @Published, and separation of concerns. Perfect for grasping the architecture.

Advanced Prompts

6. SwiftUI Complex Animations

Task: Animate a view moving from one position to another with a spring effect, and change color during animation.

Prompt:

"Write a SwiftUI view with a draggable circle. When the user drags it to a target area, it snaps back with a spring animation and changes color from red to green. Use withAnimation and matchedGeometryEffect."

Example Result (Code):

struct DraggableCircle: View {
    @State private var offset = CGSize.zero
    @State private var isInTarget = false

    var body: some View {
        Circle()
            .fill(isInTarget ? Color.green : Color.red)
            .frame(width: 50, height: 50)
            .offset(offset)
            .gesture(
                DragGesture()
                    .onChanged { value in
                        offset = value.translation
                    }
                    .onEnded { value in
                        withAnimation(.spring(response: 0.5, dampingFraction: 0.6, blendDuration: 0)) {
                            if abs(offset.width) > 100 {
                                isInTarget = true
                            } else {
                                offset = .zero
                                isInTarget = false
                            }
                        }
                    }
            )
    }
}

Explanation: This prompt asks for explicit spring animation parameters. The AI adds withAnimation and changes color based on state. Good for understanding gesture-driven animations.

7. UIKit Collection View with Diffable Data Source

Task: Create a UICollectionView using UICollectionViewDiffableDataSource and apply a snapshot with custom cells.

Prompt:

"Implement a UICollectionView in UIKit using UICollectionViewDiffableDataSource. Use a single section and populate it with numbers 1 to 20. Register a custom UICollectionViewCell that displays the number in a label centered."

Example Result (Code):

import UIKit

class NumberCell: UICollectionViewCell {
    static let reuseIdentifier = "NumberCell"
    let label = UILabel()

    override init(frame: CGRect) {
        super.init(frame: frame)
        label.textAlignment = .center
        contentView.addSubview(label)
        label.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            label.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
            label.centerYAnchor.constraint(equalTo: contentView.centerYAnchor)
        ])
    }

    required init?(coder: NSCoder) { fatalError() }
}

class ViewController: UIViewController {
    var collectionView: UICollectionView!
    var dataSource: UICollectionViewDiffableDataSource<Int, Int>()!

    override func viewDidLoad() {
        super.viewDidLoad()
        let layout = UICollectionViewFlowLayout()
        collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout)
        collectionView.register(NumberCell.self, forCellWithReuseIdentifier: NumberCell.reuseIdentifier)
        view.addSubview(collectionView)

        dataSource = UICollectionViewDiffableDataSource<Int, Int>(collectionView: collectionView) { (collectionView, indexPath, number) -> UICollectionViewCell? in
            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: NumberCell.reuseIdentifier, for: indexPath) as! NumberCell
            cell.label.text = "\(number)"
            return cell
        }

        var snapshot = NSDiffableDataSourceSnapshot<Int, Int>()
        snapshot.appendSections([0])
        snapshot.appendItems(Array(1...20))
        dataSource.apply(snapshot, animatingDifferences: false)
    }
}

Explanation: Diffable data sources simplify collection view updates. The prompt forces the AI to register a custom cell and apply a snapshot. Essential for modern UIKit.

8. Core Data Background Context and Concurrency

Task: Perform a Core Data fetch and update on a background context to avoid blocking the main thread.

Prompt:

"Write a function that performs a Core Data save operation on a background context. Create a new Person object, set its name and age, then save using performBackgroundTask. Handle errors."

Example Result (Code):

import CoreData

func savePersonInBackground(name: String, age: Int16, container: NSPersistentContainer) {
    container.performBackgroundTask { context in
        let person = Person(context: context)
        person.name = name
        person.age = age
        do {
            try context.save()
        } catch {
            print("Background save error: \(error)")
        }
    }
}

Explanation: performBackgroundTask creates a private queue context. This prompt ensures the AI uses the correct API for concurrency, a common source of bugs.

9. Combine Networking Layer with URLSession

Task: Build a networking manager that fetches JSON from an API and decodes it into a model using Combine.

Prompt:

"Create a NetworkManager class that uses URLSession dataTaskPublisher to fetch a list of users from https://jsonplaceholder.typicode.com/users. Decode the response into [User] where User has id, name, email. Return a publisher that emits the decoded array or an error."

Example Result (Code):

import Combine
import Foundation

struct User: Codable {
    let id: Int
    let name: String
    let email: String
}

enum NetworkError: Error {
    case invalidResponse, decodingError
}

class NetworkManager {
    let session = URLSession.shared

    func fetchUsers() -> AnyPublisher<[User], Error> {
        let url = URL(string: "https://jsonplaceholder.typicode.com/users")!
        return session.dataTaskPublisher(for: url)
            .map(\.data)
            .decode(type: [User].self, decoder: JSONDecoder())
            .mapError { error -> Error in
                return error
            }
            .eraseToAnyPublisher()
    }
}

Explanation: This prompt produces a clean networking layer using Combine’s dataTaskPublisher and decode. The AI includes error handling and type safety.

10. Dependency Injection Pattern

Task: Implement a simple dependency injection container for a SwiftUI app, injecting a mock data service.

Prompt:

"Write a dependency injection container that registers a DataService protocol. In the app entry point, resolve DataService and inject it into an environment object. The DataService should have a fetch method that returns a string list."

Example Result (Code):

import SwiftUI

protocol DataService {
    func fetchData() -> [String]
}

class MockDataService: DataService {
    func fetchData() -> [String] {
        return ["Mock1", "Mock2"]
    }
}

class DIContainer {
    static let shared = DIContainer()
    private var services: [String: Any] = [:]

    func register<T>(_ type: T.Type, service: T) {
        services[String(describing: type)] = service
    }

    func resolve<T>(_ type: T.Type) -> T? {
        return services[String(describing: type)] as? T
    }
}

struct ContentView: View {
    @Environment(\.dataService) var dataService

    var body: some View {
        List(dataService.fetchData(), id: \.self) { item in
            Text(item)
        }
    }
}

struct DataServiceKey: EnvironmentKey {
    static let defaultValue: DataService = MockDataService()
}

extension EnvironmentValues {
    var dataService: DataService {
        get { self[DataServiceKey.self] }
        set { self[DataServiceKey.self] = newValue }
    }
}

Explanation: This shows both a manual DI container and environment injection. The AI uses EnvironmentKey to inject the service, enabling testability.

Expert Prompts

11. SwiftUI Custom Transitions

Task: Create a custom view transition that scales and rotates when appearing, with a combined animation.

Prompt:

"Define a custom AnyTransition called scaleRotate that scales the view from 0 to 1 and rotates by 360 degrees. Use it in a conditional view display with .transition modifier."

Example Result (Code):

extension AnyTransition {
    static var scaleRotate: AnyTransition {
        AnyTransition.scale(scale: 0)
            .combined(with: .rotation(.degrees(360)))
    }
}

struct ContentView: View {
    @State private var show = false

    var body: some View {
        VStack {
            Button("Toggle") { withAnimation { show.toggle() } }
            if show {
                Text("Hello!")
                    .font(.largeTitle)
                    .transition(.scaleRotate)
            }
        }
    }
}

Explanation: Custom AnyTransition compositions let you create complex entrance effects. This prompt asks for a combined transition.

12. UIKit View Controller Lifecycle and Memory Management

Task: Write a view controller that uses didReceiveMemoryWarning and demonstrates proper cleanup of observers.

Prompt:

"Implement a UIViewController that subscribes to a NotificationCenter notification in viewWillAppear and removes it in viewDidDisappear. Also override didReceiveMemoryWarning to clear cached data."

Example Result (Code):

import UIKit

class MyViewController: UIViewController {
    private var observer: NSObjectProtocol?

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        observer = NotificationCenter.default.addObserver(forName: UIApplication.didEnterBackgroundNotification, object: nil, queue: .main) { _ in
            print("App entered background")
        }
    }

    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        if let observer = observer {
            NotificationCenter.default.removeObserver(observer)
        }
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Clear cached images, etc.
        print("Memory warning received")
    }

    deinit {
        print("MyViewController deallocated")
    }
}

Explanation: This demonstrates proper lifecycle management, removing observers to avoid crashes. The AI also includes a deinit log for debugging retain cycles.

13. Core Data Migration Strategies

Task: Write a guide or code to handle lightweight migration and a custom migration mapping model.

Prompt:

"Explain and implement a Core Data persistent container that automatically performs lightweight migration. Then provide an example of a custom migration using a mapping model for renaming an attribute."

Example Result (Code):

import CoreData

// Lightweight migration
lazy var persistentContainer: NSPersistentContainer = {
    let container = NSPersistentContainer(name: "Model")
    let description = container.persistentStoreDescriptions.first
    description?.shouldMigrateStoreAutomatically = true
    description?.shouldInferMappingModelAutomatically = true
    container.loadPersistentStores { _, error in
        if let error = error { fatalError("\(error)") }
    }
    return container
}()

// Custom migration: mapping model created externally (e.g., "MappingModel.cdm")
// In code, you'd use NSMigrationManager

Explanation: Lightweight migration covers most cases (add/remove attributes, rename). For complex changes, a mapping model is required. This prompt checks the AI’s understanding of migration options.

14. Combine Testing with Test Schedulers

Task: Write unit tests for a Combine publisher using a virtual time scheduler to simulate delays.

Prompt:

"Write a test case that uses XCTest and Combine's TestScheduler. Create a publisher that emits after 1 second. Use the scheduler to advance time and verify the result."

Example Result (Code):

import XCTest
import Combine

class CombineTests: XCTestCase {
    var cancellables: Set<AnyCancellable>!

    override func setUp() {
        cancellables = []
    }

    func testDelayedPublisher() {
        let scheduler = DispatchQueue.test
        let expectation = self.expectation(description: "delayed")

        let publisher = Just("Hello")
            .delay(for: .seconds(1), scheduler: scheduler)

        publisher.sink { value in
            XCTAssertEqual(value, "Hello")
            expectation.fulfill()
        }.store(in: &cancellables)

        scheduler.advance(by: .seconds(1))
        waitForExpectations(timeout: 0.1)
    }
}

Explanation: DispatchQueue.test (from Combine’s TestScheduler) allows you to control time. This is crucial for testing time-dependent Combine code without real waiting.

15. Swift Concurrency with Async/Await and Combine Interop

Task: Use async/await to call a Combine publisher and also bridge from an async function to Combine.

Prompt:

"Show how to convert a Combine publisher into an async sequence using AsyncPublisher. Also demonstrate converting an async throwing function into a Combine publisher using Future."

Example Result (Code):

import Combine
import SwiftUI

// Combine -> AsyncPublisher
let publisher = [1,2,3].publisher

Task {
    for await value in publisher.values {
        print(value) // 1, 2, 3
    }
}

// Async -> Combine Future
func asyncFetchUser() async throws -> String {
    try await Task.sleep(nanoseconds: 1_000_000_000)
    return "User"
}

let future = Future<String, Error> { promise in
    Task {
        do {
            let result = try await asyncFetchUser()
            promise(.success(result))
        } catch {
            promise(.failure(error))
        }
    }
}

Explanation: This shows bidirectional interop between Swift concurrency and Combine. The AI uses AsyncPublisher (iOS 15+) and Future to bridge, which is essential for modern iOS development.

Conclusion

These 15 prompts cover the breadth of iOS development from basic UI patterns to advanced concurrency and testing. The key is specificity: the more context you give the AI (framework, task, desired output), the better the generated code. Remember to always review AI-generated code for threading, memory leaks, and edge cases.

Start using these prompts with your preferred AI tool—whether it’s a chat interface or a code assistant integrated into Xcode. Experiment with modifying the prompts to fit your exact UI or data model. The more you practice crafting precise prompts, the faster you’ll ship high-quality iOS features.

Happy coding!

← All posts

Comments