// // Control.swift // VDS // // Created by Matt Bruce on 7/22/22. // import Foundation import UIKit import Combine @objc(VDSControl) open class Control: UIControl, Handlerable, ViewProtocol, Resettable, UserInfoable, Clickable { //-------------------------------------------------- // MARK: - Combine Properties //-------------------------------------------------- /// Set of Subscribers for any Publishers for this Control public var subscribers = Set() /// Sets the primary Subscriber used for the TouchUpInside public var onClickSubscriber: AnyCancellable? { willSet { if let onClickSubscriber { onClickSubscriber.cancel() } } } //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- private var initialSetupPerformed = false /// Key of whether or not updateView() is called in setNeedsUpdate() open var shouldUpdateView: Bool = true /// Dictionary for keeping information for this Control use only Primitives open var userInfo = [String: Primitive]() /// Current Surface used within this Control and used to passdown to child views open var surface: Surface = .light { didSet { setNeedsUpdate() } } /// Control is disabled or not open var disabled: Bool = false { didSet { setNeedsUpdate(); isUserInteractionEnabled = !disabled } } open override var state: UIControl.State { get { var state = super.state if disabled { state.insert(.disabled) } else { state.remove(.disabled) } return state } } /// Override for isSelected to handle setNeedsUpdate() on changes open override var isSelected: Bool { didSet { setNeedsUpdate() } } /// Reference count use to be used in isHighlighted when a subscriber is listening for TouchUpInside. public var touchUpInsideCount: Int = 0 var isHighlightAnimating = false /// Override to deal with only calling setNeedsUpdate() if needed open override var isHighlighted: Bool { didSet { if isHighlightAnimating == false && touchUpInsideCount > 0 { isHighlightAnimating = true UIView.animate(withDuration: 0.1, animations: { [weak self] in self?.setNeedsUpdate() }) { [weak self] _ in //you update the view since this is typically a quick change UIView.animate(withDuration: 0.1, animations: { [weak self] in self?.setNeedsUpdate() self?.isHighlightAnimating = false }) } } } } // /// Override to deal with setNeedsUpdate() // open override var isEnabled: Bool { // get { !disabled } // set { // if disabled != !newValue { // disabled = !newValue // } // isUserInteractionEnabled = isEnabled // setNeedsUpdate() // } // } // //-------------------------------------------------- // MARK: - Initializers //-------------------------------------------------- required public init() { super.init(frame: .zero) initialSetup() } public override init(frame: CGRect) { super.init(frame: .zero) initialSetup() } public required init?(coder: NSCoder) { super.init(coder: coder) initialSetup() } //-------------------------------------------------- // MARK: - Setup //-------------------------------------------------- /// Executed on initialization for this Control open func initialSetup() { if !initialSetupPerformed { initialSetupPerformed = true setup() setNeedsUpdate() } } ///Override to deal with sending actions for accessibility /// - Returns: Based on whether the userInteraction is enabled override open func accessibilityActivate() -> Bool { // Hold state in case User wanted isAnimated to remain off. guard isUserInteractionEnabled else { return false } sendActions(for: .touchUpInside) return true } //-------------------------------------------------- // MARK: - Overrides //-------------------------------------------------- /// Update this view based off of property changes open func updateView() { updateAccessibilityLabel() } /// Used to update any Accessibility properties open func updateAccessibilityLabel() { } /// Resets to the Controls default values open func reset() { backgroundColor = .clear surface = .light disabled = false } /// Will be called only once and should be overridden in subclasses to setup UI or defaults open func setup() { backgroundColor = .clear translatesAutoresizingMaskIntoConstraints = false insetsLayoutMarginsFromSafeArea = false } }