Raw
1 //
2 // NewViewController.swift
3 // tauri-plugin-ios-window
4 //
5 // Created by Seto Elkahfi on 2026-02-07.
6 //
7
8 import UIKit
9 import WebKit
10
11 final class NewViewController: UIViewController {
12
13 var url: String = ""
14 var windowTitle: String = "Sign in"
15
16 private var webView: WKWebView!
17 private var progressView: UIProgressView!
18 private var navigationBar: UIView!
19 private var titleLabel: UILabel!
20 private var loadingIndicator: UIActivityIndicatorView!
21
22 override func viewDidLoad() {
23 super.viewDidLoad()
24 view.backgroundColor = .systemBackground
25
26 setupNavigationBar()
27 setupWebView()
28 setupProgressIndicator()
29 loadURL()
30 }
31
32 private func setupNavigationBar() {
33 // Navigation bar container
34 navigationBar = UIView()
35 navigationBar.backgroundColor = .systemBackground
36 navigationBar.translatesAutoresizingMaskIntoConstraints = false
37 view.addSubview(navigationBar)
38
39 // Add subtle bottom border
40 let borderView = UIView()
41 borderView.backgroundColor = .separator.withAlphaComponent(0.3)
42 borderView.translatesAutoresizingMaskIntoConstraints = false
43 navigationBar.addSubview(borderView)
44
45 // Close button (leading position per Apple HIG for modal presentations)
46 let closeButton = UIButton(type: .system)
47 closeButton.setTitle("Close", for: .normal)
48 closeButton.titleLabel?.font = .systemFont(ofSize: 17, weight: .regular)
49 closeButton.translatesAutoresizingMaskIntoConstraints = false
50 closeButton.addTarget(self, action: #selector(closeButtonTapped), for: .touchUpInside)
51 navigationBar.addSubview(closeButton)
52
53 // Title label (centered)
54 titleLabel = UILabel()
55 titleLabel.text = windowTitle
56 titleLabel.font = .systemFont(ofSize: 17, weight: .semibold)
57 titleLabel.textColor = .label
58 titleLabel.textAlignment = .center
59 titleLabel.translatesAutoresizingMaskIntoConstraints = false
60 navigationBar.addSubview(titleLabel)
61
62 // Loading indicator (trailing position)
63 loadingIndicator = UIActivityIndicatorView(style: .medium)
64 loadingIndicator.hidesWhenStopped = true
65 loadingIndicator.translatesAutoresizingMaskIntoConstraints = false
66 navigationBar.addSubview(loadingIndicator)
67
68 NSLayoutConstraint.activate([
69 // Navigation bar
70 navigationBar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
71 navigationBar.leadingAnchor.constraint(equalTo: view.leadingAnchor),
72 navigationBar.trailingAnchor.constraint(equalTo: view.trailingAnchor),
73 navigationBar.heightAnchor.constraint(equalToConstant: 44),
74
75 // Border view
76 borderView.leadingAnchor.constraint(equalTo: navigationBar.leadingAnchor),
77 borderView.trailingAnchor.constraint(equalTo: navigationBar.trailingAnchor),
78 borderView.bottomAnchor.constraint(equalTo: navigationBar.bottomAnchor),
79 borderView.heightAnchor.constraint(equalToConstant: 0.5),
80
81 // Close button (leading, per Apple HIG)
82 closeButton.leadingAnchor.constraint(
83 equalTo: navigationBar.leadingAnchor, constant: 16),
84 closeButton.centerYAnchor.constraint(equalTo: navigationBar.centerYAnchor),
85
86 // Title label (centered)
87 titleLabel.centerXAnchor.constraint(equalTo: navigationBar.centerXAnchor),
88 titleLabel.centerYAnchor.constraint(equalTo: navigationBar.centerYAnchor),
89 titleLabel.leadingAnchor.constraint(
90 greaterThanOrEqualTo: closeButton.trailingAnchor, constant: 16),
91 titleLabel.trailingAnchor.constraint(
92 lessThanOrEqualTo: loadingIndicator.leadingAnchor, constant: -16),
93
94 // Loading indicator (trailing)
95 loadingIndicator.trailingAnchor.constraint(
96 equalTo: navigationBar.trailingAnchor, constant: -16),
97 loadingIndicator.centerYAnchor.constraint(equalTo: navigationBar.centerYAnchor),
98 ])
99 }
100
101 private func setupWebView() {
102 // Configure WebView with optimized settings
103 let webConfiguration = WKWebViewConfiguration()
104 webConfiguration.allowsInlineMediaPlayback = true
105 webConfiguration.mediaTypesRequiringUserActionForPlayback = []
106
107 webView = WKWebView(frame: .zero, configuration: webConfiguration)
108 webView.translatesAutoresizingMaskIntoConstraints = false
109 webView.allowsBackForwardNavigationGestures = true
110 webView.scrollView.contentInsetAdjustmentBehavior = .never
111 webView.navigationDelegate = self
112
113 // Improve rendering
114 webView.isOpaque = false
115 webView.backgroundColor = .systemBackground
116
117 view.addSubview(webView)
118
119 NSLayoutConstraint.activate([
120 webView.topAnchor.constraint(equalTo: navigationBar.bottomAnchor),
121 webView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
122 webView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
123 webView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
124 ])
125 }
126
127 private func setupProgressIndicator() {
128 // Modern progress bar
129 progressView = UIProgressView(progressViewStyle: .bar)
130 progressView.translatesAutoresizingMaskIntoConstraints = false
131 progressView.progressTintColor = .systemBlue
132 progressView.trackTintColor = .clear
133 progressView.alpha = 0
134 view.addSubview(progressView)
135
136 NSLayoutConstraint.activate([
137 progressView.topAnchor.constraint(equalTo: navigationBar.bottomAnchor),
138 progressView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
139 progressView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
140 progressView.heightAnchor.constraint(equalToConstant: 2),
141 ])
142
143 // Observe loading progress
144 webView.addObserver(
145 self, forKeyPath: #keyPath(WKWebView.estimatedProgress), options: .new, context: nil)
146 }
147
148 private func loadURL() {
149 guard let urlToLoad = URL(string: url) else {
150 showErrorState()
151 return
152 }
153
154 var request = URLRequest(url: urlToLoad)
155 request.cachePolicy = .reloadIgnoringLocalCacheData
156 webView.load(request)
157 }
158
159 private func showErrorState() {
160 let errorLabel = UILabel()
161 errorLabel.text = "Unable to load page"
162 errorLabel.textColor = .secondaryLabel
163 errorLabel.font = .systemFont(ofSize: 17, weight: .medium)
164 errorLabel.textAlignment = .center
165 errorLabel.translatesAutoresizingMaskIntoConstraints = false
166 view.addSubview(errorLabel)
167
168 NSLayoutConstraint.activate([
169 errorLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
170 errorLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor),
171 ])
172 }
173
174 override func observeValue(
175 forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?,
176 context: UnsafeMutableRawPointer?
177 ) {
178 if keyPath == "estimatedProgress" {
179 let progress = Float(webView.estimatedProgress)
180
181 UIView.animate(withDuration: 0.2) {
182 self.progressView.alpha = progress < 1.0 ? 1.0 : 0.0
183 self.progressView.setProgress(progress, animated: true)
184 }
185 }
186 }
187
188 @objc private func closeButtonTapped() {
189 dismiss(animated: true)
190 }
191
192 deinit {
193 webView.removeObserver(self, forKeyPath: #keyPath(WKWebView.estimatedProgress))
194 }
195 }
196
197 // MARK: - WKNavigationDelegate
198 extension NewViewController: WKNavigationDelegate {
199
200 func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
201 loadingIndicator.startAnimating()
202 }
203
204 func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
205 loadingIndicator.stopAnimating()
206 }
207
208 func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
209 loadingIndicator.stopAnimating()
210 print("Navigation failed: \(error.localizedDescription)")
211 }
212
213 func webView(
214 _ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!,
215 withError error: Error
216 ) {
217 loadingIndicator.stopAnimating()
218 showErrorState()
219 }
220 }