// // ToolbarView.swift // App // // Created by James Magahern on 8/14/20. // import UIKit class ToolbarView: UIView { var urlBar: URLBar? { didSet { guard let urlBar else { return } containerView.addSubview(urlBar) urlBar.textField.addAction(.init(handler: { [unowned self] _ in layoutLatch.activate() }), for: [ .editingDidBegin, .editingDidEnd ]) } } var cancelButtonVisible: Bool = false { didSet { setNeedsLayout() } } let containerView = UIView(frame: .zero) let backgroundView = GradientView(direction: .vertical, colors: [ .secondarySystemGroupedBackground, .secondarySystemGroupedBackground ]) let cancelButton = UIButton(type: .system) let leadingButtonsView = ToolbarButtonContainerView(frame: .zero) let trailingButtonsView = ToolbarButtonContainerView(frame: .zero) // Something I'm sure I'll regret: to ensure animation with the keyboard, latch layout until we get the right signal. lazy var layoutLatch = LayoutLatch(self) 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.3 layer.shadowOffset = CGSize(width: 0.0, height: 1.0) layer.shadowRadius = 1.8 } override func sizeThatFits(_ size: CGSize) -> CGSize { var height: CGFloat = 42.0 if traitCollection.userInterfaceIdiom == .mac { height = 40.0 } return CGSize(width: size.width, height: height) } override func layoutSubviews() { guard !layoutLatch.latched else { return } super.layoutSubviews() let shadowPath = UIBezierPath(rect: bounds) layer.shadowPath = shadowPath.cgPath 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) let buttonContainerInset = UIEdgeInsets(top: 1.0, left: 0.0, bottom: 1.0, right: 0.0) let urlBarPadding = CGFloat(8.0) var urlBarInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0) // Cancel button 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).inset(by: buttonContainerInset) urlBarInsets.left = urlBarPadding } 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), y: 0), size: trailingContainerSize) trailingButtonsView.frame = trailingButtonsView.frame.inset(by: buttonContainerInset) urlBarInsets.right = urlBarPadding 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 { var origin = CGPoint( x: leadingButtonsView.frame.maxX, y: 0.0 ) if origin.x == 0 { // Add some padding if url bar is flush with side origin.x = layoutMargins.left } urlBar.frame = CGRect( origin: origin, size: CGSize( width: containerView.bounds.width - avoidingSize.width - origin.x, height: trailingContainerSize.height ) ) urlBar.frame = urlBar.frame.inset(by: urlBarInsets).inset(by: buttonContainerInset) } } }