From aa66fb7ec3a28fe080ecf09beb433db4289683c1 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Fri, 5 Mar 2021 18:07:11 -0300 Subject: [PATCH] Rework down state of payment custom keyboard. --- .../Payments/SendPaymentViewController.swift | 34 ++-- SignalMessaging/Views/OWSButton.swift | 167 +----------------- 2 files changed, 22 insertions(+), 179 deletions(-) diff --git a/Signal/src/ViewControllers/Payments/SendPaymentViewController.swift b/Signal/src/ViewControllers/Payments/SendPaymentViewController.swift index d8f97c6077..c72bf5fe2c 100644 --- a/Signal/src/ViewControllers/Payments/SendPaymentViewController.swift +++ b/Signal/src/ViewControllers/Payments/SendPaymentViewController.swift @@ -277,7 +277,6 @@ public class SendPaymentViewController: OWSViewController { let keyboardHSpacing: CGFloat = 25 let buttonFont = UIFont.ows_dynamicTypeTitle1Clamped func buildAmountKeyboardButton(title: String, block: @escaping () -> Void) -> OWSButton { - // PAYMENTS TODO: Highlight down state? let button = OWSButton(block: block) let label = UILabel() @@ -290,7 +289,6 @@ public class SendPaymentViewController: OWSViewController { return button } func buildAmountKeyboardButton(imageName: String, block: @escaping () -> Void) -> OWSButton { - // PAYMENTS TODO: Highlight down state? let button = OWSButton(imageName: imageName, tintColor: Theme.primaryTextColor, block: block) @@ -304,21 +302,27 @@ public class SendPaymentViewController: OWSViewController { let buttonSize = buttonFont.lineHeight * 2.0 button.autoSetDimension(.height, toSize: buttonSize) - let downStateView = OWSLayerView.circleView() - button.downStateView = downStateView - button.sendSubviewToBack(downStateView) - downStateView.autoPinEdgesToSuperviewEdges() - downStateView.backgroundColor = (Theme.isDarkThemeEnabled - ? UIColor.ows_gray90 - : UIColor.ows_gray02) + let downStateColor = (Theme.isDarkThemeEnabled + ? UIColor.ows_gray90 + : UIColor.ows_gray02) + let downStateImage = UIImage(color: downStateColor, + size: CGSize(width: 1, height: 1)) + button.setBackgroundImage(downStateImage, for: .highlighted) + + // We clip the button to a circle so that the + // down state is circular. + let buttonClipView = OWSLayerView.circleView() + buttonClipView.addSubview(button) + button.autoPinEdgesToSuperviewEdges() + buttonClipView.clipsToBounds = true let buttonWrapper = UIView.container() - buttonWrapper.addSubview(button) - button.autoPinEdge(toSuperviewEdge: .top) - button.autoPinEdge(toSuperviewEdge: .bottom) - button.autoHCenterInSuperview() - button.autoPinEdge(toSuperviewMargin: .leading, relation: .greaterThanOrEqual) - button.autoPinEdge(toSuperviewMargin: .trailing, relation: .greaterThanOrEqual) + buttonWrapper.addSubview(buttonClipView) + buttonClipView.autoPinEdge(toSuperviewEdge: .top) + buttonClipView.autoPinEdge(toSuperviewEdge: .bottom) + buttonClipView.autoHCenterInSuperview() + buttonClipView.autoPinEdge(toSuperviewMargin: .leading, relation: .greaterThanOrEqual) + buttonClipView.autoPinEdge(toSuperviewMargin: .trailing, relation: .greaterThanOrEqual) return buttonWrapper } diff --git a/SignalMessaging/Views/OWSButton.swift b/SignalMessaging/Views/OWSButton.swift index a62d6bcc2c..7ea103b99d 100644 --- a/SignalMessaging/Views/OWSButton.swift +++ b/SignalMessaging/Views/OWSButton.swift @@ -10,26 +10,6 @@ public class OWSButton: UIButton { @objc public var block: () -> Void = { } - public var downStateView: UIView? { - didSet { - oldValue?.removeFromSuperview() - - if let downStateView = downStateView { - addSubview(downStateView) - - ensureDownGestureRecognizer() - } - - applyDownState() - } - } - - private var isDown: Bool = false { - didSet { - applyDownState() - } - } - // MARK: - @objc @@ -37,7 +17,7 @@ public class OWSButton: UIButton { super.init(frame: .zero) self.block = block - ensureTapGestureRecognizer() + addTarget(self, action: #selector(didTap), for: .touchUpInside) } @objc @@ -45,7 +25,7 @@ public class OWSButton: UIButton { super.init(frame: .zero) self.block = block - ensureTapGestureRecognizer() + addTarget(self, action: #selector(didTap), for: .touchUpInside) setTitle(title, for: .normal) } @@ -56,7 +36,7 @@ public class OWSButton: UIButton { super.init(frame: .zero) self.block = block - ensureTapGestureRecognizer() + addTarget(self, action: #selector(didTap), for: .touchUpInside) setImage(imageName: imageName) self.tintColor = tintColor @@ -79,41 +59,6 @@ public class OWSButton: UIButton { fatalError("init(coder:) has not been implemented") } - private func applyDownState() { - guard let downStateView = downStateView else { - return - } - downStateView.isHidden = !isDown - } - - private var downGestureRecognizer: DownStateGestureRecognizer? - - private func ensureDownGestureRecognizer() { - if downGestureRecognizer != nil { - return - } - let downGestureRecognizer = DownStateGestureRecognizer { [weak self] in - guard let self = self, - let downGestureRecognizer = self.downGestureRecognizer else { - return - } - self.isDown = downGestureRecognizer.hasValidTouch - } - addGestureRecognizer(downGestureRecognizer) - self.downGestureRecognizer = downGestureRecognizer - } - - private var tapGestureRecognizer: UIGestureRecognizer? - - private func ensureTapGestureRecognizer() { - if tapGestureRecognizer != nil { - return - } - let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(didTap)) - addGestureRecognizer(tapGestureRecognizer) - self.tapGestureRecognizer = tapGestureRecognizer - } - // MARK: - Common Style Reuse @objc @@ -162,109 +107,3 @@ public class OWSButton: UIButton { block() } } - -// MARK: - - -// A GR used by OWSButton to detect "down state". -// -// This GR is unusual; it must not interfere with its views other GRs. -// It can't "recognize" without blocking other GRs from recognizing. -// Therefore it never changes its own state. It uses a callback block -// to notify its view of changes to down state. -// -// A button must be tapped. This GR is a bit more permissive than that. -// It will remain down for a long press even though that isn't a valid tap. -// Although it is not "down" if a long press leaves the views bounds, it -// will re-enter "down" if the long press re-enters the views bounds. -// This gives a sense of physicality to the the button. -public class DownStateGestureRecognizer: UIGestureRecognizer { - - public typealias Callback = () -> Void - private final let callback: Callback - - public required init(callback: @escaping Callback) { - self.callback = callback - - super.init(target: nil, action: nil) - } - - public var hasValidTouch = false { - didSet { - if hasValidTouch != oldValue { - callback() - } - } - } - - @objc - public override func canPrevent(_ preventedGestureRecognizer: UIGestureRecognizer) -> Bool { - return false - } - - @objc - public override func canBePrevented(by preventingGestureRecognizer: UIGestureRecognizer) -> Bool { - return false - } - - @objc - public override func shouldRequireFailure(of otherGestureRecognizer: UIGestureRecognizer) -> Bool { - return false - } - - @objc - public override func shouldBeRequiredToFail(by otherGestureRecognizer: UIGestureRecognizer) -> Bool { - return false - } - - @objc - public override func touchesBegan(_ touches: Set, with event: UIEvent) { - handle(event: event) - } - - @objc - public override func touchesMoved(_ touches: Set, with event: UIEvent) { - handle(event: event) - } - - @objc - public override func touchesEnded(_ touches: Set, with event: UIEvent) { - handle(event: event) - } - - @objc - public override func touchesCancelled(_ touches: Set, with event: UIEvent) { - handle(event: event) - } - - private func handle(event: UIEvent) { - // Consider the button down if there is a a single active - // touch within the views bounds. - self.hasValidTouch = { () -> Bool in - guard let allTouches = event.allTouches, - allTouches.count == 1, - let touch = allTouches.first else { - return false - } - guard let view = self.view else { - return false - } - let location = touch.location(in: view) - guard view.bounds.contains(location) else { - return false - } - - switch touch.phase { - case .began, .moved, .stationary: - break - case .ended, .cancelled: - return false - case .regionEntered, .regionMoved, .regionExited: - return false - @unknown default: - return false - } - - return true - }() - } -}