AI Chatbot Guide v1
AI Chatbot
Version 1

Integrate widget into your iOS app

Copy link

This guide provides a step-by-step approach to embedding an AI chatbot widget into your iOS application using WKWebView. By loading an HTML string into a WebView, you can embed an AI chatbot widget directly into your app.


Prerequisite

Copy link

Before you start, make sure you have the following information:

  • Your Sendbird bot ID. If you don't have a bot, create one on Sendbird Dashboard under AI chatbot > Bot studio > Create bot. For further information, see the Bot studio guide.


Integration steps

Copy link

The following steps guide you through the process of embedding an AI chatbot widget into your iOS application using WKWebView.

Step 1 Configure WKWebView

Copy link
  1. Import WebKit.
  2. Initialize WKWebView with the necessary settings.
  3. Add the WebView to the view and configure the layout using Auto Layout.
  4. Load the HTML string into your WKWebView. This HTML includes the UI for the chatbot widget.

The following code snippet provides an example of how to configure the WKWebView.

import WebKit

class ChatBotWidgetView: UIView {
    lazy var webView: WKWebView = {
        let webView = WKWebView(frame: .zero, configuration: self.config)
        webView.translatesAutoresizingMaskIntoConstraints = false
        webView.scrollView.alwaysBounceVertical = false
        webView.scrollView.alwaysBounceHorizontal = false
        webView.scrollView.isScrollEnabled = false
        webView.navigationDelegate = self
        webView.layer.cornerRadius = 20
        webView.clipsToBounds = true
        return webView
    }()

    override init(frame: CGRect) {
        super.init(frame: frame)
        self.setupView()
        self.loadHTML()
    }

    // setup view autolayout
    private func setupView() {
        self.addSubview(self.webView)
        NSLayoutConstraint.activate([
           // ...
        ])
    }

    // load html string in webview
    private func loadHTML() {
        let html = widgetHTML(
            applicationId: self.applicationId,
            botId: self.botId,
            showCloseIcon: self.showCloseIcon
        )
        
        // NOTE: `baseURL` is for caching web local storage.
        let baseURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
        
        self.webView.loadHTMLString(html, baseURL: baseURL)
    }
}

Note: The purpose of setting the baseURL on load is to ensure that existing chat content is preserved when the widget is newly exposed.

Step 2 Prepare and load HTML string content

Copy link

Customize the HTML content to display your widget by setting the applicationID and botID. If the widget is not loading correctly in the WebView, this should be the first thing to check.

extension ChatBotWidgetView {
    func widgetHTML(
        applicationId: String,
        botId: String,
        showCloseIcon: Bool
    ) -> String {
        """
        <!DOCTYPE html>
        <html lang="en">
          <head>
            <meta charset="UTF-8">
            <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
            <meta http-equiv="X-UA-Compatible" content="ie=edge">
            <title>Chatbot</title>
        
            <!-- Load React and ReactDOM libraries -->
            <script crossorigin src="https://unpkg.com/react@18.2.0/umd/react.development.js"></script>
            <script crossorigin src="https://unpkg.com/react-dom@18.2.0/umd/react-dom.development.js"></script>
        
            <!-- Load chat-ai-widget script and set process.env to prevent it from being undefined -->
            <script>process = { env: { NODE_ENV: '' } }</script>
            <script crossorigin src="https://unpkg.com/@sendbird/chat-ai-widget@latest/dist/index.umd.js"></script>
            <link href="https://unpkg.com/@sendbird/chat-ai-widget@latest/dist/style.css" rel="stylesheet" />
        
            <!-- Optional; to enable JSX syntax -->
            <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
            <style>
                html, body { height: 100%; margin: 0; }
                \(showCloseIcon == true ? "" : "#aichatbot-widget-close-icon { display: none }")
            </style>
          </head>
          <body>
            <!-- div element for chat-ai-widget container -->
            <div id="root"></div>
        
            <!-- Initialize chat-ai-widget and render the widget component -->
            <script type="text/babel">
              const { ChatWindow } = window.ChatAiWidget;
              const App = () => {
                return (
                  <ChatWindow
                    applicationId="\(applicationId)"
                    botId="\(botId)"
                  />
                );
              };
              ReactDOM.createRoot(document.querySelector('#root')).render(<App />);
            </script>
            <script>
                // Attach click event to the close icon after the chat widget is loaded
                window.onload = function() {
                  setTimeout(() => {
                    const closeIcon = document.querySelector('#aichatbot-widget-close-icon');
                    closeIcon?.addEventListener('click', () => window.webkit?.messageHandlers?.bridgeHandler?.postMessage?.("closeChatBot"));
                  }, 1000); // delay to ensure the element is loaded
                };
            </script>
          </body>
        </html>
        """
    }
}

