Skip to content

iOS WebView Management

Overview

The Squad SDK uses WKWebView to provide a seamless integration of Squad features into your iOS application. This guide covers WebView setup, management, and optimization.

WebView Setup

Basic Implementation

class SquadViewController: UIViewController {
    private var webView: WKWebView!

    override func viewDidLoad() {
        super.viewDidLoad()
        setupWebView()
    }

    private func setupWebView() {
        let configuration = WKWebViewConfiguration()
        configuration.allowsInlineMediaPlayback = true
        configuration.mediaTypesRequiringUserActionForPlayback = []

        // Create WebView
        webView = WKWebView(frame: view.bounds, configuration: configuration)
        webView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        webView.navigationDelegate = self
        webView.uiDelegate = self

        view.addSubview(webView)
    }
}

Custom Configuration

extension SquadViewController {
    private func configureWebView() {
        let preferences = WKWebpagePreferences()
        preferences.allowsContentJavaScript = true

        let configuration = WKWebViewConfiguration()
        configuration.defaultWebpagePreferences = preferences

        // Configure user content controller
        let controller = WKUserContentController()
        controller.add(self, name: "squadBridge")
        configuration.userContentController = controller

        // Add custom user scripts
        let script = WKUserScript(
            source: bridgeScript,
            injectionTime: .atDocumentStart,
            forMainFrameOnly: true
        )
        controller.addUserScript(script)
    }
}

Bridge Communication

JavaScript Bridge Setup

extension SquadViewController: WKScriptMessageHandler {
    // Bridge script for communication
    private var bridgeScript: String {
        """
        window.squadBridge = {
            postMessage: function(message) {
                window.webkit.messageHandlers.squadBridge.postMessage(message);
            }
        };
        """
    }

    func userContentController(
        _ userContentController: WKUserContentController,
        didReceive message: WKScriptMessage
    ) {
        guard let body = message.body as? [String: Any] else { return }
        handleBridgeMessage(body)
    }
}

Message Handling

extension SquadViewController {
    private func handleBridgeMessage(_ message: [String: Any]) {
        guard let type = message["type"] as? String else { return }

        switch type {
        case "call":
            handleCallEvent(message)
        case "navigation":
            handleNavigation(message)
        case "error":
            handleError(message)
        default:
            print("Unknown message type: \(type)")
        }
    }

    private func sendToBridge(_ message: [String: Any]) {
        let jsonData = try? JSONSerialization.data(withJSONObject: message)
        if let jsonString = String(data: jsonData!, encoding: .utf8) {
            webView.evaluateJavaScript("window.squadBridge.handleNativeMessage(\(jsonString))") { _, error in
                if let error = error {
                    print("Bridge error: \(error)")
                }
            }
        }
    }
}

State Management

WebView State Tracking

enum WebViewState {
    case loading
    case ready
    case error(Error)
}

extension SquadViewController: WKNavigationDelegate {
    private func updateWebViewState(_ state: WebViewState) {
        switch state {
        case .loading:
            showLoadingIndicator()
        case .ready:
            hideLoadingIndicator()
        case .error(let error):
            handleWebViewError(error)
        }
    }

    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
        updateWebViewState(.ready)
    }

    func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
        updateWebViewState(.error(error))
    }
}

Session Management

extension SquadViewController {
    private func handleSession() {
        // Configure cookies and storage
        let dataStore = WKWebsiteDataStore.default()
        dataStore.httpCookieStore.getAllCookies { cookies in
            // Handle cookies
        }

        // Clear session if needed
        let types = WKWebsiteDataStore.allWebsiteDataTypes()
        dataStore.removeData(ofTypes: types, modifiedSince: .distantPast) {
            // Session cleared
        }
    }
}

Security Implementation

Content Security Policy

extension SquadViewController {
    private func configureSecurityPolicy() {
        let cspScript = WKUserScript(
            source: """
            const meta = document.createElement('meta');
            meta.httpEquiv = 'Content-Security-Policy';
            meta.content = "default-src 'self' https://*.squadforsports.com; \
                          script-src 'self' 'unsafe-inline' https://*.squadforsports.com; \
                          style-src 'self' 'unsafe-inline' https://*.squadforsports.com; \
                          img-src 'self' data: https://*.squadforsports.com;";
            document.head.appendChild(meta);
            """,
            injectionTime: .atDocumentStart,
            forMainFrameOnly: true
        )
        webView.configuration.userContentController.addUserScript(cspScript)
    }
}

Input Validation

extension SquadViewController {
    private func validateMessage(_ message: [String: Any]) -> Bool {
        guard
            let type = message["type"] as? String,
            let data = message["data"] as? [String: Any]
        else {
            return false
        }

        // Validate message contents
        switch type {
        case "call":
            return validateCallMessage(data)
        case "navigation":
            return validateNavigationMessage(data)
        default:
            return false
        }
    }
}

Performance Optimization

Memory Management

extension SquadViewController {
    private func optimizeMemoryUsage() {
        // Configure process pool
        let processPool = WKProcessPool()
        webView.configuration.processPool = processPool

        // Handle memory warnings
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(handleMemoryWarning),
            name: UIApplication.didReceiveMemoryWarningNotification,
            object: nil
        )
    }

    @objc private func handleMemoryWarning() {
        // Clear unused resources
        URLCache.shared.removeAllCachedResponses()
        webView.configuration.websiteDataStore.removeData(
            ofTypes: [.memoryCache],
            modifiedSince: .distantPast
        ) {
            print("Memory cache cleared")
        }
    }
}

Resource Loading Optimization

extension SquadViewController: WKNavigationDelegate {
    func webView(
        _ webView: WKWebView,
        decidePolicyFor navigationAction: WKNavigationAction,
        decisionHandler: @escaping (WKNavigationActionPolicy) -> Void
    ) {
        // Implement caching strategy
        if let url = navigationAction.request.url {
            if shouldLoadFromCache(url) {
                loadFromCache(url)
                decisionHandler(.cancel)
                return
            }
        }
        decisionHandler(.allow)
    }

    private func shouldLoadFromCache(_ url: URL) -> Bool {
        // Implement caching logic
        return false
    }
}

Error Handling

WebView Error Management

extension SquadViewController {
    private func handleWebViewError(_ error: Error) {
        // Analyze error type
        switch error {
        case let urlError as URLError:
            handleURLError(urlError)
        case let webError as WKError:
            handleWebKitError(webError)
        default:
            handleGenericError(error)
        }
    }

    private func handleURLError(_ error: URLError) {
        switch error.code {
        case .notConnectedToInternet:
            showOfflineView()
        case .timedOut:
            retryLoading()
        default:
            showErrorView(message: error.localizedDescription)
        }
    }
}

Lifecycle Management

View Controller Lifecycle

extension SquadViewController {
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        setupObservers()
    }

    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        removeObservers()
        pauseWebView()
    }

    private func pauseWebView() {
        webView.evaluateJavaScript("document.hidden = true;")
    }
}

Best Practices

  1. Security

  2. Implement proper CSP

  3. Validate all bridge messages
  4. Use secure content only
  5. Handle authentication properly

  6. Performance

  7. Optimize memory usage

  8. Implement efficient caching
  9. Handle lifecycle events
  10. Clear resources when appropriate

  11. User Experience

  12. Handle offline state gracefully

  13. Provide loading indicators
  14. Implement proper error handling
  15. Maintain state consistency

  16. Debugging

  17. Enable WKWebView logging
  18. Monitor memory usage
  19. Track performance metrics
  20. Log bridge communications