46. Чиним SwiftUI Preview
Одно из преимуществ верстки на SwiftUI - превью, которые во многих ситуациях можно использовать вместо запуска симулятора. Разберемся как поправить самые часты ошибки, ломающие превью.
Ошибка билда
Превью не будет работать, если проект не удается собрать (command + B). Поэтому если превью не запускается, но и ошибку не выводит, то нужно попробовать сбилдить проект. Если найдется ошибка - нужно ее поправить и превью должен завестись.
EnvironmentObject
Эту штуку можно использовать для передачи зависимостей вглубь через несколько экранов/вьюшек. Есть нюанс - на момент публикации этой статьи Xcode не умеет проверять, была ли на самом деле передана зависимость во вьюшку, или нет.
Возьмем для примера такой код:
struct RootView: View {
@EnvironmentObject private var viewModel: TabViewModel
@EnvironmentObject private var defaults: DefaultsService
var body: some View {
TabView(selection: $viewModel.selectedTab) {
ForEach(TabViewModel.Tab.allCases, id: \.rawValue) { tab in
tab.screen
.tabItem { tab.tabItemLabel }
.tag(tab)
}
}
.navigationViewStyle(.stack)
.animation(.spring(), value: defaults.isAuthorized)
}
}
#if DEBUG
#Preview {
RootView()
}
#endif
В данном случае превью работать не будет, и на первый взгляд это можно легко поправить таким образом:
struct RootView: View {
@EnvironmentObject private var viewModel: TabViewModel
@EnvironmentObject private var defaults: DefaultsService
var body: some View {
// верстка
}
}
#if DEBUG
#Preview {
RootView()
.environmentObject(DefaultsService()) // <- добавили
.environmentObject(TabViewModel()) // <- добавили
}
#endif
Но даже сейчас превью не работает, видим алерт:
Смело жмем на кнопку "Отчет..." и смотрим, что привело к ошибке:
Видим, что какая-то вьюха обращается к какому-то свойству и возникает ошибка. Переходим в эту вьюху в коде и видим еще два @EnvironmentObject:
struct SportsGroundsMapView: View {
@EnvironmentObject private var network: NetworkStatus
@EnvironmentObject private var defaults: DefaultsService
@EnvironmentObject private var groundsManager: SportsGroundsManager
// другой код
}
Теперь добавляем две оставшиеся зависимости и превью работает:
struct RootView: View {
@EnvironmentObject private var viewModel: TabViewModel
@EnvironmentObject private var defaults: DefaultsService
var body: some View {
// верстка
}
}
#if DEBUG
#Preview {
RootView()
.environmentObject(DefaultsService())
.environmentObject(TabViewModel())
.environmentObject(SportsGroundsManager())
.environmentObject(NetworkStatus())
}
#endif
Ошибка Xcode 15
В первой версии Xcode 15 превью могли не работать на всех симуляторах кроме одного - iPhone 15 pro. Нужно было выбрать его, чтобы починить превью - такие моменты в iOS-разработке тоже бывают, от них не спастись 💁♂️
Ошибка сторонних зависимостей
Бывает, что какой-то инструмент отказывается работать в превью. Самый простой пример - Firebase: достаточно подключить его к проекту, чтобы превью отвалились на всех экранах, где есть обращение к Firebase.
Решить такую проблему можно при помощи проверки:
if ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1" {
// не обращаемся и не инициализируем инструменты, ломающие превью
} else {
// выполняем обычный код со всеми нужными инструментами
}
Для удобства можно сделать обертку, например:
enum AppEnvironment {
static let isRunningPreview = ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1"
}
// где-то в коде
if AppEnvironment.isRunningPreview { ... } else { ... }
Можно дружно поблагодарить ребят за помощь на SO - именно там я впервые узнал о таком решении 🙂
Другие статьи можно посмотреть тут.