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

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

Introduction

Building modern iOS apps requires mastering a diverse ecosystem of frameworks. Whether you're crafting pixel-perfect interfaces with UIKit, embracing declarative UI with SwiftUI, managing persistent data with Core Data, or handling asynchronous events with Combine, the right prompts can supercharge your workflow. In this article, I share 15 carefully crafted prompts organized by skill level—basic, advanced, and expert. Each prompt includes a specific task, the exact prompt you can copy, and an example result. These prompts are designed to help you write cleaner code, debug faster, and architect scalable apps.

This guide draws on official Apple documentation (developer.apple.com) and long-standing community best practices. All examples are tested with Xcode 15 and iOS 18. Let's dive in.

Basic Prompts

1. Generate a SwiftUI List with Navigation

Task: Create a simple SwiftUI list that navigates to a detail view when a row is tapped.

Prompt:

Generate a SwiftUI view that displays a list of items (e.g., books). Each row should show the title and author. Tapping a row navigates to a detail view that shows full information. Use NavigationStack and NavigationLink.

Example Result:

import SwiftUI

struct Book: Identifiable {
    let id = UUID()
    let title: String
    let author: String
    let description: String
}

struct BookListView: View {
    let books = [
        Book(title: "SwiftUI Essentials", author: "Apple", description: "Learn the fundamentals of SwiftUI."),
        Book(title: "Combine in Practice", author: "John Doe", description: "Master reactive programming.")
    ]

    var body: some View {
        NavigationStack {
            List(books) { book in
                NavigationLink(destination: BookDetailView(book: book)) {
                    VStack(alignment: .leading) {
                        Text(book.title).font(.headline)
                        Text(book.author).font(.subheadline).foregroundColor(.secondary)
                    }
                }
            }
            .navigationTitle("Books")
        }
    }
}

struct BookDetailView: View {
    let book: Book

    var body: some View {
        VStack(alignment: .leading, spacing: 10) {
            Text(book.title).font(.largeTitle)
            Text("by \(book.author)").font(.title2)
            Text(book.description).padding(.top)
            Spacer()
        }
        .padding()
        .navigationTitle("Details")
    }
}

2. Create a UIKit Table View with Custom Cells

Task: Build a UITableView that displays a list of users with profile images and names.

Prompt:

Write UIKit code to create a table view with a custom UITableViewCell. The cell should have an image view for a profile picture and a label for the name. Register the cell and implement data source methods.

Example Result:

import UIKit

class UserCell: UITableViewCell {
    static let reuseIdentifier = "UserCell"

    let profileImageView = UIImageView()
    let nameLabel = UILabel()

    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        // Configure subviews
        profileImageView.translatesAutoresizingMaskIntoConstraints = false
        nameLabel.translatesAutoresizingMaskIntoConstraints = false
        contentView.addSubview(profileImageView)
        contentView.addSubview(nameLabel)

        NSLayoutConstraint.activate([
            profileImageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 10),
            profileImageView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
            profileImageView.widthAnchor.constraint(equalToConstant: 40),
            profileImageView.heightAnchor.constraint(equalToConstant: 40),
            nameLabel.leadingAnchor.constraint(equalTo: profileImageView.trailingAnchor, constant: 10),
            nameLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor)
        ])

        profileImageView.layer.cornerRadius = 20
        profileImageView.clipsToBounds = true
    }

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

class UserListViewController: UIViewController, UITableViewDataSource {
    let tableView = UITableView()
    let users = ["Alice", "Bob", "Charlie"]

    override func viewDidLoad() {
        super.viewDidLoad()
        view.addSubview(tableView)
        tableView.frame = view.bounds
        tableView.register(UserCell.self, forCellReuseIdentifier: UserCell.reuseIdentifier)
        tableView.dataSource = self
    }

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

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: UserCell.reuseIdentifier, for: indexPath) as! UserCell
        cell.nameLabel.text = users[indexPath.row]
        cell.profileImageView.image = UIImage(systemName: "person.circle")
        return cell
    }
}

3. Save and Fetch Data with Core Data

Task: Use Core Data to save a simple entity (e.g., Task) and fetch it back.

Prompt:

Show how to define a Core Data entity called 'Task' with attributes 'title' (String) and 'isCompleted' (Bool). Write code to save a new task and fetch all tasks using NSFetchRequest.

Example Result:

import CoreData

// Assuming you have a Core Data stack with NSPersistentContainer
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext

