15 Prompts for iOS Developers: SwiftUI, UIKit, Core Data, and Combine
As an iOS developer, you wear many hats: architect, UI engineer, and data modeler. The good news is that AI assistants can help you move faster when you ask the right questions. This collection of 15 prompts covers the three fundamental pillars of iOS development — SwiftUI, UIKit, and Core Data — plus Combine for reactive programming. Each prompt is designed to be copied and pasted into an AI assistant, then adapted to your own code.
Why these specific topics? SwiftUI and UIKit are the two UI frameworks Apple actively supports, and many production apps still contain both. Core Data is the most common persistence layer on Apple platforms, and Combine helps you connect UI and data in a reactive way. By mastering prompts in these areas, you'll slash your boilerplate time and avoid common pitfalls. All examples are based on Apple's official documentation and current best practices, as of Xcode 15 and iOS 17.
How to Use These Prompts
Be specific. Include the exact API, generic constraints, and the error message you're seeing. The more context you provide, the better the AI response. After generating code, always verify against the Apple Developer Documentation and run your tests. Remember, AI-generated code is a starting point, not a substitute for understanding.
SwiftUI Prompts
1. Generate a Reusable Custom View
Task: Create a custom ButtonStyle that adds a shadow, icon, and press animation.
Prompt:
Generate a SwiftUI View called ProminentButtonStyle that conforms to ButtonStyle. It should use a rounded rectangle background, a subtle shadow, a custom foreground color, and scale the button when pressed.
Example result:
struct ProminentButtonStyle: ButtonStyle {
var backgroundColor: Color
func makeBody(configuration: Configuration) -> some View {
configuration.label
.padding()
.background(RoundedRectangle(cornerRadius: 12).fill(backgroundColor))
.foregroundColor(.white)
.shadow(color: backgroundColor.opacity(0.4), radius: 5, x: 0, y: 2)
.scaleEffect(configuration.isPressed ? 0.95 : 1)
.animation(.easeOut, value: configuration.isPressed)
}
}
Use it with .buttonStyle(ProminentButtonStyle(backgroundColor: .blue)).
2. Convert UIKit to SwiftUI with UIViewRepresentable
Task: Wrap a UIActivityIndicatorView for use in SwiftUI.
Prompt:
Create a SwiftUI wrapper for UIActivityIndicatorView using UIViewRepresentable. It should expose a Binding<Bool> called isAnimating and accept a UIActivityIndicatorView.Style.
Example result:
struct ActivityIndicator: UIViewRepresentable {
@Binding var isAnimating: Bool
var style: UIActivityIndicatorView.Style
func makeUIView(context: Context) -> UIActivityIndicatorView {
UIActivityIndicatorView(style: style)
}
func updateUIView(_ uiView: UIActivityIndicatorView, context: Context) {
isAnimating ? uiView.startAnimating() : uiView.stopAnimating()
}
}
3. Debug a SwiftUI Layout Problem
Task: Understand why a Text view gets truncated in landscape and fix it.
Prompt:
My SwiftUI HStack has a Text and a Spacer, but the Text gets truncated when the device is in landscape. Explain why this happens and provide a modified layout using layoutPriority and minimumScaleFactor.
Example result:
HStack {
Text("Important message")
.lineLimit(2)
.minimumScaleFactor(0.5)
.layoutPriority(1)
Spacer()
}
The issue is that the Spacer expands and leaves less room for the Text. Setting layoutPriority(1) gives the text higher compression resistance, and minimumScaleFactor lets it shrink gracefully.
4. Create a ViewModel with Combine
Task: Build an ObservableObject that performs a network request and publishes results.
Prompt:
Write a Swift Combine ViewModel that fetches a list of articles from a URL, uses a custom decoder, and exposes @Published properties for loading state, results, and error message.
Example result:
final class ArticleViewModel: ObservableObject {
@Published var articles: [Article] = []
@Published var isLoading = false
@Published var errorMessage: String?
private var cancellable: AnyCancellable?
func fetchArticles() {
guard let url = URL(string: "https://example.com/articles") else { return }
isLoading = true
cancellable = URLSession.shared.dataTaskPublisher(for: url)
.map(\.data)
.decode(type: [Article].self, decoder: JSONDecoder())
.receive(on: DispatchQueue.main)
.sink { [weak self] completion in
self?.isLoading = false
if case .failure(let error) = completion {
self?.errorMessage = error.localizedDescription
}
} receiveValue: { [weak self] articles in
self?.articles = articles
}
}
}
5. Add a Custom Animatable Modifier
Task: Create a modifier that rotates a view continuously.
Prompt:
Create a ViewModifier that applies an infinite rotation animation. Use Animation.linear.repeatForever and a TimelineView for the rotation angle.
Example result:
struct ContinuousRotationModifier: ViewModifier {
@State private var angle: Double = 0
func body(content: Content) -> some View {
TimelineView(.animation) { timeline in
content.rotationEffect(.degrees(angle))
}
.onAppear {
withAnimation(.linear(duration: 2).repeatForever(autoreverses: false)) {
angle = 360
}
}
}
}
UIKit Prompts
6. Set Up Auto Layout Programmatically
Task: Create a view with constraints without Interface Builder.
Prompt:
Create a UIView subclass that contains a UILabel and a UIButton. Use Auto Layout anchors to place the label at the top and the button below it, with 16pt padding.
Example result:
final class CardView: UIView {
private let label = UILabel()
private let button = UIButton()
override init(frame: CGRect) {
super.init(frame: frame)
setupLayout()
}
required init?(coder: NSCoder) { fatalError() }
private func setupLayout() {
[label, button].forEach {
$0.translatesAutoresizingMaskIntoConstraints = false
addSubview($0)
}
NSLayoutConstraint.activate([
label.topAnchor.constraint(equalTo: topAnchor, constant: 16),
label.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16),
label.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16),
button.topAnchor.constraint(equalTo: label.bottomAnchor, constant: 12),
button.centerXAnchor.constraint(equalTo: centerXAnchor),
button.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -16)
])
}
}
7. Use Modern Diffable Data Source
Task: Implement a collection view with animated updates.
Prompt:
Write code for a UIViewController that uses UICollectionViewDiffableDataSource and UICollectionViewCompositionalLayout to display a list of items grouped by categories. Include a method to apply a new snapshot.
Example result:
enum Section { case main }
lazy var dataSource = UICollectionViewDiffableDataSource<Section, Item>(collectionView: collectionView) { (collectionView, indexPath, item) -> UICollectionViewCell? in
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
var content = cell.defaultContentConfiguration()
content.text = item.name
cell.contentConfiguration = content
return cell
}
func applySnapshot(items: [Item]) {
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
snapshot.appendSections([.main])
snapshot.appendItems(items)
dataSource.apply(snapshot, animatingDifferences: true)
}
8. Build a Custom UIControl
Task: Create a reusable stepper control using UIControl and target-action.
Prompt:
Create a UIControl subclass called CounterControl that has a value property, plus and minus buttons, and emits a UIControl.Event.valueChanged. Use UIAction for the button targets.
Example result:
final class CounterControl: UIControl {
var value: Int = 0 {
didSet { sendActions(for: .valueChanged) }
}
private let plusButton = UIButton(type: .system)
private let minusButton = UIButton(type: .system)
override init(frame: CGRect) {
super.init(frame: frame)
plusButton.addAction(UIAction { [weak self] _ in self?.value += 1 }, for: .touchUpInside)
minusButton.addAction(UIAction { [weak self] _ in self?.value -= 1 }, for: .touchUpInside)
// Add layout constraints here
}
required init?(coder: NSCoder) { fatalError() }
}
9. Replace Delegates with Combine Future
Task: Convert a completion-based URLSession call to a Future.
Prompt:
Convert this completion-based URLSession request to a Combine Future so it can be used with sink: session.dataTask(with: url, completionHandler:).
Example result:
func fetchData(for url: URL) -> AnyPublisher<Data, Error> {
Future<Data, Error> { promise in
URLSession.shared.dataTask(with: url) { data, _, error in
if let error = error {
promise(.failure(error))
} else if let data = data {
promise(.success(data))
} else {
promise(.failure(URLError(.badServerResponse)))
}
}.resume()
}.eraseToAnyPublisher()
}
10. Self-Sizing Table View Cells
Task: Make a UITableViewCell automatically resize with multiline labels.
Prompt:
Set up a UITableViewCell with a multiline UILabel that expands the cell height automatically. Show the required constraints and table view setup.
Example result:
// In the cell:
label.numberOfLines = 0
label.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
label.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8),
label.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -8),
label.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
label.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16)
])
// In viewDidLoad:
tableView.estimatedRowHeight = 44
tableView.rowHeight = UITableView.automaticDimension
Core Data and Combine Prompts
11. Build a Core Data Stack Manually
Task: Initialize a persistent container without Xcode's generated template.
Prompt:
Write a CoreDataManager that lazily creates an NSPersistentCloudKitContainer, loads persistent stores, and handles migration errors.
Example result:
class CoreDataManager {
static let shared = CoreDataManager()
let container: NSPersistentCloudKitContainer
init() {
container = NSPersistentCloudKitContainer(name: "MyApp")
container.loadPersistentStores { _, error in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
}
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
container.viewContext.automaticallyMergesChangesFromParent = true
}
}
12. Fetch with Predicates and Sorting
Task: Request entities with a filter and sort descriptor.
Prompt:
Create an NSFetchRequest for a Track entity that returns tracks with duration > 60 seconds, sorted by releaseDate descending, and limit to 20 results.
Example result:
let request: NSFetchRequest<Track> = Track.fetchRequest()
request.predicate = NSPredicate(format: "duration > 60")
request.sortDescriptors = [NSSortDescriptor(keyPath: \Track.releaseDate, ascending: false)]
request.fetchLimit = 20
let tracks = try context.fetch(request)
13. Use Batch Updates and Deletes
Task: Efficiently update many objects without loading them into memory.
Prompt:
Use NSBatchUpdateRequest to set all Article.archived to true for articles older than 30 days. Then use NSBatchDeleteRequest to delete all archived articles that are older than a year.
Example result:
let cutoff = Date().addingTimeInterval(-30 * 24 * 3600)
let update = NSBatchUpdateRequest(entityName: "Article")
update.predicate = NSPredicate(format: "date < %@", cutoff)
update.propertiesToUpdate = ["archived": true]
update.resultType = .updatedObjectsCountResultType
try context.execute(update)
let yearCutoff = Date().addingTimeInterval(-365 * 24 * 3600)
let deleteRequest = NSBatchDeleteRequest(fetchRequest: NSFetchRequest<NSFetchRequestResult>(entityName: "Article"))
deleteRequest.predicate = NSPredicate(format: "archived == true AND date < %@", yearCutoff)
try context.execute(deleteRequest)
14. Make NSManagedObject Codable
Task: Use Codable with Core Data models.
Prompt:
Show how to make an existing NSManagedObject subclass conform to Codable using a CodingKeys enum and a context that joins existing objects by unique identifier.
Example result:
extension Track: Codable {
enum CodingKeys: String, CodingKey { case title, duration, releaseDate }
convenience init(from decoder: Decoder) throws {
guard let context = decoder.userInfo[.context] as? NSManagedObjectContext else {
fatalError()
}
self.init(context: context)
let container = try decoder.container(keyedBy: CodingKeys.self)
title = try container.decode(String.self, forKey: .title)
duration = try container.decode(Double.self, forKey: .duration)
releaseDate = try container.decode(Date.self, forKey: .releaseDate)
}
}
15. Process Persistent History Tracking
Task: Sync changes across app groups or extensions.
Prompt:
Configure NSPersistentContainer to enable persistent history tracking and add a method to process incoming transactions into the view context.
Example result:
container.persistentStoreDescriptions.forEach {
$0.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
}
let historyRequest = NSPersistentHistoryChangeRequest.fetchHistory(after: lastTimestamp)
let result = try context.execute(historyRequest) as? NSPersistentHistoryResult
// Then merge the transactions into the view context and update lastTimestamp.
Best Practices for Writing Prompts
| Prompt Type | Do This | Avoid This |
|---|---|---|
| Code generation | Specify the exact API and iOS version | Asking for a whole app in one prompt |
| Debugging | Include the full error message and relevant code | Making assumptions about system state |
| Refactoring | State the desired outcome or constraint | Asking to "improve" without criteria |
Always validate AI-generated code against the official Apple documentation and run your test suite. For deeper examples, check out Hacking with Swift and WWDC sessions on Core Data and SwiftUI.
Conclusion
The key to getting value from prompts is iterating. Use the generated code as a first draft, modify it to match your architecture, and commit it to memory by reading Apple's docs. With regular practice, you'll find yourself reaching for these prompts less often because you'll understand the underlying APIs. Start with the SwiftUI prompts if you're modernizing a new feature, or the Core Data prompts if you're about to build an offline-first app. Bookmark this guide, and happy coding.
Comments