Перейти к основному содержимому
Версия: 5.9.0

Интеграция в SwiftUI

ChatCenterUISDK — UIKit-фасад: метод getChat() возвращает UIViewController, который встраивается в навигацию через UINavigationController или present. Для использования в SwiftUI-приложении нужны две вещи:

  1. Холдер инстанса SDKObservableObject, доступный через @EnvironmentObject или @StateObject.
  2. Обёртка 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) {}
}
ВариантКогда использовать
.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 внутри методов делегата.

Связанные разделы