A sua app iOS apresenta o widget nativo DobroDesk com estado durável por conta e preferências de movimento do sistema.
Adicionar o Swift Package publicado
No Xcode, adicione o repositório https://github.com/comstud-io/dobrodesk-mobile-sdk.git na release 0.2.0 e adicione DobroDeskSDK e DobroDeskUI ao target da app. O pacote binário suporta iOS 15.1 e posteriores. A release contém apenas artefactos. O código fonte da implementação nativa não está incluído.
https://github.com/comstud-io/dobrodesk-mobile-sdk.gitInicializar e apresentar o cliente nativo
A app deve fornecer um JWT de identidade do cliente de curta duração através de credentialProvider. A autenticação nativa exige um utilizador assinado: use um backend do host e nunca coloque o signing secret ou um support API token na app.
Com a concorrência estrita do Swift 6, declare o loader do host como uma closure @Sendable () async throws -> String e passe essa closure a 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
)Usar um acionador personalizado ou uma superfície integrada
Para uma superfície integrada, use um UIView próprio com constraints para safeArea top, leading e trailing e uma ligação inferior à keyboardLayoutGuide. Defina uma altura inicial de 560 pontos com priority .defaultHigh e monte apenas depois de a view estar ligada à window. Crie um presenter próprio em cada montagem para que um close falhado ou antigo não feche outra superfície.
O botão personalizado deve ser acessível pelo teclado e expor aria-controls e aria-expanded. O wrapper do host deve capturar os erros de embed com do/catch e encaminhá-los para o seu próprio callback onError tipado para apresentar a recuperação. Ao montar novamente, reutilize o mesmo cliente, a mesma sessão e o mesmo rascunho.
As APIs inline fazem parte dos artefactos publicados do Mobile SDK 0.2.0. Use a superfície inline quando o host controla o posicionamento e a apresentação modal quando o host precisa de gerir o ecrã inteiro.
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
}
}Manter explícitos a apresentação e o ciclo da conta
Apresente a partir de um UIViewController em primeiro plano e chame close quando o ecrã do host for eliminado. Chame client.resetSession() quando o cliente descartar intencionalmente a conversa. O cliente iOS direto não tem logout: reinicie-o, elimine-o e crie um novo cliente antes de alterar accountScope.
O presenter nativo usa o mesmo contrato de aparência do widget web. Omita reduceMotion salvo quando o cliente tiver uma escolha explícita na app. A definição do sistema para reduzir movimento continua prioritária.
Tratar carregamento, visibilidade e acessibilidade
Use hooks de carregamento ou eventos tipados para o estado da tela hospedeira. A superfície mostra o carregamento das mensagens, a espera e as tentativas novamente para evitar um segundo indicador sobre o campo de entrada.
Oculte ou mostre a superfície durante uma navegação temporária mantendo o cliente. O widget respeita as preferências do sistema para reduzir movimento. Remover ou fechar a vista não descarta a sessão nem o rascunho.
- Ao montar novamente, reutilize o mesmo cliente e dimensões estáveis do contêiner.
- Use a função de visibilidade para ocultar temporariamente, sem redefinir a sessão.
- Finalize a sessão apenas ao sair ou trocar de conta de forma intencional.
Verificar o fluxo iOS
- Abra o widget a partir de um ecrã em primeiro plano e confirme que tocar duas vezes não cria um segundo controlador.
- Inicie uma conversa, feche e abra novamente o widget e confirme que o rascunho e o envio pendente continuam associados à mesma conta.
- Rode ou envie a app para segundo plano durante uma operação pendente e aguarde o estado explícito de reconexão ou repetição antes de tentar novamente.
- Reinicie e elimine o cliente iOS direto antes de trocar de conta. Depois crie o cliente seguinte e confirme que não consegue ver a conversa anterior.
