import UIKit import AsyncDisplayKit import AppBundle public class NavigationBackButtonNode: ASControlNode { private func fontForCurrentState() -> UIFont { return UIFont.systemFont(ofSize: 17.0) } private func attributesForCurrentState() -> [NSAttributedString.Key : AnyObject] { return [ NSAttributedString.Key.font: self.fontForCurrentState(), NSAttributedString.Key.foregroundColor: self.isEnabled ? self.color : self.disabledColor ] } let arrow: ASDisplayNode let label: ImmediateTextNode private let arrowSpacing: CGFloat = 4.0 private var _text: String = "" public var text: String { get { return self._text } set(value) { self._text = value self.label.attributedText = NSAttributedString(string: text, attributes: self.attributesForCurrentState()) self.invalidateCalculatedLayout() } } public var color: UIColor = UIColor(rgb: 0x007ee5) { didSet { self.label.attributedText = NSAttributedString(string: self._text, attributes: self.attributesForCurrentState()) } } public var disabledColor: UIColor = UIColor(rgb: 0xd0d0d0) { didSet { self.label.attributedText = NSAttributedString(string: self._text, attributes: self.attributesForCurrentState()) } } private var touchCount = 0 var pressed: () -> () = {} override public init() { self.arrow = ASDisplayNode() self.label = ImmediateTextNode() super.init() self.isUserInteractionEnabled = true self.isExclusiveTouch = true self.hitTestSlop = UIEdgeInsets(top: -16.0, left: -10.0, bottom: -16.0, right: -10.0) self.displaysAsynchronously = false self.arrow.displaysAsynchronously = false self.label.displaysAsynchronously = false self.addSubnode(self.arrow) let arrowImage = UIImage(named: "NavigationBackArrowLight", in: getAppBundle(), compatibleWith: nil)?.precomposed() self.arrow.contents = arrowImage?.cgImage self.arrow.frame = CGRect(origin: CGPoint(), size: arrowImage?.size ?? CGSize()) self.addSubnode(self.label) } public override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { let _ = self.label.updateLayout(CGSize(width: max(0.0, constrainedSize.width - self.arrow.frame.size.width - self.arrowSpacing), height: constrainedSize.height)) return CGSize(width: self.arrow.frame.size.width + self.arrowSpacing + self.label.calculatedSize.width, height: max(self.arrow.frame.size.height, self.label.calculatedSize.height)) } var labelFrame: CGRect { get { return CGRect(x: self.arrow.frame.size.width + self.arrowSpacing, y: floor((self.frame.size.height - self.label.calculatedSize.height) / 2.0), width: self.label.calculatedSize.width, height: self.label.calculatedSize.height) } } public override func layout() { super.layout() self.arrow.frame = CGRect(x: 0.0, y: floor((self.frame.size.height - arrow.frame.size.height) / 2.0), width: self.arrow.frame.size.width, height: self.arrow.frame.size.height) self.label.frame = self.labelFrame } private func touchInsideApparentBounds(_ touch: UITouch) -> Bool { var apparentBounds = self.bounds let hitTestSlop = self.hitTestSlop apparentBounds.origin.x += hitTestSlop.left apparentBounds.size.width -= hitTestSlop.left + hitTestSlop.right apparentBounds.origin.y += hitTestSlop.top apparentBounds.size.height -= hitTestSlop.top + hitTestSlop.bottom return apparentBounds.contains(touch.location(in: self.view)) } public override func touchesBegan(_ touches: Set, with event: UIEvent?) { super.touchesBegan(touches, with: event) self.touchCount += touches.count self.updateHighlightedState(true, animated: false) } public override func touchesMoved(_ touches: Set, with event: UIEvent?) { super.touchesMoved(touches, with: event) self.updateHighlightedState(self.touchInsideApparentBounds(touches.first!), animated: true) } public override func touchesEnded(_ touches: Set, with event: UIEvent?) { super.touchesEnded(touches, with: event) self.updateHighlightedState(false, animated: false) let previousTouchCount = self.touchCount self.touchCount = max(0, self.touchCount - touches.count) if previousTouchCount != 0 && self.touchCount == 0 && self.isEnabled && self.touchInsideApparentBounds(touches.first!) { self.pressed() } } public override func touchesCancelled(_ touches: Set?, with event: UIEvent?) { super.touchesCancelled(touches, with: event) self.touchCount = max(0, self.touchCount - (touches?.count ?? 0)) self.updateHighlightedState(false, animated: false) } private var _highlighted = false private func updateHighlightedState(_ highlighted: Bool, animated: Bool) { if _highlighted != highlighted { _highlighted = highlighted let alpha: CGFloat = !self.isEnabled ? 1.0 : (highlighted ? 0.4 : 1.0) if animated { UIView.animate(withDuration: 0.3, delay: 0.0, options: UIView.AnimationOptions.beginFromCurrentState, animations: { () -> Void in self.alpha = alpha }, completion: nil) } else { self.alpha = alpha } } } }