Интеграция в SwiftUI
ChatCenterUISDK — UIKit-фасад: метод getChat() возвращает UIViewController, который встраивается в навигацию через UINavigationController или present. Для использования в SwiftUI-приложении нужны две вещи:
- Холдер инстанса SDK —
ObservableObject, доступный через@EnvironmentObjectили@StateObject. - Обёртка
UIViewControllerRepresentableдля отображения экрана чата вNavigationStackилиsheet.
Холдер инстанса SDK
Один экземпляр ChatCenterUISDK живёт всё время работы приложения. В SwiftUI это удобно держать в @StateObject на корневом App-объекте и пробрасывать через environmentObject:
import SwiftUI
import ChatCenterUI
final class ChatSDKHolder: ObservableObject {
let sdk: ChatCenterUISDK
init() {
let transport = ChatTransportConfig(cloudHost: "your-host.edna.ru")
let chatConfig = ChatConfig(transportConfig: transport)
self.sdk = ChatCenterUISDK(
providerUid: "YOUR_PROVIDER_UID",
appMarker: nil, // если используете несколько приложений на одном providerUid
chatConfig: chatConfig,
loggerConfig: ChatLoggerConfig(logLevel: .all)
)
}
}
@main
struct MyApp: App {
@StateObject private var chatHolder = ChatSDKHolder()
var body: some Scene {
WindowGroup {
RootView()
.environmentObject(chatHolder)
}
}
}
ChatCenterUISDKDelegate объявлен как class-only (AnyObject), а sdk.delegate хранится по weak-ссылке — поэтому делегатом может быть только класс, не struct и не View. В SwiftUI-проекте делегатом обычно становится сам ChatSDKHolder: он реализует протокол ChatCenterUISDKDelegate и назначает sdk.delegate = self в init.
Авторизация и запрос разрешений на пуши
Авторизуйте пользователя там, где приложение узнаёт о нём — например, после успешного входа. В SwiftUI это .task, onChange или ручной вызов из view-model:
struct LoginCompletedView: View {
@EnvironmentObject var chatHolder: ChatSDKHolder
var body: some View {
Color.clear.task {
let user = ChatUser(identifier: "user_uuid", name: "Иван Иванов")
chatHolder.sdk.authorize(user: user)
// Для JWT-авторизации передайте auth: ChatAuth(...) — см. [Управление пользователем](../methods/auth.md)
}
}
}
Push-разрешения и device token обрабатываются через AppDelegate (см. раздел «Передача токена» в гайде по уведомлениям). В SwiftUI App-проекте используйте @UIApplicationDelegateAdaptor:
@main
struct MyApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
@StateObject private var chatHolder = ChatSDKHolder()
var body: some Scene { ... }
}
Обёртка UIViewControllerRepresentable
struct ChatScreen: UIViewControllerRepresentable {
let chatCenterSDK: ChatCenterUISDK
let userInfo: [AnyHashable: Any]?
init(chatCenterSDK: ChatCenterUISDK, userInfo: [AnyHashable: Any]? = nil) {
self.chatCenterSDK = chatCenterSDK
self.userInfo = userInfo
}
func makeUIViewController(context: Context) -> UIViewController {
do {
return try chatCenterSDK.getChat(userInfo: userInfo)
} catch {
// Возвращаем плейсхолдер: SwiftUI не позволяет отменить отображение представимого
let vc = UIViewController()
vc.view.backgroundColor = .systemBackground
// замените на собственный error-view
return vc
}
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}
}
getChat() бросает ошибкуВ makeUIViewController нельзя отменить отображение представимого после возникновения ошибки. Поэтому решение об открытии чата принимайте до создания ChatScreen: вызывайте getChat() синхронно во view-model и переключайте @State на флаг отображения. См. рекомендованный паттерн ниже.
Рекомендованный паттерн: pre-flight открытие
Вызовите getChat() до отображения экрана и сохраните результат. Ошибка обработается до того, как SwiftUI начнёт отрисовку:
@MainActor
final class ChatLauncher: ObservableObject {
@Published var preparedController: UIViewController?
@Published var lastError: Error?
private let sdk: ChatCenterUISDK
init(sdk: ChatCenterUISDK) { self.sdk = sdk }
func openChat(userInfo: [AnyHashable: Any]? = nil) {
do {
preparedController = try sdk.getChat(userInfo: userInfo)
} catch {
lastError = error
}
}
}
struct RootView: View {
@EnvironmentObject var chatHolder: ChatSDKHolder
@StateObject private var launcher: ChatLauncher
init(chatHolder: ChatSDKHolder) {
_launcher = StateObject(wrappedValue: ChatLauncher(sdk: chatHolder.sdk))
}
var body: some View {
Button("Открыть чат") {
launcher.openChat()
}
.sheet(item: Binding(
get: { launcher.preparedController.map(IdentifiableVC.init) },
set: { _ in launcher.preparedController = nil }
)) { wrapper in
ChatViewControllerWrapper(viewController: wrapper.viewController)
.ignoresSafeArea()
}
.alert("Ошибка открытия чата",
isPresented: Binding(
get: { launcher.lastError != nil },
set: { _ in launcher.lastError = nil }
)) {
Button("OK") { launcher.lastError = nil }
}
}
}
private struct IdentifiableVC: Identifiable {
let id = UUID()
let viewController: UIViewController
}
private struct ChatViewControllerWrapper: UIViewControllerRepresentable {
let viewController: UIViewController
func makeUIViewController(context: Context) -> UIViewController { viewController }
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}
}
NavigationStack vs sheet
| Вариант | Когда использовать |
|---|---|
.sheet(item:) | Чат как модальное окно поверх основного интерфейса. Простое закрытие свайпом. |
NavigationStack + NavigationLink(value:) | Чат как часть навигационного стека, с возможностью пройти дальше. Контроллер чата получит свой navigationItem, заголовок и кнопку «Назад». |
.fullScreenCover(item:) | Полный экран без свайп-жеста закрытия — для приложений, где чат является приоритетным экраном. |
В NavigationStack-варианте вместо pushViewController используйте NavigationLink. Контроллер из getChat() управляет navigationBar через NavigationBarStyle, поэтому отключите системный navigationBar SwiftUI на этом экране:
.toolbar(.hidden, for: .navigationBar)
Ограничения
- Кастомизация навигации выполняется через
ChatTheme.flows.chatFlow.navigationBarStyle, не через SwiftUI-модификаторы. Заголовок, цвет, кнопки настраивайте вChatTheme, а не в.navigationTitle. - Смена темы (light↔dark) внутри открытого чата применяется только при следующем
getChat(). Чтобы экран перестроился поcolorSchemeсреды SwiftUI, пересоздайте его. @MainActorна делегате — методыChatCenterUISDKDelegateмогут приходить не на главном потоке (см. Делегат SDK). Для совместимости со Swift 6 используйте@preconcurrencyлибо дополнительноDispatchQueue.main.asyncвнутри методов делегата.
Связанные разделы
- Делегат SDK — обработка событий
- Управление пользователем —
authorize,logout, переключение пользователей - Отображение чата — UIKit-вариант
- Уведомления — push и cold start