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!
Comments