Ваш iOS застосунок відкриває нативний віджет DobroDesk зі станом, прив'язаним до акаунта, і системними налаштуваннями руху.
Додайте опублікований Swift Package
У Xcode додайте репозиторій пакета https://github.com/comstud-io/dobrodesk-mobile-sdk.git на релізі 0.2.0 і додайте DobroDeskSDK та DobroDeskUI до target застосунку. Бінарний пакет підтримує iOS 15.1 і новіші версії. Реліз містить лише артефакти, вихідний код нативної реалізації не входить.
https://github.com/comstud-io/dobrodesk-mobile-sdk.gitІніціалізуйте й відкрийте нативний клієнт
Застосунок має передавати короткоживучий customer identity JWT через credentialProvider. Нативна автентифікація вимагає підписаного користувача: використовуйте бекенд хоста й ніколи не додавайте signing secret або support API token до застосунку.
За суворої concurrency-моделі Swift 6 оголосіть loader хоста як closure @Sendable () async throws -> String і передайте цю closure до credentialProvider.
import Foundation
import UIKit
import DobroDeskSDK
import DobroDeskUI
let loadDobroDeskToken: @Sendable () async throws -> String = {
// Replace this placeholder with a request to your host backend.
"short-lived-token-from-host-backend"
}
let client = try DobroDeskClient(
configuration: DobroDeskConfiguration(
apiOrigin: URL(string: "https://dobrodesk.com")!,
integrationId: "wgt_0123456789abcdef0123456789abcdef",
accountScope: currentCustomer.id,
locale: "en",
credentialProvider: loadDobroDeskToken,
pollInterval: 1
)
)
try await DobroDeskWidgetBridgePresenter.shared.present(
client: client,
options: ["locale": "en"],
from: viewController
)Вбудуйте віджет або використайте власний тригер
Для inline-поверхні створіть власний UIView з обмеженнями до safeArea top, leading і trailing та нижнім прив'язуванням до keyboardLayoutGuide. Задайте початкову висоту 560 пунктів із priority .defaultHigh і монтуйте лише після підключення view до window. Створюйте власний presenter для кожного монтування, щоб помилковий або застарілий close не закрив іншу поверхню.
Власна UIButton може викликати present для модального режиму або setEmbeddedVisible для inline-поверхні. Host wrapper має перехоплювати помилки embed через do/catch і передавати їх у власний типізований onError callback для UI відновлення. Не створюйте нового client для іншого екрана.
API вбудованого відображення входять до опублікованих артефактів Mobile SDK 0.2.0. Використовуйте inline-поверхню, коли розміщення контролює хост, а модальне відкриття, коли хосту потрібен повноекранний сценарій.
import UIKit
import DobroDeskSDK
import DobroDeskUI
@MainActor
final class SupportPanelHost {
private let client: DobroDeskClient
private weak var parent: UIViewController?
private let onError: (Error) -> Void
private let supportContainer = UIView()
private var presenter: DobroDeskWidgetBridgePresenter?
private var mountTask: Task<Void, Never>?
private var mountGeneration = 0
private var isMounted = false
init(
client: DobroDeskClient,
parent: UIViewController,
onError: @escaping (Error) -> Void
) {
self.client = client
self.parent = parent
self.onError = onError
supportContainer.translatesAutoresizingMaskIntoConstraints = false
parent.view.addSubview(supportContainer)
let safeArea = parent.view.safeAreaLayoutGuide
let keyboardGuide = parent.view.keyboardLayoutGuide
let height = supportContainer.heightAnchor.constraint(equalToConstant: 560)
height.priority = .defaultHigh
NSLayoutConstraint.activate([
supportContainer.topAnchor.constraint(equalTo: safeArea.topAnchor),
supportContainer.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor),
supportContainer.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor),
supportContainer.bottomAnchor.constraint(equalTo: keyboardGuide.topAnchor),
height,
])
}
func mountAfterAppearance() {
guard mountTask == nil, !isMounted, let parent, supportContainer.window != nil else { return }
mountGeneration += 1
let generation = mountGeneration
let mountPresenter = DobroDeskWidgetBridgePresenter()
let client = self.client
let supportContainer = self.supportContainer
presenter = mountPresenter
mountTask = Task { @MainActor [weak self] in
defer {
if let self, self.mountGeneration == generation {
self.mountTask = nil
}
}
do {
try await mountPresenter.embed(
client: client,
options: ["locale": "en", "view": "home"],
in: supportContainer,
parent: parent
)
guard let self, self.mountGeneration == generation else {
mountPresenter.close()
return
}
isMounted = true
} catch {
guard let self, self.mountGeneration == generation else { return }
mountPresenter.close()
presenter = nil
onError(error)
}
}
}
func setVisible(_ visible: Bool) {
guard isMounted else { return }
presenter?.setEmbeddedVisible(visible)
}
func close() {
mountGeneration += 1
mountTask?.cancel()
mountTask = nil
presenter?.close()
presenter = nil
isMounted = false
}
}Явно керуйте відкриттям і життєвим циклом акаунта
Відкривайте віджет із foreground UIViewController і викликайте close, коли екран хоста знищується. Викликайте client.resetSession(), коли клієнт навмисно відкидає розмову. Прямий iOS-клієнт не має logout: скиньте його, відкиньте й створіть новий client перед зміною accountScope.
Нативний presenter використовує той самий контракт вигляду віджета, що й вебвіджет. Не задавайте reduceMotion, якщо клієнт не зробив явний вибір у застосунку: системне налаштування reduced motion має пріоритет.
Керуйте завантаженням, від'єднанням і доступністю
Нативна поверхня сама показує завантаження повідомлень і повтори. Для стану екрана використовуйте події client, а під час тимчасового приховування керуйте setEmbeddedVisible.
Від'єднання або close зберігає client, сесію, чернетку й outbox. Не викликайте resetSession через тимчасове приховування. Presenter поважає системне Reduce Motion, а host відповідає за safe area та клавіатуру.
- Викличте setEmbeddedVisible(false) під час покриття маршруту і збережіть client.
- Викличте setEmbeddedVisible(true), коли той самий контейнер знову підключено.
- Викликайте close лише наприкінці потоку, а reset або logout — тільки для навмисної зміни акаунта чи розмови.
Перевірте iOS-флоу
- Відкрийте віджет із foreground-екрана й перевірте, що перше відкриття не створює другого контролера, якщо кнопку натиснути двічі.
- Почніть розмову, закрийте й відкрийте віджет знову, а потім перевірте, що чернетка та незавершене надсилання залишилися прив'язаними до того самого акаунта.
- Поверніть екран або відправте застосунок у фон під час незавершеної дії та дочекайтеся явного стану повторного підключення або повтору перед новою спробою.
- Скиньте й відкиньте прямий client перед перемиканням акаунта, потім створіть наступний client і перевірте, що він не бачить попередню розмову.
