// // Button.swift // MVMCoreUI // // Created by Robinson, Blake on 12/18/19. // Copyright © 2019 Verizon Wireless. All rights reserved. // import MVMCore public typealias ButtonAction = (Button) -> () @objcMembers open class Button: UIButton, MFButtonProtocol, MoleculeViewProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- open var model: MoleculeModelProtocol? open var actionModel: ActionModelProtocol? private var initialSetupPerformed = false private var buttonAction: ButtonAction? //-------------------------------------------------- // MARK: - Delegate //-------------------------------------------------- open weak var buttonDelegate: ButtonDelegateProtocol? //-------------------------------------------------- // MARK: - Initializers //-------------------------------------------------- public override init(frame: CGRect) { super.init(frame: .zero) initialSetup() } public convenience init() { self.init(frame: .zero) initialSetup() } public required init?(coder: NSCoder) { super.init(coder: coder) fatalError("Button does not support xib.") } //-------------------------------------------------- // MARK: - Setup //-------------------------------------------------- /// Required to be called any init. Ensures setupView() only gets called once public func initialSetup() { if !initialSetupPerformed { initialSetupPerformed = true setupView() } } //-------------------------------------------------- // MARK: - Methods //-------------------------------------------------- /// Adds a block to be performed for the given event. open func addActionBlock(event: Event, _ buttonBlock: @escaping ButtonAction) { self.buttonAction = buttonBlock addTarget(self, action: #selector(callActionBlock), for: event) } @objc func callActionBlock(_ sender: Button) { buttonAction?(self) } open func set(with actionModel: ActionModelProtocol?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { self.actionModel = actionModel buttonDelegate = delegateObject?.buttonDelegate addActionBlock(event: .touchUpInside) { [weak self] sender in guard let self = self, let actionModel = actionModel else { return } Task(priority: .userInitiated) { try await Self.performButtonAction(with: actionModel, button: self, delegateObject: delegateObject, additionalData: additionalData, sourceModel: self.model) } } } open class func performButtonAction(with model: ActionModelProtocol, button: MFButtonProtocol, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?, sourceModel: MoleculeModelProtocol? = nil) async throws { guard delegateObject?.buttonDelegate?.button?(button, shouldPerformActionWithMap: model.toJSON(), additionalData: additionalData) ?? true else { return } try await (delegateObject?.actionDelegate as? ActionDelegateProtocol)?.performAction(with: model, additionalData: MVMCoreUIActionHandler.add(sourceModel: sourceModel, to: additionalData), delegateObject: delegateObject) } // MARK:- MoleculeViewProtocol open func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { self.model = model if let backgroundColor = model.backgroundColor { self.backgroundColor = backgroundColor.uiColor } if let accessibilityIdentifier = model.accessibilityIdentifier { self.accessibilityIdentifier = accessibilityIdentifier } if let model = model as? EnableableModelProtocol { isEnabled = model.enabled } if let accessibilityTraits = model.accessibilityTraits { self.accessibilityTraits = accessibilityTraits } guard let model = model as? ButtonModelProtocol else { return } set(with: model.action, delegateObject: delegateObject, additionalData: additionalData) } open func reset() { backgroundColor = .clear } // MARK: Overridables // Base classes need to implement these functions otherwise swift won't respect the subclass functions and use the ones in the protocol extension instead. open class func nameForReuse(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> String? { model.moleculeName } open class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { nil } open class func requiredModules(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, error: AutoreleasingUnsafeMutablePointer?) -> [String]? { nil } //-------------------------------------------------- // MARK: - Accessibility //-------------------------------------------------- open override func accessibilityActivate() -> Bool { guard isEnabled else { return false } buttonAction?(self) return buttonAction != nil } } // MARK: - MVMCoreViewProtocol extension Button: MVMCoreViewProtocol { open func updateView(_ size: CGFloat) { } /// Will be called only once. open func setupView() { isAccessibilityElement = true accessibilityTraits = .button translatesAutoresizingMaskIntoConstraints = false insetsLayoutMarginsFromSafeArea = false titleLabel?.numberOfLines = 0 titleLabel?.lineBreakMode = .byWordWrapping } } // MARK: AppleGuidelinesProtocol extension Button: AppleGuidelinesProtocol { override open func point(inside point: CGPoint, with event: UIEvent?) -> Bool { Self.acceptablyOutsideBounds(point: point, bounds: bounds) } }