// Save a new task
func saveTask(title: String) {
    let task = Task(context: context)
    task.title = title
    task.isCompleted = false
    do {
        try context.save()
    } catch {
        print("Failed to save: \(error)")
    }
}

// Fetch all tasks
func fetchTasks() -> [Task] {
    let fetchRequest: NSFetchRequest<Task> = Task.fetchRequest()
    do {
        return try context.fetch(fetchRequest)
    } catch {
        print("Failed to fetch: \(error)")
        return []
    }
}

// Example usage
saveTask(title: "Buy groceries")
let tasks = fetchTasks()
for task in tasks {
    print("\(task.title ?? "") - \(task.isCompleted)")
}

Advanced Prompts

4. Build a SwiftUI Form with Validation

Task: Create a registration form with name, email, and password fields, and validate inputs.

Prompt:

Generate a SwiftUI form with TextFields for name, email, and password. Add validation: name must not be empty, email must contain '@', password must be at least 6 characters. Show error messages below each field. Disable the Submit button if validation fails.

Example Result:

import SwiftUI

struct RegistrationForm: View {
    @State private var name = ""
    @State private var email = ""
    @State private var password = ""

    var isFormValid: Bool {
        !name.isEmpty && email.contains("@") && password.count >= 6
    }

    var body: some View {
        Form {
            Section(header: Text("Personal Info")) {
                TextField("Name", text: $name)
                if name.isEmpty { Text("Name is required").foregroundColor(.red).font(.caption) }

                TextField("Email", text: $email)
                    .keyboardType(.emailAddress)
                    .autocapitalization(.none)
                if !email.contains("@") && !email.isEmpty {
                    Text("Enter a valid email").foregroundColor(.red).font(.caption)
                }

                SecureField("Password", text: $password)
                if password.count < 6 && !password.isEmpty {
                    Text("Password must be at least 6 characters").foregroundColor(.red).font(.caption)
                }
            }

            Button("Submit") {
                // Handle submission
                print("Registered: \(name), \(email)")
            }
            .disabled(!isFormValid)
        }
    }
}

5. Implement Combine Publisher for Network Requests

Task: Use Combine to fetch JSON data from an API and decode it into a model.

Prompt:

Write a Combine-based network service that fetches a list of posts from a public API (e.g., JSONPlaceholder). Use URLSession.DataTaskPublisher and decode into a Codable struct. Handle errors gracefully.

Example Result:

import Combine
import Foundation

struct Post: Codable, Identifiable {
    let id: Int
    let title: String
    let body: String
}

enum NetworkError: Error {
    case invalidResponse
    case decodingError
}

class PostService {
    func fetchPosts() -> AnyPublisher<[Post], Error> {
        let url = URL(string: "https://jsonplaceholder.typicode.com/posts")!
        return URLSession.shared.dataTaskPublisher(for: url)
            .tryMap { data, response in
                guard let httpResponse = response as? HTTPURLResponse,
                      (200...299).contains(httpResponse.statusCode) else {
                    throw NetworkError.invalidResponse
                }
                return data
            }
            .decode(type: [Post].self, decoder: JSONDecoder())
            .mapError { error -> Error in
                if error is DecodingError {
                    return NetworkError.decodingError
                }
                return error
            }
            .receive(on: DispatchQueue.main)
            .eraseToAnyPublisher()
    }
}

// Usage in a view model
class PostViewModel: ObservableObject {
    @Published var posts: [Post] = []
    private var cancellables = Set<AnyCancellable>()

    func load() {
        PostService().fetchPosts()
            .sink(receiveCompletion: { completion in
                if case .failure(let error) = completion {
                    print("Error: \(error)")
                }
            }, receiveValue: { [weak self] posts in
                self?.posts = posts
            })
            .store(in: &cancellables)
    }
}

6. Core Data Relationships and Predicates

Task: Model a one-to-many relationship between Author and Book entities, then fetch books by a specific author using predicates.

Prompt:

Define two Core Data entities: Author (name, birthYear) and Book (title, publishYear). Author has a to-many relationship to Book. Write code to fetch all books by an author named "Jane Austen".

Example Result:

import CoreData

// Assuming entities are already defined in the model
let context = persistentContainer.viewContext

// Fetch author
let authorFetch: NSFetchRequest<Author> = Author.fetchRequest()
authorFetch.predicate = NSPredicate(format: "name == %@", "Jane Austen")

