logo
Entwicklung
Suchen
Workspace-Integrationsleitfaden für iOS: iOS-Integration in Unternehmensanwendungen

Workspace-Integrationsleitfaden für iOS: iOS-Integration in Unternehmensanwendungen

Dieser Leitfaden bietet Ihnen eine detaillierte Anleitung zur iOS-Integration des GPTBots-Workspaces in Ihre Unternehmensanwendungen. Sie erfahren Schritt für Schritt, wie Sie die Integration in iOS-Anwendungen umsetzen – von Berechtigungsanfragen über die Interaktion zwischen nativen Komponenten und WebView (H5) bis hin zu allen relevanten Konfigurationen.
GPTBots stellt zudem ein iOS-DEMO-Projekt bereit, das Ihnen einen schnellen Einstieg in die iOS-Integration ermöglicht: iOS DEMO Project

Systemanforderungen und grundlegende Konfigurationsanforderungen

  • iOS 13.0 oder höher
  • Xcode 12.0 oder höher
  • Swift 5.0 oder höher

iOS Frameworks hinzufügen

Für die iOS-Integration werden folgende Frameworks benötigt:

  • WebKit
  • AVFoundation (für Mikrofonzugriff)
  • Photos (für Zugriff auf die Foto-Mediathek)
  • MobileCoreServices (für Dateiauswahl)

Für iOS 14 und neuer kann zusätzlich verwendet werden:

#if canImport(UniformTypeIdentifiers) import UniformTypeIdentifiers #endif
                      
                      #if canImport(UniformTypeIdentifiers)
import UniformTypeIdentifiers
#endif

                    
Dieser Codeblock im schwebenden Fenster

Info.plist Konfiguration

Fügen Sie folgende Konfigurationen in die Info.plist ein:

<!-- HTTP-Anfragen erlauben --> <key>NSAppTransportSecurity</key> <dict> <key>NSAllowsArbitraryLoads</key> <true/> <key>NSExceptionDomains</key> <dict> <key>gptbots-auto.qa.jpushoa.com</key> <dict> <key>NSExceptionAllowsInsecureHTTPLoads</key> <true/> <key>NSExceptionMinimumTLSVersion</key> <string>TLSv1.0</string> <key>NSIncludesSubdomains</key> <true/> </dict> </dict> </dict>
                      
                      <!-- HTTP-Anfragen erlauben -->
<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
    <key>NSExceptionDomains</key>
    <dict>
        <key>gptbots-auto.qa.jpushoa.com</key>
        <dict>
            <key>NSExceptionAllowsInsecureHTTPLoads</key>
            <true/>
            <key>NSExceptionMinimumTLSVersion</key>
            <string>TLSv1.0</string>
            <key>NSIncludesSubdomains</key>
            <true/>
        </dict>
    </dict>
</dict>

                    
Dieser Codeblock im schwebenden Fenster

Berechtigungskonfiguration für die iOS-App

Ihre iOS-App benötigt folgende Berechtigungen:

Kamerazugriff

<key>NSCameraUsageDescription</key> <string>Diese iOS-App benötigt Zugriff auf die Kamera, um Fotofunktionen auf Webseiten zu unterstützen</string>
                      
                      <key>NSCameraUsageDescription</key>
<string>Diese iOS-App benötigt Zugriff auf die Kamera, um Fotofunktionen auf Webseiten zu unterstützen</string>

                    
Dieser Codeblock im schwebenden Fenster

Mikrofonzugriff

<key>NSMicrophoneUsageDescription</key> <string>Diese iOS-App benötigt Zugriff auf das Mikrofon, um Audioaufnahmen auf Webseiten zu ermöglichen</string>
                      
                      <key>NSMicrophoneUsageDescription</key>
<string>Diese iOS-App benötigt Zugriff auf das Mikrofon, um Audioaufnahmen auf Webseiten zu ermöglichen</string>

                    
Dieser Codeblock im schwebenden Fenster

