键盘打破了 UICollectionViewController 中的布局
Keyboard breaks layout in UICollectionViewController
我有一个水平的 UICollectionViewController,其中每个单元格的底部都包含一个 UITextView。当我在 UITextView 内部点击时,当键盘出现时,CollectionView 的高度减少了 260 点(我注意到这是键盘的高度)然后增加了 130 点,所以最终高度比期望的低 130 点。
你知道框架为什么会以这种方式变化吗?
我在下面包含了最相关的部分,或者您可以在此处找到测试项目:https://github.com/johntiror/testAutomaticPush/tree/master
UIViewController(简单地启动 CollectionViewController):
class ViewController: UIViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let layout = UICollectionViewFlowLayout()
layout.itemSize = view.bounds.size
layout.scrollDirection = .horizontal
layout.minimumLineSpacing = 0
let fsPicVC = CollectionViewController(collectionViewLayout: layout)
self.present(fsPicVC, animated: true) { }
}
}
集合视图控制器:
class CollectionViewController: UICollectionViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.collectionView!.register(CollectionViewCell.self, forCellWithReuseIdentifier: "Cell")
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)
return cell
}
}
非常感谢
我发现您的代码不寻常的地方在于,您将文本字段放在容器视图的单元格中。我不确定您还打算在那个容器视图中放入什么...但是让您的文本框放在那里对我来说有点奇怪。
话虽如此...您遇到的问题是:当键盘显示时,系统有两个相互竞争的目标。首先,它试图滚动集合视图的内容,以便文本字段保持可见。这给了很大的跳跃。然后它试图调整集合视图的大小以使其不妨碍键盘,但由于流布局设置,它变得非常混乱。如果您查看日志,您会看到很多:
2017-05-02 23:39:30.671 automaticPush[31629:1628527] The behavior of
the UICollectionViewFlowLayout is not defined because: 2017-05-02
23:39:30.671 automaticPush[31629:1628527] the item height must be less
than the height of the UICollectionView minus the section insets top
and bottom values, minus the content insets top and bottom values.
2017-05-02 23:39:30.672 automaticPush[31629:1628527] The relevant
UICollectionViewFlowLayout instance is , and it is attached to ;
layer = ; contentOffset: {0, 0}; contentSize:
{3750, 667}> collection view layout: . 2017-05-02 23:39:30.672 automaticPush[31629:1628527]
Make a symbolic breakpoint at
UICollectionViewFlowLayoutBreakForInvalidSizes to catch this in the
debugger.
由于系统拼命尝试协调集合视图的约束与保持文本视图可见的尝试
总而言之,我建议您将文本视图放在集合视图之外。
首先,我要说故事板很棒 :)
对于此用例,您可能不想使用 CollectionViewController。如果您决定使用它,我还发布了另一个答案。这是将 CollectionView 移动到 ViewController 的最快方法。这解决了您的问题,但不考虑自动布局。
1) 替换 ViewController 中的这些行:
let fsPicVC = CollectionViewController(collectionViewLayout: layout)
self.present(fsPicVC, animated: true) { }
和
let collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout)
collectionView.register(CollectionViewCell.self, forCellWithReuseIdentifier: "Cell")
collectionView.dataSource = self
view.addSubview(collectionView)
2) 将此添加到 ViewController 的最底部(在 ViewController class 之外):
extension ViewController: UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)
// Configure the cell
return cell
}
}
最后,您可以删除收藏集ViewController,因为它现在已被替换。
PS 您可能还想 1) 扩展 ViewController 以符合 UICollectionViewDelegateFlowLayout 和 2) 使 collectionView 全局化。
如果你想使用 CollectionViewController,这里有一个可能的解决方案(如果你想切换到通用 ViewController 请参阅我的其他答案)。
将以下内容添加到您的收藏中ViewController class:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Try commenting out this line to see the animation. It's included so the user doesn't see the fade effect
collectionView?.backgroundColor = UIColor.orange
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillChangeFrame), name: NSNotification.Name.UIKeyboardWillChangeFrame, object: nil)
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
NotificationCenter.default.removeObserver(self)
}
func keyboardWillChangeFrame(notification: NSNotification) {
guard let info = notification.userInfo else {return}
guard let keyboardFrameEndValue = info[UIKeyboardFrameEndUserInfoKey] as? NSValue else {return}
let keyboardFrameEnd = keyboardFrameEndValue.cgRectValue
var itemSize = view.bounds.size
itemSize.height = keyboardFrameEnd.origin.y
guard let layout = collectionView?.collectionViewLayout as? UICollectionViewFlowLayout else {return}
layout.itemSize = itemSize
collectionView?.setCollectionViewLayout(layout, animated: true)
}
演示 link : https://github.com/harshilkotecha/UIScrollViewWhenKeyboardAppearInSwift3
当你有多个 textview 时,它是如此困难,所以最好的解决方案 ->
第 1 步:将 Delegate 交给 UITextFieldDelegate
class ScrollViewController: UIViewController,UITextFieldDelegate {
第 2 步:创建新的 IBOutlet 但不连接故事板中的任何文本字段。
// get current text box when user Begin editing
@IBOutlet weak var activeTextField: UITextField?
第 3 步:当用户关注文本字段对象时编写这两个方法传递引用并存储在 activeTextField 中。
// get current text field
func textFieldDidBeginEditing(_ textField: UITextField)
{
activeTextField=textField;
}
func textFieldDidEndEditing(_ textField: UITextField)
{
activeTextField=nil;
}
第 4 步:在 viewdidload() setNotificationKeyboard
中设置通知
override func viewWillAppear(_ animated: Bool) {
// call method for keyboard notification
self.setNotificationKeyboard()
}
// Notification when keyboard show
func setNotificationKeyboard () {
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWasShown(notification:)), name: .UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillBeHidden(notification:)), name: .UIKeyboardWillHide, object: nil)
}
第 5 步:键盘出现和消失的两种方法。
func keyboardWasShown(notification: NSNotification)
{
var info = notification.userInfo!
let keyboardSize = (info[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue.size
let contentInsets : UIEdgeInsets = UIEdgeInsetsMake(0.0, 0.0, keyboardSize!.height+10, 0.0)
self.scrollView.contentInset = contentInsets
self.scrollView.scrollIndicatorInsets = contentInsets
var aRect : CGRect = self.view.frame
aRect.size.height -= keyboardSize!.height
if let activeField = self.activeTextField
{
if (!aRect.contains(activeField.frame.origin))
{
self.scrollView.scrollRectToVisible(activeField.frame, animated: true)
}
}
}
// when keyboard hide reduce height of scroll view
func keyboardWillBeHidden(notification: NSNotification){
let contentInsets : UIEdgeInsets = UIEdgeInsetsMake(0.0, 0.0,0.0, 0.0)
self.scrollView.contentInset = contentInsets
self.scrollView.scrollIndicatorInsets = contentInsets
self.view.endEditing(true)
}
我遇到过这样的情况,我有一个视图控制器,在屏幕底部有一个嵌入式 UICollectionViewController 运行。当键盘出现时,它会更改嵌入式控制器的 contentInset。在我看来没有必要这样做 - 键盘(部分)掩盖它是可以的。我最终做的是在嵌入式 UICollectionViewController 中重置 viewWillLayoutSubviews() 中的 contentInset: (Swift 4)
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
collectionView?.contentInset = UIEdgeInsets.zero
}
我有一个水平的 UICollectionViewController,其中每个单元格的底部都包含一个 UITextView。当我在 UITextView 内部点击时,当键盘出现时,CollectionView 的高度减少了 260 点(我注意到这是键盘的高度)然后增加了 130 点,所以最终高度比期望的低 130 点。
你知道框架为什么会以这种方式变化吗?
我在下面包含了最相关的部分,或者您可以在此处找到测试项目:https://github.com/johntiror/testAutomaticPush/tree/master
UIViewController(简单地启动 CollectionViewController):
class ViewController: UIViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let layout = UICollectionViewFlowLayout()
layout.itemSize = view.bounds.size
layout.scrollDirection = .horizontal
layout.minimumLineSpacing = 0
let fsPicVC = CollectionViewController(collectionViewLayout: layout)
self.present(fsPicVC, animated: true) { }
}
}
集合视图控制器:
class CollectionViewController: UICollectionViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.collectionView!.register(CollectionViewCell.self, forCellWithReuseIdentifier: "Cell")
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)
return cell
}
}
非常感谢
我发现您的代码不寻常的地方在于,您将文本字段放在容器视图的单元格中。我不确定您还打算在那个容器视图中放入什么...但是让您的文本框放在那里对我来说有点奇怪。
话虽如此...您遇到的问题是:当键盘显示时,系统有两个相互竞争的目标。首先,它试图滚动集合视图的内容,以便文本字段保持可见。这给了很大的跳跃。然后它试图调整集合视图的大小以使其不妨碍键盘,但由于流布局设置,它变得非常混乱。如果您查看日志,您会看到很多:
2017-05-02 23:39:30.671 automaticPush[31629:1628527] The behavior of the UICollectionViewFlowLayout is not defined because: 2017-05-02 23:39:30.671 automaticPush[31629:1628527] the item height must be less than the height of the UICollectionView minus the section insets top and bottom values, minus the content insets top and bottom values. 2017-05-02 23:39:30.672 automaticPush[31629:1628527] The relevant UICollectionViewFlowLayout instance is , and it is attached to ; layer = ; contentOffset: {0, 0}; contentSize: {3750, 667}> collection view layout: . 2017-05-02 23:39:30.672 automaticPush[31629:1628527] Make a symbolic breakpoint at UICollectionViewFlowLayoutBreakForInvalidSizes to catch this in the debugger.
由于系统拼命尝试协调集合视图的约束与保持文本视图可见的尝试
总而言之,我建议您将文本视图放在集合视图之外。
首先,我要说故事板很棒 :)
对于此用例,您可能不想使用 CollectionViewController。如果您决定使用它,我还发布了另一个答案。这是将 CollectionView 移动到 ViewController 的最快方法。这解决了您的问题,但不考虑自动布局。
1) 替换 ViewController 中的这些行:
let fsPicVC = CollectionViewController(collectionViewLayout: layout)
self.present(fsPicVC, animated: true) { }
和
let collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout)
collectionView.register(CollectionViewCell.self, forCellWithReuseIdentifier: "Cell")
collectionView.dataSource = self
view.addSubview(collectionView)
2) 将此添加到 ViewController 的最底部(在 ViewController class 之外):
extension ViewController: UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)
// Configure the cell
return cell
}
}
最后,您可以删除收藏集ViewController,因为它现在已被替换。
PS 您可能还想 1) 扩展 ViewController 以符合 UICollectionViewDelegateFlowLayout 和 2) 使 collectionView 全局化。
如果你想使用 CollectionViewController,这里有一个可能的解决方案(如果你想切换到通用 ViewController 请参阅我的其他答案)。
将以下内容添加到您的收藏中ViewController class:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Try commenting out this line to see the animation. It's included so the user doesn't see the fade effect
collectionView?.backgroundColor = UIColor.orange
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillChangeFrame), name: NSNotification.Name.UIKeyboardWillChangeFrame, object: nil)
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
NotificationCenter.default.removeObserver(self)
}
func keyboardWillChangeFrame(notification: NSNotification) {
guard let info = notification.userInfo else {return}
guard let keyboardFrameEndValue = info[UIKeyboardFrameEndUserInfoKey] as? NSValue else {return}
let keyboardFrameEnd = keyboardFrameEndValue.cgRectValue
var itemSize = view.bounds.size
itemSize.height = keyboardFrameEnd.origin.y
guard let layout = collectionView?.collectionViewLayout as? UICollectionViewFlowLayout else {return}
layout.itemSize = itemSize
collectionView?.setCollectionViewLayout(layout, animated: true)
}
演示 link : https://github.com/harshilkotecha/UIScrollViewWhenKeyboardAppearInSwift3
当你有多个 textview 时,它是如此困难,所以最好的解决方案 ->
第 1 步:将 Delegate 交给 UITextFieldDelegate
class ScrollViewController: UIViewController,UITextFieldDelegate {
第 2 步:创建新的 IBOutlet 但不连接故事板中的任何文本字段。
// get current text box when user Begin editing
@IBOutlet weak var activeTextField: UITextField?
第 3 步:当用户关注文本字段对象时编写这两个方法传递引用并存储在 activeTextField 中。
// get current text field
func textFieldDidBeginEditing(_ textField: UITextField)
{
activeTextField=textField;
}
func textFieldDidEndEditing(_ textField: UITextField)
{
activeTextField=nil;
}
第 4 步:在 viewdidload() setNotificationKeyboard
中设置通知override func viewWillAppear(_ animated: Bool) {
// call method for keyboard notification
self.setNotificationKeyboard()
}
// Notification when keyboard show
func setNotificationKeyboard () {
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWasShown(notification:)), name: .UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillBeHidden(notification:)), name: .UIKeyboardWillHide, object: nil)
}
第 5 步:键盘出现和消失的两种方法。
func keyboardWasShown(notification: NSNotification)
{
var info = notification.userInfo!
let keyboardSize = (info[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue.size
let contentInsets : UIEdgeInsets = UIEdgeInsetsMake(0.0, 0.0, keyboardSize!.height+10, 0.0)
self.scrollView.contentInset = contentInsets
self.scrollView.scrollIndicatorInsets = contentInsets
var aRect : CGRect = self.view.frame
aRect.size.height -= keyboardSize!.height
if let activeField = self.activeTextField
{
if (!aRect.contains(activeField.frame.origin))
{
self.scrollView.scrollRectToVisible(activeField.frame, animated: true)
}
}
}
// when keyboard hide reduce height of scroll view
func keyboardWillBeHidden(notification: NSNotification){
let contentInsets : UIEdgeInsets = UIEdgeInsetsMake(0.0, 0.0,0.0, 0.0)
self.scrollView.contentInset = contentInsets
self.scrollView.scrollIndicatorInsets = contentInsets
self.view.endEditing(true)
}
我遇到过这样的情况,我有一个视图控制器,在屏幕底部有一个嵌入式 UICollectionViewController 运行。当键盘出现时,它会更改嵌入式控制器的 contentInset。在我看来没有必要这样做 - 键盘(部分)掩盖它是可以的。我最终做的是在嵌入式 UICollectionViewController 中重置 viewWillLayoutSubviews() 中的 contentInset: (Swift 4)
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
collectionView?.contentInset = UIEdgeInsets.zero
}