Введение
Разработка под iOS — это постоянный поиск баланса между производительностью, читаемостью кода и скоростью итераций. SwiftUI обещает декларативную магию, UIKit остаётся стандартом для сложных интерфейсов, Core Data хранит данные миллионов пользователей, а Combine управляет асинхронностью. Но даже опытный iOS-разработчик тратит часы на шаблонные задачи: настройка Core Data стека, обработка ошибок в SwiftUI Preview, создание кастомных анимаций или отладка Combine pipeline.
В этой подборке — 15 проверенных промтов, которые я использую ежедневно. Каждый промт — это конкретная задача, решение с кодом и пояснением. Никакой воды, только рабочие примеры. Вы сможете скопировать их в свой проект или адаптировать под свои нужды.
1. SwiftUI: быстрая настройка Preview с мок-данными
Промт: "Создай SwiftUI Preview для списка задач с использованием мок-объекта TaskViewModel, который возвращает 5 тестовых задач без реального сетевого запроса."
Пример кода:
struct TaskListView_Previews: PreviewProvider {
static var previews: some View {
let mockVM = TaskViewModel()
mockVM.tasks = [
Task(id: 1, title: "Купить молоко", isDone: false),
Task(id: 2, title: "Написать статью", isDone: true)
]
return TaskListView(viewModel: mockVM)
}
}
Пояснение: Этот промт помогает избежать зависимости от реальных данных при разработке UI. Вместо того чтобы каждый раз запускать симулятор, вы получаете мгновенную обратную связь в Canvas. Для продакшена используйте фабрику моков.
2. UIKit: кастомный UICollectionView с CompositionalLayout
Промт: "Напиши UICollectionView с CompositionalLayout, который отображает две секции: первая — горизонтальный список (carousel), вторая — вертикальная сетка 2 колонки. Ячейки — UINib."
Пример кода:
let layout = UICollectionViewCompositionalLayout { sectionIndex, _ in
if sectionIndex == 0 {
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.8), heightDimension: .fractionalHeight(1.0))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.8), heightDimension: .absolute(200))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
let section = NSCollectionLayoutSection(group: group)
section.orthogonalScrollingBehavior = .continuous
return section
} else {
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.5), heightDimension: .absolute(150))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
item.contentInsets = NSDirectionalEdgeInsets(top: 4, leading: 4, bottom: 4, trailing: 4)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .absolute(150))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
return NSCollectionLayoutSection(group: group)
}
}
Пояснение: CompositionalLayout гибок, но его синтаксис легко забыть. Этот промт — шпаргалка для создания сложных макетов без Storyboard.
3. Core Data: настройка стека с NSPersistentContainer
Промт: "Создай синглтон CoreDataManager с NSPersistentContainer, поддержкой миграций и фоновым контекстом для записи."
Пример кода:
final class CoreDataManager {
static let shared = CoreDataManager()
let container: NSPersistentContainer
private init() {
container = NSPersistentContainer(name: "Model")
container.loadPersistentStores { _, error in
if let error = error { fatalError("Ошибка Core Data: \(error)") }
}
container.viewContext.automaticallyMergesChangesFromParent = true
}
var backgroundContext: NSManagedObjectContext {
return container.newBackgroundContext()
}
}
Пояснение: Этот промт — стандартный boilerplate для Core Data. Используйте backgroundContext для записи, чтобы не блокировать UI.
4. Combine: обработка поискового запроса с debounce
Промт: "Реализуй поиск в UITextField с помощью Combine: debounce 300 мс, отмена предыдущего запроса и обновление результатов на главном потоке."
Пример кода:
import Combine
class SearchViewModel: ObservableObject {
@Published var query: String = ""
@Published var results: [String] = []
private var cancellables = Set<AnyCancellable>()
init() {
$query
.debounce(for: .milliseconds(300), scheduler: RunLoop.main)
.removeDuplicates()
.sink { [weak self] text in
// Здесь ваш сетевой запрос
self?.results = ["Результат для: \(text)"]
}
.store(in: &cancellables)
}
}
Пояснение: debounce сокращает количество запросов при быстром вводе. removeDuplicates предотвращает повторные запросы с тем же текстом.
5. SwiftUI: кастомный модификатор для загрузки изображений
Промт: "Создай ViewModifier для асинхронной загрузки изображения по URL с placeholder и обработкой ошибок."
Пример кода:
struct AsyncImageModifier: ViewModifier {
let url: URL?
@State private var image: UIImage? = nil
func body(content: Content) -> some View {
content.overlay(
Group {
if let image = image {
Image(uiImage: image).resizable()
} else {
ProgressView()
}
}
)
.task {
guard let url = url else { return }
// Загрузка через URLSession
}
}
}
Пояснение: В SwiftUI нет встроенного AsyncImage до iOS 15. Этот модификатор работает на iOS 14+.
6. UIKit: анимация перехода между экранами
Промт: "Создай кастомный UIViewControllerAnimatedTransitioning для перехода с эффектом scale и fade."
Пример кода:
class ScaleTransition: NSObject, UIViewControllerAnimatedTransitioning {
let duration = 0.4
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return duration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let toView = transitionContext.view(forKey: .to) else { return }
toView.transform = CGAffineTransform(scaleX: 0.5, y: 0.5)
toView.alpha = 0
transitionContext.containerView.addSubview(toView)
UIView.animate(withDuration: duration) {
toView.transform = .identity
toView.alpha = 1
} completion: { _ in
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
}
}
Пояснение: Кастомные переходы улучшают UX. Используйте в UINavigationControllerDelegate.
7. Core Data: запрос с фильтрацией через NSPredicate
Промт: "Напиши fetch request для Core Data, который возвращает задачи с датой создания сегодня и статусом 'не выполнено'."
Пример кода:
let request: NSFetchRequest<Task> = Task.fetchRequest()
let calendar = Calendar.current
let startOfDay = calendar.startOfDay(for: Date())
let endOfDay = calendar.date(byAdding: .day, value: 1, to: startOfDay)!
request.predicate = NSPredicate(format: "createdAt >= %@ AND createdAt < %@ AND isDone == NO", startOfDay as NSDate, endOfDay as NSDate)
Пояснение: NSPredicate — мощный инструмент. Избегайте строковых форматов, используйте %@ для безопасности.
8. SwiftUI: EnvironmentObject для темы
Промт: "Реализуй переключение светлой/тёмной темы через EnvironmentObject и @Environment."
Пример кода:
class ThemeManager: ObservableObject {
@Published var isDarkMode: Bool = false
}
struct ContentView: View {
@EnvironmentObject var theme: ThemeManager
var body: some View {
Text("Привет")
.preferredColorScheme(theme.isDarkMode ? .dark : .light)
}
}
Пояснение: EnvironmentObject упрощает передачу данных через иерархию. Не забывайте inject в .environmentObject().
9. Combine: объединение двух Publishers
Промт: "Объедини два Publisher: один для ввода логина, второй для пароля, и создай Publisher для валидации формы."
Пример кода:
let loginPublisher = $login.map { $0.count >= 3 }
let passwordPublisher = $password.map { $0.count >= 6 }
let isFormValid = Publishers.CombineLatest(loginPublisher, passwordPublisher)
.map { $0 && $1 }
.eraseToAnyPublisher()
Пояснение: CombineLatest эмитит новое значение каждый раз, когда меняется любой из входных publisher.
10. UIKit: работа с UIScrollView и Keyboard
Промт: "Реализуй автоматический скролл UIScrollView при появлении клавиатуры с использованием NSNotification."
Пример кода:
NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillShowNotification, object: nil, queue: .main) { notification in
guard let keyboardFrame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else { return }
scrollView.contentInset.bottom = keyboardFrame.height
scrollView.verticalScrollIndicatorInsets.bottom = keyboardFrame.height
}
Пояснение: Это базовый паттерн для форм. Не забудьте отписаться от уведомлений в deinit.
11. SwiftUI: GeometryReader для адаптивного UI
Промт: "Используй GeometryReader для создания сетки из квадратных ячеек, ширина которых равна половине ширины экрана."
Пример кода:
GeometryReader { geometry in
let width = (geometry.size.width - 20) / 2
LazyVGrid(columns: [GridItem(.fixed(width)), GridItem(.fixed(width))]) {
ForEach(0..<10) { index in
Rectangle().frame(width: width, height: width)
}
}
}
Пояснение: GeometryReader занимает всё доступное пространство. Используйте с осторожностью, чтобы избежать лишних перерисовок.
12. Core Data: миграция через Mapping Model
Промт: "Создай Mapping Model для Core Data, если изменилось имя атрибута 'name' на 'fullName'."
Пример кода:
// В Mapping Model.xcmappingmodel задайте:
// Source: name, Destination: fullName
// В коде: NSMappingModel(from: [Bundle.main], forSourceModel: sourceModel, destinationModel: destinationModel)
Пояснение: Mapping Model позволяет переименовывать поля без потери данных. Альтернатива — лёгкая миграция (NSInferMappingModelAutomaticallyOption).
13. SwiftUI: кастомный progress bar с анимацией
Промт: "Создай анимированный progress bar с градиентом и закруглёнными краями."
Пример кода:
struct ProgressBar: View {
@Binding var progress: Double
var body: some View {
GeometryReader { geometry in
ZStack(alignment: .leading) {
RoundedRectangle(cornerRadius: 8).fill(Color.gray.opacity(0.3))
RoundedRectangle(cornerRadius: 8)
.fill(LinearGradient(gradient: Gradient(colors: [.blue, .purple]), startPoint: .leading, endPoint: .trailing))
.frame(width: geometry.size.width * CGFloat(progress))
.animation(.easeInOut, value: progress)
}
}
.frame(height: 16)
}
}
Пояснение: .animation(.easeInOut) плавно анимирует изменение progress. Используйте с @State.
14. Combine: обработка ошибок с catch
Промт: "Обработай ошибку в Combine pipeline с помощью catch и верни запасное значение."
Пример кода:
URLSession.shared.dataTaskPublisher(for: url)
.tryMap { data, response in
guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
throw URLError(.badServerResponse)
}
return data
}
.catch { error in
Just(Data())
}
.sink(receiveCompletion: { _ in }, receiveValue: { data in
// обработка
})
Пояснение: catch заменяет ошибку на fallback publisher. Для логирования используйте handleEvents.
15. UIKit: кастомный UILabel с Insets
Промт: "Создай UILabel с отступами внутри (padding) через UIEdgeInsets."
Пример кода:
class InsetLabel: UILabel {
var insets = UIEdgeInsets(top: 4, left: 8, bottom: 4, right: 8)
override func drawText(in rect: CGRect) {
super.drawText(in: rect.inset(by: insets))
}
override var intrinsicContentSize: CGSize {
let size = super.intrinsicContentSize
return CGSize(width: size.width + insets.left + insets.right, height: size.height + insets.top + insets.bottom)
}
}
Пояснение: В UIKit нет встроенного padding для UILabel. Этот класс решает задачу без Auto Layout хаков.
Заключение
Эти 15 промтов покрывают 80% ежедневных задач iOS-разработчика: от настройки Core Data до анимаций в UIKit. Сохраните эту статью как шпаргалку — каждый промт сэкономит вам от 15 минут до часа. Попробуйте адаптировать их под свои проекты: замените мок-данные на реальные, добавьте свои модификаторы или расширьте обработку ошибок.
А если хотите ещё больше готовых решений — следите за обновлениями блога Asibiont. Мы регулярно публикуем подборки промтов для Swift, SwiftUI и UIKit.
Полезные ссылки
- Apple Developer Documentation: Core Data
- Hacking with Swift: SwiftUI
- Ray Wenderlich: Combine Tutorial
Комментарии