// // BrowserView.swift // SBrowser // // Created by James Magahern on 7/21/20. // import Combine import UIKit import WebKit class BrowserView: UIView { let titlebarView = TitlebarView() var toolbarView: ToolbarView? { didSet { addSubview(toolbarView!) } } var webView: WKWebView? { didSet { if let toolbarView = toolbarView { insertSubview(webView!, belowSubview: toolbarView) } else { addSubview(webView!) } } } var keyboardWillShowObserver: AnyCancellable? var keyboardWillHideObserver: AnyCancellable? var keyboardLayoutOffset: CGFloat = 0 { didSet { setNeedsLayout() } } convenience init() { self.init(frame: .zero) addSubview(titlebarView) keyboardWillShowObserver = NotificationCenter.default.publisher(for: UIWindow.keyboardWillShowNotification).sink { notification in self.adjustOffsetForKeyboardNotification(userInfo: notification.userInfo!) } keyboardWillHideObserver = NotificationCenter.default.publisher(for: UIWindow.keyboardWillHideNotification).sink { notification in self.adjustOffsetForKeyboardNotification(userInfo: notification.userInfo!) } } private func adjustOffsetForKeyboardNotification(userInfo: [AnyHashable : Any]) { guard let 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 } 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) self.keyboardLayoutOffset = bounds.height - keyboardEndFrame.minY UIView.animate(withDuration: animationDuration, delay: 0.0, options: animationOptions, animations: { self.layoutIfNeeded() }, completion: nil) } override func layoutSubviews() { super.layoutSubviews() webView?.frame = bounds 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 == 0 { 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 } else { // Regular: toolbar is at the top toolbarView.bounds = CGRect(origin: .zero, size: toolbarSize) toolbarView.center = CGPoint(x: bounds.center.x, y: titlebarView.center.y + titlebarView.frame.height) webViewContentInset.top += toolbarView.frame.height } } // Fix web view content insets webView?.scrollView.automaticallyAdjustsScrollIndicatorInsets = false webView?.scrollView.contentInset = webViewContentInset.subtracting(safeAreaInsets) webView?.scrollView.scrollIndicatorInsets = webViewContentInset } }