SAIInputBar.swift 40 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070
  1. //
  2. // SAIbar.swift
  3. // SAIInputBar
  4. //
  5. // Created by SAGESSE on 7/23/16.
  6. // Copyright © 2016-2017 SAGESSE. All rights reserved.
  7. //
  8. import UIKit
  9. public enum SAIInputMode: CustomStringConvertible {
  10. case none
  11. case editing
  12. case audio
  13. case selecting(UIView)
  14. public var isNone: Bool {
  15. switch self {
  16. case .none: return true
  17. default: return false
  18. }
  19. }
  20. public var isEditing: Bool {
  21. switch self {
  22. case .editing: return true
  23. default: return false
  24. }
  25. }
  26. public var isAudio: Bool {
  27. switch self {
  28. case .audio: return true
  29. default: return false
  30. }
  31. }
  32. public var isSelecting: Bool {
  33. switch self {
  34. case .selecting: return true
  35. default: return false
  36. }
  37. }
  38. public var description: String {
  39. switch self {
  40. case .none: return "None"
  41. case .editing: return "Editing"
  42. case .audio: return "Audio"
  43. case .selecting(_): return "Selecting"
  44. }
  45. }
  46. }
  47. @objc public protocol SAIInputBarDelegate: NSObjectProtocol {
  48. // MARK: Text Edit
  49. @objc optional func inputBar(shouldBeginEditing inputBar: SAIInputBar) -> Bool
  50. @objc optional func inputBar(shouldEndEditing inputBar: SAIInputBar) -> Bool
  51. @objc optional func inputBar(didBeginEditing inputBar: SAIInputBar)
  52. @objc optional func inputBar(didEndEditing inputBar: SAIInputBar)
  53. @objc optional func inputBar(shouldReturn inputBar: SAIInputBar) -> Bool
  54. @objc optional func inputBar(shouldClear inputBar: SAIInputBar) -> Bool
  55. @objc optional func inputBar(didChangeSelection inputBar: SAIInputBar)
  56. @objc optional func inputBar(didChangeText inputBar: SAIInputBar)
  57. @objc optional func inputBar(_ inputBar: SAIInputBar, shouldInteractWithTextAttachment textAttachment: NSTextAttachment, inRange characterRange: NSRange) -> Bool
  58. @objc optional func inputBar(_ inputBar: SAIInputBar, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool
  59. @objc optional func inputBar(_ inputBar: SAIInputBar, shouldInteractWithURL URL: URL, inRange characterRange: NSRange) -> Bool
  60. // MARK: Accessory Item Selection
  61. @objc optional func inputBar(_ inputBar: SAIInputBar, shouldHighlightFor item: SAIInputItem) -> Bool
  62. @objc optional func inputBar(_ inputBar: SAIInputBar, shouldDeselectFor item: SAIInputItem) -> Bool
  63. @objc optional func inputBar(_ inputBar: SAIInputBar, shouldSelectFor item: SAIInputItem) -> Bool
  64. @objc optional func inputBar(_ inputBar: SAIInputBar, didHighlightFor item: SAIInputItem)
  65. @objc optional func inputBar(_ inputBar: SAIInputBar, didDeselectFor item: SAIInputItem)
  66. @objc optional func inputBar(_ inputBar: SAIInputBar, didSelectFor item: SAIInputItem)
  67. // MARK: record tap
  68. @objc optional func inputBar(touchDown recordButton: UIButton, inputBar: SAIInputBar)
  69. @objc optional func inputBar(touchUpInside recordButton: UIButton, inputBar: SAIInputBar)
  70. @objc optional func inputBar(touchUpOutside recordButton: UIButton, inputBar: SAIInputBar)
  71. @objc optional func inputBar(dragOutside recordButton: UIButton, inputBar: SAIInputBar)
  72. @objc optional func inputBar(dragInside recordButton: UIButton, inputBar: SAIInputBar)
  73. // MARK: Input Mode
  74. @objc optional func inputBar(willChangeMode inputBar: SAIInputBar)
  75. @objc optional func inputBar(didChangeMode inputBar: SAIInputBar)
  76. // MARK: Keyboard
  77. @objc optional func inputBar(_ inputBar: SAIInputBar, willShowKeyboard keyboard: UIView)
  78. @objc optional func inputBar(_ inputBar: SAIInputBar, didShowKeyboard keyboard: UIView)
  79. @objc optional func inputBar(_ inputBar: SAIInputBar, willHideKeyboard keyboard: UIView)
  80. @objc optional func inputBar(_ inputBar: SAIInputBar, didHideKeyboard keyboard: UIView)
  81. @objc optional func inputBar(_ inputBar: SAIInputBar, sizeForKeyboard keyboard: UIView) -> CGSize
  82. }
  83. // MARK: -
  84. ///
  85. /// multifunction input bar
  86. ///
  87. /// If the delegate follow SAIDisplayable agreement,
  88. // will automatically pop-up keyboard events management
  89. ///
  90. /// Sample:
  91. /// ```swift
  92. /// lazy var toolbar: SAIInputBar = SAIInputBar()
  93. ///
  94. /// override var inputAccessoryView: UIView? {
  95. /// return toolbar
  96. /// }
  97. /// override var canBecomeFirstResponder: Bool {
  98. /// return true
  99. /// }
  100. /// ```
  101. ///
  102. /// 当页面切换后键盘自动隐藏
  103. /// ```swift
  104. /// override func viewDidDisappear(animated: Bool) {
  105. /// super.viewDidDisappear(animated)
  106. /// toolbar.inputMode = .None
  107. /// }
  108. /// ```
  109. ///
  110. /// 切换到自定义输入栏, 并隐藏键盘, 效果参考: 微信的语音输入
  111. /// ```swift
  112. /// inputBar.setBarItem(_customCenterBarItem, atPosition: .Center)
  113. /// inputBar.setInputMode(.None, animated: true)
  114. /// ```
  115. ///
  116. open class SAIInputBar: UIView {
  117. open override func invalidateIntrinsicContentSize() {
  118. super.invalidateIntrinsicContentSize()
  119. _cacheContentSize = nil
  120. }
  121. open override var intrinsicContentSize: CGSize {
  122. if let size = _cacheContentSize, size.width == frame.width {
  123. return size
  124. }
  125. let size = _contentSizeWithoutCache
  126. _cacheContentSize = size
  127. return size
  128. }
  129. @discardableResult
  130. open override func resignFirstResponder() -> Bool {
  131. self.setInputMode(.none, animated: true)
  132. return _inputAccessoryView.resignFirstResponder()
  133. }
  134. @discardableResult
  135. open override func becomeFirstResponder() -> Bool {
  136. return _inputAccessoryView.becomeFirstResponder()
  137. }
  138. open override var next: UIResponder? {
  139. return ib_nextResponderOverride ?? super.next
  140. }
  141. open var textItem: SAIInputItem {
  142. return _inputAccessoryView.textField.item
  143. }
  144. open var inputMode: SAIInputMode {
  145. set { return _updateInputMode(newValue, animated: true) }
  146. get { return _inputMode }
  147. }
  148. open func setInputMode(_ mode: SAIInputMode, animated: Bool) {
  149. _updateInputMode(mode, animated: animated)
  150. }
  151. open var contentSize: CGSize {
  152. return _inputAccessoryView.intrinsicContentSize
  153. }
  154. open var keyboardSize: CGSize {
  155. return _keyboardSizeWithoutCache
  156. }
  157. open var allowsSelection: Bool = true // default is YES
  158. open var allowsMultipleSelection: Bool = false // default is NO
  159. open weak var delegate: SAIInputBarDelegate? {
  160. didSet {
  161. _displayable = delegate as? SAIInputBarDisplayable
  162. }
  163. }
  164. // MARK: - Private Method
  165. fileprivate func _updateInputMode(_ newMode: SAIInputMode, animated: Bool) {
  166. let oldMode = _inputMode
  167. delegate?.inputBar?(willChangeMode: self)
  168. _inputMode = newMode
  169. _inputView.updateInputMode(newMode, oldMode: oldMode, animated: animated)
  170. // NOTE: must be updated `contentSize` before at `resignFirstResponder`
  171. _updateKeyboardKeyboardWithInputMode(newMode, animated: animated)
  172. _inputAccessoryView.updateInputMode(newMode, oldMode: oldMode, animated: animated)
  173. delegate?.inputBar?(didChangeMode: self)
  174. }
  175. fileprivate func _updateInputModeForResponder(_ newMode: SAIInputMode, animated: Bool) {
  176. let oldMode = _inputMode
  177. if newMode.isAudio {
  178. if oldMode.isAudio {
  179. return
  180. }
  181. }
  182. if newMode.isNone {
  183. if !oldMode.isEditing {
  184. return
  185. }
  186. } else {
  187. if oldMode.isEditing {
  188. return
  189. }
  190. }
  191. delegate?.inputBar?(willChangeMode: self)
  192. _inputMode = newMode
  193. _inputView.updateInputMode(newMode, oldMode: oldMode, animated: animated)
  194. // unfortunately not update, because I don't know keyboardSize
  195. //_updateKeyboardKeyboardWithInputMode(newMode, animated: animated)
  196. delegate?.inputBar?(didChangeMode: self)
  197. }
  198. fileprivate func _updateContentSizeIfNeeded(_ animated: Bool) {
  199. let newContentSize = _contentSizeWithoutCache
  200. guard _cacheContentSize != newContentSize else {
  201. let newKeyboardSize = _keyboardSizeWithoutCache
  202. // 只处理同一次的事件
  203. if _cacheKeyboardSize?.width == newKeyboardSize.width &&
  204. _cacheKeyboardSize?.height != newKeyboardSize.height {
  205. let height = newKeyboardSize.height - (_cacheKeyboardSize?.height ?? 0)
  206. // 重置移动事件, 主要针对第三方输入法多次触发willShow的处理
  207. if let ani = _inputAccessoryView.layer.animation(forKey: "position")?.copy() as? CABasicAnimation {
  208. // 系统键盘的大小改变了呢
  209. let layer = _inputAccessoryView.layer
  210. if let fm = layer.presentation()?.frame {
  211. ani.fromValue = NSValue(cgPoint: CGPoint(x: 0, y: fm.minY + height))
  212. }
  213. ani.duration = ani.duration - (ani.beginTime - CACurrentMediaTime())
  214. if ani.duration > 0 {
  215. //_backgroundView.layer.add(ani, forKey: "position")
  216. //_inputView.layer.add(ani, forKey: "position")
  217. //_inputAccessoryView.layer.add(ani, forKey: "position")
  218. _backgroundView.layer.removeAllAnimations()
  219. _inputView.layer.removeAnimation(forKey: "position")
  220. _inputAccessoryView.layer.removeAnimation(forKey: "position")
  221. }
  222. }
  223. }
  224. return // no change
  225. }
  226. if animated {
  227. UIView.beginAnimations("SAIB-ANI-AC", context: nil)
  228. UIView.setAnimationDuration(_SAInputDefaultAnimateDuration)
  229. UIView.setAnimationCurve(_SAInputDefaultAnimateCurve)
  230. }
  231. _inputView.setNeedsLayout()
  232. _inputAccessoryView.setNeedsLayout()
  233. _backgroundView.setNeedsLayout()
  234. //_containerView?.layoutIfNeeded()
  235. superview?.layoutIfNeeded()
  236. if animated {
  237. UIView.commitAnimations()
  238. }
  239. invalidateIntrinsicContentSize()
  240. // 必须立即更新, 否则的话会导致生成多个动画
  241. // 必须在invalidate之后, 否则无效
  242. _cacheContentSize = newContentSize
  243. // 关闭动画的更新, 主要是为了防止contentSize改变之后的动画效果
  244. UIView.performWithoutAnimation {
  245. // _inputView.setNeedsLayout()
  246. // _inputAccessoryView.setNeedsLayout()
  247. _containerView?.setNeedsLayout()
  248. superview?.setNeedsLayout()
  249. superview?.layoutIfNeeded()
  250. //_containerView?.layoutIfNeeded()
  251. }
  252. // 如果没有初始化, 那将他初始
  253. if !_cacheKeyboardIsInitialized {
  254. _cacheKeyboardIsInitialized = true
  255. _displayable?.ib_inputBar(self, initWithFrame: _frameInWindow)
  256. }
  257. }
  258. fileprivate func _updateKeyboardSizeIfNeeded(_ animated: Bool) {
  259. let newVisableSize = _visableKeybaordSize
  260. if _inputAccessoryViewBottom?.constant != -newVisableSize.height {
  261. _inputAccessoryViewBottom?.constant = -newVisableSize.height
  262. }
  263. _updateContentSizeIfNeeded(animated)
  264. _cacheKeyboardOffset = CGPoint.zero
  265. _cacheKeyboardSize = _keyboardSizeWithoutCache
  266. }
  267. fileprivate func _updateKeyboardOffsetIfNeeded(_ newPoint: CGPoint, animated: Bool) {
  268. let ny = _visableKeybaordSize.height - newPoint.y
  269. guard _inputAccessoryViewBottom?.constant != -ny else {
  270. return // no change
  271. }
  272. if animated {
  273. UIView.beginAnimations("SAIB-ANI-AC", context: nil)
  274. UIView.setAnimationDuration(_SAInputDefaultAnimateDuration)
  275. UIView.setAnimationCurve(_SAInputDefaultAnimateCurve)
  276. }
  277. _inputAccessoryViewBottom?.constant = -ny
  278. _containerView?.layoutIfNeeded()
  279. if animated {
  280. UIView.commitAnimations()
  281. }
  282. }
  283. fileprivate func _updateCustomKeyboard(_ newSize: CGSize, animated: Bool) {
  284. _cacheCustomKeyboardSize = newSize
  285. _updateKeyboardSizeIfNeeded(animated)
  286. }
  287. fileprivate func _updateSystemKeyboard(_ newSize: CGSize, animated: Bool) {
  288. _cacheSystemKeyboardSize = newSize
  289. _updateKeyboardSizeIfNeeded(animated)
  290. }
  291. fileprivate func _updateKeyboardKeyboardWithInputMode(_ mode: SAIInputMode, animated: Bool) {
  292. _updateCustomKeyboard(_inputView.intrinsicContentSize, animated: animated)
  293. }
  294. private func _addNotifications() {
  295. let center = NotificationCenter.default
  296. // keyboard
  297. center.addObserver(self, selector:#selector(ntf_keyboard(willShow:)), name:UIResponder.keyboardWillShowNotification, object:nil)
  298. center.addObserver(self, selector:#selector(ntf_keyboard(willHide:)), name:UIResponder.keyboardWillHideNotification, object:nil)
  299. // accessory
  300. center.addObserver(self, selector: #selector(ntf_accessory(didChangeFrame:)), name: NSNotification.Name(rawValue: SAIAccessoryDidChangeFrameNotification), object: nil)
  301. }
  302. private func _removeNotifications() {
  303. let center = NotificationCenter.default
  304. center.removeObserver(self)
  305. }
  306. private func _addComponents(toView view: UIView) {
  307. _inputView.isHidden = false
  308. _inputAccessoryView.isHidden = false
  309. view.addSubview(_backgroundView)
  310. view.addSubview(_inputAccessoryView)
  311. view.addSubview(_inputView)
  312. // add the constraints
  313. _containerView = view
  314. _constraints = [
  315. _SAInputLayoutConstraintMake(_inputAccessoryView, .left, .equal, view, .left),
  316. _SAInputLayoutConstraintMake(_inputAccessoryView, .right, .equal, view, .right),
  317. //_SAInputLayoutConstraintMake(_inputAccessoryView, .Bottom, .Equal, view, .Bottom),
  318. _SAInputLayoutConstraintMake(_inputAccessoryView, .bottom, .equal, view, .bottom, output: &_inputAccessoryViewBottom),
  319. _SAInputLayoutConstraintMake(_inputView, .top, .equal, _inputAccessoryView, .bottom),
  320. _SAInputLayoutConstraintMake(_inputView, .left, .equal, view, .left),
  321. _SAInputLayoutConstraintMake(_inputView, .right, .equal, view, .right),
  322. //_SAInputLayoutConstraintMake(_inputView, .bottom, .equal, view, .bottom),
  323. _SAInputLayoutConstraintMake(_backgroundView, .top, .equal, _inputAccessoryView, .top),
  324. _SAInputLayoutConstraintMake(_backgroundView, .left, .equal, view, .left),
  325. _SAInputLayoutConstraintMake(_backgroundView, .right, .equal, view, .right),
  326. _SAInputLayoutConstraintMake(_backgroundView, .bottom, .equal, view, .bottom),
  327. ]
  328. view.addConstraints(_constraints)
  329. }
  330. private func _removeComponents(formView view: UIView?) {
  331. // remove the constraints
  332. view?.removeConstraints(_constraints)
  333. _constraints = []
  334. _inputView.removeFromSuperview()
  335. _inputAccessoryView.removeFromSuperview()
  336. }
  337. private func _init() {
  338. autoresizingMask = .flexibleHeight
  339. backgroundColor = .clear
  340. // let color = UIColor(colorLiteralRed: 0xec / 0xff, green: 0xed / 0xff, blue: 0xf1 / 0xff, alpha: 1)
  341. //_inputView.backgroundColor = color
  342. //_inputView.clipsToBounds = true
  343. _inputView.backgroundColor = .clear
  344. _inputView.translatesAutoresizingMaskIntoConstraints = false
  345. _inputAccessoryView.delegate = self
  346. _inputAccessoryView.translatesAutoresizingMaskIntoConstraints = false
  347. _inputAccessoryView.setContentHuggingPriority(UILayoutPriority.required, for: .vertical)
  348. _inputAccessoryView.setContentCompressionResistancePriority(UILayoutPriority.required, for: .vertical)
  349. //_inputAccessoryView.backgroundColor = color
  350. _inputAccessoryView.backgroundColor = .clear
  351. _backgroundView.translatesAutoresizingMaskIntoConstraints = false
  352. _backgroundView.setContentHuggingPriority(UILayoutPriority(rawValue: 1), for: .vertical)
  353. _backgroundView.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 1), for: .vertical)
  354. _backgroundView.barTintColor = .white
  355. _backgroundView.isTranslucent = false // 毛玻璃效果还有bug
  356. //_backgroundView.barStyle = .black
  357. _addComponents(toView: self)
  358. _addNotifications()
  359. }
  360. private func _deinit() {
  361. _removeNotifications()
  362. _removeComponents(formView: self)
  363. }
  364. fileprivate var _visableKeybaordSize: CGSize {
  365. if _inputMode.isSelecting {
  366. return _cacheCustomKeyboardSize
  367. }
  368. return CGSize.zero
  369. }
  370. fileprivate var _keyboardSizeWithoutCache: CGSize {
  371. if _inputMode.isSelecting {
  372. return _cacheCustomKeyboardSize
  373. }
  374. return _cacheSystemKeyboardSize
  375. }
  376. fileprivate var _contentSizeWithoutCache: CGSize {
  377. var size = _inputAccessoryView.intrinsicContentSize
  378. // Append the keyboard size
  379. if _inputMode.isSelecting {
  380. size.height += _cacheCustomKeyboardSize.height //_inputView.intrinsicContentSize.height
  381. }
  382. return size
  383. }
  384. fileprivate var _frameInWindow: CGRect {
  385. guard let window = window else {
  386. return CGRect.zero
  387. }
  388. let ivheight = _inputAccessoryView.intrinsicContentSize.height
  389. let height = ivheight + max(_keyboardSizeWithoutCache.height, 0)
  390. return CGRect(x: 0, y: window.frame.height - height, width: window.frame.width, height: height)
  391. }
  392. // MARK: -
  393. fileprivate var _inputMode: SAIInputMode = .none
  394. fileprivate var _inputViewBottom: NSLayoutConstraint?
  395. fileprivate var _inputAccessoryViewBottom: NSLayoutConstraint?
  396. fileprivate lazy var _inputView: SAIInputView = SAIInputView()
  397. fileprivate lazy var _inputAccessoryView: SAIInputAccessoryView = SAIInputAccessoryView()
  398. fileprivate lazy var _backgroundView: SAIInputBackgroundView = SAIInputBackgroundView()
  399. fileprivate lazy var _constraints: [NSLayoutConstraint] = []
  400. fileprivate lazy var _selectedItems: Set<SAIInputItem> = []
  401. fileprivate weak var _containerView: UIView?
  402. fileprivate weak var _displayable: SAIInputBarDisplayable?
  403. fileprivate var _cacheBounds: CGRect?
  404. fileprivate var _cacheContentSize: CGSize?
  405. fileprivate var _cacheKeyboardSize: CGSize?
  406. fileprivate var _cacheKeyboardOffset: CGPoint = .zero
  407. fileprivate var _cacheSystemKeyboardSize: CGSize = .zero
  408. fileprivate var _cacheCustomKeyboardSize: CGSize = .zero
  409. fileprivate var _cacheKeyboardIsInitialized: Bool = false
  410. // MARK: -
  411. // open override class func initialize() {
  412. // _ = _ib_inputBar_once
  413. // }
  414. func initialize() {
  415. _ = _ib_inputBar_once
  416. }
  417. public override init(frame: CGRect) {
  418. super.init(frame: frame)
  419. _init()
  420. }
  421. public required init?(coder aDecoder: NSCoder) {
  422. super.init(coder: aDecoder)
  423. _init()
  424. }
  425. deinit {
  426. _deinit()
  427. }
  428. }
  429. // MARK: - System Keyboard Event
  430. extension SAIInputBar {
  431. @objc func ntf_keyboard(willShow sender: Notification) {
  432. guard let window = window else {
  433. return
  434. }
  435. _ntf_animation(sender) { bf, ef in
  436. let ef1 = window.frame.inset(by: UIEdgeInsets(top: ef.minY, left: 0, bottom: 0, right: 0))
  437. _updateSystemKeyboard(ef1.size, animated: false)
  438. _displayable?.ib_inputBar(self, showWithFrame: _frameInWindow)
  439. }
  440. }
  441. @objc func ntf_keyboard(willHide sender: Notification) {
  442. guard let window = window else {
  443. return
  444. }
  445. _ntf_animation(sender) { bf, ef in
  446. let ef1 = window.frame.inset(by: UIEdgeInsets(top: ef.minY, left: 0, bottom: 0, right: 0))
  447. _cacheSystemKeyboardSize = ef1.size
  448. // _updateSystemKeyboard(ef1.size, animated: false)
  449. _cacheKeyboardSize = _keyboardSizeWithoutCache
  450. _displayable?.ib_inputBar(self, hideWithFrame: _frameInWindow)
  451. }
  452. }
  453. @objc func ntf_keyboard(didScroll sender: UIPanGestureRecognizer) {
  454. // if inputbar state is `None`, ignore this event
  455. if _inputMode.isNone {
  456. return
  457. }
  458. // if recgognizer state is end, process custom event
  459. guard sender.state == .began || sender.state == .changed || sender.state == .possible else {
  460. // clear keyboard offset
  461. _cacheKeyboardOffset = CGPoint.zero
  462. // ignore system keyboard, in system keyboard, the show/dismiss is automatic process
  463. guard _inputMode.isSelecting else {
  464. return
  465. }
  466. // if nheight > height, this means that it at outside of the keyboard, cancel the event
  467. if sender.location(in: _inputAccessoryView).y < 0 {
  468. // cancel touch at outside
  469. _updateKeyboardOffsetIfNeeded(CGPoint.zero, animated: true)
  470. } else if sender.velocity(in: _inputAccessoryView).y <= 0 {
  471. // cancel touch at inside
  472. _updateKeyboardOffsetIfNeeded(CGPoint.zero, animated: true)
  473. _displayable?.ib_inputBar(self, showWithFrame: _frameInWindow)
  474. } else {
  475. // dismiss
  476. _updateInputMode(.none, animated: true)
  477. //_displayable?.ib_inputBar(self, hideWithFrame: _frameInWindow)
  478. }
  479. return
  480. }
  481. guard let window = self.window, sender.numberOfTouches != 0 else {
  482. return
  483. }
  484. // Must use the first touch to calculate the position
  485. let nheight = window.frame.height - sender.location(ofTouch: 0, in: window).y
  486. let kbheight = _keyboardSizeWithoutCache.height
  487. let iavheight = _inputAccessoryView.intrinsicContentSize.height
  488. let height = iavheight + kbheight
  489. let ty = height - min(max(nheight, iavheight), height)
  490. if _cacheKeyboardOffset.y != ty {
  491. // in editing(system keybaord), system automatic process
  492. if _inputMode.isSelecting {
  493. _updateKeyboardOffsetIfNeeded(CGPoint(x: 0, y: ty), animated: false)
  494. }
  495. _displayable?.ib_inputBar(self, didChangeOffset: CGPoint(x: 0, y: ty))
  496. }
  497. _cacheKeyboardOffset.y = ty
  498. }
  499. @objc func ntf_accessory(didChangeFrame sender: Notification) {
  500. let newCustomKeyboardSize = _inputView.intrinsicContentSize
  501. if _cacheCustomKeyboardSize.height != newCustomKeyboardSize.height {
  502. _cacheCustomKeyboardSize = newCustomKeyboardSize
  503. _updateKeyboardSizeIfNeeded(false)
  504. } else {
  505. _updateContentSizeIfNeeded(false)
  506. }
  507. // update in advance, don't wait for willShow event, otherwise there will be a delay
  508. _displayable?.ib_inputBar(self, didChangeFrame: _frameInWindow)
  509. }
  510. private func _ntf_flatMap(_ ntf: Notification, handler: (CGRect, CGRect, TimeInterval, UIView.AnimationCurve) -> ()) {
  511. guard let u = (ntf as NSNotification).userInfo,
  512. let bf = (u[UIResponder.keyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue,
  513. let ef = (u[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue,
  514. let cv = (u[UIResponder.keyboardAnimationCurveUserInfoKey] as? Int),
  515. let dr = (u[UIResponder.keyboardAnimationDurationUserInfoKey] as? TimeInterval) else {
  516. return
  517. }
  518. let edg = UIEdgeInsets(top: intrinsicContentSize.height, left: 0, bottom: 0, right: 0)
  519. // rect correction
  520. let bf1 = bf.inset(by: edg)
  521. let ef1 = ef.inset(by: edg)
  522. let cv1 = UIView.AnimationCurve(rawValue: cv) ?? _SAInputDefaultAnimateCurve
  523. handler(bf1, ef1, dr, cv1)
  524. }
  525. private func _ntf_animation(_ ntf: Notification, handler: (CGRect, CGRect) -> Void) {
  526. _ntf_flatMap(ntf) { bf, ef, dr, cv in
  527. guard dr != 0 else {
  528. handler(bf, ef)
  529. return
  530. }
  531. UIView.beginAnimations("SAIB-ANI-KB", context: nil)
  532. UIView.setAnimationDuration(dr)
  533. UIView.setAnimationCurve(cv)
  534. handler(bf, ef)
  535. UIView.commitAnimations()
  536. }
  537. }
  538. }
  539. // MARK: - UITextView(Forwarding)
  540. extension SAIInputBar: UIKeyInput {
  541. // UITextView
  542. open var text: String! {
  543. set { return _inputAccessoryView.textField.text = newValue }
  544. get { return _inputAccessoryView.textField.text }
  545. }
  546. open var font: UIFont? {
  547. set { return _inputAccessoryView.textField.font = newValue }
  548. get { return _inputAccessoryView.textField.font }
  549. }
  550. open var textColor: UIColor? {
  551. set { return _inputAccessoryView.textField.textColor = newValue }
  552. get { return _inputAccessoryView.textField.textColor }
  553. }
  554. open var attributedText: NSAttributedString! {
  555. set { return _inputAccessoryView.textField.attributedText = newValue }
  556. get { return _inputAccessoryView.textField.attributedText }
  557. }
  558. open var textAlignment: NSTextAlignment {
  559. set { return _inputAccessoryView.textField.textAlignment = newValue }
  560. get { return _inputAccessoryView.textField.textAlignment }
  561. }
  562. open var selectedRange: NSRange {
  563. set { return _inputAccessoryView.textField.selectedRange = newValue }
  564. get { return _inputAccessoryView.textField.selectedRange }
  565. }
  566. open var editable: Bool {
  567. set { return _inputAccessoryView.textField.isEditable = newValue }
  568. get { return _inputAccessoryView.textField.isEditable }
  569. }
  570. open var selectable: Bool {
  571. set { return _inputAccessoryView.textField.isSelectable = newValue }
  572. get { return _inputAccessoryView.textField.isSelectable }
  573. }
  574. // UIKeyInput(Forwarding)
  575. open var hasText: Bool {
  576. return _inputAccessoryView.textField.hasText
  577. }
  578. open func insertText(_ text: String) {
  579. _inputAccessoryView.textField.insertText(text)
  580. _inputAccessoryView.textViewDidChange(_inputAccessoryView.textField)
  581. }
  582. open func insertAttributedText(_ attributedText: NSAttributedString) {
  583. _inputAccessoryView.textField.insertAttributedText(attributedText)
  584. _inputAccessoryView.textViewDidChange(_inputAccessoryView.textField)
  585. }
  586. open func deleteBackward() {
  587. _inputAccessoryView.textField.deleteBackward()
  588. _inputAccessoryView.textViewDidChange(_inputAccessoryView.textField)
  589. }
  590. // UITextInputTraits(Forwarding)
  591. open var autocapitalizationType: UITextAutocapitalizationType {
  592. set { return _inputAccessoryView.textField.autocapitalizationType = newValue }
  593. get { return _inputAccessoryView.textField.autocapitalizationType }
  594. }
  595. open var autocorrectionType: UITextAutocorrectionType {
  596. set { return _inputAccessoryView.textField.autocorrectionType = newValue }
  597. get { return _inputAccessoryView.textField.autocorrectionType }
  598. }
  599. open var spellCheckingType: UITextSpellCheckingType {
  600. set { return _inputAccessoryView.textField.spellCheckingType = newValue }
  601. get { return _inputAccessoryView.textField.spellCheckingType }
  602. }
  603. open var keyboardType: UIKeyboardType {
  604. set { return _inputAccessoryView.textField.keyboardType = newValue }
  605. get { return _inputAccessoryView.textField.keyboardType }
  606. }
  607. open var keyboardAppearance: UIKeyboardAppearance {
  608. set { return _inputAccessoryView.textField.keyboardAppearance = newValue }
  609. get { return _inputAccessoryView.textField.keyboardAppearance }
  610. }
  611. open var returnKeyType: UIReturnKeyType {
  612. set { return _inputAccessoryView.textField.returnKeyType = newValue }
  613. get { return _inputAccessoryView.textField.returnKeyType }
  614. }
  615. open var enablesReturnKeyAutomatically: Bool {
  616. set { return _inputAccessoryView.textField.enablesReturnKeyAutomatically = newValue }
  617. get { return _inputAccessoryView.textField.enablesReturnKeyAutomatically }
  618. }
  619. open var isSecureTextEntry: Bool {
  620. @objc(setSecureTextEntry:)
  621. set { return _inputAccessoryView.textField.isSecureTextEntry = newValue }
  622. @objc(isSecureTextEntry) get { return _inputAccessoryView.textField.isSecureTextEntry }
  623. }
  624. }
  625. // MARK: - UITextViewDelegate(Forwarding)
  626. extension SAIInputBar: UITextViewDelegate {
  627. open func textViewShouldBeginEditing(_ textView: UITextView) -> Bool {
  628. if let r = delegate?.inputBar?(shouldBeginEditing: self), !r {
  629. return false
  630. }
  631. _updateInputModeForResponder(.editing, animated: true)
  632. return true
  633. }
  634. open func textViewShouldEndEditing(_ textView: UITextView) -> Bool {
  635. if let r = delegate?.inputBar?(shouldEndEditing: self), !r {
  636. return false
  637. }
  638. return true
  639. }
  640. open func textViewDidBeginEditing(_ textView: UITextView) {
  641. delegate?.inputBar?(didBeginEditing: self)
  642. }
  643. open func textViewDidEndEditing(_ textView: UITextView) {
  644. delegate?.inputBar?(didEndEditing: self)
  645. _updateInputModeForResponder(.none, animated: true)
  646. }
  647. open func textViewDidChangeSelection(_ textView: UITextView) {
  648. delegate?.inputBar?(didChangeSelection: self)
  649. }
  650. open func textViewDidChange(_ textView: UITextView) {
  651. delegate?.inputBar?(didChangeText: self)
  652. }
  653. open func textView(_ textView: UITextView, shouldInteractWith textAttachment: NSTextAttachment, in characterRange: NSRange) -> Bool {
  654. if let r = delegate?.inputBar?(self, shouldInteractWithTextAttachment: textAttachment, inRange: characterRange), !r {
  655. return false
  656. }
  657. return true
  658. }
  659. open func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
  660. if let r = delegate?.inputBar?(self, shouldChangeCharactersInRange: range, replacementString: text), !r {
  661. return false
  662. }
  663. // This is return
  664. if text == "\n" {
  665. return delegate?.inputBar?(shouldReturn: self) ?? true
  666. }
  667. // This is clear
  668. if text.isEmpty && range.length - range.location == (textView.text as NSString).length {
  669. return delegate?.inputBar?(shouldClear: self) ?? true
  670. }
  671. return true
  672. }
  673. open func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange) -> Bool {
  674. if let r = delegate?.inputBar?(self, shouldInteractWithURL: URL, inRange: characterRange), !r {
  675. return false
  676. }
  677. return true
  678. }
  679. }
  680. // MARK: - SAIInputAccessoryView(Forwarding)
  681. extension SAIInputBar {
  682. open func barItems(atPosition position: SAIInputItemPosition) -> [SAIInputItem] {
  683. return _inputAccessoryView.barItems(atPosition: position)
  684. }
  685. open func setBarItem(_ barItem: SAIInputItem, atPosition position: SAIInputItemPosition, animated: Bool = true) {
  686. return _inputAccessoryView.setBarItems([barItem], atPosition: position, animated: animated)
  687. }
  688. open func setBarItems(_ barItems: [SAIInputItem], atPosition position: SAIInputItemPosition, animated: Bool = true) {
  689. return _inputAccessoryView.setBarItems(barItems, atPosition: position, animated: animated)
  690. }
  691. open func canSelectBarItem(_ barItem: SAIInputItem) -> Bool {
  692. return _inputAccessoryView.canSelectBarItem(barItem)
  693. }
  694. open func canDeselectBarItem(_ barItem: SAIInputItem) -> Bool {
  695. return _inputAccessoryView.canDeselectBarItem(barItem)
  696. }
  697. open func selectBarItem(_ barItem: SAIInputItem, animated: Bool) {
  698. _selectedItems.insert(barItem)
  699. return _inputAccessoryView.selectBarItem(barItem, animated: animated)
  700. }
  701. open func deselectBarItem(_ barItem: SAIInputItem, animated: Bool) {
  702. _selectedItems.remove(barItem)
  703. _inputAccessoryView.isShowRecordButton = false
  704. return _inputAccessoryView.deselectBarItem(barItem, animated: animated)
  705. }
  706. open func deselectBarAllItem() {
  707. _selectedItems.forEach{
  708. self.deselectBarItem($0, animated: true)
  709. self.barItem(didDeselectFor: $0)
  710. }
  711. _selectedItems = []
  712. _inputAccessoryView.isShowRecordButton = true
  713. self.setInputMode(.audio, animated: true)
  714. }
  715. open func editMode() {
  716. _selectedItems.forEach{
  717. self.deselectBarItem($0, animated: true)
  718. self.barItem(didDeselectFor: $0)
  719. }
  720. _selectedItems = []
  721. _inputAccessoryView.isShowRecordButton = false
  722. self.setInputMode(.editing, animated: true)
  723. // self.becomeFirstResponder()
  724. }
  725. }
  726. // MARK: - SAIInputItemViewDelegate(Forwarding)
  727. extension SAIInputBar: SAIInputItemViewDelegate {
  728. open func barItem(shouldHighlightFor barItem: SAIInputItem) -> Bool {
  729. return delegate?.inputBar?(self, shouldHighlightFor: barItem) ?? true
  730. }
  731. open func barItem(shouldDeselectFor barItem: SAIInputItem) -> Bool {
  732. // if !allowsMultipleSelection {
  733. // return false // not allowed to cancel
  734. // }
  735. return delegate?.inputBar?(self, shouldDeselectFor: barItem) ?? true
  736. }
  737. open func barItem(shouldSelectFor barItem: SAIInputItem) -> Bool {
  738. guard allowsSelection else {
  739. // do not allow the selected
  740. return false
  741. }
  742. if _selectedItems.contains(barItem) {
  743. // has been selected
  744. return false
  745. }
  746. guard delegate?.inputBar?(self, shouldSelectFor: barItem) ?? true else {
  747. // users are not allowed to select
  748. return false
  749. }
  750. if !allowsMultipleSelection {
  751. // don't allow a multiple-select, cancel has been chosen
  752. for item in _selectedItems {
  753. if !(self.delegate?.inputBar?(self, shouldDeselectFor: item) ?? true) {
  754. // Not allowed to cancel, so do not allow the selected
  755. return false
  756. }
  757. }
  758. //
  759. _selectedItems.forEach{
  760. self.deselectBarItem($0, animated: true)
  761. // self.barItem(didDeselectFor: $0)
  762. _selectedItems.remove(barItem)
  763. }
  764. _selectedItems = []
  765. }
  766. return true
  767. }
  768. open func barItem(didHighlightFor barItem: SAIInputItem) {
  769. delegate?.inputBar?(self, didHighlightFor: barItem)
  770. }
  771. open func barItem(didDeselectFor barItem: SAIInputItem) {
  772. // delegate?.inputBar?(self, didDeselectFor: barItem)
  773. // Remove from the selected list
  774. _selectedItems.remove(barItem)
  775. // self.editMode()
  776. _inputAccessoryView.isShowRecordButton = false
  777. let _ = self.becomeFirstResponder()
  778. }
  779. open func barItem(didSelectFor barItem: SAIInputItem) {
  780. delegate?.inputBar?(self, didSelectFor: barItem)
  781. // Added to the selected list
  782. _selectedItems.insert(barItem)
  783. }
  784. }
  785. extension SAIInputBar: SAIInputAccessoryViewDelegate {
  786. open func inputAccessoryView(touchDown recordButton: UIButton) {
  787. delegate?.inputBar?(touchDown: recordButton, inputBar: self)
  788. }
  789. open func inputAccessoryView(dragInside recordButton: UIButton) {
  790. delegate?.inputBar?(dragInside: recordButton, inputBar: self)
  791. }
  792. open func inputAccessoryView(dragOutside recordButton: UIButton) {
  793. delegate?.inputBar?(dragOutside: recordButton, inputBar: self)
  794. }
  795. open func inputAccessoryView(touchUpInside recordButton: UIButton) {
  796. delegate?.inputBar?(touchUpInside: recordButton, inputBar: self)
  797. }
  798. open func inputAccessoryView(touchUpOutside recordButton: UIButton) {
  799. delegate?.inputBar?(touchUpOutside: recordButton, inputBar: self)
  800. }
  801. }
  802. // MARK: -
  803. private extension UIResponder {
  804. @objc func ib_overrideInputAccessoryViewNextResponderWithResponder(_ arg1: UIResponder?) {
  805. ib_nextResponderOverride = arg1
  806. return ib_overrideInputAccessoryViewNextResponderWithResponder(arg1)
  807. }
  808. var ib_nextResponderOverride: UIResponder? {
  809. set { return objc_setAssociatedObject(self, &_SAInputUIResponderNextResponderOverride, newValue, .OBJC_ASSOCIATION_ASSIGN) }
  810. get { return objc_getAssociatedObject(self, &_SAInputUIResponderNextResponderOverride) as? UIResponder }
  811. }
  812. }
  813. // MARK: -
  814. private extension UIScrollView {
  815. // gesture recognizer handler
  816. @objc private func ib_handlePan(_ sender: UIPanGestureRecognizer) {
  817. ib_handlePan(sender)
  818. guard let inputBar = inputAccessoryView as? SAIInputBar else {
  819. return
  820. }
  821. if keyboardDismissMode == .onDrag {
  822. // is `OnDrag`
  823. guard inputBar.inputMode.isSelecting else {
  824. return
  825. }
  826. inputBar.setInputMode(.none, animated: true)
  827. } else if keyboardDismissMode == .interactive {
  828. // is `Interactive`
  829. inputBar.ntf_keyboard(didScroll: sender)
  830. }
  831. }
  832. }
  833. // MARK: -
  834. private extension UIPresentationController {
  835. @objc func _preserveResponderAcrossWindows() -> Bool {
  836. // repair the iOS 8.1 bugs, if return true
  837. // system will invoke `_preserveInputViewsWithId:animated:reset:`
  838. // to save the input environment
  839. return true
  840. }
  841. }
  842. // MARK: -
  843. internal func SAIInputBarLoad() {
  844. // 解释一下为什么采用这个方法, 因为swift没有不能重写load方法,
  845. // 如果写在initialize可能会被其他库覆盖掉
  846. // 采用这个方法安全一点
  847. _SAInputExchangeSelector(UIScrollView.self, "handlePan:", "ib_handlePan:")
  848. // 计划中止, 复杂度略高
  849. //_SAInputExchangeSelector(NSClassFromString("UIInputSetContainerView"), "snapshotViewAfterScreenUpdates:", "ib_snapshotViewAfterScreenUpdates:")
  850. // 解决iOS8中的bug
  851. _SAInputExchangeSelector(UIResponder.self, "_overrideInputAccessoryViewNextResponderWithResponder:", "ib_overrideInputAccessoryViewNextResponderWithResponder:")
  852. }
  853. @inline(__always)
  854. internal func _SAInputLayoutConstraintMake(_ item: AnyObject, _ attr1: NSLayoutConstraint.Attribute, _ related: NSLayoutConstraint.Relation, _ toItem: AnyObject? = nil, _ attr2: NSLayoutConstraint.Attribute = .notAnAttribute, _ constant: CGFloat = 0, _ multiplier: CGFloat = 1, output: UnsafeMutablePointer<NSLayoutConstraint?>? = nil) -> NSLayoutConstraint {
  855. let c = NSLayoutConstraint(item:item, attribute:attr1, relatedBy:related, toItem:toItem, attribute:attr2, multiplier:multiplier, constant:constant)
  856. if output != nil {
  857. output?.pointee = c
  858. }
  859. return c
  860. }
  861. @inline(__always)
  862. internal func _SAInputExchangeSelector(_ cls: AnyClass?, _ sel1: String, _ sel2: String) {
  863. _SAInputExchangeSelector(cls, Selector(sel1), Selector(sel2))
  864. }
  865. @inline(__always)
  866. internal func _SAInputExchangeSelector(_ cls: AnyClass?, _ sel1: Selector, _ sel2: Selector) {
  867. guard let cls = cls else {
  868. return
  869. }
  870. method_exchangeImplementations(class_getInstanceMethod(cls, sel1)!, class_getInstanceMethod(cls, sel2)!)
  871. }
  872. private var _ib_inputBar_once: Bool = {
  873. SAIInputBarLoad()
  874. SAIInputBarDisplayableLoad()
  875. return true
  876. }()
  877. private var SAIInputBarWillSnapshot = "SAIInputBarWillSnapshot"
  878. private var SAIInputBarDidSnapshot = "SAIInputBarDidSnapshot"
  879. private var _SAInputUIResponderNextResponderOverride = "_SAInputUIResponderNextResponderOverride"
  880. internal var _SAInputDefaultAnimateDuration: TimeInterval = 0.25
  881. internal var _SAInputDefaultAnimateCurve: UIView.AnimationCurve = UIView.AnimationCurve(rawValue: 7) ?? .easeInOut
  882. internal var _SAInputDefaultTextFieldBackgroundImage: UIImage? = {
  883. // 生成默认图片
  884. let radius = CGFloat(8)
  885. let rect = CGRect(x: 0, y: 0, width: 32, height: 32)
  886. let path = UIBezierPath(roundedRect: rect, cornerRadius: radius)
  887. UIGraphicsBeginImageContextWithOptions(rect.size, false, UIScreen.main.scale)
  888. UIColor.white.setFill()
  889. path.fill()
  890. path.addClip()
  891. let image = UIGraphicsGetImageFromCurrentImageContext()
  892. UIGraphicsEndImageContext()
  893. return image?.resizableImage(withCapInsets: UIEdgeInsets(top: radius, left: radius, bottom: radius, right: radius))
  894. }()