Zugriff auf die Foto-Mediathek

<key>NSPhotoLibraryUsageDescription</key> <string>Diese iOS-App benötigt Zugriff auf Ihre Foto-Mediathek, um Bilder und Videodateien auszuwählen</string>
                      
                      <key>NSPhotoLibraryUsageDescription</key>
<string>Diese iOS-App benötigt Zugriff auf Ihre Foto-Mediathek, um Bilder und Videodateien auszuwählen</string>

                    
Dieser Codeblock im schwebenden Fenster

Zugriff auf Dokumente

<key>NSDocumentUsageDescription</key> <string>Diese iOS-App benötigt Zugriff auf Dokumente, um Datei-Uploads auf Webseiten zu ermöglichen</string>
                      
                      <key>NSDocumentUsageDescription</key>
<string>Diese iOS-App benötigt Zugriff auf Dokumente, um Datei-Uploads auf Webseiten zu ermöglichen</string>

                    
Dieser Codeblock im schwebenden Fenster

WebView-Controller erstellen und Integration umsetzen

WebView-Controller erstellen

Erstellen Sie einen dedizierten Controller zur Anzeige der WebView:

class WebViewController: UIViewController { private var webView: WKWebView! private var webViewBridge: WebViewBridge! var urlString: String = "" override func viewDidLoad() { super.viewDidLoad() setupWebView() setupWebViewBridge() loadURL() } private func setupWebView() { let configuration = WKWebViewConfiguration() configuration.allowsInlineMediaPlayback = true configuration.mediaTypesRequiringUserActionForPlayback = [] configuration.applicationNameForUserAgent = "WebViewApp/1.0" webView = WKWebView(frame: view.bounds, configuration: configuration) webView.autoresizingMask = [.flexibleWidth, .flexibleHeight] webView.navigationDelegate = self webView.uiDelegate = self view.addSubview(webView) } private func loadURL() { guard !urlString.isEmpty, let url = URL(string: urlString) else { return } let request = URLRequest(url: url) webView.load(request) } }
                      
                      class WebViewController: UIViewController {
    private var webView: WKWebView!
    private var webViewBridge: WebViewBridge!
    var urlString: String = ""
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupWebView()
        setupWebViewBridge()
        loadURL()
    }
    
    private func setupWebView() {
        let configuration = WKWebViewConfiguration()
        configuration.allowsInlineMediaPlayback = true
        configuration.mediaTypesRequiringUserActionForPlayback = []
        configuration.applicationNameForUserAgent = "WebViewApp/1.0"
        
        webView = WKWebView(frame: view.bounds, configuration: configuration)
        webView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        webView.navigationDelegate = self
        webView.uiDelegate = self
        
        view.addSubview(webView)
    }
    
    private func loadURL() {
        guard !urlString.isEmpty, let url = URL(string: urlString) else {
            return
        }
        
        let request = URLRequest(url: url)
        webView.load(request)
    }
}

                    
Dieser Codeblock im schwebenden Fenster

JavaScript-Bridge implementieren

Erstellen Sie eine Bridge-Klasse, um die Kommunikation zwischen nativen Komponenten und JavaScript zu ermöglichen:

