Integrasi Mobile•iOS (Swift)

Implementasi Native iOS (Swift)

Panduan integrasi lengkap untuk aplikasi **iOS Native** menggunakan bahasa Swift, framework WebKit (WKWebView), dan WKScriptMessageHandler.

1. Konfigurasi Info.plist

Info.plist
<key>NSLocationWhenInUseUsageDescription</key>
<string>Aplikasi membutuhkan izin lokasi untuk validasi keamanan dompet digital QoinCash.</string>
<key>NSCameraUsageDescription</key>
<string>Aplikasi membutuhkan izin kamera untuk memindai kode QRIS.</string>

2. ViewController Lengkap (QoinWebviewViewController.swift)

QoinWebviewViewController.swift
import UIKit
import WebKit
import CoreLocation

class QoinWebviewViewController: UIViewController, WKNavigationDelegate, WKScriptMessageHandler, CLLocationManagerDelegate {

    var signature: String = ""
    var palette: String = "original"
    var baseUrl: String = "https://seamless.qoin.id"

    private var webView: WKWebView!
    private let locationManager = CLLocationManager()

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white

        // Minta izin lokasi GPS perangkat iOS
        locationManager.delegate = self
        locationManager.requestWhenInUseAuthorization()

        setupWebView()
        loadWebview()
    }

    private func setupWebView() {
        let contentController = WKUserContentController()
        // Daftarkan handler bridge ke script webkit
        contentController.add(self, name: "QoinBridge")

        let config = WKWebViewConfiguration()
        config.userContentController = contentController
        config.allowsInlineMediaPlayback = true

        webView = WKWebView(frame: view.bounds, configuration: config)
        webView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        webView.navigationDelegate = self
        view.addSubview(webView)
    }

    private func loadWebview() {
        guard let url = URL(string: "\(baseUrl)/") else { return }
        webView.load(URLRequest(url: url))
    }

    // Event saat halaman selesai dimuat
    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
        injectPostMessage()
    }

    private func injectPostMessage() {
        let jsonPayload: [String: Any] = [
            "Signature": signature,
            "Palette": palette
        ]
        guard let jsonData = try? JSONSerialization.data(withJSONObject: jsonPayload),
              let jsonString = String(data: jsonData, encoding: .utf8) else { return }

        let bridgeScript = """
            window.addEventListener('message', function(event) {
                if (window.webkit && window.webkit.messageHandlers.QoinBridge) {
                    var d = typeof event.data === 'string' ? event.data : JSON.stringify(event.data);
                    window.webkit.messageHandlers.QoinBridge.postMessage(d);
                }
            });
            window.postMessage(\(jsonString), '*');
        """

        webView.evaluateJavaScript(bridgeScript, completionHandler: nil)

        // Retry 500ms kemudian
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
            self.webView.evaluateJavaScript("window.postMessage(\(jsonString), '*');", completionHandler: nil)
        }
    }

    // Menangkap pesan balasan dari webview bridge
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        guard message.name == "QoinBridge", let bodyString = message.body as? String else { return }
        
        if let data = bodyString.data(using: .utf8),
           let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {
            if let isClose = json["close"] as? Bool, isClose == true {
                // Tutup screen webview
                dismiss(animated: true, completion: nil)
            }
        }
    }
}
Pembersihan Memory
Ingat untuk memanggil contentController.removeScriptMessageHandler(forName: "QoinBridge") saat view controller di-deallocate agar tidak terjadi retain cycle memory leak.