InputBarButtonItem.swift 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. /*
  2. MIT License
  3. Copyright (c) 2017-2018 MessageKit
  4. Permission is hereby granted, free of charge, to any person obtaining a copy
  5. of this software and associated documentation files (the "Software"), to deal
  6. in the Software without restriction, including without limitation the rights
  7. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. copies of the Software, and to permit persons to whom the Software is
  9. furnished to do so, subject to the following conditions:
  10. The above copyright notice and this permission notice shall be included in all
  11. copies or substantial portions of the Software.
  12. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  13. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  14. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  15. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  16. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  17. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  18. SOFTWARE.
  19. */
  20. import UIKit
  21. /**
  22. A subclass of UIButton that conforms to InputItem
  23. ## Important Notes ##
  24. 1. Intended to be used in an `InputStackView`
  25. */
  26. open class InputBarButtonItem: UIButton, InputItem {
  27. /// The spacing properties of the InputBarButtonItem
  28. ///
  29. /// - fixed: The spacing is fixed
  30. /// - flexible: The spacing is flexible
  31. /// - none: There is no spacing
  32. public enum Spacing {
  33. case fixed(CGFloat)
  34. case flexible
  35. case none
  36. }
  37. public typealias InputBarButtonItemAction = ((InputBarButtonItem) -> Void)
  38. // MARK: - Properties
  39. /// A weak reference to the MessageInputBar that the InputBarButtonItem used in
  40. open weak var messageInputBar: MessageInputBar?
  41. /// The spacing property of the InputBarButtonItem that determines the contentHuggingPriority and any
  42. /// additional space to the intrinsicContentSize
  43. open var spacing: Spacing = .none {
  44. didSet {
  45. switch spacing {
  46. case .flexible:
  47. setContentHuggingPriority(UILayoutPriority(rawValue: 1), for: .horizontal)
  48. case .fixed:
  49. setContentHuggingPriority(UILayoutPriority(rawValue: 1000), for: .horizontal)
  50. case .none:
  51. setContentHuggingPriority(UILayoutPriority(rawValue: 500), for: .horizontal)
  52. }
  53. }
  54. }
  55. /// When not nil this size overrides the intrinsicContentSize
  56. private var size: CGSize? = CGSize(width: 20, height: 20) {
  57. didSet {
  58. invalidateIntrinsicContentSize()
  59. }
  60. }
  61. open override var intrinsicContentSize: CGSize {
  62. var contentSize = size ?? super.intrinsicContentSize
  63. switch spacing {
  64. case .fixed(let width):
  65. contentSize.width += width
  66. case .flexible, .none:
  67. break
  68. }
  69. return contentSize
  70. }
  71. /// A reference to the stack view position that the InputBarButtonItem is held in
  72. open var parentStackViewPosition: InputStackView.Position?
  73. /// The title for the UIControlState.normal
  74. open var title: String? {
  75. get {
  76. return title(for: .normal)
  77. }
  78. set {
  79. setTitle(newValue, for: .normal)
  80. }
  81. }
  82. /// The image for the UIControlState.normal
  83. open var image: UIImage? {
  84. get {
  85. return image(for: .normal)
  86. }
  87. set {
  88. setImage(newValue, for: .normal)
  89. }
  90. }
  91. /// Calls the onSelectedAction or onDeselectedAction when set
  92. open override var isHighlighted: Bool {
  93. get {
  94. return super.isHighlighted
  95. }
  96. set {
  97. guard newValue != isHighlighted else { return }
  98. super.isHighlighted = newValue
  99. if newValue {
  100. onSelectedAction?(self)
  101. } else {
  102. onDeselectedAction?(self)
  103. }
  104. }
  105. }
  106. /// Calls the onEnabledAction or onDisabledAction when set
  107. open override var isEnabled: Bool {
  108. didSet {
  109. if isEnabled {
  110. onEnabledAction?(self)
  111. } else {
  112. onDisabledAction?(self)
  113. }
  114. }
  115. }
  116. // MARK: - Reactive Hooks
  117. private var onTouchUpInsideAction: InputBarButtonItemAction?
  118. private var onKeyboardEditingBeginsAction: InputBarButtonItemAction?
  119. private var onKeyboardEditingEndsAction: InputBarButtonItemAction?
  120. private var onTextViewDidChangeAction: ((InputBarButtonItem, InputTextView) -> Void)?
  121. private var onSelectedAction: InputBarButtonItemAction?
  122. private var onDeselectedAction: InputBarButtonItemAction?
  123. private var onEnabledAction: InputBarButtonItemAction?
  124. private var onDisabledAction: InputBarButtonItemAction?
  125. // MARK: - Initialization
  126. public convenience init() {
  127. self.init(frame: .zero)
  128. }
  129. public override init(frame: CGRect) {
  130. super.init(frame: frame)
  131. setup()
  132. }
  133. required public init?(coder aDecoder: NSCoder) {
  134. super.init(coder: aDecoder)
  135. setup()
  136. }
  137. // MARK: - Setup
  138. /// Sets up the default properties
  139. open func setup() {
  140. contentVerticalAlignment = .center
  141. contentHorizontalAlignment = .center
  142. imageView?.contentMode = .scaleAspectFit
  143. setContentHuggingPriority(UILayoutPriority(rawValue: 500), for: .horizontal)
  144. setContentHuggingPriority(UILayoutPriority(rawValue: 500), for: .vertical)
  145. setTitleColor(UIColor(red: 0, green: 122/255, blue: 1, alpha: 1), for: .normal)
  146. setTitleColor(UIColor(red: 0, green: 122/255, blue: 1, alpha: 0.3), for: .highlighted)
  147. setTitleColor(.lightGray, for: .disabled)
  148. adjustsImageWhenHighlighted = false
  149. addTarget(self, action: #selector(InputBarButtonItem.touchUpInsideAction), for: .touchUpInside)
  150. }
  151. // MARK: - Size Adjustment
  152. /// Sets the size of the InputBarButtonItem which overrides the intrinsicContentSize. When set to nil
  153. /// the default intrinsicContentSize is used. The new size will be laid out in the UIStackView that
  154. /// the InputBarButtonItem is held in
  155. ///
  156. /// - Parameters:
  157. /// - newValue: The new size
  158. /// - animated: If the layout should be animated
  159. open func setSize(_ newValue: CGSize?, animated: Bool) {
  160. size = newValue
  161. if animated, let position = parentStackViewPosition {
  162. messageInputBar?.performLayout(animated) { [weak self] in
  163. self?.messageInputBar?.layoutStackViews([position])
  164. }
  165. }
  166. }
  167. // MARK: - Hook Setup Methods
  168. /// Used to setup your own initial properties
  169. ///
  170. /// - Parameter item: A reference to Self
  171. /// - Returns: Self
  172. @discardableResult
  173. open func configure(_ item: InputBarButtonItemAction) -> Self {
  174. item(self)
  175. return self
  176. }
  177. /// Sets the onKeyboardEditingBeginsAction
  178. ///
  179. /// - Parameter action: The new onKeyboardEditingBeginsAction
  180. /// - Returns: Self
  181. @discardableResult
  182. open func onKeyboardEditingBegins(_ action: @escaping InputBarButtonItemAction) -> Self {
  183. onKeyboardEditingBeginsAction = action
  184. return self
  185. }
  186. /// Sets the onKeyboardEditingEndsAction
  187. ///
  188. /// - Parameter action: The new onKeyboardEditingEndsAction
  189. /// - Returns: Self
  190. @discardableResult
  191. open func onKeyboardEditingEnds(_ action: @escaping InputBarButtonItemAction) -> Self {
  192. onKeyboardEditingEndsAction = action
  193. return self
  194. }
  195. /// Sets the onTextViewDidChangeAction
  196. ///
  197. /// - Parameter action: The new onTextViewDidChangeAction
  198. /// - Returns: Self
  199. @discardableResult
  200. open func onTextViewDidChange(_ action: @escaping (_ item: InputBarButtonItem, _ textView: InputTextView) -> Void) -> Self {
  201. onTextViewDidChangeAction = action
  202. return self
  203. }
  204. /// Sets the onTouchUpInsideAction
  205. ///
  206. /// - Parameter action: The new onTouchUpInsideAction
  207. /// - Returns: Self
  208. @discardableResult
  209. open func onTouchUpInside(_ action: @escaping InputBarButtonItemAction) -> Self {
  210. onTouchUpInsideAction = action
  211. return self
  212. }
  213. /// Sets the onSelectedAction
  214. ///
  215. /// - Parameter action: The new onSelectedAction
  216. /// - Returns: Self
  217. @discardableResult
  218. open func onSelected(_ action: @escaping InputBarButtonItemAction) -> Self {
  219. onSelectedAction = action
  220. return self
  221. }
  222. /// Sets the onDeselectedAction
  223. ///
  224. /// - Parameter action: The new onDeselectedAction
  225. /// - Returns: Self
  226. @discardableResult
  227. open func onDeselected(_ action: @escaping InputBarButtonItemAction) -> Self {
  228. onDeselectedAction = action
  229. return self
  230. }
  231. /// Sets the onEnabledAction
  232. ///
  233. /// - Parameter action: The new onEnabledAction
  234. /// - Returns: Self
  235. @discardableResult
  236. open func onEnabled(_ action: @escaping InputBarButtonItemAction) -> Self {
  237. onEnabledAction = action
  238. return self
  239. }
  240. /// Sets the onDisabledAction
  241. ///
  242. /// - Parameter action: The new onDisabledAction
  243. /// - Returns: Self
  244. @discardableResult
  245. open func onDisabled(_ action: @escaping InputBarButtonItemAction) -> Self {
  246. onDisabledAction = action
  247. return self
  248. }
  249. // MARK: - InputItem Protocol
  250. /// Executes the onTextViewDidChangeAction with the given textView
  251. ///
  252. /// - Parameter textView: A reference to the InputTextView
  253. open func textViewDidChangeAction(with textView: InputTextView) {
  254. onTextViewDidChangeAction?(self, textView)
  255. }
  256. /// Executes the onKeyboardEditingEndsAction
  257. open func keyboardEditingEndsAction() {
  258. onKeyboardEditingEndsAction?(self)
  259. }
  260. /// Executes the onKeyboardEditingBeginsAction
  261. open func keyboardEditingBeginsAction() {
  262. onKeyboardEditingBeginsAction?(self)
  263. }
  264. /// Executes the onTouchUpInsideAction
  265. @objc
  266. open func touchUpInsideAction() {
  267. onTouchUpInsideAction?(self)
  268. }
  269. // MARK: - Static Spacers
  270. /// An InputBarButtonItem that's spacing property is set to be .flexible
  271. public static var flexibleSpace: InputBarButtonItem {
  272. let item = InputBarButtonItem()
  273. item.setSize(.zero, animated: false)
  274. item.spacing = .flexible
  275. return item
  276. }
  277. /// An InputBarButtonItem that's spacing property is set to be .fixed with the width arguement
  278. public static func fixedSpace(_ width: CGFloat) -> InputBarButtonItem {
  279. let item = InputBarButtonItem()
  280. item.setSize(.zero, animated: false)
  281. item.spacing = .fixed(width)
  282. return item
  283. }
  284. }