Swift CollectionViewCell 的框架与其 CollectionView 不同

Swift CollectionViewCell has different frame than its CollectionView

我有一个 CollectionView,它填满了整个屏幕,具有水平 scrollDirection 并且启用了分页。它位于 NavigationController 内部,隐藏 navigationBarCollectionViewCells 也有屏幕的高度。

我希望发生的事情:
每个单元格都会填满整个屏幕,当我滑动到 left/right 时,另一个单元格会出现并填满整个屏幕。

实际发生了什么:
细胞没有填满整个屏幕。单元格顶部和屏幕顶部边框之间有一点 space(大约 20 像素)。此外,单元格的底部(也是大约 20 像素)是 "under" 屏幕的底部边框。
我也收到警告:

The behavior of the UICollectionViewFlowLayout is not defined because:
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.

因此单元格高度看起来是正确的,但 y 值是错误的。

我已经尝试过的: override shouldInvalidateLayoutForBoundsChange 到 return 正确。

如你所见here蓝色边框是collectionView的边框,红色边框是cell的边框。蓝色边框如我所料,但我希望红色边框与蓝色边框一样。

代码如下:

class LoginController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout, LoginControllerDelegate, UIGestureRecognizerDelegate {

    lazy var loginCollectionView: UICollectionView = {
        let layout = UICollectionViewFlowLayout()
        layout.scrollDirection = .horizontal 
        layout.minimumLineSpacing = 0
        let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
        cv.dataSource = self
        cv.delegate = self
        cv.isPagingEnabled = true
        cv.layer.borderWidth = 1.0
        cv.layer.borderColor = UIColor.blue.cgColor
        return cv
    }()

    let introCellId = "IntroCellId"
    let loginCellId = "LoginCellId"

    let pages: [Page] = {
        let firstPage = Page(title: String,
                             message: String,
                             imageName: String)

        let secondPage = Page(title: String,
                             message: String,
                             imageName: String)

        let thirdPage = Page(title: String,
                             message: String,
                             imageName: String)

        return [firstPage, secondPage, thirdPage]
    }()

    override func viewDidLoad() {        
        super.viewDidLoad()

        navigationController?.navigationBar.isHidden = true

        view.addSubview(loginCollectionView)

        registerCells()
    }

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return pages.count + 1
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {

        if indexPath.item == pages.count {
            let loginCell = collectionView.dequeueReusableCell(withReuseIdentifier: loginCellId, for: indexPath) as! LoginCell
            loginCell.loginControllerDelegate = self
            loginCell.layer.borderColor = UIColor.red.cgColor
            loginCell.layer.borderWidth = 2.0
            return loginCell
        }

        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: introCellId, for: indexPath) as! PageCell
        let page = pages[indexPath.item]
        cell.page = page
        cell.layer.borderColor = UIColor.red.cgColor
        cell.layer.borderWidth = 2.0
        return cell
    }

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

页面单元格:

class PageCell: UICollectionViewCell {

    var page: Page? {
        didSet {
            guard let page = page
            else {
                return
            }

            imageView.image = UIImage(named: page.imageName)

            let color = UIColor(white: 0.2, alpha: 1)

            let attributedText = NSMutableAttributedString(string: page.title, attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 18, weight: UIFont.Weight.medium), NSAttributedString.Key.foregroundColor: color])

