InputBarItem.swift 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  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 InputItem that inherits from UIButton
  23. ## Important Notes ##
  24. 1. Intended to be used in an `InputStackView`
  25. */
  26. open class InputBarButtonItem: UIButton {
  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. didSet {
  94. if isHighlighted {
  95. onSelectedAction?(self)
  96. } else {
  97. onDeselectedAction?(self)
  98. }
  99. }
  100. }
  101. /// Calls the onEnabledAction or onDisabledAction when set
  102. open override var isEnabled: Bool {
  103. didSet {
  104. if isEnabled {
  105. onEnabledAction?(self)
  106. } else {
  107. onDisabledAction?(self)
  108. }
  109. }
  110. }
  111. // MARK: - Reactive Hooks
  112. private var onTouchUpInsideAction: InputBarButtonItemAction?
  113. private var onKeyboardEditingBeginsAction: InputBarButtonItemAction?
  114. private var onKeyboardEditingEndsAction: InputBarButtonItemAction?
  115. private var onTextViewDidChangeAction: ((InputBarButtonItem, InputTextView) -> Void)?
  116. private var onSelectedAction: InputBarButtonItemAction?
  117. private var onDeselectedAction: InputBarButtonItemAction?
  118. private var onEnabledAction: InputBarButtonItemAction?
  119. private var onDisabledAction: InputBarButtonItemAction?
  120. // MARK: - Initialization
  121. public convenience init() {
  122. self.init(frame: .zero)
  123. }
  124. public override init(frame: CGRect) {
  125. super.init(frame: frame)
  126. setup()
  127. }
  128. required public init?(coder aDecoder: NSCoder) {
  129. super.init(coder: aDecoder)
  130. setup()
  131. }
  132. // MARK: - Setup
  133. /// Sets up the default properties
  134. open func setup() {
  135. contentVerticalAlignment = .center
  136. contentHorizontalAlignment = .center
  137. imageView?.contentMode = .scaleAspectFit
  138. setContentHuggingPriority(UILayoutPriority(rawValue: 500), for: .horizontal)
  139. setContentHuggingPriority(UILayoutPriority(rawValue: 500), for: .vertical)
  140. setTitleColor(UIColor(red: 0, green: 122/255, blue: 1, alpha: 1), for: .normal)
  141. setTitleColor(UIColor(red: 0, green: 122/255, blue: 1, alpha: 0.3), for: .highlighted)
  142. setTitleColor(.lightGray, for: .disabled)
  143. adjustsImageWhenHighlighted = false
  144. addTarget(self, action: #selector(InputBarButtonItem.touchUpInsideAction), for: .touchUpInside)
  145. }
  146. // MARK: - Size Adjustment
  147. /// Sets the size of the InputBarButtonItem which overrides the intrinsicContentSize. When set to nil
  148. /// the default intrinsicContentSize is used. The new size will be laid out in the UIStackView that
  149. /// the InputBarButtonItem is held in
  150. ///
  151. /// - Parameters:
  152. /// - newValue: The new size
  153. /// - animated: If the layout should be animated
  154. open func setSize(_ newValue: CGSize?, animated: Bool) {
  155. size = newValue
  156. if animated, let position = parentStackViewPosition {
  157. messageInputBar?.performLayout(animated) { [weak self] in
  158. self?.messageInputBar?.layoutStackViews([position])
  159. }
  160. }
  161. }
  162. // MARK: - Hook Setup Methods
  163. /// Used to setup your own initial properties
  164. ///
  165. /// - Parameter item: A reference to Self
  166. /// - Returns: Self
  167. @discardableResult
  168. open func configure(_ item: InputBarButtonItemAction) -> Self {
  169. item(self)
  170. return self
  171. }
  172. /// Sets the onKeyboardEditingBeginsAction
  173. ///
  174. /// - Parameter action: The new onKeyboardEditingBeginsAction
  175. /// - Returns: Self
  176. @discardableResult
  177. open func onKeyboardEditingBegins(_ action: @escaping InputBarButtonItemAction) -> Self {
  178. onKeyboardEditingBeginsAction = action
  179. return self
  180. }
  181. /// Sets the onKeyboardEditingEndsAction
  182. ///
  183. /// - Parameter action: The new onKeyboardEditingEndsAction
  184. /// - Returns: Self
  185. @discardableResult
  186. open func onKeyboardEditingEnds(_ action: @escaping InputBarButtonItemAction) -> Self {
  187. onKeyboardEditingEndsAction = action
  188. return self
  189. }
  190. /// Sets the onTextViewDidChangeAction
  191. ///
  192. /// - Parameter action: The new onTextViewDidChangeAction
  193. /// - Returns: Self
  194. @discardableResult
  195. open func onTextViewDidChange(_ action: @escaping (_ item: InputBarButtonItem, _ textView: InputTextView) -> Void) -> Self {
  196. onTextViewDidChangeAction = action
  197. return self
  198. }
  199. /// Sets the onTouchUpInsideAction
  200. ///
  201. /// - Parameter action: The new onTouchUpInsideAction
  202. /// - Returns: Self
  203. @discardableResult
  204. open func onTouchUpInside(_ action: @escaping InputBarButtonItemAction) -> Self {
  205. onTouchUpInsideAction = action
  206. return self
  207. }
  208. /// Sets the onSelectedAction
  209. ///
  210. /// - Parameter action: The new onSelectedAction
  211. /// - Returns: Self
  212. @discardableResult
  213. open func onSelected(_ action: @escaping InputBarButtonItemAction) -> Self {
  214. onSelectedAction = action
  215. return self
  216. }
  217. /// Sets the onDeselectedAction
  218. ///
  219. /// - Parameter action: The new onDeselectedAction
  220. /// - Returns: Self
  221. @discardableResult
  222. open func onDeselected(_ action: @escaping InputBarButtonItemAction) -> Self {
  223. onDeselectedAction = action
  224. return self
  225. }
  226. /// Sets the onEnabledAction
  227. ///
  228. /// - Parameter action: The new onEnabledAction
  229. /// - Returns: Self
  230. @discardableResult
  231. open func onEnabled(_ action: @escaping InputBarButtonItemAction) -> Self {
  232. onEnabledAction = action
  233. return self
  234. }
  235. /// Sets the onDisabledAction
  236. ///
  237. /// - Parameter action: The new onDisabledAction
  238. /// - Returns: Self
  239. @discardableResult
  240. open func onDisabled(_ action: @escaping InputBarButtonItemAction) -> Self {
  241. onDisabledAction = action
  242. return self
  243. }
  244. // MARK: - InputItem Protocol
  245. /// Executes the onTextViewDidChangeAction with the given textView
  246. ///
  247. /// - Parameter textView: A reference to the InputTextView
  248. open func textViewDidChangeAction(with textView: InputTextView) {
  249. onTextViewDidChangeAction?(self, textView)
  250. }
  251. /// Executes the onKeyboardEditingEndsAction
  252. open func keyboardEditingEndsAction() {
  253. onKeyboardEditingEndsAction?(self)
  254. }
  255. /// Executes the onKeyboardEditingBeginsAction
  256. open func keyboardEditingBeginsAction() {
  257. onKeyboardEditingBeginsAction?(self)
  258. }
  259. /// Executes the onTouchUpInsideAction
  260. @objc
  261. open func touchUpInsideAction() {
  262. onTouchUpInsideAction?(self)
  263. }
  264. // MARK: - Static Spacers
  265. /// An InputBarButtonItem that's spacing property is set to be .flexible
  266. public static var flexibleSpace: InputBarButtonItem {
  267. let item = InputBarButtonItem()
  268. item.setSize(.zero, animated: false)
  269. item.spacing = .flexible
  270. return item
  271. }
  272. /// An InputBarButtonItem that's spacing property is set to be .fixed with the width arguement
  273. open class func fixedSpace(_ width: CGFloat) -> InputBarButtonItem {
  274. let item = InputBarButtonItem()
  275. item.setSize(.zero, animated: false)
  276. item.spacing = .fixed(width)
  277. return item
  278. }
  279. }