class WebViewBridge: NSObject { static let EVENT_CLICK = "click" static let EVENT_MESSAGE = "message" private weak var webView: WKWebView? weak var delegate: WebViewBridgeDelegate? init(webView: WKWebView, viewController: UIViewController) { self.webView = webView super.init() } func registerJSInterface() { webView?.configuration.userContentController.add(self, name: "agentWebBridge") } func callH5(eventType: String, data: [String: Any]) { let message: [String: Any] = [ "eventType": eventType, "data": data ] if let jsonData = try? JSONSerialization.data(withJSONObject: message), let jsonString = String(data: jsonData, encoding: .utf8) { let escapedJson = jsonString .replacingOccurrences(of: "\\", with: "\\\\") .replacingOccurrences(of: "'", with: "\\'") .replacingOccurrences(of: "\"", with: "\\\"") let jsCode = "window.onCallH5Message('\(escapedJson)')" webView?.evaluateJavaScript(jsCode) } } }
                      
                      class WebViewBridge: NSObject {
    static let EVENT_CLICK = "click"
    static let EVENT_MESSAGE = "message"
    
    private weak var webView: WKWebView?
    weak var delegate: WebViewBridgeDelegate?
    
    init(webView: WKWebView, viewController: UIViewController) {
        self.webView = webView
        super.init()
    }
    
    func registerJSInterface() {
        webView?.configuration.userContentController.add(self, name: "agentWebBridge")
    }
    
    func callH5(eventType: String, data: [String: Any]) {
        let message: [String: Any] = [
            "eventType": eventType,
            "data": data
        ]
        
        if let jsonData = try? JSONSerialization.data(withJSONObject: message),
           let jsonString = String(data: jsonData, encoding: .utf8) {
            let escapedJson = jsonString
                .replacingOccurrences(of: "\\", with: "\\\\")
                .replacingOccurrences(of: "'", with: "\\'")
                .replacingOccurrences(of: "\"", with: "\\\"")
            
            let jsCode = "window.onCallH5Message('\(escapedJson)')"
            
            webView?.evaluateJavaScript(jsCode)
        }
    }
}

                    
Dieser Codeblock im schwebenden Fenster

WebView-Delegate-Methoden implementieren

Implementieren Sie die erforderlichen WebView-Delegate-Methoden, um Dateiauswahl, Berechtigungsanfragen usw. zu behandeln:

extension WebViewController: WKUIDelegate { // Datei-Upload behandeln @available(iOS 18.4, *) func webView(_ webView: WKWebView, runOpenPanelWith parameters: WKOpenPanelParameters, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping ([URL]?) -> Void) { let alert = UIAlertController(title: "Datei auswählen", message: nil, preferredStyle: .actionSheet) alert.addAction(UIAlertAction(title: "Aus Fotos auswählen", style: .default) { _ in self.presentImagePicker(completionHandler: completionHandler) }) alert.addAction(UIAlertAction(title: "Aus Dateien auswählen", style: .default) { _ in self.presentDocumentPicker(completionHandler: completionHandler) }) alert.addAction(UIAlertAction(title: "Abbrechen", style: .cancel) { _ in completionHandler(nil) }) present(alert, animated: true) } // Medien-Berechtigungsanfragen behandeln @available(iOS 15.0, *) func webView(_ webView: WKWebView, requestMediaCapturePermissionFor origin: WKSecurityOrigin, initiatedByFrame frame: WKFrameInfo, type: WKMediaCaptureType, decisionHandler: @escaping (WKPermissionDecision) -> Void) { switch type { case .microphone: // Mikrofonberechtigung prüfen switch AVAudioSession.sharedInstance().recordPermission { case .granted: decisionHandler(.grant) case .denied: decisionHandler(.deny) case .undetermined: AVAudioSession.sharedInstance().requestRecordPermission { granted in DispatchQueue.main.async { if granted { decisionHandler(.grant) } else { decisionHandler(.deny) } } } @unknown default: decisionHandler(.deny) } case .camera, .cameraAndMicrophone: decisionHandler(.grant) @unknown default: decisionHandler(.deny) } } }
                      
                      extension WebViewController: WKUIDelegate {
    // Datei-Upload behandeln
    @available(iOS 18.4, *)
    func webView(_ webView: WKWebView, runOpenPanelWith parameters: WKOpenPanelParameters, 
                 initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping ([URL]?) -> Void) {
        
        let alert = UIAlertController(title: "Datei auswählen", message: nil, preferredStyle: .actionSheet)
        
        alert.addAction(UIAlertAction(title: "Aus Fotos auswählen", style: .default) { _ in
            self.presentImagePicker(completionHandler: completionHandler)
        })
        
        alert.addAction(UIAlertAction(title: "Aus Dateien auswählen", style: .default) { _ in
            self.presentDocumentPicker(completionHandler: completionHandler)
        })
        
        alert.addAction(UIAlertAction(title: "Abbrechen", style: .cancel) { _ in
            completionHandler(nil)
        })
        
        present(alert, animated: true)
    }
    
