-
Notifications
You must be signed in to change notification settings - Fork 2.9k
/
SettingsContentViewController.swift
181 lines (153 loc) · 6.25 KB
/
SettingsContentViewController.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import Shared
import SnapKit
import UIKit
import WebKit
let DefaultTimeoutTimeInterval = 10.0 // Seconds. We'll want some telemetry on load times in the wild.
private var TODOPageLoadErrorString = NSLocalizedString("Could not load page.", comment: "Error message that is shown in settings when there was a problem loading")
/**
* A controller that manages a single web view and provides a way for
* the user to navigate back to Settings.
*/
class SettingsContentViewController: UIViewController, WKNavigationDelegate {
let interstitialBackgroundColor: UIColor
var settingsTitle: NSAttributedString?
var url: URL!
var timer: Timer?
var isLoaded: Bool = false {
didSet {
if isLoaded {
UIView.transition(from: interstitialView, to: webView,
duration: 0.5,
options: .transitionCrossDissolve,
completion: { finished in
self.interstitialView.removeFromSuperview()
self.interstitialSpinnerView.stopAnimating()
})
}
}
}
fileprivate var isError: Bool = false {
didSet {
if isError {
interstitialErrorView.isHidden = false
UIView.transition(from: interstitialSpinnerView, to: interstitialErrorView,
duration: 0.5,
options: .transitionCrossDissolve,
completion: { finished in
self.interstitialSpinnerView.removeFromSuperview()
self.interstitialSpinnerView.stopAnimating()
})
}
}
}
// The view shown while the content is loading in the background web view.
fileprivate var interstitialView: UIView!
fileprivate var interstitialSpinnerView: UIActivityIndicatorView!
fileprivate var interstitialErrorView: UILabel!
// The web view that displays content.
var webView: WKWebView!
fileprivate func startLoading(_ timeout: Double = DefaultTimeoutTimeInterval) {
if self.isLoaded {
return
}
if timeout > 0 {
self.timer = Timer.scheduledTimer(timeInterval: timeout, target: self, selector: #selector(didTimeOut), userInfo: nil, repeats: false)
} else {
self.timer = nil
}
self.webView.load(URLRequest(url: url))
self.interstitialSpinnerView.startAnimating()
}
init(backgroundColor: UIColor = UIColor.white, title: NSAttributedString? = nil) {
interstitialBackgroundColor = backgroundColor
settingsTitle = title
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
// This background agrees with the web page background.
// Keeping the background constant prevents a pop of mismatched color.
view.backgroundColor = interstitialBackgroundColor
self.webView = makeWebView()
view.addSubview(webView)
self.webView.snp.remakeConstraints { make in
make.edges.equalTo(self.view)
}
// Destructuring let causes problems.
let ret = makeInterstitialViews()
self.interstitialView = ret.0
self.interstitialSpinnerView = ret.1
self.interstitialErrorView = ret.2
view.addSubview(interstitialView)
self.interstitialView.snp.remakeConstraints { make in
make.edges.equalTo(self.view)
}
startLoading()
}
func makeWebView() -> WKWebView {
let config = WKWebViewConfiguration()
let webView = WKWebView(
frame: CGRect(width: 1, height: 1),
configuration: config
)
webView.allowsLinkPreview = false
webView.navigationDelegate = self
return webView
}
fileprivate func makeInterstitialViews() -> (UIView, UIActivityIndicatorView, UILabel) {
let view = UIView()
// Keeping the background constant prevents a pop of mismatched color.
view.backgroundColor = interstitialBackgroundColor
let spinner = UIActivityIndicatorView(activityIndicatorStyle: .gray)
view.addSubview(spinner)
let error = UILabel()
if let _ = settingsTitle {
error.text = TODOPageLoadErrorString
error.textColor = UIColor.red // Firefox Orange!
error.textAlignment = .center
}
error.isHidden = true
view.addSubview(error)
spinner.snp.makeConstraints { make in
make.center.equalTo(view)
return
}
error.snp.makeConstraints { make in
make.center.equalTo(view)
make.left.equalTo(view.snp.left).offset(20)
make.right.equalTo(view.snp.right).offset(-20)
make.height.equalTo(44)
return
}
return (view, spinner, error)
}
@objc func didTimeOut() {
self.timer = nil
self.isError = true
}
func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
// If this is a request to our local web server, use our private credentials.
if challenge.protectionSpace.host == "localhost" && challenge.protectionSpace.port == Int(WebServer.sharedInstance.server.port) {
completionHandler(.useCredential, WebServer.sharedInstance.credentials)
return
}
completionHandler(.performDefaultHandling, nil)
}
func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
didTimeOut()
}
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
didTimeOut()
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
self.timer?.invalidate()
self.timer = nil
self.isLoaded = true
}
}