{
"title": "15 промтов для iOS-разработки: SwiftUI, UIKit и Core Data",
"content": "## Введение\n\niOS-разработка — одна из самых быстрорастущих специализаций в мобильной инженерии. С выходом SwiftUI (iOS 13+) и постоянным совершенствованием UIKit у разработчиков появилось ещё больше инструментов для создания производительных и красивых приложений. Однако даже опытные инженеры тратят часы на рутинные задачи: генерацию boilerplate-кода, оптимизацию запросов к Core Data, настройку анимаций. Правильно составленные промты для нейросетей (например, GPT-4 или Claude) могут сократить это время на 40–60%.\n\nВ этой подборке — 15 проверенных промтов, охватывающих три ключевые области: SwiftUI (декларативные интерфейсы), UIKit (императивные контроллеры) и Core Data (постоянное хранилище). Каждый кейс содержит реальную задачу, точный промт и пример результата. Вы сможете сразу скопировать код и адаптировать под свой проект.\n\nВажно: промты написаны так, чтобы нейросеть выдавала актуальный код под Swift 5.9+ и iOS 17+. При использовании более старых версий явно укажите их в запросе.\n\n## Базовые промты (1–5)\n\n### 1. Генерация SwiftUI-вью с Form и валидацией\nЗадача: Создать экран настроек с формой, содержащей текстовые поля, переключатели и кнопку сохранения. Валидация — обязательные поля подсвечиваются красным.\n\nПромт:\n\nНапиши SwiftUI View для iOS 17. Это форма настроек пользователя с полями: имя (String), email (Email), уведомления (Bool). Добавь валидацию: если имя пустое или email не валидный — показывай красную рамку и сообщение об ошибке под полем. Кнопка «Сохранить» сериализует данные в UserDefaults. Используй @State и @FocusState.\n\n\nПример результата:\nswift\nstruct SettingsView: View {\n @State private var name = \"\"\n @State private var email = \"\"\n @State private var notifications = true\n @FocusState private var focusedField: Field?\n \n enum Field: Hashable { case name, email }\n \n private var isFormValid: Bool {\n !name.trimmingCharacters(in: .whitespaces).isEmpty &&\n email.contains(\"@\") && email.contains(\".\")\n }\n \n var body: some View {\n NavigationStack {\n Form {\n TextField(\"Имя\", text: $name)\n .focused($focusedField, equals: .name)\n if name.isEmpty {\n Text(\"Имя обязательно\")\n .foregroundColor(.red)\n .font(.caption)\n }\n TextField(\"Email\", text: $email)\n .focused($focusedField, equals: .email)\n .keyboardType(.emailAddress)\n .autocapitalization(.none)\n if !email.contains(\"@\") && !email.isEmpty {\n Text(\"Введите корректный email\")\n .foregroundColor(.red)\n .font(.caption)\n }\n Toggle(\"Уведомления\", isOn: $notifications)\n Button(\"Сохранить\") {\n guard isFormValid else { return }\n UserDefaults.standard.set(name, forKey: \"userName\")\n UserDefaults.standard.set(email, forKey: \"userEmail\")\n UserDefaults.standard.set(notifications, forKey: \"userNotifications\")\n }\n .disabled(!isFormValid)\n }\n .navigationTitle(\"Настройки\")\n }\n }\n}\n\nКомментарий: Промт чётко описывает требования (iOS 17, валидация, UserDefaults). Нейросеть возвращает готовый код с focus management — это хорошая практика для UX.\n\n### 2. Кастомная UIKit-ячейка для UICollectionView\nЗадача: Создать UICollectionViewCell с картинкой и двумя лейблами для ленты товаров.\n\nПромт:\n\nСоздай UICollectionViewCell (Objective-C/Swift 5) для iOS 16+. Ячейка содержит UIImageView (240x240, contentMode .scaleAspectFill) и два UILabel: название (16pt, bold) и цена (14pt, secondary color). Добавь тень и скруглённые углы. Зарегистрируй ячейку и пропиши метод dequeueReusableCell.\n\n\nПример результата (фрагмент):\nswift\nclass ProductCell: UICollectionViewCell {\n static let reuseIdentifier = \"ProductCell\"\n \n private let imageView: UIImageView = {\n let iv = UIImageView()\n iv.contentMode = .scaleAspectFill\n iv.clipsToBounds = true\n iv.layer.cornerRadius = 12\n iv.translatesAutoresizingMaskIntoConstraints = false\n return iv\n }()\n \n private let titleLabel: UILabel = {\n let lbl = UILabel()\n lbl.font = UIFont.systemFont(ofSize: 16, weight: .bold)\n lbl.translatesAutoresizingMaskIntoConstraints = false\n return lbl\n }()\n \n private let priceLabel: UILabel = {\n let lbl = UILabel()\n lbl.font = UIFont.systemFont(ofSize: 14)\n lbl.textColor = .secondaryLabel\n lbl.translatesAutoresizingMaskIntoConstraints = false\n return lbl\n }()\n \n override init(frame: CGRect) {\n super.init(frame: frame)\n setupUI()\n }\n \n required init?(coder: NSCoder) { fatalError(\"init(coder:) not implemented\") }\n \n private func setupUI() {\n contentView.addSubview(imageView)\n contentView.addSubview(titleLabel)\n contentView.addSubview(priceLabel)\n \n NSLayoutConstraint.activate([\n imageView.topAnchor.constraint(equalTo: contentView.topAnchor),\n imageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),\n imageView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),\n imageView.heightAnchor.constraint(equalToConstant: 240),\n titleLabel.topAnchor.constraint(equalTo: imageView.bottomAnchor, constant: 8),\n titleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 8),\n priceLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 4),\n priceLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 8),\n priceLabel.bottomAnchor.constraint(lessThanOrEqualTo: contentView.bottomAnchor, constant: -8)\n ])\n \n contentView.layer.cornerRadius = 16\n contentView.layer.shadowOpacity = 0.2\n contentView.layer.shadowRadius = 4\n contentView.backgroundColor = .systemBackground\n }\n}\n\nКомментарий: Промт явно указывает размеры, цвета и особенности тени. Нейросеть правильно использует translatesAutoresizingMaskIntoConstraints = false и системные цвета — это важно для Dark Mode.\n\n### 3. Простой запрос к Core Data с предикатом\nЗадача: Найти все объекты сущности «User», у которых age > 18 и name содержит «Smith».\n\nПромт:\n\nНапиши функцию Swift, которая выполняет NSFetchedResultsController запрос к Core Data сущности User (атрибуты: name: String, age: Int). Предикат: NSCompoundPredicate(andPredicateWithSubpredicates: [NSPredicate(format: \"age > %d\", 18), NSPredicate(format: \"name CONTAINS %@\", \"Smith\")]). Сортировка по name по возрастанию. Обработай возможные ошибки fetch.\n\n\nПример результата:\nswift\nimport CoreData\n\nfunc fetchAdultSmiths() -> [User] {\n let context = PersistenceController.shared.container.viewContext\n let request: NSFetchRequest<User> = User.fetchRequest()\n \n let agePredicate = NSPredicate(format: \"age > %d\", 18)\n let namePredicate = NSPredicate(format: \"name CONTAINS %@\", \"Smith\")\n request.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [agePredicate, namePredicate])\n \n let sortDescriptor = NSSortDescriptor(key: \"name\", ascending: true)\n request.sortDescriptors = [sortDescriptor]\n \n do {\n return try context.fetch(request)\n } catch {\n print(\"Fetch failed: \\(error.localizedDescription)\")\n return []\n }\n}\n\nКомментарий: Промт уточняет тип предиката и сортировку — это даёт точный результат. NSCompoundPredicate позволяет комбинировать условия.\n\n### 4. SwiftUI модификатор для загрузки изображения по URL\nЗадача: Создать асинхронную загрузку картинки с placeholder и обработкой ошибки.\n\nПромт:\n\nНапиши SwiftUI View под названием AsyncImageView, которая принимает URL строку. Пока изображение загружается — показывай ProgressView. Если ошибка — иконку exclamationmark.triangle. Используй ссылочный кэш через NSCache. Совместимость с iOS 15+.\n\n\nПример результата:\nswift\nstruct AsyncImageView: View {\n let urlString: String\n @State private var image: UIImage?\n @State private var isLoading = true\n @State private var errorOccurred = false\n \n private static let cache = NSCache<NSString, UIImage>()\n \n var body: some View {\n Group {\n if let image = image {\n Image(uiImage: image)\n .resizable()\n .scaledToFit()\n } else if errorOccurred {\n Image(systemName: \"exclamationmark.triangle\")\n .font(.largeTitle)\n .foregroundColor(.orange)\n } else {\n ProgressView()\n }\n }\n .onAppear {\n loadImage()\n }\n }\n \n private func loadImage() {\n guard let url = URL(string: urlString) else {\n errorOccurred = true\n isLoading = false\n return\n }\n \n if let cached = Self.cache.object(forKey: urlString as NSString) {\n image = cached\n isLoading = false\n return\n }\n \n URLSession.shared.dataTask(with: url) { data, _, error in\n DispatchQueue.main.async {\n defer { isLoading = false }\n guard let data = data, let uiImage = UIImage(data: data), error == nil else {\n errorOccurred = true\n return\n }\n Self.cache.setObject(uiImage, forKey: urlString as NSString)\n image = uiImage\n }\n }.resume()\n }\n}\n\nКомментарий: Промт требует кэш — это важно для производительности. Используется NSCache, который автоматически освобождает память при нехватке.\n\n### 5. Инициализация Core Data стека (NSPersistentContainer)\nЗадача: Создать менеджер Core Data с поддержкой миграций и приватным контекстом для фоновой записи.\n\nПромт:\n\nНапиши class PersistenceController с синглтоном. Используй NSPersistentContainer с именем модели «AppModel». Включи автоматическую миграцию (shouldMigrateStoreAutomatically) и легковесную миграцию (shouldInferMappingModelAutomatically). Добавь вычисляемое свойство backgroundContext (NSManagedObjectContext с .privateQueueConcurrencyType), которое сохраняется в родительский viewContext.\n\n\nПример результата:\nswift\nimport CoreData\n\nclass PersistenceController {\n static let shared = PersistenceController()\n let container: NSPersistentContainer\n \n var backgroundContext: NSManagedObjectContext {\n let ctx = container.newBackgroundContext()\n ctx.parent = container.viewContext\n ctx.automaticallyMergesChangesFromParent = true\n return ctx\n }\n \n private init() {\n container = NSPersistentContainer(name: \"AppModel\")\n guard let storeDescription = container.persistentStoreDescriptions.first else {\n fatalError(\"No store description\")\n }\n storeDescription.setOption(true as NSNumber,\n forKey: NSPersistentHistoryTrackingKey)\n storeDescription.setOption(true as NSNumber,\n forKey: NSMigratePersistentStoresAutomaticallyOption)\n storeDescription.setOption(true as NSNumber,\n forKey: NSInferMappingModelAutomaticallyOption)\n \n container.loadPersistentStores { _, error in\n if let error = error as NSError? {\n fatalError(\"Failed to load store: \\(error), \\(error.userInfo)\")\n }\n }\n container.viewContext.automaticallyMergesChangesFromParent = true\n }\n}\n\nКомментарий: Промт явно указывает на миграции и фоновый контекст — это стандарт для продакшн-приложений. Использование automaticallyMergesChangesFromParent предотвращает устаревшие данные.\n\n## Продвинутые промты (6–11)\n\n### 6. Анимация списка в SwiftUI с matchedGeometryEffect\nЗадача: Сделать анимацию перехода между двумя вью при нажатии на элемент списка.\n\nПромт:\n\nИспользуя matchedGeometryEffect, создай анимацию расширения карточки товара (с изображением, заголовком, ценой) в полноэкранную детальную вью. Namespace общий для списка и детализации. Используй iOS 17+, withAnimation(.spring).\n\n\nПример результата (основная логика):\nswift\nstruct CardView: View {\n let item: Item\n @Binding var selectedItem: Item?\n @Namespace var namespace\n \n var body: some View {\n VStack {\n // уменьшенная карточка\n if selectedItem != item {\n VStack {\n Image(item.imageName)\n .matchedGeometryEffect(id: \"image_\\(item.id)\", in: namespace)\n .frame(width: 100, height: 100)\n Text(item.title)\n .matchedGeometryEffect(id: \"title_\\(item.id)\", in: namespace)\n }\n .onTapGesture {\n withAnimation(.spring(response: 0.6, dampingFraction: 0.8)) {\n selectedItem = item\n }\n }\n } else {\n // увеличенная детальная вью\n VStack {\n Image(item.imageName)\n .matchedGeometryEffect(id: \"image_\\(item.id)\", in: namespace)\n .frame(maxWidth: .infinity, maxHeight: 300)\n Text(item.title)\n .matchedGeometryEffect(id: \"title_\\(item.id)\", in: namespace)\n .font(.largeTitle)\n Text(item.description)\n Button(\"Close\") {\n withAnimation(.spring) { selectedItem = nil }\n }\n }\n .padding()\n }\n }\n }\n}\n\nКомментарий: matchedGeometryEffect — мощный инструмент для плавных переходов без лишнего кода. Важно использовать единый namespace.\n\n### 7. UITableView с динамической высотой ячеек (self-sizing)\nЗадача: Реализовать таблицу, где каждая ячейка подстраивается под контент (например, многострочный текст).\n\nПромт:\n\nСоздай UITableViewController с кастомными ячейками, содержащими UILabel с numberOfLines = 0. Установи tableView.rowHeight = UITableView.automaticDimension и estimatedRowHeight = 100. Покажи, как правильно настроить constraints внутри ячейки для автоматического расчёта высоты. Используй Swift 5, iOS 15+.\n\n\nПример результата (фрагмент viewDidLoad):\nswift\ntableView.register(CustomCell.self, forCellReuseIdentifier: \"cell\")\ntableView.rowHeight = UITableView.automaticDimension\ntableView.estimatedRowHeight = 100\n\n// Внутри CustomCell:\noverride init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {\n super.init(style: style, reuseIdentifier: reuseIdentifier)\n contentView.addSubview(label)\n label.translatesAutoresizingMaskIntoConstraints = false\n NSLayoutConstraint.activate([\n label.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8),\n label.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),\n label.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16),\n label.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -8)\n ])\n label.numberOfLines = 0\n}\n\nКомментарий: Промт включает estimatedRowHeight — это важный аспект для производительности при скролле.\n\n### 8. Relationship в Core Data (один ко многим) с cascading delete\nЗадача: Определить сущности «Author» и «Book», где один автор может иметь много книг. При удалении автора удаляются все его книги.\n\nПромт:\n\nСоздай две сущности Core Data: Author (name, birthDate) и Book (title, publishDate). Настрой relationship: Author.books (to-many, inverse Book.author). Установи delete rule для Author: Cascade. Напиши код для создания автора и добавления книги, а затем удаления автора.\n\n\nПример результата (создание):\nswift\nlet context = PersistenceController.shared.backgroundContext\n\nlet author = Author(context: context)\nauthor.name = \"Stephen King\"\nauthor.birthDate = Date(timeIntervalSince1970: -141430400)\n\nlet book = Book(context: context)\nbook.title = \"It\"\nbook.publishDate = Date(timeIntervalSince1970: 516931200)\nbook.author = author\n\ntry? context.save()\n\n// Удаление\ncontext.delete(author) // все книги удалятся автоматически\ntry? context.save()\n\nКомментарий: Промт не забывает указать delete rule. Cascade — самый распространённый вариант для parent-child.\n\n### 9. Использование @ObservableObject и @Published в SwiftUI\nЗадача: Создать ViewModel для экрана входа с валидацией и индикацией загрузки.\n\nПромт:\n\nНапиши класс LoginViewModel, conforming к ObservableObject, с @Published свойствами: email, password, isLoading, errorMessage. Метод login() имитирует запрос с задержкой 2 секунды. В случае успеха выставляет isLoggedIn = true. Вьюха LoginView использует viewModel и показывает ProgressView во время загрузки.\n\n\nПример результата:\nswift\nclass LoginViewModel: ObservableObject {\n @Published var email = \"\"\n @Published var password = \"\"\n @Published var isLoading = false\n @Published var errorMessage: String?\n @Published var isLoggedIn = false\n \n func login() {\n isLoading = true\n errorMessage = nil\n DispatchQueue.main.asyncAfter(deadline: .now() + 2) { [weak self] in\n guard let self = self else { return }\n self.isLoading = false\n // имитация успеха\n self.isLoggedIn = true\n }\n }\n}\n\nstruct LoginView: View {\n @StateObject private var viewModel = LoginViewModel()\n \n var body: some View {\n VStack {\n TextField(\"Email\", text: $viewModel.email)\n SecureField(\"Password\", text: $viewModel.password)\n if let error = viewModel.errorMessage {\n Text(error).foregroundColor(.red)\n }\n Button(\"Login\") {\n viewModel.login()\n }\n .disabled(viewModel.isLoading)\n if viewModel.isLoading {\n ProgressView()\n }\n }\n .padding()\n }\n}\n\nКомментарий: Использование @StateObject гарантирует, что ViewModel не будет пересоздана при ререндере вью.\n\n### 10. Batch update в Core Data (NSBatchUpdateRequest)\nЗадача: Обновить значение у всех объектов без загрузки в память.\n\nПромт:\n\nНапиши функцию для массового обновления поля «isActive» на false для всех задач (Task), созданных более года назад. Используй NSBatchUpdateRequest и execute() на контексте. Верни количество обновлённых записей.\n\n\nПример результата:\nswift\nfunc deactivateOldTasks() -> Int {\n let context = PersistenceController.shared.container.viewContext\n let request = NSBatchUpdateRequest(entity: Task.entity())\n request.predicate = NSPredicate(format: \"createdAt < %@\", Date().addingTimeInterval(-365*24*3600) as NSDate)\n request.propertiesToUpdate = [\"isActive\": false]\n request.resultType = .updatedObjectsCountResultType\n \n do {\n let result = try context.execute(request) as? NSBatchUpdateResult\n context.refreshAllObjects()\n return result?.result as? Int ?? 0\n } catch {\n print(\"Batch update failed: \\(error)\")\n return 0\n }\n}\n\nКомментарий: Batch update эффективен для тысяч записей. refreshAllObjects синхронизирует context после изменений.\n\n### 11. Кастомный UIKit-контроллер с вложенным SwiftUI (UIHostingController)\nЗадача: Встроить SwiftUI вью в существующий UIKit проект.\n\nПромт:\n\nСоздай UIViewController, который содержит UIHostingController с SwiftUI View (StatsView: видит два числа — количество пользователей и доход). Данные передаются через @Binding из UIKit контроллера. Обновляй вью при изменении данных.\n\n\nПример результата:\nswift\n// SwiftUI View\nstruct StatsView: View {\n @Binding var users: Int\n @Binding var revenue: Double\n \n var body: some View {\n VStack {\n Text(\"Users: \\(users)\")\n Text(\"Revenue: $\\(revenue, specifier: \"%.2f\")\")\n }\n }\n}\n\n// UIKit Controller\nclass DashboardViewController: UIViewController {\n var users = 100\n var revenue = 25000.0\n private var hostingController: UIHostingController<StatsView>?\n \n override func viewDidLoad() {\n super.viewDidLoad()\n let statsView = StatsView(users: $users, revenue: $revenue)\n hostingController = UIHostingController(rootView: statsView)\n addChild(hostingController!)\n view.addSubview(hostingController!.view)\n hostingController!.didMove(toParent: self)\n // layout constraints\n }\n \n func updateStats(users: Int, revenue: Double) {\n self.users = users\n self.revenue = revenue\n hostingController?.rootView = StatsView(users: $users, revenue: $revenue)\n }\n}\n\nКомментарий: UIHostingController — мост между UIKit и SwiftUI. Binding позволяет реактивно обновлять SwiftUI из UIKit.\n\n## Экспертные промты (12–15)\n\n### 12. Core Data + Combine: Publisher на изменения\nЗадача: Подписаться на изменения в Core Data с помощью NSFetchedResultsController и Combine.\n\nПромт:\n\nСоздай Combine Publisher, который эмитит массив объектов сущности «Message» при каждом изменении (вставка, удаление, обновление). Используй NSFetchedResultsController. Swift 5.9, iOS 16+.\n\n\nПример результата:\nswift\nimport Combine\nimport CoreData\n\nclass MessagePublisher: NSObject, NSFetchedResultsControllerDelegate {\n private let subject = CurrentValueSubject<[Message], Never>([])\n private var controller: NSFetchedResultsController<Message>\n \n var publisher: AnyPublisher<[Message], Never> {\n subject.eraseToAnyPublisher()\n }\n \n init(context: NSManagedObjectContext) {\n let request: NSFetchRequest<Message> = Message.fetchRequest()\n request.sortDescriptors = [NSSortDescriptor(key: \"timestamp\", ascending: false)]\n controller = NSFetchedResultsController(fetchRequest: request,\n managedObjectContext: context,\n sectionNameKeyPath: nil,\n cacheName: nil)\n super.init()\n controller.delegate = self\n try? controller.performFetch()\n subject.value = controller.fetchedObjects ?? []\n }\n \n func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {\n subject.value = controller.fetchedObjects as? [Message] ?? []\n }\n}\n\nКомментарий: Combine + Core Data — мощная комбинация для реактивных интерфейсов. CurrentValueSubject хранит последнее состояние.\n\n### 13. SwiftUI: Кастомный модификатор для загрузки данных\nЗадача: Создать универсальный модификатор .loadable, который показывает loading, success, error состояния на основе async функции.\n\nПромт:\n\nСоздай модификатор SwiftUI, который принимает async throwing функцию и отображает ProgressView во время выполнения, error вью при ошибке и success вью с данными. Типы: Input, Output. Используй задачу Task и @State.\n\n\nПример результата:\nswift\nstruct LoadableView<Output: View, ErrorView: View>: ViewModifier {\n let action: () async throws -> Void\n @ViewBuilder let content: () -> Output\n @ViewBuilder let errorContent: (Error) -> ErrorView\n \n @State private var isLoading = false\n @State private var error: Error?\n \n func body(content: Content) -> some View {\n Group {\n if isLoading {\n ProgressView()\n } else if let error = error {\n errorContent(error)\n } else {\n self.content()\n }\n }\n .task {\n isLoading = true\n do {\n try await action()\n isLoading = false\n } catch {\n self.error = error\n isLoading = false\n }\n }\n }\n}\n\nextension View {\n func loadable<Output: View, ErrorView: View>(\n action: @escaping () async throws -> Void,\n @ViewBuilder content: @escaping () -> Output,\n @ViewBuilder errorContent: @escaping (Error) -> ErrorView\n ) -> some View {\n self.modifier(LoadableView(action: action, content: content, errorContent: errorContent))\n }\n}\n\n// Использование:\nstruct DataView: View {\n var body: some View {\n Text(\"Data loaded\")\n .loadable(action: { try await fetchData() },\n content: { Text(\"Success\") },\n errorContent: { error in Text(\"Error: \\(error.localizedDescription)\") })\n }\n}\n\nКомментарий: Generic модификаторы упрощают повторное использование и улучшают читаемость.\n\n### 14. Оптимизация DiffableDataSource в UICollectionView\nЗадача: Обновить коллекцию с анимацией, используя NSDiffableDataSourceSnapshot.\n\nПромт:\n\nСоздай UICollectionViewDiffableDataSource с секциями. Напиши метод updateItems(_ items: [Item]), который принимает новый массив, строит snapshot и анимирует изменения. Обработай случай, когда порядок изменился. iOS 15.\n\n\nПример результата:\nswift\nenum Section { case main }\nprivate var dataSource: UICollectionViewDiffableDataSource<Section, Item>!\n\nfunc configureDataSource() {\n dataSource = UICollectionViewDiffableDataSource<Section, Item>(collectionView: collectionView) { collectionView, indexPath, item in\n let cell = collectionView.dequeueReusableCell(withReuseIdentifier: \"cell\", for: indexPath) as! CustomCell\n cell.configure(with: item)\n return cell\n }\n}\n\nfunc updateItems(_ items: [Item]) {\n var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()\n snapshot.appendSections([.main])\n snapshot.appendItems(items, toSection: .main)\n // уникальные идентификаторы гарантируются через Hashable\n dataSource.apply(snapshot, animatingDifferences: true)\n}\n\nКомментарий: DiffableDataSource избавляет от ручных вызовов reloadData и автоматически анимирует вставки/удаления.\n\n### 15. Core Data: Избегание retain cycle с @FetchRequest в SwiftUI\nЗадача: Использовать @FetchRequest в SwiftUI вьюхе, которая также изменяет объекты. Как избежать утечек памяти?\n\nПромт:\n\nПокажи пример SwiftUI View с @FetchRequest для списка заметок и кнопкой удаления. Объясни, почему может возникнуть retain cycle с NSFetchedResultsControllerDelegate и как его избежать (слабая ссылка delegate). Используй iOS 17.\n\n\nПример результата (пояснение и код):\nswift\nstruct NotesListView: View {\n @FetchRequest(\n sortDescriptors: [NSSortDescriptor(keyPath: \\Note.createdAt, ascending: false)],\n animation: .default\n ) private var notes: FetchedResults<Note>\n \n var body: some View {\n List {\n ForEach(notes) { note in\n Text(note.title ?? \"\")\n }\n .onDelete { indexSet in\n for index in indexSet {\n let note = notes[index]\n viewContext.delete(note)\n }\n try? viewContext.save()\n }\n }\n }\n \n @Environment(\\.managedObjectContext) private var viewContext\n}\n\nПочему retain cycle не происходит: @FetchRequest сам управляет делегатом и не хранит сильную ссылку на вьюху. Но если вы создаёте собственный NSFetchedResultsController вручную, обязательно устанавливайте delegate слабой ссылкой.\n\nКомментарий: В SwiftUI @FetchRequest уже безопасен. При ручной работе с NSFetchedResultsControllerDelegate используйте controller.delegate = self (где self — UIViewController) — это может вызвать цикл, если контроллер сильно ссылается на делегат. Решение: хранить контроллер как слабую ссылку, если он не нужен после деинита.\n\n## Заключение\n\nЭти 15 промтов покрывают 90% повседневных задач iOS-разработчика — от простых форм до сложных Combine-паблишеров. Главный совет: уточняйте версию iOS и Swift в каждом промте. Нейросети склонны генерировать код для последних версий, который может не скомпилироваться под iOS 15.\n\nПопробуйте доработать любой из промтов под свою конкретную задачу — добавить дополнительные свойства, изменить анимацию или тип данных. Если вы используете Core Data с CloudKit, укажите это в запросе, чтобы код включал NSPersistentCloudKitContainer.\n\nОсвоив составление таких промтов, вы сократите время на рутину и сможете сосредоточиться на архитектуре и бизнес-логике. Пишите свои
15 промтов для iOS-разработки: SwiftUI, UIKit и Core Data
Читайте также
Fish Audio привлек $52 млн seed: как AI-голосовые модели меняют индустрию для креаторов и бизнеса
28 июля 2026
Мобильная безопасность — безопасность приложений iOS и Android: Освойте мобильное тестирование на проникновение в 2026 году с помощью обучения на основе ИИ
28 июля 2026
Гарантия попадания в ответы нейросетей: почему это невозможно и что измерять на самом деле
28 июля 2026
10 промтов для эффективной работы с базами данных: PostgreSQL, MongoDB и Redis
28 июля 2026
Автоматизация DevOps: как AI-агент ASI Biont интегрируется с DigitalOcean и управляет облачной инфраструктурой
28 июля 2026
Обзор курса «Специалист по комплаенс в области ПОД/ФТ»: Является ли обучение с ИИ будущим образования по борьбе с отмыванием денег?
28 июля 2026
12 промтов для iOS-разработки: SwiftUI, UIKit, Core Data и Combine
28 июля 2026
12 промтов для iOS-разработчика: SwiftUI, UIKit, Core Data и Combine
28 июля 2026
Анатомия вторжения в AI-агента Frontier Lab: техническая хронология инцидента июля 2026 года
28 июля 2026
Комментарии