Reputation: 6112
I'm a new Swift developer implementing the main parts of Safari with a WKWebView (I need to interface between Javascript & Swift, so SFSafariViewController is not an option) and am trying to declare all elements programmatically.
To imitate Safari's search bar and progress bar, I want to set a UISearchBar, stacked on top of a UIProgressView, as the titleView
for UIViewController's UINavigationItem. I can manage it with just one element, but not with a stack of two elements.
Here's what my project looks like right now. The UISearchBar and UIProgressView are either too wide or too thin to fill the UINavigationBar properly, depending on the rotation:
Here is my code for ViewController.swift:
import WebKit
import UIKit
class ViewController: UIViewController, WKNavigationDelegate, UISearchBarDelegate {
var searchBar: UISearchBar = UISearchBar()
var progressView: UIProgressView = UIProgressView(progressViewStyle: .bar)
var stackView: UIStackView = UIStackView()
var webView: WKWebView!
override func loadView() {
webView = WKWebView()
webView.navigationDelegate = self
view = webView
}
override func viewDidLoad() {
super.viewDidLoad()
/** Watches for changes in the WKWebView.estimatedProgress variable, and */
webView.addObserver(self, forKeyPath: #keyPath(WKWebView.estimatedProgress), options: .new, context: nil)
/** Initialise toolbar elements */
let spacer = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
let refresh = UIBarButtonItem(barButtonSystemItem: .refresh, target: webView, action: #selector(webView.reload))
toolbarItems = [spacer, refresh]
navigationController?.isToolbarHidden = false
/** Initialise the UISearchBar */
searchBar.delegate = self // not clear yet whether setting this is necessary.
searchBar.searchBarStyle = UISearchBarStyle.minimal
searchBar.showsCancelButton = true
searchBar.widthAnchor.constraint(equalToConstant: (navigationController?.navigationBar.bounds.width)!).isActive = true
// searchBar.heightAnchor.constraint(equalToConstant: 44.0).isActive = true
// searchBar.sizeToFit()
/** Initialise the UIProgressView */
progressView.widthAnchor.constraint(equalToConstant: (navigationController?.navigationBar.bounds.width)!).isActive = true
// progressView.heightAnchor.constraint(equalToConstant: 4.0).isActive = true
// progressView.sizeToFit()
/** Add the UISearchBar & UIProgressView to the UIStackView, then initialise it and finally set it as the UINavigationItem's titleView.*/
stackView.axis = UILayoutConstraintAxis.vertical
stackView.alignment = UIStackViewAlignment.center
stackView.distribution = UIStackViewDistribution.fillProportionally
stackView.addArrangedSubview(searchBar)
stackView.addArrangedSubview(progressView)
stackView.translatesAutoresizingMaskIntoConstraints = false;
/* These two constraints are causing a crash, so disabling them for now. */
// stackView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
// stackView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true
navigationItem.titleView = stackView
navigationController?.hidesBarsOnSwipe = true
let url = URL(string: "https://en.wikipedia.org")!
webView.load(URLRequest(url: url))
webView.allowsBackForwardNavigationGestures = true
}
/** Updates the UIProgressView. */
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
// keyPath "estimatedProgress" is equivalent to #keyPath(WKWebView.estimatedProgress)
if keyPath == "estimatedProgress" {
// progressView.isHidden = webView.estimatedProgress == 1 // if we want to hide upon 100%
progressView.progress = Float(webView.estimatedProgress)
}
}
/** Sets the webView's title upon navigation finishing. */
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
title = webView.title
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Note: any accepted solution must continue to display the stack of elements properly following the callback to hide the UINavigationBar initiated by navigationController?.hidesBarsOnSwipe
, ie. when a user performs a swipe gesture on the WKWebView.
Upvotes: 1
Views: 1792
Reputation: 6112
Reporting back with my final code, gratefully guided by the solution given on Reddit by the author of Hacking With Swift:
class ViewController: UIViewController, UISearchBarDelegate {
var webView: WKWebView?
var searchBar: UISearchBar?
var progressView: UIProgressView?
override func loadView() {
super.loadView()
setUpWebView()
view = self.webView! // TODO: fix constraints error when video is run.
setUpSearchbar()
setUpProgressView()
let url = URL(string: "http://www.bbc.com")!
webView!.load(URLRequest(url: url))
}
func setUpWebView(){
webView = WKWebView()
}
func setUpSearchbar(){
searchBar = UISearchBar()
searchBar!.delegate = self
}
func setUpProgressView() {
webView!.addObserver(self, forKeyPath: #keyPath(WKWebView.estimatedProgress), options: .new, context: nil)
guard let bar = navigationController?.navigationBar else { return; }
progressView = UIProgressView(progressViewStyle: .bar)
progressView!.translatesAutoresizingMaskIntoConstraints = false
bar.addSubview(progressView!)
progressView!.leadingAnchor.constraint(equalTo: bar.leadingAnchor).isActive = true
progressView!.trailingAnchor.constraint(equalTo: bar.trailingAnchor).isActive = true
progressView!.bottomAnchor.constraint(equalTo: bar.bottomAnchor).isActive = true
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "estimatedProgress" {
// progressView.isHidden = webView.estimatedProgress == 1 /* Optional. This hides progressView on 100% */
progressView!.progress = Float((webView?.estimatedProgress)!)
}
}
}
Upvotes: 1