    // Medien-Berechtigungsanfragen behandeln
    @available(iOS 15.0, *)
    func webView(_ webView: WKWebView, requestMediaCapturePermissionFor origin: WKSecurityOrigin, 
                 initiatedByFrame frame: WKFrameInfo, type: WKMediaCaptureType, 
                 decisionHandler: @escaping (WKPermissionDecision) -> Void) {
        
        switch type {
        case .microphone:
            // Mikrofonberechtigung prüfen
            switch AVAudioSession.sharedInstance().recordPermission {
            case .granted:
                decisionHandler(.grant)
            case .denied:
                decisionHandler(.deny)
            case .undetermined:
                AVAudioSession.sharedInstance().requestRecordPermission { granted in
                    DispatchQueue.main.async {
                        if granted {
                            decisionHandler(.grant)
                        } else {
                            decisionHandler(.deny)
                        }
                    }
                }
            @unknown default:
                decisionHandler(.deny)
            }
        case .camera, .cameraAndMicrophone:
            decisionHandler(.grant)
        @unknown default:
            decisionHandler(.deny)
        }
    }
}

                    
Dieser Codeblock im schwebenden Fenster

Datei-Upload in iOS: Dateiauswahl-Funktionalität

extension WebViewController { private func presentImagePicker(completionHandler: @escaping ([URL]?) -> Void) { let picker = UIImagePickerController() picker.delegate = self picker.sourceType = .photoLibrary picker.mediaTypes = ["public.image", "public.movie"] fileUploadCallback = { url in if let url = url { completionHandler([url]) } else { completionHandler(nil) } } present(picker, animated: true) } private func presentDocumentPicker(completionHandler: @escaping ([URL]?) -> Void) { let picker: UIDocumentPickerViewController if #available(iOS 14.0, *) { picker = UIDocumentPickerViewController(forOpeningContentTypes: [.data, .text, .image, .movie, .audio]) } else { picker = UIDocumentPickerViewController(documentTypes: [ "public.data", "public.text", "public.image", "public.movie", "public.audio" ], in: .import) } picker.delegate = self picker.allowsMultipleSelection = false fileUploadCallback = { url in if let url = url { completionHandler([url]) } else { completionHandler(nil) } } present(picker, animated: true) } }
                      
                      extension WebViewController {
    private func presentImagePicker(completionHandler: @escaping ([URL]?) -> Void) {
        let picker = UIImagePickerController()
        picker.delegate = self
        picker.sourceType = .photoLibrary
        picker.mediaTypes = ["public.image", "public.movie"]
        
        fileUploadCallback = { url in
            if let url = url {
                completionHandler([url])
            } else {
                completionHandler(nil)
            }
        }
        
        present(picker, animated: true)
    }
    
    private func presentDocumentPicker(completionHandler: @escaping ([URL]?) -> Void) {
        let picker: UIDocumentPickerViewController
        
        if #available(iOS 14.0, *) {
            picker = UIDocumentPickerViewController(forOpeningContentTypes: [.data, .text, .image, .movie, .audio])
        } else {
            picker = UIDocumentPickerViewController(documentTypes: [
                "public.data",
                "public.text", 
                "public.image",
                "public.movie",
                "public.audio"
            ], in: .import)
        }
        
        picker.delegate = self
        picker.allowsMultipleSelection = false
        
        fileUploadCallback = { url in
            if let url = url {
                completionHandler([url])
            } else {
                completionHandler(nil)
            }
        }
        