Close button configuration

Copy link

In the HTML code above, the showCloseIcon parameter is used to determine whether the close button of the widget should be displayed. By default, the close button is displayed. If you want to hide the close button, set showCloseIcon to false.

<style>
    html, body { height: 100%; margin: 0; }
    \(showCloseIcon == true ? "" : "#aichatbot-widget-close-icon { display: none }")
</style>

In the script section of the HTML, you will find that the click event of the close button is added to webkit.messageHandlers.

<script>
    // Attach click event to the close icon after the chat widget is loaded.
    window.onload = function() {
      setTimeout(() => {
        const closeIcon = document.querySelector('#aichatbot-widget-close-icon');
        closeIcon?.addEventListener('click', () => window.webkit?.messageHandlers?.bridgeHandler?.postMessage?.("closeChatBot"));
      }, 1000); // delay to ensure the element is loaded.
    };
</script>

To handle this event, you need to configure the bridgeHandler when creating the WKWebView.

class ChatBotWidgetView: UIView {    
    lazy var config: WKWebViewConfiguration = {
        let contentController = WKUserContentController()
        contentController.add(self, name: "bridgeHandler")
        
        let config = WKWebViewConfiguration()
        config.userContentController = contentController
        return config
    }()
}

extension ChatBotWidgetView: WKScriptMessageHandler {
    func userContentController(
        _ userContentController: WKUserContentController,
        didReceive message: WKScriptMessage
    ) {
        guard message.name == "bridgeHandler" else { return }
        guard let messageBody = message.body as? String else { return }
        guard messageBody == "closeChatBot" else { return }
 
        // handle close action
    }
}

Additional implementation

Copy link

The following sections provide additional implementation details to enhance the functionality of the chatbot widget.

Error Handling

Copy link

Implement error handling to manage any issues during the loading of the widget.

// NOTE: webview delegate methods
extension ChatBotWidgetView: WKNavigationDelegate {
    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
        // did load widget
    }
    
    // Methods called when an error occurs
    func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
        handleError(error)
    }
    
    // Methods called on page load failure
    func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
        handleError(error)
    }
    
    // handle error
    private func handleError(_ error: Error) {
        debugPrint("\(error.localizedDescription)")
    }
}

Keyboard interaction

Copy link

Handle keyboard events to adjust the chat interface when the keyboard is visible.

func setupKeyboardObservers() {
    NotificationCenter.default.addObserver(
        self,
        selector: #selector(keyboardWillShow),
        name: UIResponder.keyboardWillShowNotification, 
        object: nil
    )
    NotificationCenter.default.addObserver(
        self,
        selector: #selector(keyboardWillHide),
        name: UIResponder.keyboardWillHideNotification,
        object: nil
    )
}

func clearKeyboardObservers() {
    NotificationCenter.default.removeObserver(
        self,
        name: UIResponder.keyboardWillShowNotification,
        object: nil
    )
    NotificationCenter.default.removeObserver(
        self,
        name: UIResponder.keyboardWillHideNotification,
        object: nil
    )
}

@objc private func keyboardWillShow(notification: NSNotification) {
    // handle autolayout height with duration
}

@objc private func keyboardWillHide(notification: NSNotification) {
    // handle autolayout height with duration
}

Layout adjustments

Copy link

Ensure the widget's layout adjusts properly when device orientations or view sizes change.

override func layoutSubviews() {
    super.layoutSubviews()
    
    // A delay of `0.01` sec may be necessary.
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) { [weak self] in
        self?.webView.scrollView.setContentOffset(.zero, animated: true)
    }
}

For a complete sample of this guide, see this sample page.