diff --git a/App/Browser View/BrowserView.swift b/App/Browser View/BrowserView.swift index e7d7f7e..0d0452d 100644 --- a/App/Browser View/BrowserView.swift +++ b/App/Browser View/BrowserView.swift @@ -98,6 +98,7 @@ class BrowserView: UIView } }(animationCurve) + toolbarView?.layoutLatch.deactivate() keyboardLayoutOffset = bounds.height - keyboardEndFrame.minY UIView.animate(withDuration: animationDuration, delay: 0.0, options: animationOptions, animations: { self.layoutIfNeeded() }, completion: nil) } diff --git a/App/Common UI/ReliefButton.swift b/App/Common UI/ReliefButton.swift index 651e1ad..b54f0ff 100644 --- a/App/Common UI/ReliefButton.swift +++ b/App/Common UI/ReliefButton.swift @@ -46,6 +46,8 @@ class ReliefButton: UIButton } }) + self.imageView?.contentMode = .scaleAspectFit + shadowView.alpha = 0.28 shadowView.backgroundColor = UIColor(white: 0.0, alpha: 1.0) shadowView.isUserInteractionEnabled = false @@ -132,8 +134,6 @@ class ReliefButton: UIButton } override func layoutSubviews() { - self.imageView?.contentMode = .scaleAspectFit - super.layoutSubviews() sendSubviewToBack(backgroundView) diff --git a/App/Titlebar and URL Bar/ToolbarView.swift b/App/Titlebar and URL Bar/ToolbarView.swift index a654844..161c8cb 100644 --- a/App/Titlebar and URL Bar/ToolbarView.swift +++ b/App/Titlebar and URL Bar/ToolbarView.swift @@ -9,9 +9,18 @@ import UIKit class ToolbarView: UIView { - var urlBar: URLBar? { didSet { containerView.addSubview(urlBar!) } } + 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 { layoutSubviews() } } + var cancelButtonVisible: Bool = false { didSet { setNeedsLayout() } } let containerView = UIView(frame: .zero) let backgroundView = GradientView(direction: .vertical, colors: [ @@ -23,6 +32,9 @@ class ToolbarView: UIView 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) @@ -54,6 +66,7 @@ class ToolbarView: UIView override func layoutSubviews() { + guard !layoutLatch.latched else { return } super.layoutSubviews() let shadowPath = UIBezierPath(rect: bounds) diff --git a/App/Titlebar and URL Bar/URLBar.swift b/App/Titlebar and URL Bar/URLBar.swift index 44fcb80..248b518 100644 --- a/App/Titlebar and URL Bar/URLBar.swift +++ b/App/Titlebar and URL Bar/URLBar.swift @@ -153,20 +153,12 @@ class URLBar: ReliefButton textField.font = .systemFont(ofSize: 13.0) textField.clearButtonMode = .never textField.placeholder = "URL or search term" - textField.addAction(UIAction(handler: { [unowned self] _ in - // Mask view visibility is affected by editing state. - self.layoutSubviews() - }), for: [ .editingDidBegin, .editingDidEnd ]) textField.keyCommands = [ UIKeyCommand(action: #selector(Self.downKeyPressed), input: UIKeyCommand.inputDownArrow) .prioritizeOverSystem() ] addSubview(textField) - textField.addAction(.init(handler: { [textField, refreshButton ] _ in - refreshButton.isHidden = textField.isFirstResponder - }), for: [ .editingDidBegin, .editingDidEnd ]) - refreshButton.tintColor = .secondaryLabel refreshButton.setImage(refreshImage, for: .normal) refreshButton.isPointerInteractionEnabled = true @@ -189,6 +181,7 @@ class URLBar: ReliefButton documentSeparatorView.backgroundColor = .secondarySystemFill addSubview(documentSeparatorView) + controlsView.autoresizingMask = [] controlsView.clearButton.addAction(.init(handler: { [textField] _ in textField.clearText() }), for: .primaryActionTriggered) @@ -346,7 +339,7 @@ class URLBar: ReliefButton documentSeparatorView.frame = documentSeparatorView.frame.insetBy(dx: 0.0, dy: 3.0) // Text field controls - controlsView.frame = CGRect(origin: .zero, size: controlsView.sizeThatFits(bounds.size)) + controlsView.frame = CGRect(origin: controlsView.frame.origin, size: controlsView.sizeThatFits(bounds.size)) // Text field let textFieldPadding: CGFloat = 6.0 @@ -365,8 +358,18 @@ class URLBar: ReliefButton let refreshButtonSize = CGSize(width: textField.frame.height, height: textField.frame.height) refreshButton.frame = CGRect(origin: CGPoint(x: bounds.width - refreshButtonSize.width, y: 0), size: refreshButtonSize) + // Refresh vs. controls view visibility + let isFocused = textField.isFirstResponder + if isFocused { + controlsView.alpha = 1.0 + refreshButton.alpha = 0.0 + } else { + controlsView.alpha = 0.0 + refreshButton.alpha = 1.0 + } + // Error button - if case .error(error: _) = loadProgress, !textField.isFirstResponder { + if case .error(error: _) = loadProgress, !isFocused { errorButton.isHidden = false errorButton.sizeToFit() errorButton.frame = CGRect( @@ -384,7 +387,7 @@ class URLBar: ReliefButton // Fade mask fadeMaskView.frame = textField.bounds fadeMaskView.image = fadeBackgroundImageForSize(fadeMaskView.frame.size, cutoffLocation: fadeCutoffLocation) - if !textField.isFirstResponder { + if !isFocused { textField.mask = fadeMaskView } else { textField.mask = nil diff --git a/App/Utilities/LayoutLatch.swift b/App/Utilities/LayoutLatch.swift new file mode 100644 index 0000000..43c069c --- /dev/null +++ b/App/Utilities/LayoutLatch.swift @@ -0,0 +1,30 @@ +// +// LayoutLatch.swift +// App +// +// Created by James Magahern on 8/10/23. +// + +import Foundation + +class LayoutLatch { + public weak var view: UIView? + public private(set) var latched: Bool = false + + init(_ view: UIView) { + self.view = view + } + + public func activate() { + latched = true + + DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(100)) { [weak self] in + self?.deactivate() + } + } + + public func deactivate() { + latched = false + view?.setNeedsLayout() + } +} diff --git a/SBrowser.xcodeproj/project.pbxproj b/SBrowser.xcodeproj/project.pbxproj index 2c0cae6..5445619 100644 --- a/SBrowser.xcodeproj/project.pbxproj +++ b/SBrowser.xcodeproj/project.pbxproj @@ -59,6 +59,7 @@ CD853BCE24E7763900D2BDCC /* BrowserHistory.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD853BCD24E7763900D2BDCC /* BrowserHistory.swift */; }; CD853BD124E778B800D2BDCC /* History.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = CD853BCF24E778B800D2BDCC /* History.xcdatamodeld */; }; CD853BD424E77BF900D2BDCC /* HistoryItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD853BD324E77BF900D2BDCC /* HistoryItem.swift */; }; + CD8DBE7B2A85D892006A0FE0 /* LayoutLatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD8DBE7A2A85D892006A0FE0 /* LayoutLatch.swift */; }; CD936A3B289DB3380093A1AC /* TabInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD936A3A289DB3380093A1AC /* TabInfo.swift */; }; CD936A3D289DB88B0093A1AC /* UIView+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD936A3C289DB88B0093A1AC /* UIView+Utils.swift */; }; CD97CF9225D5BE6F00288FEE /* NavigationControlsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD97CF9125D5BE6F00288FEE /* NavigationControlsView.swift */; }; @@ -172,6 +173,7 @@ CD853BCD24E7763900D2BDCC /* BrowserHistory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserHistory.swift; sourceTree = ""; }; CD853BD024E778B800D2BDCC /* History.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = History.xcdatamodel; sourceTree = ""; }; CD853BD324E77BF900D2BDCC /* HistoryItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryItem.swift; sourceTree = ""; }; + CD8DBE7A2A85D892006A0FE0 /* LayoutLatch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutLatch.swift; sourceTree = ""; }; CD936A3A289DB3380093A1AC /* TabInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabInfo.swift; sourceTree = ""; }; CD936A3C289DB88B0093A1AC /* UIView+Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Utils.swift"; sourceTree = ""; }; CD97CF9125D5BE6F00288FEE /* NavigationControlsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationControlsView.swift; sourceTree = ""; }; @@ -396,6 +398,7 @@ 1AB88F0524D4D3A90006F850 /* UIGestureRecognizer+Actions.swift */, CDEDD8A925D62ADB00862605 /* UITraitCollection+MacLike.swift */, CD936A3C289DB88B0093A1AC /* UIView+Utils.swift */, + CD8DBE7A2A85D892006A0FE0 /* LayoutLatch.swift */, ); path = Utilities; sourceTree = ""; @@ -656,6 +659,7 @@ CD16844D269E709400B8F8A5 /* Box.swift in Sources */, CD853BCE24E7763900D2BDCC /* BrowserHistory.swift in Sources */, 1A03810B24E71C5600826501 /* ToolbarButtonContainerView.swift in Sources */, + CD8DBE7B2A85D892006A0FE0 /* LayoutLatch.swift in Sources */, CD7A7EA12686B2E600E20BA3 /* RedirectRulesSettingsViewController.swift in Sources */, 1ADFF4CB24CB8278006DC7AE /* ScriptControllerIconView.swift in Sources */, CD7A8915251975B70075991E /* AutocompleteViewController.swift in Sources */,