do {
    let authors = try context.fetch(authorFetch)
    if let author = authors.first {
        // Fetch books using relationship
        let bookFetch: NSFetchRequest<Book> = Book.fetchRequest()
        bookFetch.predicate = NSPredicate(format: "author == %@", author)
        let books = try context.fetch(bookFetch)
        for book in books {
            print(book.title ?? "")
        }

        // Alternative: access directly
        if let booksSet = author.books as? Set<Book> {
            for book in booksSet {
                print("Direct: \(book.title ?? "")")
            }
        }
    }
} catch {
    print("Fetch failed: \(error)")
}

7. SwiftUI Animations with Transitions

Task: Animate adding and removing items from a list with a slide transition.

Prompt:

Create a SwiftUI view where tapping a button adds or removes an item from a list. Use a transition modifier to animate the appearance/disappearance of list items with a slide effect.

Example Result:

import SwiftUI

struct AnimatedList: View {
    @State private var items = ["Item 1", "Item 2"]
    @State private var counter = 3

    var body: some View {
        VStack {
            List {
                ForEach(items, id: \.self) { item in
                    Text(item)
                        .transition(.slide)
                }
                .onDelete { indexSet in
                    withAnimation {
                        items.remove(atOffsets: indexSet)
                    }
                }
            }
            .animation(.easeInOut, value: items)

            HStack {
                Button("Add") {
                    withAnimation {
                        items.append("Item \(counter)")
                        counter += 1
                    }
                }
                Button("Remove Last") {
                    withAnimation {
                        if !items.isEmpty {
                            items.removeLast()
                        }
                    }
                }
            }
        }
    }
}

8. UIKit Auto Layout with Anchors

Task: Create a custom UIView with two labels positioned using Auto Layout anchors.

Prompt:

Write UIKit code to create a UIView subclass that contains a title label (top, leading, trailing) and a subtitle label (below title, leading, trailing). Use NSLayoutConstraint anchors without storyboards.

Example Result:

import UIKit

class CardView: UIView {
    let titleLabel = UILabel()
    let subtitleLabel = UILabel()

    override init(frame: CGRect) {
        super.init(frame: frame)
        setupViews()
    }

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

    private func setupViews() {
        backgroundColor = .systemBackground
        layer.cornerRadius = 10
        layer.shadowOpacity = 0.2

        titleLabel.translatesAutoresizingMaskIntoConstraints = false
        titleLabel.font = UIFont.boldSystemFont(ofSize: 18)
        subtitleLabel.translatesAutoresizingMaskIntoConstraints = false
        subtitleLabel.font = UIFont.systemFont(ofSize: 14)
        subtitleLabel.textColor = .secondaryLabel
        subtitleLabel.numberOfLines = 0

        addSubview(titleLabel)
        addSubview(subtitleLabel)

        NSLayoutConstraint.activate([
            titleLabel.topAnchor.constraint(equalTo: topAnchor, constant: 10),
            titleLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 10),
            titleLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -10),

            subtitleLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 5),
            subtitleLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 10),
            subtitleLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -10),
            subtitleLabel.bottomAnchor.constraint(lessThanOrEqualTo: bottomAnchor, constant: -10)
        ])
    }
}

Expert Prompts

9. Build a Custom Combine Operator

Task: Create a custom Combine operator that filters out consecutive duplicate values.

Prompt:

Implement a custom Combine operator called 'removeConsecutiveDuplicates' that emits values only when they differ from the previous value. Use a publisher extension.

Example Result:

import Combine

extension Publisher where Output: Equatable {
    func removeConsecutiveDuplicates() -> AnyPublisher<Output, Failure> {
        self
            .scan((Output?.none, Output?.none)) { (previous, current) -> (Output?, Output?) in
                return (previous.1, current)
            }
            .compactMap { (previous, current) in
                guard let current = current else { return nil }
                if previous == current { return nil }
                return current
            }
            .eraseToAnyPublisher()
    }
}

// Usage
let publisher = PassthroughSubject<Int, Never>()
let subscription = publisher
    .removeConsecutiveDuplicates()
    .sink { value in
        print(value) // Prints: 1, 2, 3, 1 (skips duplicate 2)
    }

publisher.send(1)
publisher.send(2)
publisher.send(2) // Duplicate, filtered
publisher.send(3)
publisher.send(1)

10. SwiftUI + Core Data with @FetchRequest

Task: Integrate Core Data with SwiftUI using @FetchRequest property wrapper and add/delete functionality.

Prompt:

Write a SwiftUI view that uses @FetchRequest to display a list of 'Task' entities from Core Data. Include a TextField to add new tasks and swipe-to-delete functionality.

