如何在 swift 项目中使用 TPKeyboardAvoiding?
Posted
技术标签:
【中文标题】如何在 swift 项目中使用 TPKeyboardAvoiding?【英文标题】:How to use TPKeyboardAvoiding in swift project? 【发布时间】:2016-02-25 11:03:31 【问题描述】:我在我的 Objective c 应用程序中使用了 TPKeyboardAvoiding 滚动视图。 现在我想在我的 swift 项目中使用它。 我已经搜索过,但我没有得到确切的步骤。
感谢帮助
【问题讨论】:
使用桥接头.... 【参考方案1】:Swift 4.1, 只需在情节提要或 xib 中为您的滚动视图分配相应的类。
// MARK: - TableView
class TPKeyboardAvoidingTableView:UITableView,UITextFieldDelegate, UITextViewDelegate
override var frame:CGRect
willSet
super.frame = frame
didSet
if hasAutomaticKeyboardAvoidingBehaviour() return
TPKeyboardAvoiding_updateContentInset()
override var contentSize:CGSize
willSet(newValue)
if hasAutomaticKeyboardAvoidingBehaviour()
super.contentSize = newValue
return
if newValue.equalTo(self.contentSize)
return
super.contentSize = newValue
self.TPKeyboardAvoiding_updateContentInset()
// didSet
// self.TPKeyboardAvoiding_updateContentInset()
//
override init(frame: CGRect, style: UITableViewStyle)
super.init(frame: frame, style: style)
self.setup()
required init?(coder aDecoder: NSCoder)
super.init(coder: aDecoder)
self.setup()
override func awakeFromNib()
setup()
deinit
NotificationCenter.default.removeObserver(self)
func hasAutomaticKeyboardAvoidingBehaviour()->Bool
if #available(ios 8.3, *)
if self.delegate is UITableViewController
return true
return false
func focusNextTextField()->Bool
return self.TPKeyboardAvoiding_focusNextTextField()
@objc func scrollToActiveTextField()
return self.TPKeyboardAvoiding_scrollToActiveTextField()
override func willMove(toSuperview newSuperview: UIView?)
super.willMove(toSuperview: newSuperview)
if newSuperview != nil
NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(TPKeyboardAvoiding_assignTextDelegateForViewsBeneathView(_:)), object: self)
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?)
self.TPKeyboardAvoiding_findFirstResponderBeneathView(self)?.resignFirstResponder()
super.touchesEnded(touches, with: event)
func textFieldShouldReturn(_ textField: UITextField) -> Bool
if !self.focusNextTextField()
textField.resignFirstResponder()
return true
override func layoutSubviews()
super.layoutSubviews()
NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(TPKeyboardAvoiding_assignTextDelegateForViewsBeneathView(_:)), object: self)
Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(TPKeyboardAvoiding_assignTextDelegateForViewsBeneathView(_:)), userInfo: nil, repeats: false)
private extension TPKeyboardAvoidingTableView
func setup()
if self.hasAutomaticKeyboardAvoidingBehaviour() return
NotificationCenter.default.addObserver(self,
selector: #selector(TPKeyboardAvoiding_keyboardWillShow(_:)),
name: NSNotification.Name.UIKeyboardWillChangeFrame,
object: nil)
NotificationCenter.default.addObserver(self,
selector: #selector(TPKeyboardAvoiding_keyboardWillHide(_:)),
name: NSNotification.Name.UIKeyboardWillHide,
object: nil)
NotificationCenter.default.addObserver(self,
selector: #selector(scrollToActiveTextField),
name: NSNotification.Name.UITextViewTextDidBeginEditing,
object: nil)
NotificationCenter.default.addObserver(self,
selector: #selector(scrollToActiveTextField),
name: NSNotification.Name.UITextFieldTextDidBeginEditing,
object: nil)
// MARK: - CollectionView
class TPKeyboardAvoidingCollectionView:UICollectionView,UITextViewDelegate
override var contentSize:CGSize
willSet(newValue)
if newValue.equalTo(self.contentSize)
return
super.contentSize = newValue
self.TPKeyboardAvoiding_updateContentInset()
override var frame:CGRect
willSet
super.frame = frame
didSet
self.TPKeyboardAvoiding_updateContentInset()
// override init(frame: CGRect)
// super.init(frame: frame)
//
override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout)
super.init(frame: frame, collectionViewLayout: layout)
setup()
required init?(coder aDecoder: NSCoder)
// fatalError("init(coder:) has not been implemented")
super.init(coder: aDecoder)
self.setup()
override func awakeFromNib()
setup()
deinit
NotificationCenter.default.removeObserver(self)
func focusNextTextField()->Bool
return self.TPKeyboardAvoiding_focusNextTextField()
@objc func scrollToActiveTextField()
return self.TPKeyboardAvoiding_scrollToActiveTextField()
override func willMove(toSuperview newSuperview: UIView?)
super.willMove(toSuperview: newSuperview)
if newSuperview != nil
NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(TPKeyboardAvoiding_assignTextDelegateForViewsBeneathView(_:)), object: self)
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?)
self.TPKeyboardAvoiding_findFirstResponderBeneathView(self)?.resignFirstResponder()
super.touchesEnded(touches, with: event)
func textFieldShouldReturn(_ textField: UITextField) -> Bool
if !self.focusNextTextField()
textField.resignFirstResponder()
return true
override func layoutSubviews()
super.layoutSubviews()
NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(TPKeyboardAvoiding_assignTextDelegateForViewsBeneathView(_:)), object: self)
Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(TPKeyboardAvoiding_assignTextDelegateForViewsBeneathView(_:)), userInfo: nil, repeats: false)
private extension TPKeyboardAvoidingCollectionView
func setup()
NotificationCenter.default.addObserver(self,
selector: #selector(TPKeyboardAvoiding_keyboardWillShow(_:)),
name: NSNotification.Name.UIKeyboardWillChangeFrame,
object: nil)
NotificationCenter.default.addObserver(self,
selector: #selector(TPKeyboardAvoiding_keyboardWillHide(_:)),
name: NSNotification.Name.UIKeyboardWillHide,
object: nil)
NotificationCenter.default.addObserver(self,
selector: #selector(scrollToActiveTextField),
name: NSNotification.Name.UITextViewTextDidBeginEditing,
object: nil)
NotificationCenter.default.addObserver(self,
selector: #selector(scrollToActiveTextField),
name: NSNotification.Name.UITextFieldTextDidBeginEditing,
object: nil)
// MARK: - ScrollView
class TPKeyboardAvoidingScrollView:UIScrollView,UITextFieldDelegate,UITextViewDelegate
override var contentSize:CGSize
didSet
self.TPKeyboardAvoiding_updateFromContentSizeChange()
override var frame:CGRect
didSet
self.TPKeyboardAvoiding_updateContentInset()
override init(frame: CGRect)
super.init(frame: frame)
self.setup()
override func awakeFromNib()
setup()
func contentSizeToFit()
self.contentSize = self.TPKeyboardAvoiding_calculatedContentSizeFromSubviewFrames()
func focusNextTextField() ->Bool
return self.TPKeyboardAvoiding_focusNextTextField()
@objc func scrollToActiveTextField()
return self.TPKeyboardAvoiding_scrollToActiveTextField()
required init?(coder aDecoder: NSCoder)
super.init(coder: aDecoder)
self.setup()
deinit
NotificationCenter.default.removeObserver(self)
override func willMove(toSuperview newSuperview: UIView?)
super.willMove(toSuperview: newSuperview)
if newSuperview != nil
NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(TPKeyboardAvoiding_assignTextDelegateForViewsBeneathView(_:)), object: self)
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?)
self.TPKeyboardAvoiding_findFirstResponderBeneathView(self)?.resignFirstResponder()
super.touchesEnded(touches, with: event)
func textFieldShouldReturn(_ textField: UITextField) -> Bool
if !self.focusNextTextField()
textField.resignFirstResponder()
return true
override func layoutSubviews()
super.layoutSubviews()
NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(TPKeyboardAvoiding_assignTextDelegateForViewsBeneathView(_:)), object: self)
Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(TPKeyboardAvoiding_assignTextDelegateForViewsBeneathView(_:)), userInfo: nil, repeats: false)
private extension TPKeyboardAvoidingScrollView
func setup()
NotificationCenter.default.addObserver(self,
selector: #selector(TPKeyboardAvoiding_keyboardWillShow(_:)),
name: NSNotification.Name.UIKeyboardWillChangeFrame,
object: nil)
NotificationCenter.default.addObserver(self,
selector: #selector(TPKeyboardAvoiding_keyboardWillHide(_:)),
name: NSNotification.Name.UIKeyboardWillHide,
object: nil)
NotificationCenter.default.addObserver(self,
selector: #selector(scrollToActiveTextField),
name: NSNotification.Name.UITextViewTextDidBeginEditing,
object: nil)
NotificationCenter.default.addObserver(self,
selector: #selector(scrollToActiveTextField),
name: NSNotification.Name.UITextFieldTextDidBeginEditing,
object: nil)
// MARK: - Process Event
let kCalculatedContentPadding:CGFloat = 10;
let kMinimumScrollOffsetPadding:CGFloat = 20;
extension UIScrollView
@objc func TPKeyboardAvoiding_keyboardWillShow(_ notification:Notification)
guard let userInfo = notification.userInfo else return
guard let rectNotification = notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue else
return
let keyboardRect = self.convert(rectNotification.cgRectValue , from: nil)
if keyboardRect.isEmpty
return
let state = self.keyboardAvoidingState()
guard let firstResponder = self.TPKeyboardAvoiding_findFirstResponderBeneathView(self) else return
state.keyboardRect = keyboardRect
if !state.keyboardVisible
state.priorInset = self.contentInset
state.priorScrollIndicatorInsets = self.scrollIndicatorInsets
state.priorPagingEnabled = self.isPagingEnabled
state.keyboardVisible = true
self.isPagingEnabled = false
if self is TPKeyboardAvoidingScrollView
state.priorContentSize = self.contentSize
if self.contentSize.equalTo(CGSize.zero)
self.contentSize = self.TPKeyboardAvoiding_calculatedContentSizeFromSubviewFrames()
let duration = userInfo[UIKeyboardAnimationDurationUserInfoKey] as? Float ?? 0.0
let curve = userInfo[UIKeyboardAnimationCurveUserInfoKey] as? Int ?? 0
let options = UIViewAnimationOptions(rawValue: UInt(curve))
UIView.animate(withDuration: TimeInterval(duration),
delay: 0,
options: options,
animations: [weak self]() -> Void in
if let actualSelf = self
actualSelf.contentInset = actualSelf.TPKeyboardAvoiding_contentInsetForKeyboard()
let viewableHeight = actualSelf.bounds.size.height - actualSelf.contentInset.top - actualSelf.contentInset.bottom
let point = CGPoint(x: actualSelf.contentOffset.x, y: actualSelf.TPKeyboardAvoiding_idealOffsetForView(firstResponder, viewAreaHeight: viewableHeight))
actualSelf.setContentOffset(point, animated: false)
actualSelf.scrollIndicatorInsets = actualSelf.contentInset
actualSelf.layoutIfNeeded()
) (finished) -> Void in
@objc func TPKeyboardAvoiding_keyboardWillHide(_ notification:Notification)
guard let userInfo = notification.userInfo else return
guard let rectNotification = userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue else
return
let keyboardRect = self.convert(rectNotification.cgRectValue , from: nil)
if keyboardRect.isEmpty
return
let state = self.keyboardAvoidingState()
if !state.keyboardVisible
return
state.keyboardRect = CGRect.zero
state.keyboardVisible = false
let duration = userInfo[UIKeyboardAnimationDurationUserInfoKey] as? Float ?? 0.0
let curve = userInfo[UIKeyboardAnimationCurveUserInfoKey] as? Int ?? 0
let options = UIViewAnimationOptions(rawValue: UInt(curve))
UIView.animate(withDuration: TimeInterval(duration),
delay: 0,
options: options,
animations: [weak self]() -> Void in
if let actualSelf = self
if actualSelf is TPKeyboardAvoidingScrollView
actualSelf.contentSize = state.priorContentSize
actualSelf.contentInset = state.priorInset
actualSelf.scrollIndicatorInsets = state.priorScrollIndicatorInsets
actualSelf.isPagingEnabled = state.priorPagingEnabled
actualSelf.layoutIfNeeded()
) (finished) -> Void in
func TPKeyboardAvoiding_updateFromContentSizeChange()
let state = self.keyboardAvoidingState()
if state.keyboardVisible
state.priorContentSize = self.contentSize
func TPKeyboardAvoiding_focusNextTextField() ->Bool
guard let firstResponder = self.TPKeyboardAvoiding_findFirstResponderBeneathView(self) else return false
guard let view = self.TPKeyboardAvoiding_findNextInputViewAfterView(firstResponder, beneathView: self) else return false
Timer.scheduledTimer(timeInterval: 0.1, target: view, selector: #selector(becomeFirstResponder), userInfo: nil, repeats: false)
return true
func TPKeyboardAvoiding_scrollToActiveTextField()
let state = self.keyboardAvoidingState()
if !state.keyboardVisible return
let visibleSpace = self.bounds.size.height - self.contentInset.top - self.contentInset.bottom
let idealOffset = CGPoint(x: 0,
y: self.TPKeyboardAvoiding_idealOffsetForView(self.TPKeyboardAvoiding_findFirstResponderBeneathView(self),
viewAreaHeight: visibleSpace))
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Double((Int64)(0 * NSEC_PER_SEC)) / Double(NSEC_PER_SEC)) [weak self] () -> Void in
self?.setContentOffset(idealOffset, animated: true)
//Helper
func TPKeyboardAvoiding_findFirstResponderBeneathView(_ view:UIView) -> UIView?
for childView in view.subviews
if childView.responds(to: #selector(getter: isFirstResponder)) && childView.isFirstResponder
return childView
let result = TPKeyboardAvoiding_findFirstResponderBeneathView(childView)
if result != nil
return result
return nil
func TPKeyboardAvoiding_updateContentInset()
let state = self.keyboardAvoidingState()
if state.keyboardVisible
self.contentInset = self.TPKeyboardAvoiding_contentInsetForKeyboard()
func TPKeyboardAvoiding_calculatedContentSizeFromSubviewFrames() ->CGSize
let wasShowingVerticalScrollIndicator = self.showsVerticalScrollIndicator
let wasShowingHorizontalScrollIndicator = self.showsHorizontalScrollIndicator
self.showsVerticalScrollIndicator = false
self.showsHorizontalScrollIndicator = false
var rect = CGRect.zero
for view in self.subviews
rect = rect.union(view.frame)
rect.size.height += kCalculatedContentPadding
self.showsVerticalScrollIndicator = wasShowingVerticalScrollIndicator
self.showsHorizontalScrollIndicator = wasShowingHorizontalScrollIndicator
return rect.size
func TPKeyboardAvoiding_idealOffsetForView(_ view:UIView?,viewAreaHeight:CGFloat) -> CGFloat
let contentSize = self.contentSize
var offset:CGFloat = 0.0
let subviewRect = view != nil ? view!.convert(view!.bounds, to: self) : CGRect.zero
var padding = (viewAreaHeight - subviewRect.height)/2
if padding < kMinimumScrollOffsetPadding
padding = kMinimumScrollOffsetPadding
offset = subviewRect.origin.y - padding - self.contentInset.top
if offset > (contentSize.height - viewAreaHeight)
offset = contentSize.height - viewAreaHeight
if offset < -self.contentInset.top
offset = -self.contentInset.top
return offset
func TPKeyboardAvoiding_contentInsetForKeyboard() -> UIEdgeInsets
let state = self.keyboardAvoidingState()
var newInset = self.contentInset;
let keyboardRect = state.keyboardRect
newInset.bottom = keyboardRect.size.height - max(keyboardRect.maxY - self.bounds.maxY, 0)
return newInset
func TPKeyboardAvoiding_viewIsValidKeyViewCandidate(_ view:UIView)->Bool
if view.isHidden || !view.isUserInteractionEnabled return false
if view is UITextField
if (view as! UITextField).isEnabled return true
if view is UITextView
if (view as! UITextView).isEditable return true
return false
func TPKeyboardAvoiding_findNextInputViewAfterView(_ priorView:UIView,beneathView view:UIView, candidateView bestCandidate: inout UIView?)
let priorFrame = self.convert(priorView.frame, to: priorView.superview)
let candidateFrame = bestCandidate == nil ? CGRect.zero : self.convert(bestCandidate!.frame, to: bestCandidate!.superview)
var bestCandidateHeuristic = -sqrt(candidateFrame.origin.x*candidateFrame.origin.x + candidateFrame.origin.y*candidateFrame.origin.y) + ( Float(fabs(candidateFrame.minY - priorFrame.minY))<Float.ulpOfOne ? 1e6 : 0)
for childView in view.subviews
if TPKeyboardAvoiding_viewIsValidKeyViewCandidate(childView)
let frame = self.convert(childView.frame, to: view)
let heuristic = -sqrt(frame.origin.x*frame.origin.x + frame.origin.y*frame.origin.y)
+ (Float(fabs(frame.minY - priorFrame.minY)) < Float.ulpOfOne ? 1e6 : 0)
if childView != priorView && (Float(fabs(frame.minY - priorFrame.minY)) < Float.ulpOfOne
&& frame.minX > priorFrame.minX
|| frame.minY > priorFrame.minY)
&& (bestCandidate == nil || heuristic > bestCandidateHeuristic)
bestCandidate = childView
bestCandidateHeuristic = heuristic
else
self.TPKeyboardAvoiding_findNextInputViewAfterView(priorView, beneathView: view, candidateView: &bestCandidate)
func TPKeyboardAvoiding_findNextInputViewAfterView(_ priorView:UIView,beneathView view:UIView) ->UIView?
var candidate:UIView?
self.TPKeyboardAvoiding_findNextInputViewAfterView(priorView, beneathView: view, candidateView: &candidate)
return candidate
@objc func TPKeyboardAvoiding_assignTextDelegateForViewsBeneathView(_ obj: AnyObject)
func processWithView(_ view: UIView)
for childView in view.subviews
if childView is UITextField || childView is UITextView
self.TPKeyboardAvoiding_initializeView(childView)
else
self.TPKeyboardAvoiding_assignTextDelegateForViewsBeneathView(childView)
if let timer = obj as? Timer, let view = timer.userInfo as? UIView
processWithView(view)
else if let view = obj as? UIView
processWithView(view)
func TPKeyboardAvoiding_initializeView(_ view:UIView)
if let textField = view as? UITextField,
let delegate = self as? UITextFieldDelegate, textField.returnKeyType == UIReturnKeyType.default &&
textField.delegate !== delegate
textField.delegate = delegate
let otherView = self.TPKeyboardAvoiding_findNextInputViewAfterView(view, beneathView: self)
textField.returnKeyType = otherView != nil ? .next : .done
func keyboardAvoidingState()->TPKeyboardAvoidingState
var state = objc_getAssociatedObject(self, &AssociatedKeysKeyboard.DescriptiveName) as? TPKeyboardAvoidingState
if state == nil
state = TPKeyboardAvoidingState()
self.state = state
return self.state!
// MARK: - Internal object observer
internal class TPKeyboardAvoidingState:NSObject
var priorInset = UIEdgeInsets.zero
var priorScrollIndicatorInsets = UIEdgeInsets.zero
var keyboardVisible = false
var keyboardRect = CGRect.zero
var priorContentSize = CGSize.zero
var priorPagingEnabled = false
internal extension UIScrollView
fileprivate struct AssociatedKeysKeyboard
static var DescriptiveName = "KeyBoard_DescriptiveName"
var state:TPKeyboardAvoidingState?
get
let optionalObject:AnyObject? = objc_getAssociatedObject(self, &AssociatedKeysKeyboard.DescriptiveName) as AnyObject?
if let object:AnyObject = optionalObject
return object as? TPKeyboardAvoidingState
else
return nil
set
objc_setAssociatedObject(self, &AssociatedKeysKeyboard.DescriptiveName, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
【讨论】:
相应的意思是,滚动视图、表格视图或集合视图,无论您使用的是哪个。【参考方案2】:将文件添加到您的项目中,而不是为每个 .h 文件添加一个桥接头。然后您可以使用 Storyboard 将自定义类更改为相关类。
【讨论】:
【参考方案3】:要运行示例项目,请克隆存储库,然后首先从示例目录运行 pod install。 安装 TPKeyboardAvoidingSwift 可通过 CocoaPods 获得。要安装它,只需将以下行添加到您的 Podfile 中: pod "TPKeyboardAvoidingSwift" 给 TPKeyboardAvoiding 滚动视图。 https://github.com/apple-avadhesh/TPKeyboardAvoiding
【讨论】:
我认为给定的代码会更新到 swift 3.0。所以我对 Swift 4.1 中的代码编译做了一些小改动,答案贴在下面【参考方案4】:您可以像在 Obj-C 应用程序中一样使用它。
首先将 pod 'TPKeyboardAvoiding' 添加到您的 Podfile。
然后只需将 TPKeyboardAvoiding 导入 .swift 文件。
示例用法:
private var scrollView: TPKeyboardAvoidingScrollView!
(...)
self.scrollView = TPKeyboardAvoidingScrollView()
行为将与 Obj-C 应用程序中的行为相同
【讨论】:
以上是关于如何在 swift 项目中使用 TPKeyboardAvoiding?的主要内容,如果未能解决你的问题,请参考以下文章
Swift - 如何对 ScrollView 中的项目使用自动布局?
如何在目标 c-swift 桥接项目中使用 intentdefinition 文件?