UICollectionViewController 页脚在 endEditing 后不 re-positioning 本身正确

UICollectionViewController footer not re-positioning itself properly after endEditing

我有一个简单的 UICollectionViewController,这是一个带有固定到可见边界的页眉和页脚的部分。当我单击页脚中的 textField 时,页脚会自动在键盘上方设置动画,并且 collectionView 会滚动以显示单元格,否则这些单元格会像您预期的那样被键盘隐藏。但是,当我在键盘外部单击一个并通过调用 self.view.endEditing(true) 将其关闭时,页脚和 collectionView 不会做出反应,除非 collectionView 滚动到底部附近。如果 collectionView 滚动到底部附近,页脚和 collectionView 会按预期进行动画处理。

如何强制页脚和 collectionView 每次都正确设置动画?

在下图中,页眉为橙色,页脚为绿色,并且有 10 个红蓝交替的单元格。

实际行为(当键盘关闭时您没有滚动到 collectionView 底部附近时发生)

Desired Behavior(当您在 collectionView 的底部或附近滚动时键盘消失时发生)

我知道有一些方法可以在键盘出现和消失时使用通知来实现这一点,但我更愿意尽可能使用默认的 UICollectionViewController 行为。是否缺少某些配置或设置?

代码: 新的单视图应用程序。删除 StoryBoard 和 ViewController.swift。从信息 plist 文件中删除主要故事板条目。

AppDelegate.swift

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

var window: UIWindow?

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

    window = UIWindow(frame: UIScreen.main.bounds)
    window?.makeKeyAndVisible()
    window?.rootViewController = CollectionViewController.init()

    return true
}
}

CollectionViewController.swift

import UIKit

private let reuseIdentifier = "Cell"
private let collectionViewHeaderFooterReuseIdentifier = "MyHeaderFooterClass"

class CollectionViewController: UICollectionViewController {

init() {
    let collectionViewFlowLayout = UICollectionViewFlowLayout.init()
    collectionViewFlowLayout.sectionHeadersPinToVisibleBounds = true
    collectionViewFlowLayout.sectionFootersPinToVisibleBounds = true
    super.init(collectionViewLayout: collectionViewFlowLayout)
}

required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
}

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
}

override func viewDidLoad() {
    super.viewDidLoad()
    // Register cell classes
    self.collectionView?.register(UICollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier)
    self.collectionView?.register(MyHeaderFooterClass.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: collectionViewHeaderFooterReuseIdentifier)
    self.collectionView?.register(MyHeaderFooterClass.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter, withReuseIdentifier: collectionViewHeaderFooterReuseIdentifier)

    self.collectionView?.backgroundColor = UIColor.white

    self.collectionView?.dataSource = self
    self.collectionView?.delegate = self
}
}

// MARK: UICollectionViewDelegate
extension CollectionViewController {
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
    DispatchQueue.main.async {
        self.view.endEditing(true)
        self.collectionViewLayout.invalidateLayout()
        self.collectionView?.layoutIfNeeded()
    }
}
}

// MARK: - UICollectionViewDataSource
extension CollectionViewController {
override func numberOfSections(in collectionView: UICollectionView) -> Int {
    // #warning Incomplete implementation, return the number of sections
    return 1
}

override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    // #warning Incomplete implementation, return the number of items
    return 10
}

override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath)

    switch indexPath.row % 2 {
    case 0:
        cell.backgroundColor = UIColor.red
    case 1:
        cell.backgroundColor = UIColor.blue
    default:
        cell.backgroundColor = UIColor.gray
    }

    return cell
}

override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
    let view = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: collectionViewHeaderFooterReuseIdentifier, for: indexPath)

    if kind == UICollectionView.elementKindSectionHeader {
        view.backgroundColor = UIColor.orange
    }
    else { //footer
        view.backgroundColor = UIColor.green
    }

    return view
}
}

// MARK: - Collection View Flow Layout Delegate
extension CollectionViewController: UICollectionViewDelegateFlowLayout {

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    return CGSize(width: collectionView.frame.width, height: 100)
}

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
    return UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
}

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
    return 0
}

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
    return CGSize(width: collectionView.frame.width, height: 100)
}

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize {
    return CGSize(width: collectionView.frame.width, height: 100)
}
}

MyHeaderFooterClass

import UIKit

class MyHeaderFooterClass: UICollectionReusableView {
let textField: UITextField = {
    let view = UITextField.init()
    view.placeholder = "enter text here"
    view.translatesAutoresizingMaskIntoConstraints = false
    return view
}()

override init(frame: CGRect) {
    super.init(frame: frame)

    self.addSubview(textField)
    self.setNeedsUpdateConstraints()
}

override func updateConstraints() {
    textFieldConstraints()
    super.updateConstraints()
}

private func textFieldConstraints() {
    NSLayoutConstraint(item: textField, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1.0, constant: 0.0).isActive = true
    NSLayoutConstraint(item: textField, attribute: .left, relatedBy: .equal, toItem: self, attribute: .left, multiplier: 1.0, constant: 0.0).isActive = true
    NSLayoutConstraint(item: textField, attribute: .right, relatedBy: .equal, toItem: self, attribute: .right, multiplier: 1.0, constant: 0.0).isActive = true
    NSLayoutConstraint(item: textField, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1.0, constant: 0.0).isActive = true
}

required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}
}

来自文档:

func layoutIfNeeded()

Use this method to force the view to update its layout immediately.

func invalidateLayout()

This method invalidates the layout of the collection view itself and returns right away.

解法:

查看您的代码,您已经在更新 collectionViewLayout。因此,您无需再次强制更新 collectionView。因此,只需删除 self.collectionView?.layoutIfNeeded() 即可解决您的问题。

如下更改代码:

override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
    DispatchQueue.main.async {
        self.view.endEditing(true)
        self.collectionViewLayout.invalidateLayout()
        //self.collectionView?.layoutIfNeeded()
    }
}