Ми спеціалізуємося на розробці 3D-ігор для iOS з використанням нативного фреймворку SceneKit. Часто клієнти приходять з готовою ідеєю, але не знають, який технологічний стек обрати між Unity, Unreal та SceneKit. SceneKit — не Unity і не Unreal: немає візуального редактора pipeline, немає Asset Store, менше готових рішень. Але це й не сирий Metal: фізичний рушій, анімації, shader модифікатори, AR-інтеграція через ARKit — все входить в SDK без сторонніх залежностей. Для iOS-ексклюзивних 3D-ігор середньої складності це цілком робочий варіант, особливо якщо команда глибоко в Swift/Objective-C.
Сцена, ноди, рендеринг
Базова одиниця — SCNNode. Сцена — дерево нодів: корінь SCNScene.rootNode, до нодів прикріплені геометрія (SCNGeometry), світло (SCNLight), камера (SCNCamera), фізичне тіло (SCNPhysicsBody). Відмінність від SpriteKit у тому, що SceneKit працює в 3D-просторі з координатами SCNVector3, а трансформації задаються через simd_float4x4 (або зручні властивості position, eulerAngles, simdTransform).
Завантаження сцени з .scn-файлу (редагується прямо в Xcode Scene Editor):
let scene = SCNScene(named: "GameLevel.scn")!
let scnView = SCNView(frame: view.bounds)
scnView.scene = scene
scnView.allowsCameraControl = false
scnView.rendersContinuously = true // важливо для анімацій
view.addSubview(scnView)
rendersContinuously = true — якщо не виставити, SCNView рендерить тільки при зміні сцени. Для ігор з постійним рухом потрібен постійний рендер. Зворотна сторона — споживання батареї. Для меню з рідкісними змінами залишаємо false.
Фізика та колізії в 3D
SCNPhysicsBody трьох типів: .static (нерухомі об'єкти, колайдер не рухається), .dynamic (під керуванням фізики), .kinematic (рухається кодом, ігнорує сили, але бере участь у колізіях). Персонаж гравця зазвичай kinematic — ми рухаємо його через код, але стіни та підлога його зупиняють.
Форма колайдера впливає на продуктивність сильніше, ніж у 2D. Використовуйте капсулу для персонажа, а не складну геометрію:
let capsule = SCNCapsule(capRadius: 0.3, height: 1.8)
let physicsShape = SCNPhysicsShape(geometry: capsule, options: nil)
player.physicsBody = SCNPhysicsBody(type: .kinematic, shape: physicsShape)
Колізії обробляються через SCNPhysicsContactDelegate. Налаштовуйте categoryBitMask, collisionBitMask, contactTestBitMask — тільки для потрібних пар, щоб не перевантажувати didBegin.
Анімації: CAAnimation та SCNAnimationPlayer
Скелетна анімація імпортується з .dae (Collada). Перемикання між idle, run, jump робимо з blendInDuration для плавності:
func transition(to key: String, blendDuration: CGFloat = 0.3) {
let player = characterNode.animationPlayer(forKey: key)!
player.blendInDuration = blendDuration
player.play()
currentAnimationKey.flatMap {
characterNode.animationPlayer(forKey: $0)?.stop(blendOutDuration: blendDuration)
}
currentAnimationKey = key
}
Без blendInDuration персонаж «стрибає» між позами — це перше, що помічає гравець.
Шейдери та постефекти
SceneKit дозволяє підключати GLSL/Metal шейдери через SCNMaterial.shaderModifiers. Типове застосування — розчинення об'єкта при смерті:
let dissolveShader = """
#pragma transparent
#pragma body
float threshold = u_dissolveAmount;
float noise = ... // шум за координатами
if (noise < threshold) discard_fragment();
_output.color.a = smoothstep(threshold - 0.05, threshold, noise);
"""
material.shaderModifiers = [.fragment: dissolveShader]
SCNTechnique — для постобробки всього екрану (bloom, outline, depth of field). Конфігурується через plist-словник.
ARKit інтеграція
SceneKit — перший і основний рендерер для ARKit:
let arView = ARSCNView(frame: view.bounds)
let config = ARWorldTrackingConfiguration()
config.planeDetection = [.horizontal, .vertical]
arView.session.run(config)
ARSCNView автоматично синхронізує SCNScene з AR-координатним простором. Це основа для AR-ігор — ринок ARKit + SceneKit добре закритий прикладами Apple.
Як оптимізувати продуктивність SceneKit?
SCNView за замовчуванням використовує Metal на iOS 9+. Кілька правил, які реально впливають на fps.
Batching: SceneKit автоматично об'єднує draw calls для нодів з однаковим матеріалом. Тому 1000 дерев з одним матеріалом набагато дешевше, ніж 100 дерев з 100 різними матеріалами. Використовуй SCNMaterial інстанси, не створюй новий об'єкт матеріалу для кожного нода.
Level of Detail через SCNLevelOfDetail: для об'єктів на відстані більше 20 метрів можна показувати спрощену геометрію. Це знижує навантаження на GPU.
Occlusion culling: SceneKit робить frustum culling автоматично, але occlusion culling (приховування об'єктів за іншими об'єктами) — ні. Для складних сцен це потрібно робити вручну через isHidden = true за результатами ray cast або логіки рівня.
Інструмент для профілювання: Xcode → Metal System Trace + Render Graph. Ціль — не більше 30–50 draw calls для гри зі стабільними 60 fps на iPhone X. На одному з проектів ми оптимізували рендеринг з 40 до 60 fps, зменшивши кількість draw calls вдвічі за рахунок об'єднання матеріалів.
Коли варто відмовитися від SceneKit?
Мультиплатформа (iOS + Android + PC) — Unity або Godot. Складна фізика тіл з деформацією — Unity з Havok. Масивні відкриті світи — теж Unity/Unreal. SceneKit хороший для iOS-ексклюзиву з помірною 3D-складністю, AR-додатків та ігор, де важлива нативна інтеграція (Game Center, CloudKit).
Процес роботи
- Аудит ТЗ: тип гри, чи потрібен AR, цільові пристрої, мінімальна версія iOS, асети.
- Прототип ігрової механіки: рух, фізика, базова камера — 1–2 тижні.
- Core gameplay: рівні, вороги/перешкоди, UI (SwiftUI поверх
SCNView).
- Аудіо:
AVAudioEngine для 3D-звуку.
- Інтеграція Game Center, IAP, ARKit.
- Полірування продуктивності, тестування на слабких пристроях.
Що входить в роботу
- Вихідний код з коментарями
- Документація по архітектурі та збірці
- Тестовий білд через TestFlight
- Публікація в App Store (аккаунт розробника надається клієнтом)
- Гарантійна підтримка 30 днів після релізу
Орієнтири по термінах
| Тип проекту |
Термін |
| Прототип / proof of concept |
2–3 тижні |
| Казуальна 3D-гра без AR |
1,5–2 місяці |
| 3D-гра з AR + Game Center |
2–3 місяці |
| Складний проект (відкритий світ, мультиплеєр) |
Обговорюється окремо |
Вартість розраховується індивідуально після аналізу ТЗ та наявності готових асетів.
Наш досвід: реалізували більше 10 3D-проектів на SceneKit, включаючи AR-додатки з високою продуктивністю. Всі проекти пройшли модерацію App Store без відхилень.
Пишіть — оцінимо ваш проект за 1 робочий день. Розробка під ключ з нуля до публікації. Офіційна документація Apple по SceneKit.
Чому нативна розробка iOS — найкращий вибір для складних додатків
Додаток крашиться на cold start — EXC_BAD_ACCESS в момент ініціалізації синглтона, який звертається до іншого синглтона, який ще не ініціалізований. Або: ViewController витікає в пам'яті, тому що closure захоплює self без [weak self], і цей ViewController висить у пам'яті через два переходи після того, як користувач його покинув. Це не гіпотетичні сценарії — це два найпоширеніші класи проблем на iOS-проектах, які приходять до нас після іншої команди.
Ми займаємося iOS-розробкою понад 6 років, реалізували 50+ проєктів — від стартапів до enterprise-рішень з мільйонами користувачів. Кожен проєкт проходить через 3 етапи Code Review, власний набір UI-тестів (в середньому 150+ тест-кейсів) та обов'язковий прогін через Xcode Instruments до релізу. Нативна iOS-розробка на Swift — це прямий доступ до платформи. Без прошарку, без компромісів щодо продуктивності, з повним контролем над тим, що відбувається на кожному кадрі.
Чому нативна розробка iOS на Swift — вибір для enterprise-додатків?
Нативний код дає гарантію сумісності з новими API Apple у день їх виходу, а не через місяці адаптації в кроссплатформних фреймворках. Для додатків із чутливою до затримок логікою (фінансові термінали, медичні монітори, AR-навігація) це критично. Swift з ARC та строгою типізацією дозволяє тримати crash-free rate на рівні 99.9% за правильної архітектури. На практиці середній показник на наших проєктах — 99.8%, що на 15% вище середнього по ринку.
Як вибрати між SwiftUI та UIKit для нативної розробки iOS?
На сьогодні SwiftUI покриває переважну більшість production-завдань. Але UIKit не застарів і не зникне — Apple не deprecate-ить його, а продовжує додавати API. Реальна картина на великих проєктах: гібридний підхід. SwiftUI для більшості екранів, UIKit там, де SwiftUI впирається в обмеження.
Які сценарії SwiftUI виграє беззаперечно
Декларативний синтаксис SwiftUI скорочує код UI у 3–5 разів порівняно з UIKit. Екран налаштувань з List, Toggle, Picker — це 40 рядків SwiftUI проти 200 рядків UIKit з делегатами UITableViewDataSource. Економія часу на UI-розробку досягає 60%. Apple рекомендує починати нові проєкти на SwiftUI (Human Interface Guidelines).
@State, @Binding, @ObservableObject (а з iOS 17 — макрос @Observable) створюють реактивний зв'язок між даними та UI без ручного reloadData(). Зміна @State-змінної автоматично перемальовує порушену частину ієрархії. Це працює правильно, якщо розуміти, як SwiftUI обчислює diff — через Equatable та id у ForEach.
AsyncImage, NavigationStack з типобезпечним роутингом через NavigationPath, searchable, refreshable — це готові патерни, які UIKit вимагає реалізовувати вручну.
Коли UIKit залишається необхідним
UICollectionView з compositional layout та diffable data source — складні сітки з різними типами комірок, горизонтальними секціями всередині вертикального скролу, динамічними розмірами комірок. SwiftUI LazyVGrid / LazyHGrid не дають такого контролю.
Кастомні переходи між екранами. UIViewControllerAnimatedTransitioning та UIViewControllerInteractiveTransitioning — інтерактивний pop gesture з частковим прогресом, кастомний hero-перехід з точним керуванням frame. SwiftUI matchedGeometryEffect покриває частину випадків, але не всі.
UITextView з TextKit 2. Багатий редактор тексту, кастомні атрибути, кастомний рендеринг — TextKit 2 (доступний з iOS 16) перейшов на async layout, що вирішило проблеми з продуктивністю на довгих документах. SwiftUI TextEditor — це обгортка навколо UITextView без прямого доступу до TextKit.
UIScrollView з кастомною поведінкою. scrollViewDidScroll, parallax-ефекти, sticky headers з кастомною логікою, pull-to-refresh з кастомним індикатором. SwiftUI ScrollView з scrollPosition та onScrollGeometryChange (iOS 17) закриває частину випадків, але не всі.
Як ми інтегруємо SwiftUI та UIKit: крок за кроком
- Ідентифікуємо екрани, де SwiftUI дає максимальний виграш (списки, форми, налаштування) — зазвичай 70-80% екранів.
- Для критичних до продуктивності ділянок (складні колекції, кастомні анімації) залишаємо UIKit.
- Використовуємо UIHostingController для вбудовування SwiftUI-в'ю в UIKit navigation stack.
- Для зворотної сумісності обгортаємо UIKit-компоненти через UIViewRepresentable.
- Coordinator pattern (UIKit) керує навігацією на рівні флоу, екрани реалізовані на SwiftUI.
Один патерн, який ми використовуємо на проєктах: UIKit-координатор керує навігацією, а самі екрани на SwiftUI. Координатор створює UIHostingController, передає ViewModel через ініціалізатор або @EnvironmentObject, керує переходами. Це дає чисте розділення: SwiftUI займається UI, Coordinator — навігацією. Завдяки такому підходу ми скорочуємо час дебагу на 30% порівняно з чистим UIKit.
Як async/await та Combine працюють разом?
До Swift 5.5 асинхронний код на iOS будувався на Combine або callback-ланцюжках. З появою async/await та Actor модель конкурентності стала частиною мови. На нових проєктах ми використовуємо async/await як основний інструмент для мережевих викликів та бізнес-логіки, Combine — для реактивної прив'язки UI-стану.
@MainActor
class UserViewModel: ObservableObject {
@Published var user: User?
@Published var isLoading = false
func loadUser(id: String) async {
isLoading = true
defer { isLoading = false }
do {
user = try await userService.fetch(id: id)
} catch {
// обробити помилку
}
}
}
Combine залишається незамінним для дебаунсингу введення, об'єднання кількох Publishers (CombineLatest, Zip) та функціональної обробки потоку значень (map, flatMap, filter). На практиці 80% проєктів використовують обидва підходи, обираючи інструмент під задачу. Це дозволяє підвищити швидкість розробки на 20% без втрати якості.
Архітектура iOS-додатку
MVVM — базовий патерн. ViewModel містить логіку та @Published-стан, SwiftUI View підписується через @ObservedObject або @StateObject. Правило одне: View не знає про URLSession, CoreData, UserDefaults.
Clean Architecture додає шари Repository та UseCase. UserRepository абстрагує джерело даних (мережа vs кеш). FetchUserUseCase містить бізнес-правило. UserViewModel викликає UseCase та керує UI-станом.
TCA (The Composable Architecture) — більш строгий патерн від Point-Free. State, Action, Reducer, Effect — все явне, все тестоване, composable через Scope. Добре працює у великих командах (5+ iOS-розробників), де важлива передбачуваність поведінки.
Що входить у розробку iOS-додатку
| Етап |
Результати |
| Аналіз та проектування |
Технічне завдання, архітектурна схема, вибір стеків |
| Розробка |
Код з дотриманням App Store Review Guidelines, інтеграція з бекендом (REST/GraphQL) |
| Тестування |
Unit-тести (XCTest, покриття >75%), UI-тести (XCUITest, 150+ сценаріїв), навантажувальне тестування через Firebase Test Lab |
| Публікація |
Оформлення облікового запису розробника, підпис коду, відправка в App Store Connect |
| Підтримка |
Гарантія 30 днів після релізу, оновлення під нові версії iOS |
Інструменти, без яких не обходиться жоден реліз
Xcode Instruments. Time Profiler показує, де CPU витрачає час. Allocations — витоки пам'яті та excessive allocations. Leaks — об'єкти, які не звільняються. Перед кожним релізом — обов'язковий прогін.
Firebase Crashlytics. Crash-free rate, групування по stack trace, breadcrumbs подій до крашу. Налаштовується за 30 хвилин, дає картину по всьому парку пристроїв. В середньому crash-free rate на наших проєктах — 99.8%.
Fastlane match. Керування сертифікатами та provisioning profiles через зашифрований git-репозиторій. Усуває «у мене локально збирається, а на CI ні» раз і назавжди. Економить до 4 годин на кожну збірку при ручному підписуванні.
XCTest + XCUITest. Unit-тести для ViewModel та UseCase, UI-тести для критичних флоу (онбординг, оплата, авторизація). В середньому код покритий на 75%.
Типові помилки на iOS-проектах та їх рішення
| Проблема |
Рішення |
Витік пам'яті через захоплення self у замиканні |
Використовувати [weak self] у всіх хендлерах, де self не повинен жити довше замикання |
| Конфлікти Provisioning Profiles |
Налаштувати Fastlane match та зберігати сертифікати в окремому репозиторії |
| Повільний старт додатку через синхронну ініціалізацію синглтонів |
Перенести ініціалізацію на перший виклик або використовувати lazy var |
| Відмова App Store через невідповідність Section 4.2 (мінімальна функціональність) |
Провести попередній аудит за чек-листом App Store Review Guidelines |
Процес і терміни
| Складність |
Орієнтовний термін |
| MVP (5–8 екранів, базовий API) |
6–10 тижнів |
| Середній додаток (15–25 екранів) |
3–5 місяців |
| Складний (платежі, AR, CoreML, кастомний UI) |
5–9 місяців |
Замовте розробку під ключ — ми оцінимо ваш проєкт за 2 робочі дні та запропонуємо оптимальну архітектуру. Зв'яжіться з нами, щоб обговорити вашу задачу: гарантуємо якість коду, дотримання App Store Review Guidelines та досвід роботи з проєктами будь-якого масштабу. Отримайте консультацію — ми допоможемо вибрати правильний стек та уникнути типових помилок на старті.