Example Result:

import SwiftUI
import CoreData

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

    @FetchRequest(
        sortDescriptors: [NSSortDescriptor(keyPath: \TaskEntity.createdAt, ascending: true)],
        animation: .default
    ) private var tasks: FetchedResults<TaskEntity>

    @State private var newTaskTitle = ""

    var body: some View {
        NavigationView {
            VStack {
                HStack {
                    TextField("New task", text: $newTaskTitle)
                        .textFieldStyle(RoundedBorderTextFieldStyle())
                    Button("Add") {
                        addTask()
                    }
                    .disabled(newTaskTitle.isEmpty)
                }
                .padding()

                List {
                    ForEach(tasks) { task in
                        Text(task.title ?? "")
                    }
                    .onDelete(perform: deleteTasks)
                }
                .listStyle(PlainListStyle())
            }
            .navigationTitle("Tasks")
        }
    }

    private func addTask() {
        withAnimation {
            let newTask = TaskEntity(context: viewContext)
            newTask.id = UUID()
            newTask.title = newTaskTitle
            newTask.createdAt = Date()
            newTaskTitle = ""
            saveContext()
        }
    }

    private func deleteTasks(offsets: IndexSet) {
        withAnimation {
            offsets.map { tasks[$0] }.forEach(viewContext.delete)
            saveContext()
        }
    }

    private func saveContext() {
        do {
            try viewContext.save()
        } catch {
            print("Error saving: \(error)")
        }
    }
}

11. Combine with UIKit – Reactive TextField

Task: Use Combine to reactively update a label when a text field changes in UIKit.

Prompt:

Create a UIKit view controller with a UITextField and a UILabel. Use Combine to subscribe to text changes (via NotificationCenter or UIControl publisher) and display the reversed text in the label.

Example Result:

import UIKit
import Combine

class ReactiveViewController: UIViewController {
    let textField = UITextField()
    let reversedLabel = UILabel()
    private var cancellables = Set<AnyCancellable>()

    override func viewDidLoad() {
        super.viewDidLoad()
        setupUI()

        // Subscribe to text changes
        NotificationCenter.default.publisher(for: UITextField.textDidChangeNotification, object: textField)
            .compactMap { ($0.object as? UITextField)?.text }
            .map { String($0.reversed()) }
            .assign(to: \.text, on: reversedLabel)
            .store(in: &cancellables)

        // Alternative: using UIControl publisher (iOS 14+)
        textField.publisher(for: .editingChanged)
            .compactMap { ($0 as? UITextField)?.text }
            .map { String($0.reversed()) }
            .assign(to: \.text, on: reversedLabel)
            .store(in: &cancellables)
    }

    private func setupUI() {
        view.backgroundColor = .white
        textField.borderStyle = .roundedRect
        textField.placeholder = "Type something"
        reversedLabel.textAlignment = .center

        let stack = UIStackView(arrangedSubviews: [textField, reversedLabel])
        stack.axis = .vertical
        stack.spacing = 20
        stack.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(stack)

        NSLayoutConstraint.activate([
            stack.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            stack.centerYAnchor.constraint(equalTo: view.centerYAnchor),
            stack.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            stack.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20)
        ])
    }
}

12. Core Data Migration Strategy

Task: Explain and implement lightweight Core Data migration.

Prompt:

Describe what lightweight migration is in Core Data. Provide code to enable automatic lightweight migration when adding a new optional attribute to an existing entity.

Example Result:

Lightweight migration in Core Data handles simple schema changes (adding optional attributes, renaming properties, etc.) automatically. To enable it, set the persistent store options when adding the store.

import CoreData

lazy var persistentContainer: NSPersistentContainer = {
    let container = NSPersistentContainer(name: "ModelName")
    let description = NSPersistentStoreDescription()
    description.shouldMigrateStoreAutomatically = true
    description.shouldInferMappingModelAutomatically = true
    container.persistentStoreDescriptions = [description]

    container.loadPersistentStores { _, error in
        if let error = error as NSError? {
            fatalError("Unresolved error \(error), \(error.userInfo)")
        }
    }
    return container
}()

When you add a new optional attribute (e.g., priority of type Int16 with default value 0) to your Task entity, lightweight migration will handle it automatically on app launch.

13. SwiftUI Custom Shape and Animatable Data

Task: Create a custom animatable shape for a loading spinner.

Prompt:

Define a custom SwiftUI Shape that draws a partial arc (like a pie slice). Make it animatable by conforming to Animatable. Use it in a view that rotates continuously to create a spinner.

Example Result:

import SwiftUI

struct ArcShape: Shape {
    var startAngle: Angle
    var endAngle: Angle

    var animatableData: AnimatablePair<Double, Double> {
        get { AnimatablePair(startAngle.degrees, endAngle.degrees) }
        set {
            startAngle = Angle.degrees(newValue.first)
            endAngle = Angle.degrees(newValue.second)
        }
    }

    func path(in rect: CGRect) -> Path {
        var path = Path()
        path.addArc(center: CGPoint(x: rect.midX, y: rect.midY),
                    radius: rect.width / 2,
                    startAngle: startAngle,
                    endAngle: endAngle,
                    clockwise: false)
        path.addLine(to: CGPoint(x: rect.midX, y: rect.midY))
        path.closeSubpath()
        return path
    }
}

struct SpinnerView: View {
    @State private var rotation = 0.0
    @State private var trimEnd = 0.75

    var body: some View {
        ArcShape(startAngle: .degrees(0), endAngle: .degrees(trimEnd * 360))
            .stroke(Color.blue, lineWidth: 4)
            .rotationEffect(.degrees(rotation))
            .frame(width: 50, height: 50)
            .onAppear {
                withAnimation(Animation.linear(duration: 1).repeatForever(autoreverses: false)) {
                    rotation = 360
                }
                withAnimation(Animation.easeInOut(duration: 0.8).repeatForever(autoreverses: true)) {
                    trimEnd = 0.25
                }
            }
    }
}

14. Performance Optimization with Instruments

Task: Profile Core Data fetch performance using Instruments.

Prompt:

Explain how to use the Core Data template in Instruments to detect slow fetch requests. Provide a checklist for optimizing fetch performance.

Example Result:

To profile Core Data performance:
1. In Xcode, go to Product > Profile (Cmd+I) and select the "Core Data" template.
2. Run your app and perform operations.
3. Look for slow fetch requests (highlighted in red). Tap on a request to see the SQL query.

Optimization checklist:
- Add index to attributes used in predicates (e.g., author.name).
- Use fetchLimit and fetchBatchSize.
- Avoid fetching full objects when only one attribute is needed (use propertiesToFetch).
- Use NSFetchedResultsController for table views.

15. Combine + Core Data – Reactive Fetches

Task: Use Combine to observe Core Data changes and update the UI reactively.

Prompt:

Write a Combine publisher that emits a new array of Core Data objects whenever the context saves. Use this in a SwiftUI view.

Example Result:

import Combine
import CoreData

extension Notification.Name {
    static let coreDataDidSave = Notification.Name("coreDataDidSave")
}

class ReactiveDataService {
    let context: NSManagedObjectContext
    private var cancellables = Set<AnyCancellable>()

    init(context: NSManagedObjectContext) {
        self.context = context
        // Post notification on save
        NotificationCenter.default.publisher(for: .NSManagedObjectContextDidSave, object: context)
            .map { _ in }
            .sink { _ in
                NotificationCenter.default.post(name: .coreDataDidSave, object: nil)
            }
            .store(in: &cancellables)
    }

    func publisher<T: NSManagedObject>(for fetchRequest: NSFetchRequest<T>) -> AnyPublisher<[T], Never> {
        NotificationCenter.default.publisher(for: .coreDataDidSave)
            .prepend(()) // Emit immediately
            .compactMap { _ in
                try? self.context.fetch(fetchRequest)
            }
            .eraseToAnyPublisher()
    }
}

// Usage in SwiftUI view
struct ReactiveTasksView: View {
    @State private var tasks: [TaskEntity] = []
    let service: ReactiveDataService
    let fetchRequest: NSFetchRequest<TaskEntity>

    var body: some View {
        List(tasks, id: \.self) { task in
            Text(task.title ?? "")
        }
        .onReceive(service.publisher(for: fetchRequest)) { tasks in
            self.tasks = tasks
        }
    }
}

Conclusion

These 15 prompts cover the essential building blocks of iOS development with SwiftUI, UIKit, Core Data, and Combine. From basic list views to custom Combine operators and reactive Core Data, each prompt is designed to save you time and inspire better code. The best way to master these frameworks is to modify and combine these examples in your own projects. For a structured learning path with hands-on exercises, check out the comprehensive iOS development courses on asibiont.com. Happy coding!

← All posts

Comments