Error state in URL bar
This commit is contained in:
@@ -28,6 +28,8 @@ class BrowserViewController: UIViewController, WKNavigationDelegate,
|
|||||||
private var backButtonObservation: NSKeyValueObservation?
|
private var backButtonObservation: NSKeyValueObservation?
|
||||||
private var forwardButtonObservation: NSKeyValueObservation?
|
private var forwardButtonObservation: NSKeyValueObservation?
|
||||||
|
|
||||||
|
private var loadError: Error?
|
||||||
|
|
||||||
override var preferredStatusBarStyle: UIStatusBarStyle { .lightContent }
|
override var preferredStatusBarStyle: UIStatusBarStyle { .lightContent }
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
@@ -139,6 +141,22 @@ class BrowserViewController: UIViewController, WKNavigationDelegate,
|
|||||||
// New tab button
|
// New tab button
|
||||||
toolbarController.newTabButton.addAction(newTabAction, for: .touchUpInside)
|
toolbarController.newTabButton.addAction(newTabAction, for: .touchUpInside)
|
||||||
|
|
||||||
|
// Error button
|
||||||
|
toolbarController.urlBar.errorButton.addAction(UIAction(handler: { [unowned self] _ in
|
||||||
|
let alert = UIAlertController(title: "Error", message: self.loadError?.localizedDescription, preferredStyle: .actionSheet)
|
||||||
|
|
||||||
|
alert.addAction(UIAlertAction(title: "Reload", style: .destructive, handler: { _ in
|
||||||
|
self.webView.reload()
|
||||||
|
alert.dismiss(animated: true, completion: nil)
|
||||||
|
}))
|
||||||
|
|
||||||
|
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { _ in
|
||||||
|
alert.dismiss(animated: true, completion: nil)
|
||||||
|
}))
|
||||||
|
|
||||||
|
self.present(alert, animated: true, completion: nil)
|
||||||
|
}), for: .touchUpInside)
|
||||||
|
|
||||||
// TextField delegate
|
// TextField delegate
|
||||||
toolbarController.urlBar.textField.delegate = self
|
toolbarController.urlBar.textField.delegate = self
|
||||||
|
|
||||||
@@ -146,7 +164,9 @@ class BrowserViewController: UIViewController, WKNavigationDelegate,
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func updateLoadProgress(forWebView webView: WKWebView) {
|
private func updateLoadProgress(forWebView webView: WKWebView) {
|
||||||
if webView.estimatedProgress == 1.0 {
|
if let loadError = loadError {
|
||||||
|
toolbarController.urlBar.loadProgress = .error(error: loadError)
|
||||||
|
} else if webView.estimatedProgress == 1.0 {
|
||||||
toolbarController.urlBar.loadProgress = .complete
|
toolbarController.urlBar.loadProgress = .complete
|
||||||
} else {
|
} else {
|
||||||
toolbarController.urlBar.loadProgress = .loading(progress: webView.estimatedProgress)
|
toolbarController.urlBar.loadProgress = .loading(progress: webView.estimatedProgress)
|
||||||
@@ -243,6 +263,8 @@ class BrowserViewController: UIViewController, WKNavigationDelegate,
|
|||||||
// MARK: Navigation Delegate
|
// MARK: Navigation Delegate
|
||||||
|
|
||||||
func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
|
func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
|
||||||
|
loadError = nil
|
||||||
|
|
||||||
// Reset tracking this
|
// Reset tracking this
|
||||||
tab.allowedScriptOrigins.removeAll()
|
tab.allowedScriptOrigins.removeAll()
|
||||||
tab.blockedScriptOrigins.removeAll()
|
tab.blockedScriptOrigins.removeAll()
|
||||||
@@ -272,6 +294,14 @@ class BrowserViewController: UIViewController, WKNavigationDelegate,
|
|||||||
decisionHandler(.allow, preferences)
|
decisionHandler(.allow, preferences)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
|
||||||
|
self.loadError = error
|
||||||
|
}
|
||||||
|
|
||||||
|
func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
|
||||||
|
self.loadError = error
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: UITextField Delegate
|
// MARK: UITextField Delegate
|
||||||
|
|
||||||
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
||||||
|
|||||||
@@ -11,10 +11,12 @@ class URLBar: UIView
|
|||||||
{
|
{
|
||||||
let textField = UITextField(frame: .zero)
|
let textField = UITextField(frame: .zero)
|
||||||
let refreshButton = UIButton(frame: .zero)
|
let refreshButton = UIButton(frame: .zero)
|
||||||
|
let errorButton = UIButton(frame: .zero)
|
||||||
|
|
||||||
public enum LoadProgress {
|
public enum LoadProgress {
|
||||||
case complete
|
case complete
|
||||||
case loading(progress: Double)
|
case loading(progress: Double)
|
||||||
|
case error(error: Error)
|
||||||
}
|
}
|
||||||
|
|
||||||
public var loadProgress: LoadProgress = .complete {
|
public var loadProgress: LoadProgress = .complete {
|
||||||
@@ -65,9 +67,21 @@ class URLBar: UIView
|
|||||||
refreshButton.tintColor = .secondaryLabel
|
refreshButton.tintColor = .secondaryLabel
|
||||||
refreshButton.setImage(refreshImage, for: .normal)
|
refreshButton.setImage(refreshImage, for: .normal)
|
||||||
addSubview(refreshButton)
|
addSubview(refreshButton)
|
||||||
|
|
||||||
|
errorButton.backgroundColor = .systemRed
|
||||||
|
errorButton.layer.cornerRadius = 3.0
|
||||||
|
errorButton.layer.masksToBounds = true
|
||||||
|
errorButton.titleLabel?.font = UIFont.boldSystemFont(ofSize: 11.0)
|
||||||
|
errorButton.setTitleColor(.white, for: .normal)
|
||||||
|
errorButton.setTitle("ERR", for: .normal)
|
||||||
|
addSubview(errorButton)
|
||||||
|
|
||||||
|
setErrorButtonAnimating(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateProgressIndicator() {
|
private func updateProgressIndicator() {
|
||||||
|
setErrorButtonAnimating(false)
|
||||||
|
|
||||||
UIView.animate(withDuration: 0.4) {
|
UIView.animate(withDuration: 0.4) {
|
||||||
switch self.loadProgress {
|
switch self.loadProgress {
|
||||||
case .complete:
|
case .complete:
|
||||||
@@ -79,12 +93,26 @@ class URLBar: UIView
|
|||||||
// Reset back to zero
|
// Reset back to zero
|
||||||
self.progressIndicatorView.progress = 0.0
|
self.progressIndicatorView.progress = 0.0
|
||||||
}
|
}
|
||||||
|
|
||||||
case .loading(let progress):
|
case .loading(let progress):
|
||||||
self.refreshButton.setImage(self.stopImage, for: .normal)
|
self.refreshButton.setImage(self.stopImage, for: .normal)
|
||||||
self.progressIndicatorView.progress = progress
|
self.progressIndicatorView.progress = progress
|
||||||
self.progressIndicatorView.alpha = 1.0
|
self.progressIndicatorView.alpha = 1.0
|
||||||
|
|
||||||
|
case .error(let error):
|
||||||
|
self.setErrorButtonAnimating(true)
|
||||||
|
self.progressIndicatorView.alpha = 0.0
|
||||||
|
self.progressIndicatorView.progress = 0.0
|
||||||
|
|
||||||
|
if let nserror = error as NSError? {
|
||||||
|
self.errorButton.setTitle("\(nserror.code)", for: .normal)
|
||||||
|
} else {
|
||||||
|
self.errorButton.setTitle("ERR", for: .normal)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.setNeedsLayout()
|
||||||
}
|
}
|
||||||
|
|
||||||
override var intrinsicContentSize: CGSize {
|
override var intrinsicContentSize: CGSize {
|
||||||
@@ -92,7 +120,7 @@ class URLBar: UIView
|
|||||||
return CGSize(width: 1000.0, height: preferredHeight)
|
return CGSize(width: 1000.0, height: preferredHeight)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func fadeBackgroundImageForSize(_ size: CGSize) -> UIImage? {
|
private func fadeBackgroundImageForSize(_ size: CGSize, cutoffLocation: CGFloat) -> UIImage? {
|
||||||
var image: UIImage? = nil
|
var image: UIImage? = nil
|
||||||
|
|
||||||
UIGraphicsBeginImageContext(CGSize(width: size.width, height: 1.0))
|
UIGraphicsBeginImageContext(CGSize(width: size.width, height: 1.0))
|
||||||
@@ -105,7 +133,7 @@ class URLBar: UIView
|
|||||||
]
|
]
|
||||||
|
|
||||||
let locations: [CGFloat] = [
|
let locations: [CGFloat] = [
|
||||||
0.0, 0.80, 0.90, 1.0
|
0.0, cutoffLocation, cutoffLocation + 0.10, 1.0
|
||||||
]
|
]
|
||||||
|
|
||||||
if let gradient = CGGradient(colorsSpace: nil, colors: gradientColorsArray as CFArray, locations: locations) {
|
if let gradient = CGGradient(colorsSpace: nil, colors: gradientColorsArray as CFArray, locations: locations) {
|
||||||
@@ -119,22 +147,57 @@ class URLBar: UIView
|
|||||||
return image
|
return image
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func setErrorButtonAnimating(_ animating: Bool) {
|
||||||
|
let animationKey = "blinkAnimation"
|
||||||
|
if animating {
|
||||||
|
let blinkAnimation = CAKeyframeAnimation(keyPath: "opacity")
|
||||||
|
blinkAnimation.calculationMode = .discrete
|
||||||
|
blinkAnimation.values = [ 1.0, 0.1, 1.0 ]
|
||||||
|
blinkAnimation.keyTimes = [ 0.0, 0.5, 1.0 ]
|
||||||
|
blinkAnimation.duration = 1.0
|
||||||
|
blinkAnimation.repeatCount = .infinity
|
||||||
|
|
||||||
|
self.errorButton.isHidden = false
|
||||||
|
self.errorButton.layer.add(blinkAnimation, forKey: animationKey)
|
||||||
|
} else {
|
||||||
|
self.errorButton.isHidden = true
|
||||||
|
self.errorButton.layer.removeAnimation(forKey: animationKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override func layoutSubviews() {
|
override func layoutSubviews() {
|
||||||
super.layoutSubviews()
|
super.layoutSubviews()
|
||||||
backgroundView.frame = bounds
|
backgroundView.frame = bounds
|
||||||
progressIndicatorView.frame = backgroundView.contentView.bounds
|
progressIndicatorView.frame = backgroundView.contentView.bounds
|
||||||
textField.frame = bounds.insetBy(dx: 6.0, dy: 0)
|
textField.frame = bounds.insetBy(dx: 6.0, dy: 0)
|
||||||
|
|
||||||
|
var fadeCutoffLocation: CGFloat = 0.8
|
||||||
|
|
||||||
|
// Refresh button
|
||||||
|
let refreshButtonSize = CGSize(width: textField.frame.height, height: textField.frame.height)
|
||||||
|
refreshButton.frame = CGRect(origin: CGPoint(x: bounds.width - refreshButtonSize.width, y: 0), size: refreshButtonSize)
|
||||||
|
|
||||||
|
// Error button
|
||||||
|
if case .error(error: _) = loadProgress {
|
||||||
|
errorButton.sizeToFit()
|
||||||
|
errorButton.frame = CGRect(
|
||||||
|
x: refreshButton.frame.minX - errorButton.frame.width - 8.0,
|
||||||
|
y: 0.0,
|
||||||
|
width: errorButton.frame.width + 8.0,
|
||||||
|
height: 22.0
|
||||||
|
)
|
||||||
|
errorButton.frame = errorButton.frame.centeredY(inRect: bounds)
|
||||||
|
fadeCutoffLocation = (errorButton.frame.minX / bounds.width) - 0.1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fade mask
|
||||||
fadeMaskView.frame = textField.bounds
|
fadeMaskView.frame = textField.bounds
|
||||||
fadeMaskView.image = fadeBackgroundImageForSize(fadeMaskView.frame.size)
|
fadeMaskView.image = fadeBackgroundImageForSize(fadeMaskView.frame.size, cutoffLocation: fadeCutoffLocation)
|
||||||
if !textField.isFirstResponder {
|
if !textField.isFirstResponder {
|
||||||
textField.mask = fadeMaskView
|
textField.mask = fadeMaskView
|
||||||
} else {
|
} else {
|
||||||
textField.mask = nil
|
textField.mask = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
let refreshButtonSize = CGSize(width: textField.frame.height, height: textField.frame.height)
|
|
||||||
refreshButton.frame = CGRect(origin: CGPoint(x: bounds.width - refreshButtonSize.width, y: 0), size: refreshButtonSize)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user