logo
Entwicklung
Suchen
Dokumentation zur Arbeitsbereich-Integration unter iOS

Dokumentation zur Arbeitsbereich-Integration unter iOS

Dieser Leitfaden erläutert ausführlich, wie Sie den GPTBots-Arbeitsbereich in eine iOS-Anwendung integrieren, einschließlich Berechtigungsanforderungen, nativer und H5-Interaktion sowie weiterer zugehöriger Konfigurationen.

GPTBots stellt gleichzeitig ein DEMO-Projekt für den Arbeitsbereich bereit, das Ihnen einen schnellen Einstieg ermöglicht. DEMO-Projekt für iOS: ios-webview-bridge


Grundlegende Konfigurationsanforderungen

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

Erforderliche Frameworks hinzufügen

Das Projekt benötigt die folgenden Frameworks:

  • WebKit
  • AVFoundation (für den Mikrofonzugriff)
  • Photos (für den Zugriff auf die Fotomediathek)
  • MobileCoreServices (für die Dateiauswahl)

Ab iOS 14 und höher können Sie außerdem verwenden:

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

                    
Dieser Codeblock im schwebenden Fenster

Info.plist konfigurieren

Fügen Sie in der Info.plist die folgende Konfiguration hinzu:

<!-- 允许HTTP请求 --> <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请求 -->
<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

Die Anwendung benötigt die folgenden Berechtigungen:

Kameraberechtigung

<key>NSCameraUsageDescription</key> <string>This app needs to use the camera to support photo capture features in web pages</string>
                      
                      <key>NSCameraUsageDescription</key>
<string>This app needs to use the camera to support photo capture features in web pages</string>

                    
Dieser Codeblock im schwebenden Fenster

Mikrofonberechtigung

<key>NSMicrophoneUsageDescription</key> <string>This app needs to use the microphone to support audio recording features in web pages</string>
                      
                      <key>NSMicrophoneUsageDescription</key>
<string>This app needs to use the microphone to support audio recording features in web pages</string>

                    
Dieser Codeblock im schwebenden Fenster

Berechtigung für die Fotomediathek

<key>NSPhotoLibraryUsageDescription</key> <string>This app needs to access your photo library to select images and video files</string>
                      
                      <key>NSPhotoLibraryUsageDescription</key>
<string>This app needs to access your photo library to select images and video files</string>

                    
Dieser Codeblock im schwebenden Fenster

Berechtigung für den Dokumentenzugriff

<key>NSDocumentUsageDescription</key> <string>This app needs to access documents to support file upload features in web pages</string>
                      
                      <key>NSDocumentUsageDescription</key>
<string>This app needs to access documents to support file upload features in web pages</string>

                    
Dieser Codeblock im schwebenden Fenster

Interaktion zwischen nativem Code und WebView

WebView-Controller erstellen

Erstellen Sie einen speziellen 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 für die Kommunikation zwischen nativem Code und JavaScript:

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 zur Verarbeitung von Dateiauswahl, Berechtigungsanforderungen usw.:

extension WebViewController: WKUIDelegate { // 处理文件上传 @available(iOS 18.4, *) func webView(_ webView: WKWebView, runOpenPanelWith parameters: WKOpenPanelParameters, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping ([URL]?) -> Void) { let alert = UIAlertController(title: "Select File", message: nil, preferredStyle: .actionSheet) alert.addAction(UIAlertAction(title: "Select from Photos", style: .default) { _ in self.presentImagePicker(completionHandler: completionHandler) }) alert.addAction(UIAlertAction(title: "Select from Files", style: .default) { _ in self.presentDocumentPicker(completionHandler: completionHandler) }) alert.addAction(UIAlertAction(title: "Cancel", style: .cancel) { _ in completionHandler(nil) }) present(alert, animated: true) } // 处理媒体权限请求 @available(iOS 15.0, *) func webView(_ webView: WKWebView, requestMediaCapturePermissionFor origin: WKSecurityOrigin, initiatedByFrame frame: WKFrameInfo, type: WKMediaCaptureType, decisionHandler: @escaping (WKPermissionDecision) -> Void) { switch type { case .microphone: // 检查麦克风权限 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 {
    // 处理文件上传
    @available(iOS 18.4, *)
    func webView(_ webView: WKWebView, runOpenPanelWith parameters: WKOpenPanelParameters,
                 initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping ([URL]?) -> Void) {

        let alert = UIAlertController(title: "Select File", message: nil, preferredStyle: .actionSheet)

        alert.addAction(UIAlertAction(title: "Select from Photos", style: .default) { _ in
            self.presentImagePicker(completionHandler: completionHandler)
        })

        alert.addAction(UIAlertAction(title: "Select from Files", style: .default) { _ in
            self.presentDocumentPicker(completionHandler: completionHandler)
        })

        alert.addAction(UIAlertAction(title: "Cancel", style: .cancel) { _ in
            completionHandler(nil)
        })

        present(alert, animated: true)
    }

    // 处理媒体权限请求
    @available(iOS 15.0, *)
    func webView(_ webView: WKWebView, requestMediaCapturePermissionFor origin: WKSecurityOrigin,
                 initiatedByFrame frame: WKFrameInfo, type: WKMediaCaptureType,
                 decisionHandler: @escaping (WKPermissionDecision) -> Void) {

        switch type {
        case .microphone:
            // 检查麦克风权限
            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

Dateiauswahlfunktion implementieren

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

WebView-Nachrichten verarbeiten

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] ?? [:] // 根据事件类型分发处理 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("Error parsing H5 message: \(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] ?? [:]

            // 根据事件类型分发处理
            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("Error parsing H5 message: \(error.localizedDescription)")
        }
    }
}

                    
Dieser Codeblock im schwebenden Fenster

Häufige Fragen

1. WebView kann die Seite nicht laden

  • Überprüfen Sie die Netzwerkverbindung
  • Stellen Sie sicher, dass die NSAppTransportSecurity-Konfiguration in der Info.plist korrekt ist
  • Überprüfen Sie, ob das URL-Format korrekt ist

2. Berechtigungsanforderung wird abgelehnt

  • Stellen Sie sicher, dass Sie in der Info.plist alle erforderlichen Berechtigungsbeschreibungen hinzugefügt haben
  • Leiten Sie den Benutzer an, die Berechtigungen manuell in den Geräteeinstellungen zu aktivieren

3. JavaScript-Interaktion schlägt fehl

  • Stellen Sie sicher, dass die WebViewBridge die JavaScript-Schnittstelle korrekt registriert hat
  • Überprüfen Sie, ob das Format der JavaScript-Nachricht den Erwartungen entspricht
  • Verwenden Sie die Safari-Entwicklertools, um JavaScript-Fehler in der WebView zu debuggen

4. Probleme beim Datei-Upload

  • Stellen Sie bei iOS-Versionen unter 14 sicher, dass Sie die korrekten Dokumenttyp-Bezeichner verwenden
  • Stellen Sie sicher, dass die Anwendung über ausreichende Berechtigungen für den Zugriff auf die Fotomediathek und das Dateisystem verfügt
  • Verwalten Sie den Lebenszyklus der Datei-URL, um sicherzustellen, dass die URL zum Zeitpunkt der Verwendung noch gültig ist

Verwandte Dokumente