// // ToolbarViewController.swift // SBrowser // // Created by James Magahern on 7/23/20. // import UIKit class ToolbarButtonView: UIView { private var buttonPadding = CGFloat(24.0) private var buttonViews: [UIView] = [] public var numberOfButtonViews: Int { buttonViews.count } func addButtonView(_ button: UIView) { buttonViews.append(button) addSubview(button) setNeedsLayout() } func removeAllButtonViews() { buttonViews.forEach { $0.removeFromSuperview() } buttonViews.removeAll() setNeedsLayout() } override func sizeThatFits(_ size: CGSize) -> CGSize { let width: CGFloat = buttonViews.reduce(0.0) { (result, button) -> CGFloat in return result + button.sizeThatFits(size).width + buttonPadding } return CGSize(width: width, height: size.height) } override func layoutSubviews() { var buttonRect = CGRect(origin: .zero, size: CGSize(width: 0, height: bounds.height)) buttonRect.origin.x = layoutMargins.left for button in buttonViews { let buttonSize = button.sizeThatFits(bounds.size) buttonRect.size = CGSize(width: buttonSize.width, height: bounds.height) button.frame = buttonRect buttonRect.origin.x += buttonRect.width + buttonPadding } } } class ToolbarView: UIView { var urlBar: URLBar? { didSet { containerView.addSubview(urlBar!) } } var cancelButtonVisible: Bool = false { didSet { layoutSubviews() } } let containerView = UIView(frame: .zero) let backgroundView = UIVisualEffectView(effect: UIBlurEffect(style: .systemThickMaterial)) let cancelButton = UIButton(type: .system) let leadingButtonsView = ToolbarButtonView(frame: .zero) let trailingButtonsView = ToolbarButtonView(frame: .zero) convenience init() { self.init(frame: .zero) addSubview(backgroundView) addSubview(containerView) containerView.addSubview(leadingButtonsView) containerView.addSubview(trailingButtonsView) cancelButton.setTitle("Cancel", for: .normal) containerView.addSubview(cancelButton) layer.masksToBounds = false layer.shadowColor = UIColor.black.cgColor layer.shadowOpacity = 0.2 layer.shadowOffset = CGSize(width: 0.0, height: 1.0) layer.shadowRadius = 1.5 } override func sizeThatFits(_ size: CGSize) -> CGSize { return CGSize(width: size.width, height: 44.0) } override func layoutSubviews() { super.layoutSubviews() backgroundView.frame = bounds var containerBounds = bounds containerBounds.size.height -= safeAreaInsets.bottom containerView.frame = containerBounds containerView.frame = containerView.frame.insetBy(dx: 8.0, dy: 4.0) // Cancel button let urlBarPadding: CGFloat = 8.0 var cancelButtonSize = cancelButton.sizeThatFits(containerView.bounds.size) cancelButtonSize.width += (urlBarPadding * 2) cancelButton.frame = CGRect(origin: CGPoint(x: (containerView.bounds.maxX - cancelButtonSize.width), y: 0), size: CGSize(width: cancelButtonSize.width + urlBarPadding, height: containerView.bounds.height)) // Leading toolbar buttons if leadingButtonsView.numberOfButtonViews > 0 { let leadingContainerSize = leadingButtonsView.sizeThatFits(containerView.bounds.size) leadingButtonsView.frame = CGRect(origin: .zero, size: leadingContainerSize) } else { leadingButtonsView.frame = .zero } // Trailing toolbar buttons let trailingContainerSize = trailingButtonsView.sizeThatFits(containerView.bounds.size) trailingButtonsView.frame = CGRect(origin: CGPoint(x: (containerView.bounds.maxX - trailingContainerSize.width) + urlBarPadding, y: 0), size: trailingContainerSize) var avoidingSize: CGSize = .zero if cancelButtonVisible { cancelButton.alpha = 1.0 trailingButtonsView.alpha = 0.0 avoidingSize = cancelButtonSize } else { cancelButton.alpha = 0.0 trailingButtonsView.alpha = 1.0 avoidingSize = trailingContainerSize } if let urlBar = urlBar { let origin = CGPoint( x: leadingButtonsView.frame.maxX, y: 0.0 ) urlBar.frame = CGRect( origin: origin, size: CGSize( width: containerView.bounds.width - avoidingSize.width - origin.x, height: containerView.bounds.height ) ) } } } class ToolbarViewController: UIViewController { let urlBar = URLBar() let toolbarView = ToolbarView() let scriptControllerIconView = ScriptControllerIconView() let shareButton = UIButton(frame: .zero) let darkModeButton = UIButton(frame: .zero) let windowButton = UIButton(frame: .zero) let backButton = UIButton(frame: .zero) let forwardButton = UIButton(frame: .zero) let newTabButton = UIButton(frame: .zero) var darkModeEnabled: Bool = false { didSet { if darkModeEnabled { darkModeButton.setImage(darkModeEnabledImage, for: .normal) } else { darkModeButton.setImage(darkModeDisabledImage, for: .normal) } } } private let darkModeDisabledImage = UIImage(systemName: "moon.circle") private let darkModeEnabledImage = UIImage(systemName: "moon.circle.fill") init() { super.init(nibName: nil, bundle: nil) toolbarView.urlBar = urlBar // Dark mode button darkModeButton.setImage(darkModeDisabledImage, for: .normal) // Share button shareButton.setImage(UIImage(systemName: "square.and.arrow.up"), for: .normal) // Window button windowButton.setImage(UIImage(systemName: "rectangle.on.rectangle"), for: .normal) // Back button backButton.setImage(UIImage(systemName: "chevron.left"), for: .normal) // Forward button forwardButton.setImage(UIImage(systemName: "chevron.right"), for: .normal) // New tab button newTabButton.setImage(UIImage(systemName: "plus"), for: .normal) let toolbarAnimationDuration: TimeInterval = 0.3 urlBar.textField.addAction(.init(handler: { [traitCollection, toolbarView, urlBar] _ in if traitCollection.horizontalSizeClass == .compact { UIView.animate(withDuration: toolbarAnimationDuration) { toolbarView.cancelButtonVisible = urlBar.textField.isFirstResponder } } }), for: [ .editingDidBegin, .editingDidEnd ]) toolbarView.cancelButton.addAction(.init(handler: { [urlBar] action in urlBar.textField.resignFirstResponder() }), for: .touchUpInside) traitCollectionDidChange(nil) } override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { super.traitCollectionDidChange(previousTraitCollection) toolbarView.leadingButtonsView.removeAllButtonViews() toolbarView.trailingButtonsView.removeAllButtonViews() // Setup toolbar based on trait collection if traitCollection.horizontalSizeClass == .compact { toolbarView.trailingButtonsView.addButtonView(darkModeButton) toolbarView.trailingButtonsView.addButtonView(scriptControllerIconView) toolbarView.trailingButtonsView.addButtonView(windowButton) } else { toolbarView.leadingButtonsView.addButtonView(backButton) toolbarView.leadingButtonsView.addButtonView(forwardButton) toolbarView.trailingButtonsView.addButtonView(darkModeButton) toolbarView.trailingButtonsView.addButtonView(shareButton) toolbarView.trailingButtonsView.addButtonView(scriptControllerIconView) toolbarView.trailingButtonsView.addButtonView(newTabButton) toolbarView.trailingButtonsView.addButtonView(windowButton) } } override func loadView() { self.view = toolbarView } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } }