Guía de integración del espacio de trabajo para iOS
Esta guía proporciona instrucciones detalladas sobre cómo integrar el espacio de trabajo de GPTBots en aplicaciones iOS, incluidas las solicitudes de permisos, las interacciones entre código nativo y H5 y otras configuraciones relevantes.
GPTBots también proporciona un proyecto de demostración (DEMO) del espacio de trabajo para ayudarle a empezar rápidamente. Proyecto DEMO de iOS: Proyecto DEMO de iOS
Requisitos básicos de configuración
- iOS 13.0 o superior
- Xcode 12.0 o superior
- Swift 5.0 o superior
Adición de los frameworks necesarios
El proyecto requiere los siguientes frameworks:
- WebKit
- AVFoundation (para acceso al micrófono)
- Photos (para acceso a la fototeca/biblioteca de fotos)
- MobileCoreServices (para selección de archivos)
Para iOS 14 o superior, también se puede utilizar:
#if canImport(UniformTypeIdentifiers)
import UniformTypeIdentifiers
#endif
Configuración de Info.plist
Añadir la siguiente configuración a Info.plist:
<!-- Allow HTTP requests -->
<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>
Configuración de permisos
La aplicación requiere los siguientes permisos:
Permiso de cámara
<key>NSCameraUsageDescription</key>
<string>Esta aplicación necesita utilizar la cámara para admitir las funciones de captura de fotos en páginas web</string>
Permiso de micrófono
<key>NSMicrophoneUsageDescription</key>
<string>Esta aplicación necesita utilizar el micrófono para admitir las funciones de grabación de audio en páginas web</string>
Permiso de fototeca
<key>NSPhotoLibraryUsageDescription</key>
<string>Esta aplicación necesita acceder a su fototeca para seleccionar imágenes y archivos de vídeo</string>
Permiso de acceso a documentos
<key>NSDocumentUsageDescription</key>
<string>Esta aplicación necesita acceder a documentos para admitir las funciones de carga de archivos en páginas web</string>
Interacción entre nativo y WebView
Creación de un controlador de WebView
Crear un controlador dedicado para mostrar la 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)
}
}
Implementación del puente JavaScript
Crear una clase de puente para gestionar la comunicación entre el código nativo y 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)
}
}
}
Implementación de métodos delegados de WebView
Implementar los métodos delegados de WebView necesarios para gestionar la selección de archivos, las solicitudes de permisos, etc.:
extension WebViewController: WKUIDelegate {
// Handle file upload
@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)
}
// Handle media permission requests
@available(iOS 15.0, *)
func webView(_ webView: WKWebView, requestMediaCapturePermissionFor origin: WKSecurityOrigin,
initiatedByFrame frame: WKFrameInfo, type: WKMediaCaptureType,
decisionHandler: @escaping (WKPermissionDecision) -> Void) {
switch type {
case .microphone:
// Check microphone permission
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)
}
}
}
Implementación de la funcionalidad de selección de archivos
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)
}
}
Gestión de mensajes de WebView
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] ?? [:]
// Dispatch event handling on main thread
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)")
}
}
}
Solución de problemas
1. WebView no carga la página
- Comprobar la conexión de red.
- Verificar que la entrada NSAppTransportSecurity en Info.plist esté configurada correctamente.
- Asegurarse de que la URL tenga un formato correcto y sea accesible.
2. Se deniegan las solicitudes de permisos
- Asegurarse de que todas las claves usage-description requeridas estén presentes en Info.plist.
- Guiar a los usuarios para que habiliten manualmente el permiso en Ajustes > Privacidad si la solicitud inicial se denegó.
3. Se interrumpe la comunicación del puente JavaScript
- Confirmar que WebViewBridge haya registrado su interfaz de JavaScript en WKUserContentController.
- Validar que la carga útil del mensaje desde JavaScript coincida con el esquema JSON esperado.
- Utilizar Web Inspector de Safari (menú Develop (Desarrollo)) para depurar errores de JavaScript dentro de WKWebView.
4. Problemas de carga de archivos
- En iOS 13 y versiones anteriores, utilizar los UTI correctos (p. ej., public.image, public.data) al presentar el selector de documentos.
- Asegurarse de que la aplicación disponga de los entitlements de privacidad necesarios para el acceso a Photos y Files.
- Conservar la URL del archivo seleccionado hasta que la carga se complete; evitar acceder a URL con ámbito de seguridad obsoleto.
