| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070 |
- //
- // SAIbar.swift
- // SAIInputBar
- //
- // Created by SAGESSE on 7/23/16.
- // Copyright © 2016-2017 SAGESSE. All rights reserved.
- //
- import UIKit
- public enum SAIInputMode: CustomStringConvertible {
-
- case none
- case editing
- case audio
- case selecting(UIView)
-
- public var isNone: Bool {
- switch self {
- case .none: return true
- default: return false
- }
- }
- public var isEditing: Bool {
- switch self {
- case .editing: return true
- default: return false
- }
- }
- public var isAudio: Bool {
- switch self {
- case .audio: return true
- default: return false
- }
- }
- public var isSelecting: Bool {
- switch self {
- case .selecting: return true
- default: return false
- }
- }
- public var description: String {
- switch self {
- case .none: return "None"
- case .editing: return "Editing"
- case .audio: return "Audio"
- case .selecting(_): return "Selecting"
- }
- }
- }
- @objc public protocol SAIInputBarDelegate: NSObjectProtocol {
-
- // MARK: Text Edit
-
- @objc optional func inputBar(shouldBeginEditing inputBar: SAIInputBar) -> Bool
- @objc optional func inputBar(shouldEndEditing inputBar: SAIInputBar) -> Bool
-
- @objc optional func inputBar(didBeginEditing inputBar: SAIInputBar)
- @objc optional func inputBar(didEndEditing inputBar: SAIInputBar)
-
- @objc optional func inputBar(shouldReturn inputBar: SAIInputBar) -> Bool
- @objc optional func inputBar(shouldClear inputBar: SAIInputBar) -> Bool
-
- @objc optional func inputBar(didChangeSelection inputBar: SAIInputBar)
- @objc optional func inputBar(didChangeText inputBar: SAIInputBar)
-
- @objc optional func inputBar(_ inputBar: SAIInputBar, shouldInteractWithTextAttachment textAttachment: NSTextAttachment, inRange characterRange: NSRange) -> Bool
- @objc optional func inputBar(_ inputBar: SAIInputBar, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool
- @objc optional func inputBar(_ inputBar: SAIInputBar, shouldInteractWithURL URL: URL, inRange characterRange: NSRange) -> Bool
-
- // MARK: Accessory Item Selection
-
- @objc optional func inputBar(_ inputBar: SAIInputBar, shouldHighlightFor item: SAIInputItem) -> Bool
- @objc optional func inputBar(_ inputBar: SAIInputBar, shouldDeselectFor item: SAIInputItem) -> Bool
- @objc optional func inputBar(_ inputBar: SAIInputBar, shouldSelectFor item: SAIInputItem) -> Bool
-
- @objc optional func inputBar(_ inputBar: SAIInputBar, didHighlightFor item: SAIInputItem)
- @objc optional func inputBar(_ inputBar: SAIInputBar, didDeselectFor item: SAIInputItem)
- @objc optional func inputBar(_ inputBar: SAIInputBar, didSelectFor item: SAIInputItem)
-
- // MARK: record tap
-
- @objc optional func inputBar(touchDown recordButton: UIButton, inputBar: SAIInputBar)
- @objc optional func inputBar(touchUpInside recordButton: UIButton, inputBar: SAIInputBar)
- @objc optional func inputBar(touchUpOutside recordButton: UIButton, inputBar: SAIInputBar)
- @objc optional func inputBar(dragOutside recordButton: UIButton, inputBar: SAIInputBar)
- @objc optional func inputBar(dragInside recordButton: UIButton, inputBar: SAIInputBar)
-
- // MARK: Input Mode
-
- @objc optional func inputBar(willChangeMode inputBar: SAIInputBar)
- @objc optional func inputBar(didChangeMode inputBar: SAIInputBar)
-
-
- // MARK: Keyboard
-
- @objc optional func inputBar(_ inputBar: SAIInputBar, willShowKeyboard keyboard: UIView)
- @objc optional func inputBar(_ inputBar: SAIInputBar, didShowKeyboard keyboard: UIView)
-
- @objc optional func inputBar(_ inputBar: SAIInputBar, willHideKeyboard keyboard: UIView)
- @objc optional func inputBar(_ inputBar: SAIInputBar, didHideKeyboard keyboard: UIView)
-
- @objc optional func inputBar(_ inputBar: SAIInputBar, sizeForKeyboard keyboard: UIView) -> CGSize
- }
- // MARK: -
- ///
- /// multifunction input bar
- ///
- /// If the delegate follow SAIDisplayable agreement,
- // will automatically pop-up keyboard events management
- ///
- /// Sample:
- /// ```swift
- /// lazy var toolbar: SAIInputBar = SAIInputBar()
- ///
- /// override var inputAccessoryView: UIView? {
- /// return toolbar
- /// }
- /// override var canBecomeFirstResponder: Bool {
- /// return true
- /// }
- /// ```
- ///
- /// 当页面切换后键盘自动隐藏
- /// ```swift
- /// override func viewDidDisappear(animated: Bool) {
- /// super.viewDidDisappear(animated)
- /// toolbar.inputMode = .None
- /// }
- /// ```
- ///
- /// 切换到自定义输入栏, 并隐藏键盘, 效果参考: 微信的语音输入
- /// ```swift
- /// inputBar.setBarItem(_customCenterBarItem, atPosition: .Center)
- /// inputBar.setInputMode(.None, animated: true)
- /// ```
- ///
- open class SAIInputBar: UIView {
-
- open override func invalidateIntrinsicContentSize() {
- super.invalidateIntrinsicContentSize()
- _cacheContentSize = nil
- }
- open override var intrinsicContentSize: CGSize {
- if let size = _cacheContentSize, size.width == frame.width {
- return size
- }
- let size = _contentSizeWithoutCache
- _cacheContentSize = size
- return size
- }
- @discardableResult
- open override func resignFirstResponder() -> Bool {
- self.setInputMode(.none, animated: true)
- return _inputAccessoryView.resignFirstResponder()
- }
- @discardableResult
- open override func becomeFirstResponder() -> Bool {
- return _inputAccessoryView.becomeFirstResponder()
- }
- open override var next: UIResponder? {
- return ib_nextResponderOverride ?? super.next
- }
-
- open var textItem: SAIInputItem {
- return _inputAccessoryView.textField.item
- }
-
- open var inputMode: SAIInputMode {
- set { return _updateInputMode(newValue, animated: true) }
- get { return _inputMode }
- }
- open func setInputMode(_ mode: SAIInputMode, animated: Bool) {
- _updateInputMode(mode, animated: animated)
- }
-
- open var contentSize: CGSize {
- return _inputAccessoryView.intrinsicContentSize
- }
- open var keyboardSize: CGSize {
- return _keyboardSizeWithoutCache
- }
-
- open var allowsSelection: Bool = true // default is YES
- open var allowsMultipleSelection: Bool = false // default is NO
-
- open weak var delegate: SAIInputBarDelegate? {
- didSet {
- _displayable = delegate as? SAIInputBarDisplayable
- }
- }
-
- // MARK: - Private Method
-
- fileprivate func _updateInputMode(_ newMode: SAIInputMode, animated: Bool) {
- let oldMode = _inputMode
-
- delegate?.inputBar?(willChangeMode: self)
-
- _inputMode = newMode
- _inputView.updateInputMode(newMode, oldMode: oldMode, animated: animated)
- // NOTE: must be updated `contentSize` before at `resignFirstResponder`
- _updateKeyboardKeyboardWithInputMode(newMode, animated: animated)
- _inputAccessoryView.updateInputMode(newMode, oldMode: oldMode, animated: animated)
-
- delegate?.inputBar?(didChangeMode: self)
- }
- fileprivate func _updateInputModeForResponder(_ newMode: SAIInputMode, animated: Bool) {
- let oldMode = _inputMode
- if newMode.isAudio {
- if oldMode.isAudio {
- return
- }
- }
- if newMode.isNone {
- if !oldMode.isEditing {
- return
- }
- } else {
- if oldMode.isEditing {
- return
- }
- }
-
- delegate?.inputBar?(willChangeMode: self)
-
- _inputMode = newMode
- _inputView.updateInputMode(newMode, oldMode: oldMode, animated: animated)
- // unfortunately not update, because I don't know keyboardSize
- //_updateKeyboardKeyboardWithInputMode(newMode, animated: animated)
-
- delegate?.inputBar?(didChangeMode: self)
- }
-
- fileprivate func _updateContentSizeIfNeeded(_ animated: Bool) {
- let newContentSize = _contentSizeWithoutCache
- guard _cacheContentSize != newContentSize else {
- let newKeyboardSize = _keyboardSizeWithoutCache
- // 只处理同一次的事件
- if _cacheKeyboardSize?.width == newKeyboardSize.width &&
- _cacheKeyboardSize?.height != newKeyboardSize.height {
- let height = newKeyboardSize.height - (_cacheKeyboardSize?.height ?? 0)
- // 重置移动事件, 主要针对第三方输入法多次触发willShow的处理
- if let ani = _inputAccessoryView.layer.animation(forKey: "position")?.copy() as? CABasicAnimation {
- // 系统键盘的大小改变了呢
- let layer = _inputAccessoryView.layer
- if let fm = layer.presentation()?.frame {
- ani.fromValue = NSValue(cgPoint: CGPoint(x: 0, y: fm.minY + height))
- }
- ani.duration = ani.duration - (ani.beginTime - CACurrentMediaTime())
- if ani.duration > 0 {
-
- //_backgroundView.layer.add(ani, forKey: "position")
- //_inputView.layer.add(ani, forKey: "position")
- //_inputAccessoryView.layer.add(ani, forKey: "position")
- _backgroundView.layer.removeAllAnimations()
- _inputView.layer.removeAnimation(forKey: "position")
- _inputAccessoryView.layer.removeAnimation(forKey: "position")
- }
- }
- }
- return // no change
- }
-
- if animated {
- UIView.beginAnimations("SAIB-ANI-AC", context: nil)
- UIView.setAnimationDuration(_SAInputDefaultAnimateDuration)
- UIView.setAnimationCurve(_SAInputDefaultAnimateCurve)
- }
-
- _inputView.setNeedsLayout()
- _inputAccessoryView.setNeedsLayout()
- _backgroundView.setNeedsLayout()
- //_containerView?.layoutIfNeeded()
- superview?.layoutIfNeeded()
-
- if animated {
- UIView.commitAnimations()
- }
-
- invalidateIntrinsicContentSize()
- // 必须立即更新, 否则的话会导致生成多个动画
- // 必须在invalidate之后, 否则无效
- _cacheContentSize = newContentSize
-
- // 关闭动画的更新, 主要是为了防止contentSize改变之后的动画效果
- UIView.performWithoutAnimation {
- // _inputView.setNeedsLayout()
- // _inputAccessoryView.setNeedsLayout()
- _containerView?.setNeedsLayout()
-
- superview?.setNeedsLayout()
- superview?.layoutIfNeeded()
- //_containerView?.layoutIfNeeded()
- }
-
- // 如果没有初始化, 那将他初始
- if !_cacheKeyboardIsInitialized {
- _cacheKeyboardIsInitialized = true
- _displayable?.ib_inputBar(self, initWithFrame: _frameInWindow)
- }
- }
-
- fileprivate func _updateKeyboardSizeIfNeeded(_ animated: Bool) {
- let newVisableSize = _visableKeybaordSize
- if _inputAccessoryViewBottom?.constant != -newVisableSize.height {
- _inputAccessoryViewBottom?.constant = -newVisableSize.height
- }
- _updateContentSizeIfNeeded(animated)
- _cacheKeyboardOffset = CGPoint.zero
- _cacheKeyboardSize = _keyboardSizeWithoutCache
- }
- fileprivate func _updateKeyboardOffsetIfNeeded(_ newPoint: CGPoint, animated: Bool) {
- let ny = _visableKeybaordSize.height - newPoint.y
- guard _inputAccessoryViewBottom?.constant != -ny else {
- return // no change
- }
-
- if animated {
- UIView.beginAnimations("SAIB-ANI-AC", context: nil)
- UIView.setAnimationDuration(_SAInputDefaultAnimateDuration)
- UIView.setAnimationCurve(_SAInputDefaultAnimateCurve)
- }
-
- _inputAccessoryViewBottom?.constant = -ny
- _containerView?.layoutIfNeeded()
-
- if animated {
- UIView.commitAnimations()
- }
- }
-
- fileprivate func _updateCustomKeyboard(_ newSize: CGSize, animated: Bool) {
-
- _cacheCustomKeyboardSize = newSize
- _updateKeyboardSizeIfNeeded(animated)
- }
- fileprivate func _updateSystemKeyboard(_ newSize: CGSize, animated: Bool) {
-
- _cacheSystemKeyboardSize = newSize
- _updateKeyboardSizeIfNeeded(animated)
- }
- fileprivate func _updateKeyboardKeyboardWithInputMode(_ mode: SAIInputMode, animated: Bool) {
-
- _updateCustomKeyboard(_inputView.intrinsicContentSize, animated: animated)
- }
-
- private func _addNotifications() {
-
- let center = NotificationCenter.default
-
- // keyboard
- center.addObserver(self, selector:#selector(ntf_keyboard(willShow:)), name:UIResponder.keyboardWillShowNotification, object:nil)
- center.addObserver(self, selector:#selector(ntf_keyboard(willHide:)), name:UIResponder.keyboardWillHideNotification, object:nil)
-
- // accessory
- center.addObserver(self, selector: #selector(ntf_accessory(didChangeFrame:)), name: NSNotification.Name(rawValue: SAIAccessoryDidChangeFrameNotification), object: nil)
- }
- private func _removeNotifications() {
-
- let center = NotificationCenter.default
- center.removeObserver(self)
- }
-
- private func _addComponents(toView view: UIView) {
-
- _inputView.isHidden = false
- _inputAccessoryView.isHidden = false
-
- view.addSubview(_backgroundView)
- view.addSubview(_inputAccessoryView)
- view.addSubview(_inputView)
-
- // add the constraints
- _containerView = view
- _constraints = [
-
- _SAInputLayoutConstraintMake(_inputAccessoryView, .left, .equal, view, .left),
- _SAInputLayoutConstraintMake(_inputAccessoryView, .right, .equal, view, .right),
- //_SAInputLayoutConstraintMake(_inputAccessoryView, .Bottom, .Equal, view, .Bottom),
- _SAInputLayoutConstraintMake(_inputAccessoryView, .bottom, .equal, view, .bottom, output: &_inputAccessoryViewBottom),
-
- _SAInputLayoutConstraintMake(_inputView, .top, .equal, _inputAccessoryView, .bottom),
- _SAInputLayoutConstraintMake(_inputView, .left, .equal, view, .left),
- _SAInputLayoutConstraintMake(_inputView, .right, .equal, view, .right),
- //_SAInputLayoutConstraintMake(_inputView, .bottom, .equal, view, .bottom),
-
- _SAInputLayoutConstraintMake(_backgroundView, .top, .equal, _inputAccessoryView, .top),
- _SAInputLayoutConstraintMake(_backgroundView, .left, .equal, view, .left),
- _SAInputLayoutConstraintMake(_backgroundView, .right, .equal, view, .right),
- _SAInputLayoutConstraintMake(_backgroundView, .bottom, .equal, view, .bottom),
- ]
- view.addConstraints(_constraints)
- }
- private func _removeComponents(formView view: UIView?) {
-
- // remove the constraints
- view?.removeConstraints(_constraints)
- _constraints = []
-
- _inputView.removeFromSuperview()
- _inputAccessoryView.removeFromSuperview()
- }
-
- private func _init() {
-
- autoresizingMask = .flexibleHeight
- backgroundColor = .clear
-
- // let color = UIColor(colorLiteralRed: 0xec / 0xff, green: 0xed / 0xff, blue: 0xf1 / 0xff, alpha: 1)
-
- //_inputView.backgroundColor = color
- //_inputView.clipsToBounds = true
- _inputView.backgroundColor = .clear
- _inputView.translatesAutoresizingMaskIntoConstraints = false
-
- _inputAccessoryView.delegate = self
- _inputAccessoryView.translatesAutoresizingMaskIntoConstraints = false
- _inputAccessoryView.setContentHuggingPriority(UILayoutPriority.required, for: .vertical)
- _inputAccessoryView.setContentCompressionResistancePriority(UILayoutPriority.required, for: .vertical)
- //_inputAccessoryView.backgroundColor = color
- _inputAccessoryView.backgroundColor = .clear
-
- _backgroundView.translatesAutoresizingMaskIntoConstraints = false
- _backgroundView.setContentHuggingPriority(UILayoutPriority(rawValue: 1), for: .vertical)
- _backgroundView.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 1), for: .vertical)
- _backgroundView.barTintColor = .white
- _backgroundView.isTranslucent = false // 毛玻璃效果还有bug
- //_backgroundView.barStyle = .black
-
- _addComponents(toView: self)
- _addNotifications()
- }
- private func _deinit() {
-
- _removeNotifications()
- _removeComponents(formView: self)
- }
-
- fileprivate var _visableKeybaordSize: CGSize {
- if _inputMode.isSelecting {
- return _cacheCustomKeyboardSize
- }
- return CGSize.zero
- }
- fileprivate var _keyboardSizeWithoutCache: CGSize {
- if _inputMode.isSelecting {
- return _cacheCustomKeyboardSize
- }
- return _cacheSystemKeyboardSize
- }
- fileprivate var _contentSizeWithoutCache: CGSize {
- var size = _inputAccessoryView.intrinsicContentSize
- // Append the keyboard size
- if _inputMode.isSelecting {
- size.height += _cacheCustomKeyboardSize.height //_inputView.intrinsicContentSize.height
- }
- return size
- }
- fileprivate var _frameInWindow: CGRect {
- guard let window = window else {
- return CGRect.zero
- }
- let ivheight = _inputAccessoryView.intrinsicContentSize.height
- let height = ivheight + max(_keyboardSizeWithoutCache.height, 0)
-
- return CGRect(x: 0, y: window.frame.height - height, width: window.frame.width, height: height)
- }
-
- // MARK: -
-
- fileprivate var _inputMode: SAIInputMode = .none
-
- fileprivate var _inputViewBottom: NSLayoutConstraint?
- fileprivate var _inputAccessoryViewBottom: NSLayoutConstraint?
-
- fileprivate lazy var _inputView: SAIInputView = SAIInputView()
- fileprivate lazy var _inputAccessoryView: SAIInputAccessoryView = SAIInputAccessoryView()
- fileprivate lazy var _backgroundView: SAIInputBackgroundView = SAIInputBackgroundView()
-
- fileprivate lazy var _constraints: [NSLayoutConstraint] = []
- fileprivate lazy var _selectedItems: Set<SAIInputItem> = []
-
- fileprivate weak var _containerView: UIView?
- fileprivate weak var _displayable: SAIInputBarDisplayable?
-
- fileprivate var _cacheBounds: CGRect?
- fileprivate var _cacheContentSize: CGSize?
- fileprivate var _cacheKeyboardSize: CGSize?
- fileprivate var _cacheKeyboardOffset: CGPoint = .zero
-
- fileprivate var _cacheSystemKeyboardSize: CGSize = .zero
- fileprivate var _cacheCustomKeyboardSize: CGSize = .zero
-
- fileprivate var _cacheKeyboardIsInitialized: Bool = false
-
- // MARK: -
-
- // open override class func initialize() {
- // _ = _ib_inputBar_once
- // }
- func initialize() {
- _ = _ib_inputBar_once
- }
-
- public override init(frame: CGRect) {
- super.init(frame: frame)
- _init()
- }
- public required init?(coder aDecoder: NSCoder) {
- super.init(coder: aDecoder)
- _init()
- }
-
- deinit {
- _deinit()
- }
- }
- // MARK: - System Keyboard Event
- extension SAIInputBar {
-
- @objc func ntf_keyboard(willShow sender: Notification) {
- guard let window = window else {
- return
- }
- _ntf_animation(sender) { bf, ef in
- let ef1 = window.frame.inset(by: UIEdgeInsets(top: ef.minY, left: 0, bottom: 0, right: 0))
- _updateSystemKeyboard(ef1.size, animated: false)
-
- _displayable?.ib_inputBar(self, showWithFrame: _frameInWindow)
- }
- }
- @objc func ntf_keyboard(willHide sender: Notification) {
- guard let window = window else {
- return
- }
- _ntf_animation(sender) { bf, ef in
- let ef1 = window.frame.inset(by: UIEdgeInsets(top: ef.minY, left: 0, bottom: 0, right: 0))
-
- _cacheSystemKeyboardSize = ef1.size
- // _updateSystemKeyboard(ef1.size, animated: false)
- _cacheKeyboardSize = _keyboardSizeWithoutCache
-
- _displayable?.ib_inputBar(self, hideWithFrame: _frameInWindow)
- }
- }
- @objc func ntf_keyboard(didScroll sender: UIPanGestureRecognizer) {
- // if inputbar state is `None`, ignore this event
- if _inputMode.isNone {
- return
- }
- // if recgognizer state is end, process custom event
- guard sender.state == .began || sender.state == .changed || sender.state == .possible else {
- // clear keyboard offset
- _cacheKeyboardOffset = CGPoint.zero
- // ignore system keyboard, in system keyboard, the show/dismiss is automatic process
- guard _inputMode.isSelecting else {
- return
- }
- // if nheight > height, this means that it at outside of the keyboard, cancel the event
- if sender.location(in: _inputAccessoryView).y < 0 {
- // cancel touch at outside
- _updateKeyboardOffsetIfNeeded(CGPoint.zero, animated: true)
- } else if sender.velocity(in: _inputAccessoryView).y <= 0 {
- // cancel touch at inside
- _updateKeyboardOffsetIfNeeded(CGPoint.zero, animated: true)
- _displayable?.ib_inputBar(self, showWithFrame: _frameInWindow)
- } else {
- // dismiss
- _updateInputMode(.none, animated: true)
- //_displayable?.ib_inputBar(self, hideWithFrame: _frameInWindow)
- }
- return
- }
- guard let window = self.window, sender.numberOfTouches != 0 else {
- return
- }
- // Must use the first touch to calculate the position
- let nheight = window.frame.height - sender.location(ofTouch: 0, in: window).y
- let kbheight = _keyboardSizeWithoutCache.height
- let iavheight = _inputAccessoryView.intrinsicContentSize.height
- let height = iavheight + kbheight
- let ty = height - min(max(nheight, iavheight), height)
-
- if _cacheKeyboardOffset.y != ty {
- // in editing(system keybaord), system automatic process
- if _inputMode.isSelecting {
- _updateKeyboardOffsetIfNeeded(CGPoint(x: 0, y: ty), animated: false)
- }
- _displayable?.ib_inputBar(self, didChangeOffset: CGPoint(x: 0, y: ty))
- }
-
- _cacheKeyboardOffset.y = ty
- }
-
- @objc func ntf_accessory(didChangeFrame sender: Notification) {
-
- let newCustomKeyboardSize = _inputView.intrinsicContentSize
- if _cacheCustomKeyboardSize.height != newCustomKeyboardSize.height {
- _cacheCustomKeyboardSize = newCustomKeyboardSize
- _updateKeyboardSizeIfNeeded(false)
- } else {
- _updateContentSizeIfNeeded(false)
- }
- // update in advance, don't wait for willShow event, otherwise there will be a delay
- _displayable?.ib_inputBar(self, didChangeFrame: _frameInWindow)
- }
-
- private func _ntf_flatMap(_ ntf: Notification, handler: (CGRect, CGRect, TimeInterval, UIView.AnimationCurve) -> ()) {
- guard let u = (ntf as NSNotification).userInfo,
- let bf = (u[UIResponder.keyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue,
- let ef = (u[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue,
- let cv = (u[UIResponder.keyboardAnimationCurveUserInfoKey] as? Int),
- let dr = (u[UIResponder.keyboardAnimationDurationUserInfoKey] as? TimeInterval) else {
- return
- }
- let edg = UIEdgeInsets(top: intrinsicContentSize.height, left: 0, bottom: 0, right: 0)
-
- // rect correction
- let bf1 = bf.inset(by: edg)
- let ef1 = ef.inset(by: edg)
-
- let cv1 = UIView.AnimationCurve(rawValue: cv) ?? _SAInputDefaultAnimateCurve
-
- handler(bf1, ef1, dr, cv1)
- }
- private func _ntf_animation(_ ntf: Notification, handler: (CGRect, CGRect) -> Void) {
- _ntf_flatMap(ntf) { bf, ef, dr, cv in
- guard dr != 0 else {
- handler(bf, ef)
- return
- }
- UIView.beginAnimations("SAIB-ANI-KB", context: nil)
- UIView.setAnimationDuration(dr)
- UIView.setAnimationCurve(cv)
- handler(bf, ef)
- UIView.commitAnimations()
- }
- }
- }
- // MARK: - UITextView(Forwarding)
- extension SAIInputBar: UIKeyInput {
-
- // UITextView
-
- open var text: String! {
- set { return _inputAccessoryView.textField.text = newValue }
- get { return _inputAccessoryView.textField.text }
- }
- open var font: UIFont? {
- set { return _inputAccessoryView.textField.font = newValue }
- get { return _inputAccessoryView.textField.font }
- }
- open var textColor: UIColor? {
- set { return _inputAccessoryView.textField.textColor = newValue }
- get { return _inputAccessoryView.textField.textColor }
- }
-
- open var attributedText: NSAttributedString! {
- set { return _inputAccessoryView.textField.attributedText = newValue }
- get { return _inputAccessoryView.textField.attributedText }
- }
-
- open var textAlignment: NSTextAlignment {
- set { return _inputAccessoryView.textField.textAlignment = newValue }
- get { return _inputAccessoryView.textField.textAlignment }
- }
- open var selectedRange: NSRange {
- set { return _inputAccessoryView.textField.selectedRange = newValue }
- get { return _inputAccessoryView.textField.selectedRange }
- }
-
- open var editable: Bool {
- set { return _inputAccessoryView.textField.isEditable = newValue }
- get { return _inputAccessoryView.textField.isEditable }
- }
- open var selectable: Bool {
- set { return _inputAccessoryView.textField.isSelectable = newValue }
- get { return _inputAccessoryView.textField.isSelectable }
- }
-
- // UIKeyInput(Forwarding)
-
- open var hasText: Bool {
- return _inputAccessoryView.textField.hasText
- }
- open func insertText(_ text: String) {
- _inputAccessoryView.textField.insertText(text)
- _inputAccessoryView.textViewDidChange(_inputAccessoryView.textField)
- }
- open func insertAttributedText(_ attributedText: NSAttributedString) {
- _inputAccessoryView.textField.insertAttributedText(attributedText)
- _inputAccessoryView.textViewDidChange(_inputAccessoryView.textField)
- }
- open func deleteBackward() {
- _inputAccessoryView.textField.deleteBackward()
- _inputAccessoryView.textViewDidChange(_inputAccessoryView.textField)
- }
-
- // UITextInputTraits(Forwarding)
-
- open var autocapitalizationType: UITextAutocapitalizationType {
- set { return _inputAccessoryView.textField.autocapitalizationType = newValue }
- get { return _inputAccessoryView.textField.autocapitalizationType }
- }
- open var autocorrectionType: UITextAutocorrectionType {
- set { return _inputAccessoryView.textField.autocorrectionType = newValue }
- get { return _inputAccessoryView.textField.autocorrectionType }
- }
- open var spellCheckingType: UITextSpellCheckingType {
- set { return _inputAccessoryView.textField.spellCheckingType = newValue }
- get { return _inputAccessoryView.textField.spellCheckingType }
- }
- open var keyboardType: UIKeyboardType {
- set { return _inputAccessoryView.textField.keyboardType = newValue }
- get { return _inputAccessoryView.textField.keyboardType }
- }
- open var keyboardAppearance: UIKeyboardAppearance {
- set { return _inputAccessoryView.textField.keyboardAppearance = newValue }
- get { return _inputAccessoryView.textField.keyboardAppearance }
- }
- open var returnKeyType: UIReturnKeyType {
- set { return _inputAccessoryView.textField.returnKeyType = newValue }
- get { return _inputAccessoryView.textField.returnKeyType }
- }
- open var enablesReturnKeyAutomatically: Bool {
- set { return _inputAccessoryView.textField.enablesReturnKeyAutomatically = newValue }
- get { return _inputAccessoryView.textField.enablesReturnKeyAutomatically }
- }
- open var isSecureTextEntry: Bool {
- @objc(setSecureTextEntry:)
- set { return _inputAccessoryView.textField.isSecureTextEntry = newValue }
- @objc(isSecureTextEntry) get { return _inputAccessoryView.textField.isSecureTextEntry }
- }
- }
- // MARK: - UITextViewDelegate(Forwarding)
- extension SAIInputBar: UITextViewDelegate {
-
- open func textViewShouldBeginEditing(_ textView: UITextView) -> Bool {
- if let r = delegate?.inputBar?(shouldBeginEditing: self), !r {
- return false
- }
- _updateInputModeForResponder(.editing, animated: true)
- return true
- }
- open func textViewShouldEndEditing(_ textView: UITextView) -> Bool {
- if let r = delegate?.inputBar?(shouldEndEditing: self), !r {
- return false
- }
- return true
- }
-
- open func textViewDidBeginEditing(_ textView: UITextView) {
- delegate?.inputBar?(didBeginEditing: self)
- }
- open func textViewDidEndEditing(_ textView: UITextView) {
- delegate?.inputBar?(didEndEditing: self)
- _updateInputModeForResponder(.none, animated: true)
- }
-
- open func textViewDidChangeSelection(_ textView: UITextView) {
- delegate?.inputBar?(didChangeSelection: self)
- }
- open func textViewDidChange(_ textView: UITextView) {
- delegate?.inputBar?(didChangeText: self)
- }
- open func textView(_ textView: UITextView, shouldInteractWith textAttachment: NSTextAttachment, in characterRange: NSRange) -> Bool {
- if let r = delegate?.inputBar?(self, shouldInteractWithTextAttachment: textAttachment, inRange: characterRange), !r {
- return false
- }
- return true
- }
- open func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
- if let r = delegate?.inputBar?(self, shouldChangeCharactersInRange: range, replacementString: text), !r {
- return false
- }
- // This is return
- if text == "\n" {
- return delegate?.inputBar?(shouldReturn: self) ?? true
- }
- // This is clear
- if text.isEmpty && range.length - range.location == (textView.text as NSString).length {
- return delegate?.inputBar?(shouldClear: self) ?? true
- }
- return true
- }
- open func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange) -> Bool {
- if let r = delegate?.inputBar?(self, shouldInteractWithURL: URL, inRange: characterRange), !r {
- return false
- }
- return true
- }
- }
- // MARK: - SAIInputAccessoryView(Forwarding)
- extension SAIInputBar {
-
- open func barItems(atPosition position: SAIInputItemPosition) -> [SAIInputItem] {
- return _inputAccessoryView.barItems(atPosition: position)
- }
- open func setBarItem(_ barItem: SAIInputItem, atPosition position: SAIInputItemPosition, animated: Bool = true) {
- return _inputAccessoryView.setBarItems([barItem], atPosition: position, animated: animated)
- }
- open func setBarItems(_ barItems: [SAIInputItem], atPosition position: SAIInputItemPosition, animated: Bool = true) {
- return _inputAccessoryView.setBarItems(barItems, atPosition: position, animated: animated)
- }
-
- open func canSelectBarItem(_ barItem: SAIInputItem) -> Bool {
- return _inputAccessoryView.canSelectBarItem(barItem)
- }
- open func canDeselectBarItem(_ barItem: SAIInputItem) -> Bool {
- return _inputAccessoryView.canDeselectBarItem(barItem)
- }
-
- open func selectBarItem(_ barItem: SAIInputItem, animated: Bool) {
- _selectedItems.insert(barItem)
- return _inputAccessoryView.selectBarItem(barItem, animated: animated)
- }
- open func deselectBarItem(_ barItem: SAIInputItem, animated: Bool) {
- _selectedItems.remove(barItem)
- _inputAccessoryView.isShowRecordButton = false
- return _inputAccessoryView.deselectBarItem(barItem, animated: animated)
- }
- open func deselectBarAllItem() {
- _selectedItems.forEach{
- self.deselectBarItem($0, animated: true)
- self.barItem(didDeselectFor: $0)
- }
- _selectedItems = []
- _inputAccessoryView.isShowRecordButton = true
- self.setInputMode(.audio, animated: true)
- }
-
- open func editMode() {
- _selectedItems.forEach{
- self.deselectBarItem($0, animated: true)
- self.barItem(didDeselectFor: $0)
- }
- _selectedItems = []
- _inputAccessoryView.isShowRecordButton = false
- self.setInputMode(.editing, animated: true)
- // self.becomeFirstResponder()
- }
- }
- // MARK: - SAIInputItemViewDelegate(Forwarding)
- extension SAIInputBar: SAIInputItemViewDelegate {
-
- open func barItem(shouldHighlightFor barItem: SAIInputItem) -> Bool {
- return delegate?.inputBar?(self, shouldHighlightFor: barItem) ?? true
- }
- open func barItem(shouldDeselectFor barItem: SAIInputItem) -> Bool {
- // if !allowsMultipleSelection {
- // return false // not allowed to cancel
- // }
- return delegate?.inputBar?(self, shouldDeselectFor: barItem) ?? true
- }
- open func barItem(shouldSelectFor barItem: SAIInputItem) -> Bool {
- guard allowsSelection else {
- // do not allow the selected
- return false
- }
- if _selectedItems.contains(barItem) {
- // has been selected
- return false
- }
- guard delegate?.inputBar?(self, shouldSelectFor: barItem) ?? true else {
- // users are not allowed to select
- return false
- }
- if !allowsMultipleSelection {
- // don't allow a multiple-select, cancel has been chosen
- for item in _selectedItems {
- if !(self.delegate?.inputBar?(self, shouldDeselectFor: item) ?? true) {
- // Not allowed to cancel, so do not allow the selected
- return false
- }
- }
- //
- _selectedItems.forEach{
- self.deselectBarItem($0, animated: true)
- // self.barItem(didDeselectFor: $0)
- _selectedItems.remove(barItem)
- }
- _selectedItems = []
- }
- return true
- }
-
- open func barItem(didHighlightFor barItem: SAIInputItem) {
- delegate?.inputBar?(self, didHighlightFor: barItem)
- }
- open func barItem(didDeselectFor barItem: SAIInputItem) {
- // delegate?.inputBar?(self, didDeselectFor: barItem)
- // Remove from the selected list
- _selectedItems.remove(barItem)
- // self.editMode()
- _inputAccessoryView.isShowRecordButton = false
- let _ = self.becomeFirstResponder()
- }
- open func barItem(didSelectFor barItem: SAIInputItem) {
- delegate?.inputBar?(self, didSelectFor: barItem)
- // Added to the selected list
- _selectedItems.insert(barItem)
- }
- }
- extension SAIInputBar: SAIInputAccessoryViewDelegate {
- open func inputAccessoryView(touchDown recordButton: UIButton) {
- delegate?.inputBar?(touchDown: recordButton, inputBar: self)
- }
- open func inputAccessoryView(dragInside recordButton: UIButton) {
- delegate?.inputBar?(dragInside: recordButton, inputBar: self)
- }
- open func inputAccessoryView(dragOutside recordButton: UIButton) {
- delegate?.inputBar?(dragOutside: recordButton, inputBar: self)
- }
- open func inputAccessoryView(touchUpInside recordButton: UIButton) {
- delegate?.inputBar?(touchUpInside: recordButton, inputBar: self)
- }
- open func inputAccessoryView(touchUpOutside recordButton: UIButton) {
- delegate?.inputBar?(touchUpOutside: recordButton, inputBar: self)
- }
- }
-
- // MARK: -
- private extension UIResponder {
-
- @objc func ib_overrideInputAccessoryViewNextResponderWithResponder(_ arg1: UIResponder?) {
- ib_nextResponderOverride = arg1
- return ib_overrideInputAccessoryViewNextResponderWithResponder(arg1)
- }
-
- var ib_nextResponderOverride: UIResponder? {
- set { return objc_setAssociatedObject(self, &_SAInputUIResponderNextResponderOverride, newValue, .OBJC_ASSOCIATION_ASSIGN) }
- get { return objc_getAssociatedObject(self, &_SAInputUIResponderNextResponderOverride) as? UIResponder }
- }
- }
- // MARK: -
- private extension UIScrollView {
-
- // gesture recognizer handler
- @objc private func ib_handlePan(_ sender: UIPanGestureRecognizer) {
- ib_handlePan(sender)
- guard let inputBar = inputAccessoryView as? SAIInputBar else {
- return
- }
- if keyboardDismissMode == .onDrag {
- // is `OnDrag`
- guard inputBar.inputMode.isSelecting else {
- return
- }
- inputBar.setInputMode(.none, animated: true)
- } else if keyboardDismissMode == .interactive {
- // is `Interactive`
- inputBar.ntf_keyboard(didScroll: sender)
- }
- }
- }
- // MARK: -
- private extension UIPresentationController {
-
- @objc func _preserveResponderAcrossWindows() -> Bool {
- // repair the iOS 8.1 bugs, if return true
- // system will invoke `_preserveInputViewsWithId:animated:reset:`
- // to save the input environment
- return true
- }
- }
- // MARK: -
- internal func SAIInputBarLoad() {
-
- // 解释一下为什么采用这个方法, 因为swift没有不能重写load方法,
- // 如果写在initialize可能会被其他库覆盖掉
- // 采用这个方法安全一点
- _SAInputExchangeSelector(UIScrollView.self, "handlePan:", "ib_handlePan:")
-
- // 计划中止, 复杂度略高
- //_SAInputExchangeSelector(NSClassFromString("UIInputSetContainerView"), "snapshotViewAfterScreenUpdates:", "ib_snapshotViewAfterScreenUpdates:")
-
- // 解决iOS8中的bug
- _SAInputExchangeSelector(UIResponder.self, "_overrideInputAccessoryViewNextResponderWithResponder:", "ib_overrideInputAccessoryViewNextResponderWithResponder:")
- }
- @inline(__always)
- 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 {
-
- let c = NSLayoutConstraint(item:item, attribute:attr1, relatedBy:related, toItem:toItem, attribute:attr2, multiplier:multiplier, constant:constant)
- if output != nil {
- output?.pointee = c
- }
-
- return c
- }
- @inline(__always)
- internal func _SAInputExchangeSelector(_ cls: AnyClass?, _ sel1: String, _ sel2: String) {
- _SAInputExchangeSelector(cls, Selector(sel1), Selector(sel2))
- }
- @inline(__always)
- internal func _SAInputExchangeSelector(_ cls: AnyClass?, _ sel1: Selector, _ sel2: Selector) {
- guard let cls = cls else {
- return
- }
- method_exchangeImplementations(class_getInstanceMethod(cls, sel1)!, class_getInstanceMethod(cls, sel2)!)
- }
- private var _ib_inputBar_once: Bool = {
-
- SAIInputBarLoad()
- SAIInputBarDisplayableLoad()
- return true
-
- }()
- private var SAIInputBarWillSnapshot = "SAIInputBarWillSnapshot"
- private var SAIInputBarDidSnapshot = "SAIInputBarDidSnapshot"
- private var _SAInputUIResponderNextResponderOverride = "_SAInputUIResponderNextResponderOverride"
- internal var _SAInputDefaultAnimateDuration: TimeInterval = 0.25
- internal var _SAInputDefaultAnimateCurve: UIView.AnimationCurve = UIView.AnimationCurve(rawValue: 7) ?? .easeInOut
- internal var _SAInputDefaultTextFieldBackgroundImage: UIImage? = {
- // 生成默认图片
-
- let radius = CGFloat(8)
- let rect = CGRect(x: 0, y: 0, width: 32, height: 32)
- let path = UIBezierPath(roundedRect: rect, cornerRadius: radius)
-
- UIGraphicsBeginImageContextWithOptions(rect.size, false, UIScreen.main.scale)
-
- UIColor.white.setFill()
-
- path.fill()
- path.addClip()
-
- let image = UIGraphicsGetImageFromCurrentImageContext()
-
- UIGraphicsEndImageContext()
-
- return image?.resizableImage(withCapInsets: UIEdgeInsets(top: radius, left: radius, bottom: radius, right: radius))
- }()
|