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
-
Security
-
Implement proper CSP
- Validate all bridge messages
- Use secure content only
-
Handle authentication properly
-
Performance
-
Optimize memory usage
- Implement efficient caching
- Handle lifecycle events
-
Clear resources when appropriate
-
User Experience
-
Handle offline state gracefully
- Provide loading indicators
- Implement proper error handling
-
Maintain state consistency
-
Debugging
- Enable WKWebView logging
- Monitor memory usage
- Track performance metrics
- Log bridge communications