231 lines
9.2 KiB
Swift
231 lines
9.2 KiB
Swift
//
|
|
// BrowserView.swift
|
|
// SBrowser
|
|
//
|
|
// Created by James Magahern on 7/21/20.
|
|
//
|
|
|
|
import Combine
|
|
import UIKit
|
|
import WebKit
|
|
|
|
class BrowserView: UIView
|
|
{
|
|
let titlebarView = TitlebarView()
|
|
|
|
var findOnPageView: FindOnPageView? {
|
|
didSet { addSubview(findOnPageView!) }
|
|
}
|
|
|
|
var toolbarView: ToolbarView? {
|
|
didSet { addSubview(toolbarView!) }
|
|
}
|
|
|
|
var tabBarView: TabBarView? {
|
|
didSet { addSubview(tabBarView!) }
|
|
}
|
|
|
|
var tabBarViewVisible: Bool = true {
|
|
didSet { setNeedsLayout() }
|
|
}
|
|
|
|
var autocompleteView: UICollectionView? {
|
|
didSet {
|
|
addSubview(autocompleteView!)
|
|
if let toolbarView = toolbarView {
|
|
bringSubviewToFront(toolbarView)
|
|
}
|
|
}
|
|
}
|
|
|
|
var webView: WKWebView? {
|
|
didSet {
|
|
oldValue?.removeFromSuperview()
|
|
|
|
if let toolbarView = toolbarView {
|
|
insertSubview(webView!, belowSubview: toolbarView)
|
|
} else {
|
|
addSubview(webView!)
|
|
}
|
|
}
|
|
}
|
|
|
|
var findOnPageVisible: Bool = false {
|
|
didSet { layoutSubviews() }
|
|
}
|
|
|
|
func setFindOnPageVisible(_ visible: Bool, animated: Bool) {
|
|
if animated {
|
|
UIView.animate(withDuration: 0.33, animations: { self.findOnPageVisible = visible })
|
|
} else {
|
|
findOnPageVisible = visible
|
|
}
|
|
}
|
|
|
|
var keyboardWillShowObserver: AnyCancellable?
|
|
var keyboardWillHideObserver: AnyCancellable?
|
|
var keyboardLayoutOffset: CGFloat = 0 { didSet { setNeedsLayout() } }
|
|
|
|
convenience init() {
|
|
self.init(frame: .zero)
|
|
backgroundColor = .systemBackground
|
|
|
|
addSubview(titlebarView)
|
|
|
|
keyboardWillShowObserver = NotificationCenter.default.publisher(for: UIWindow.keyboardWillShowNotification).sink { [weak self] notification in
|
|
self?.adjustOffsetForKeyboardNotification(userInfo: notification.userInfo!)
|
|
}
|
|
|
|
keyboardWillHideObserver = NotificationCenter.default.publisher(for: UIWindow.keyboardWillHideNotification).sink { [weak self] notification in
|
|
self?.adjustOffsetForKeyboardNotification(userInfo: notification.userInfo!)
|
|
}
|
|
}
|
|
|
|
private func adjustOffsetForKeyboardNotification(userInfo: [AnyHashable : Any]) {
|
|
guard var keyboardEndFrame = userInfo[UIWindow.keyboardFrameEndUserInfoKey] as? CGRect else { return }
|
|
guard let animationDuration = userInfo[UIWindow.keyboardAnimationDurationUserInfoKey] as? TimeInterval else { return }
|
|
guard let animationCurve = userInfo[UIWindow.keyboardAnimationCurveUserInfoKey] as? Int else { return }
|
|
|
|
// Convert keyboard frame to screen coordinate space
|
|
keyboardEndFrame = self.convert(keyboardEndFrame, from: UIScreen.main.coordinateSpace)
|
|
|
|
let animationOptions: UIView.AnimationOptions = { curve -> UIView.AnimationOptions in
|
|
switch UIView.AnimationCurve(rawValue: curve) {
|
|
case .easeIn: return .curveEaseIn
|
|
case .easeOut: return .curveEaseOut
|
|
case .easeInOut: return .curveEaseInOut
|
|
default: return .init()
|
|
}
|
|
}(animationCurve)
|
|
|
|
toolbarView?.layoutLatch.deactivate()
|
|
keyboardLayoutOffset = bounds.height - keyboardEndFrame.minY
|
|
UIView.animate(withDuration: animationDuration, delay: 0.0, options: animationOptions, animations: { self.layoutIfNeeded() }, completion: nil)
|
|
}
|
|
|
|
override func layoutSubviews() {
|
|
super.layoutSubviews()
|
|
|
|
var webViewContentInset = UIEdgeInsets()
|
|
|
|
bringSubviewToFront(titlebarView)
|
|
|
|
var titlebarHeight: CGFloat = 24.0
|
|
titlebarHeight += safeAreaInsets.top
|
|
titlebarView.frame = CGRect(origin: .zero, size: CGSize(width: bounds.width, height: titlebarHeight))
|
|
webViewContentInset.top += titlebarView.frame.height
|
|
|
|
if let toolbarView = toolbarView {
|
|
var toolbarSize = toolbarView.sizeThatFits(bounds.size)
|
|
|
|
// Compact: toolbar is at the bottom
|
|
if traitCollection.horizontalSizeClass == .compact {
|
|
var bottomOffset: CGFloat = 0.0
|
|
if keyboardLayoutOffset < CGFloat.leastNonzeroMagnitude {
|
|
toolbarSize.height += safeAreaInsets.bottom
|
|
} else if toolbarView.urlBar?.textField.isFirstResponder ?? false {
|
|
bottomOffset = keyboardLayoutOffset
|
|
}
|
|
|
|
toolbarView.bounds = CGRect(origin: .zero, size: toolbarSize)
|
|
toolbarView.center = CGPoint(x: bounds.center.x, y: bounds.maxY - (toolbarView.bounds.height / 2) - bottomOffset)
|
|
webViewContentInset.bottom += toolbarView.frame.height
|
|
|
|
if findOnPageVisible {
|
|
// Hide off the bottom
|
|
toolbarView.center = CGPoint(x: toolbarView.center.x, y: toolbarView.center.y + toolbarView.frame.height)
|
|
}
|
|
} else {
|
|
// Regular: toolbar is at the top
|
|
toolbarView.frame = CGRect(origin: CGPoint(x: 0.0, y: titlebarView.frame.maxY), size: toolbarSize)
|
|
webViewContentInset.top += toolbarView.frame.height
|
|
}
|
|
}
|
|
|
|
if let tabBarView = tabBarView {
|
|
bringSubviewToFront(tabBarView)
|
|
|
|
if tabBarViewVisible {
|
|
tabBarView.isHidden = false
|
|
|
|
let tabViewSize = tabBarView.sizeThatFits(bounds.size)
|
|
|
|
tabBarView.frame = CGRect(
|
|
x: 0.0, y: webViewContentInset.top,
|
|
width: bounds.width, height: tabViewSize.height
|
|
)
|
|
|
|
webViewContentInset.top += tabBarView.frame.height
|
|
} else {
|
|
tabBarView.isHidden = true
|
|
}
|
|
}
|
|
|
|
// Fix web view content insets
|
|
if let webView = webView {
|
|
webView.scrollView.layer.masksToBounds = true
|
|
webView.frame = bounds.inset(by: webViewContentInset)
|
|
}
|
|
|
|
// Autocomplete view
|
|
if let autocompleteView = autocompleteView {
|
|
// Compact: autocomplete view takes the space of the webview
|
|
autocompleteView.frame = bounds.inset(by: webViewContentInset)
|
|
autocompleteView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: keyboardLayoutOffset, right: 0)
|
|
autocompleteView.scrollIndicatorInsets = autocompleteView.contentInset
|
|
|
|
if traitCollection.horizontalSizeClass == .regular {
|
|
// Regular: shows up just underneath the url bar
|
|
autocompleteView.layer.shadowColor = UIColor.black.cgColor
|
|
autocompleteView.layer.shadowOffset = CGSize(width: 0.0, height: 1.0)
|
|
autocompleteView.layer.shadowRadius = 8.0
|
|
autocompleteView.layer.shadowOpacity = 0.6
|
|
autocompleteView.layer.cornerRadius = 8.0
|
|
autocompleteView.layer.masksToBounds = true
|
|
|
|
let shadowPath = UIBezierPath(roundedRect: autocompleteView.bounds,
|
|
cornerRadius: autocompleteView.layer.cornerRadius)
|
|
autocompleteView.layer.shadowPath = shadowPath.cgPath
|
|
|
|
if let toolbarView = toolbarView, let urlBar = toolbarView.urlBar {
|
|
var yOffset = toolbarView.frame.maxY + 3.0
|
|
if let tabBarView = tabBarView, tabBarViewVisible {
|
|
yOffset += tabBarView.frame.height
|
|
}
|
|
|
|
let urlFrame = self.convert(urlBar.frame, from: urlBar.superview)
|
|
autocompleteView.frame = CGRect(
|
|
x: urlFrame.minX,
|
|
y: yOffset,
|
|
width: urlFrame.width,
|
|
height: bounds.height / 2.5
|
|
)
|
|
}
|
|
} else {
|
|
autocompleteView.layer.cornerRadius = 0.0
|
|
autocompleteView.layer.shadowOpacity = 0.0
|
|
}
|
|
}
|
|
|
|
// Find on page view
|
|
if let findOnPageView = findOnPageView {
|
|
var bottomOffset: CGFloat = 0.0
|
|
var findOnPageSize = CGSize(width: bounds.width, height: 54.0)
|
|
if keyboardLayoutOffset == 0 {
|
|
findOnPageSize.height += safeAreaInsets.bottom
|
|
} else if findOnPageView.textField.isFirstResponder {
|
|
bottomOffset = keyboardLayoutOffset
|
|
}
|
|
|
|
findOnPageView.bounds = CGRect(origin: .zero, size: findOnPageSize)
|
|
findOnPageView.center = CGPoint(x: bounds.center.x, y: bounds.maxY - (findOnPageView.bounds.height / 2) - bottomOffset)
|
|
bringSubviewToFront(findOnPageView)
|
|
|
|
if !findOnPageVisible {
|
|
// Hide off the bottom
|
|
findOnPageView.center = CGPoint(x: findOnPageView.center.x, y: findOnPageView.center.y + findOnPageView.frame.height)
|
|
}
|
|
}
|
|
}
|
|
}
|