            attributedText.append(NSMutableAttributedString(string: "\n\n\(page.message)", attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 15, weight: UIFont.Weight.medium), NSAttributedString.Key.foregroundColor: color]))

            let paragraphStyle = NSMutableParagraphStyle()

            paragraphStyle.alignment = .center

            let length = attributedText.string.count

            attributedText.addAttributes([NSAttributedString.Key.paragraphStyle: paragraphStyle], range: NSRange(location: 0, length: length))

            textView.attributedText = attributedText
        }
    }

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

        setupViews()
    }

    let imageView: UIImageView = {
        let iv = UIImageView()
        iv.contentMode = .scaleAspectFill
        iv.backgroundColor = UIColor(displayP3Red: 139/255, green: 221/255, blue: 116/255, alpha: 1)
        iv.clipsToBounds = true
        return iv
    }()

    let textView: UITextView = {
        let tv = UITextView()
        tv.isEditable = false
        tv.contentInset = UIEdgeInsets(top: 24, left: 0, bottom: 0, right: 0)
        tv.textColor = UIColor(displayP3Red: 51/255, green: 51/255, blue: 51/255, alpha: 1)
        return tv
    }()

    func setupViews() {

        addSubview(imageView)
        addSubview(textView)

        _ = imageView.anchor(topAnchor, left: leftAnchor, bottom: nil, right: rightAnchor, topConstant: 0, leftConstant: 0, bottomConstant: 0, rightConstant: 0, widthConstant: screenWidth, heightConstant: (screenHeight) / 2)

        _ = textView.anchor(imageView.bottomAnchor, left: leftAnchor, bottom: bottomAnchor, right: rightAnchor, topConstant: 0, leftConstant: 0, bottomConstant: 0, rightConstant: 0, widthConstant: screenWidth, heightConstant: (screenHeight) / 2)

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

登录单元格:

class LoginCell: UICollectionViewCell {

    let logoImageView: UIImageView = {
        let image = UIImage(named: "logo green")
        let imageView = UIImageView(image: image)
        return imageView
    }()

    let emailTextField: UITextField = {
        let textField = UITextField()
        textField.placeholder = "E-Mail"
        textField.layer.borderColor = UIColor.lightGray.cgColor
        let border = CALayer()
        border.frame = CGRect(x: 0, y: 40, width: UIScreen.main.bounds.width - 64, height: 2.0)
        border.backgroundColor = UIColor(displayP3Red: 51/255, green: 51/255, blue: 51/255, alpha: 1).cgColor
        textField.layer.addSublayer(border)
        textField.keyboardType = .emailAddress
        return textField
    }()

    let emailAlertLabel: UILabel = {
        let label = UILabel()
        label.text = "E-Mail-Adresse nicht gefunden"
        label.textColor = .red
        label.isHidden = true
        return label
    }()

    let passwordTextField: UITextField = {
        let textField = UITextField()
        textField.placeholder = "Passwort"
        textField.layer.borderColor = UIColor.lightGray.cgColor
        let border = CALayer()
        border.frame = CGRect(x: 0, y: 40, width: UIScreen.main.bounds.width - 64, height: 2.0)
        border.backgroundColor = UIColor(displayP3Red: 51/255, green: 51/255, blue: 51/255, alpha: 1).cgColor
        textField.layer.addSublayer(border)
        textField.isSecureTextEntry = true
        return textField
    }()

    let passwordAlertLabel: UILabel = {
        let label = UILabel()
        label.text = "Ungültiges Passwort"
        label.textColor = .red
        label.isHidden = true
        return label
    }()

    lazy var forgotPasswordButton: UIButton = {
        let button = UIButton(type: .system)
        button.backgroundColor = .white
        button.setTitle("Passwort vergessen?", for: .normal)
        button.setTitleColor(UIColor(displayP3Red: 139/255, green: 221/255, blue: 116/255, alpha: 1), for: .normal)
        button.addTarget(self, action: #selector(handleForgotPassword), for: .touchUpInside)
        button.contentHorizontalAlignment = .left
        return button
    }()

    lazy var loginButton: UIButton = {
        let button = UIButton(type: .system)
        button.backgroundColor = UIColor(displayP3Red: 139/255, green: 221/255, blue: 116/255, alpha: 1)
        button.setTitle("Login", for: .normal)
        button.setTitleColor(.white, for: .normal)
        button.addTarget(self, action: #selector(checkUserData), for: .touchUpInside)
        button.layer.cornerRadius = 25.0
        return button
    }()

    weak var loginControllerDelegate: LoginControllerDelegate?

    lazy var stayLoggedInSwitch: UISwitch = {
        let loginSwitch = UISwitch(frame: CGRect(x: 150, y: 150, width: 0, height: 0))
        loginSwitch.addTarget(self, action: #selector(LoginCell.handleStayLoggedInState(_:)), for: .valueChanged)
        loginSwitch.setOn(false, animated: true)
        return loginSwitch
    }()

    let stayLoggedInTextField: UITextField = {
        let textField = UITextField()
        textField.text = "Angemeldet bleiben"
        textField.textColor = UIColor(displayP3Red: 51/255, green: 51/255, blue: 51/255, alpha: 1)
        textField.isUserInteractionEnabled = false
        return textField
    }()

    let registerLabel: UILabel = {
        let label = UILabel()
        label.text = "Sie haben noch keinen Account?"
        label.textColor = .lightGray
        label.backgroundColor = .white
        label.font = label.font.withSize(13)
        return label
    }()

    lazy var registerButton: UIButton = {
        let button = UIButton(type: .system)
        button.backgroundColor = .white
        button.setTitle("Registrieren", for: .normal)
        button.setTitleColor(UIColor(displayP3Red: 139/255, green: 221/255, blue: 116/255, alpha: 1), for: .normal)
        button.addTarget(self, action: #selector(handleRegister), for: .touchUpInside)
        button.contentHorizontalAlignment = .left
        button.titleLabel?.font = button.titleLabel?.font.withSize(13)
        return button
    }()

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

        setupViews()
    }

    func setupViews() {

        addSubview(logoImageView)
        addSubview(emailTextField)
        addSubview(emailAlertLabel)
        addSubview(passwordTextField)
        addSubview(passwordAlertLabel)
        addSubview(forgotPasswordButton)
        addSubview(loginButton)
        addSubview(stayLoggedInSwitch)
        addSubview(stayLoggedInTextField)
        addSubview(registerLabel)
        addSubview(registerButton)

        _ = logoImageView.anchor(centerYAnchor, left: nil, bottom: nil, right: nil, topConstant: -230, leftConstant: 0, bottomConstant: 0, rightConstant: 0, widthConstant: 160, heightConstant: 160)
        logoImageView.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true

        _ = emailTextField.anchor(logoImageView.bottomAnchor, left: leftAnchor, bottom: nil, right: rightAnchor, topConstant: 35, leftConstant: 32, bottomConstant: 0, rightConstant: 32, widthConstant: 0, heightConstant: 50)

        _ = emailAlertLabel.anchor(emailTextField.bottomAnchor, left: leftAnchor, bottom: nil, right: rightAnchor, topConstant: 0, leftConstant: 32, bottomConstant: 0, rightConstant: 32, widthConstant: 0, heightConstant: 20)

        _ = passwordTextField.anchor(emailAlertLabel.bottomAnchor, left: leftAnchor, bottom: nil, right: rightAnchor, topConstant: 0, leftConstant: 32, bottomConstant: 0, rightConstant: 32, widthConstant: 0, heightConstant: 50)

        _ = passwordAlertLabel.anchor(passwordTextField.bottomAnchor, left: leftAnchor, bottom: nil, right: rightAnchor, topConstant: 0, leftConstant: 32, bottomConstant: 0, rightConstant: 32, widthConstant: 0, heightConstant: 20)

        _ = forgotPasswordButton.anchor(passwordAlertLabel.bottomAnchor, left: leftAnchor, bottom: nil, right: nil, topConstant: 0, leftConstant: 32, bottomConstant: 0, rightConstant: 0, widthConstant: 200, heightConstant: 25)

        _ = loginButton.anchor(forgotPasswordButton.bottomAnchor, left: leftAnchor, bottom: nil, right: rightAnchor, topConstant: 4, leftConstant: 32, bottomConstant: 0, rightConstant: 32, widthConstant: 0, heightConstant: 50)

        _ = stayLoggedInSwitch.anchor(loginButton.bottomAnchor, left: leftAnchor, bottom: nil, right: nil, topConstant: 16, leftConstant: 32, bottomConstant: 0, rightConstant: 0, widthConstant: 60, heightConstant: 50)

        _ = stayLoggedInTextField.anchor(loginButton.bottomAnchor, left: stayLoggedInSwitch.rightAnchor, bottom: nil, right: rightAnchor, topConstant: 16, leftConstant: 0, bottomConstant: 0, rightConstant: 32, widthConstant: 0, heightConstant: 32)

        _ = registerLabel.anchor(nil, left: leftAnchor, bottom: bottomAnchor, right: nil, topConstant: 0, leftConstant: 32, bottomConstant: 32, rightConstant: 0, widthConstant: 200, heightConstant: 25)

        _ = registerButton.anchor(nil, left: registerLabel.rightAnchor, bottom: bottomAnchor, right: rightAnchor, topConstant: 0, leftConstant: 0, bottomConstant: 32, rightConstant: 32, widthConstant: 0, heightConstant: 25)

        emailTextField.addTarget(self, action: #selector(underlineTextFieldColor(sender:)), for: .editingDidBegin)
        emailTextField.addTarget(self, action: #selector(underlineTextFieldDark(sender:)), for: .editingDidEnd)
        passwordTextField.addTarget(self, action: #selector(underlineTextFieldColor(sender:)), for: .editingDidBegin)
        passwordTextField.addTarget(self, action: #selector(underlineTextFieldDark(sender:)), for: .editingDidEnd)
    }

    @objc func underlineTextFieldDark(sender: UITextField) {
        sender.layer.sublayers![0].backgroundColor = UIColor(displayP3Red: 51/255, green: 51/255, blue: 51/255, alpha: 1).cgColor
    }

    @objc func underlineTextFieldColor(sender: UITextField) {
        sender.layer.sublayers![0].backgroundColor = UIColor(displayP3Red: 139/255, green: 221/255, blue: 116/255, alpha: 1).cgColor
    }

    @objc func handleLogin() {
        loginControllerDelegate?.finishLoggingIn()
    }

    @objc func handleStayLoggedInState(_ sender: UISwitch) {
        if (sender.isOn == true) {
            UserDefaults.standard.setIsLoggedIn(value: true)
        }
        else {
            UserDefaults.standard.setIsLoggedIn(value: false)
        }
    }

    @objc func handleForgotPassword() {
        loginControllerDelegate?.finishLoggingIn()
    }

    @objc func handleRegister() {
        loginControllerDelegate?.finishLoggingIn()
    }

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

锚点方法:

extension UIView {

    func anchor(_ top: NSLayoutYAxisAnchor? = nil, left: NSLayoutXAxisAnchor? = nil, bottom: NSLayoutYAxisAnchor? = nil, right: NSLayoutXAxisAnchor? = nil, topConstant: CGFloat = 0, leftConstant: CGFloat = 0, bottomConstant: CGFloat = 0, rightConstant: CGFloat = 0, widthConstant: CGFloat = 0, heightConstant: CGFloat = 0) -> [NSLayoutConstraint] {
        translatesAutoresizingMaskIntoConstraints = false

        var anchors = [NSLayoutConstraint]()

        if let top = top {
            anchors.append(topAnchor.constraint(equalTo: top, constant: topConstant))
        }

        if let left = left {
            anchors.append(leftAnchor.constraint(equalTo: left, constant: leftConstant))
        }

        if let bottom = bottom {
            anchors.append(bottomAnchor.constraint(equalTo: bottom, constant: -bottomConstant))
        }

        if let right = right {
            anchors.append(rightAnchor.constraint(equalTo: right, constant: -rightConstant))
        }

        if widthConstant > 0 {
            anchors.append(widthAnchor.constraint(equalToConstant: widthConstant))
        }

        if heightConstant > 0 {
            anchors.append(heightAnchor.constraint(equalToConstant: heightConstant))
        }

        anchors.forEach({[=14=].isActive = true})

        return anchors
    }
}

集合视图自动设置为整体视图。如果您不设置任何行为,collectionView 的内容能够自动调整昆虫。在这种情况下,将自动调整设置为 false 以获得全屏视图。屏幕的顶部和底部有一个安全区域,用于通过凹口调整视图。

试试这个

collectionView?.contentInsetAdjustmentBehavior = .never

希望这会解决这个问题。