        present(picker, animated: true)
    }
}

                    
Dieser Codeblock im schwebenden Fenster

Verarbeitung von WebView-Nachrichten

extension WebViewBridge: WKScriptMessageHandler { func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { guard message.name == "agentWebBridge", let messageBody = message.body as? String else { return } do { guard let data = messageBody.data(using: .utf8), let jsonObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any], let eventType = jsonObject["eventType"] as? String else { return } let eventData = jsonObject["data"] as? [String: Any] ?? [:] // Event-Handling im Hauptthread ausführen DispatchQueue.main.async { [weak self] in guard let self = self else { return } switch eventType { case WebViewBridge.EVENT_CLICK: self.delegate?.onClickEvent(data: eventData) case WebViewBridge.EVENT_MESSAGE: self.delegate?.onMessageEvent(data: eventData) default: self.delegate?.onUnhandledEvent(eventType: eventType, data: eventData) } } } catch { print("Fehler beim Parsen der H5-Nachricht: \(error.localizedDescription)") } } }
                      
                      extension WebViewBridge: WKScriptMessageHandler {
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        guard message.name == "agentWebBridge",
              let messageBody = message.body as? String else {
            return
        }
        
        do {
            guard let data = messageBody.data(using: .utf8),
                  let jsonObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
                  let eventType = jsonObject["eventType"] as? String else {
                return
            }
            
            let eventData = jsonObject["data"] as? [String: Any] ?? [:]
            
            // Event-Handling im Hauptthread ausführen
            DispatchQueue.main.async { [weak self] in
                guard let self = self else { return }
                
                switch eventType {
                case WebViewBridge.EVENT_CLICK:
                    self.delegate?.onClickEvent(data: eventData)
                    
                case WebViewBridge.EVENT_MESSAGE:
                    self.delegate?.onMessageEvent(data: eventData)
                    
                default:
                    self.delegate?.onUnhandledEvent(eventType: eventType, data: eventData)
                }
            }
        } catch {
            print("Fehler beim Parsen der H5-Nachricht: \(error.localizedDescription)")
        }
    }
}

                    
Dieser Codeblock im schwebenden Fenster

Fehlerbehebung iOS Integration

1. WebView lädt die Seite nicht

  • Prüfen Sie die Netzwerkverbindung.
  • Überprüfen Sie, ob der NSAppTransportSecurity-Eintrag in der Info.plist korrekt konfiguriert ist.
  • Stellen Sie sicher, dass die URL korrekt formatiert und erreichbar ist.

2. Berechtigungsanfragen werden abgelehnt

  • Stellen Sie sicher, dass alle erforderlichen Schlüssel für die Nutzungsbeschreibung in der Info.plist vorhanden sind.
  • Weisen Sie Nutzer:innen darauf hin, die Berechtigung manuell unter Einstellungen > Datenschutz zu aktivieren, falls die Anfrage abgelehnt wurde.

3. Kommunikation der JavaScript-Bridge funktioniert nicht

  • Überprüfen Sie, ob die WebViewBridge ihre JavaScript-Schnittstelle beim WKUserContentController registriert hat.
  • Validieren Sie, dass das Nachrichtenformat von JavaScript dem erwarteten JSON-Schema entspricht.
  • Nutzen Sie den Web Inspector von Safari (im Entwicklermenü), um JavaScript-Fehler innerhalb der WKWebView zu debuggen.

4. Probleme beim Datei-Upload

  • Verwenden Sie unter iOS 13 und älter die korrekten UTIs (z. B. public.image, public.data) beim Öffnen des Dokumentpickers.
  • Stellen Sie sicher, dass die App die notwendigen Datenschutzberechtigungen für Fotos und Dateien besitzt.
  • Behalten Sie die ausgewählte Datei-URL bis zum Abschluss des Uploads bei und vermeiden Sie den Zugriff auf veraltete sicherheitsbezogene URLs.