0

I have built a form where the user can write down his report. When I click on a textField the keyboard appears and the view is messed up and I can't figure out why it's happening. I also get this error message a bunch of times. Some informations of how I have built the view. The cells of this collectionsView are also collectionViews which show the view that is displayed (the part with the white background color). The light blue color (bottom) at the end of the gif is the background color of the homeContoller collectionView. The buttonContainer is a part of the newReportCell object which is a cell inside the homeController collectionView. It would be really helpful if someone has an idea of how to fix this bug.

2018-08-21 14:28:53.772211+0200 TestApp[91209:4755287] The behavior of the UICollectionViewFlowLayout is not defined because: 2018-08-21 14:28:53.772389+0200 TestApp[91209:4755287] 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. 2018-08-21 14:28:53.772610+0200 TestApp[91209:4755287] The relevant UICollectionViewFlowLayout instance is , and it is attached to ; layer = ; contentOffset: {768, 0}; contentSize: {1536, 858}; adjustedContentInset: {0, 0, 263, 0}> collection view layout: . 2018-08-21 14:28:53.772728+0200 TestApp[91209:4755287] Make a symbolic breakpoint at UICollectionViewFlowLayoutBreakForInvalidSizes to catch this in the debugger.

import UIKit

class HomeController: UICollectionViewController, UICollectionViewDelegateFlowLayout{

    let cellId = "cellId"
    let cellId2 = "cellId2"
    let appTitles = ["Test" , "TestView"]
    var currentIndexPath = IndexPath(item: 0, section: 0)

    lazy var menuBar: MenuBar = {
        let mb = MenuBar()
        mb.homeController = self
        mb.translatesAutoresizingMaskIntoConstraints = false
        return mb
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        initView()
    }

    func initView() {           
        navigationItem.title = "Test"
        navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Logout", style: .plain, target: self, action: #selector(handleLogout))

        navigationController?.navigationBar.prefersLargeTitles = true


        setupMenuBar()
        setupCollectionView()
        setupMenuButtons()

        observeKeyboardNotifications()
    }

    func setupCollectionView() {

        if let flowLayout = collectionView?.collectionViewLayout as? UICollectionViewFlowLayout {
            flowLayout.scrollDirection = .horizontal
            flowLayout.minimumLineSpacing = 0
        }

        // blue background color
        collectionView?.backgroundColor = UIColor(r: 198, g: 225, b: 243)

        collectionView?.register(FeedCell.self, forCellWithReuseIdentifier: cellId)
        collectionView?.register(NewReportCell.self, forCellWithReuseIdentifier: cellId2)

        collectionView?.translatesAutoresizingMaskIntoConstraints = false
        collectionView?.topAnchor.constraint(equalTo: menuBar.bottomAnchor).isActive = true
        collectionView?.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
        collectionView?.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
        collectionView?.heightAnchor.constraint(equalTo: view.heightAnchor, constant: -166).isActive =  true
        collectionView?.isPagingEnabled = true
    }

    func setupMenuButtons() {
        let synchroImage = UIImage(named: "synchronize")
        let synchroBarButtonItem = UIBarButtonItem(image: synchroImage, style: .plain, target: self, action: #selector(handleSync))
        navigationItem.rightBarButtonItems = [synchroBarButtonItem]
    }

    private func setTitleForIndex(index: Int) {
        navigationItem.title = appTitles[Int(index)]
        dismissKeyboard()
    }

    @objc func dismissKeyboard() {
        self.view.endEditing(true)
    }

    override func scrollViewDidScroll(_ scrollView: UIScrollView) {
        menuBar.horizontalBarLeadingAnchorConstraint?.constant = scrollView.contentOffset.x / CGFloat(appTitles.count)
    }

    override func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
        let index = targetContentOffset.pointee.x / view.frame.width

        let indexPath = IndexPath(item: Int(index), section: 0)
        currentIndexPath = indexPath
        menuBar.collectionView.selectItem(at: indexPath, animated: true, scrollPosition: [])

        setTitleForIndex(index: Int(index))
        self.invalidateLayoutForCell(index: Int(index))
    }

    func invalidateLayoutForCell(index: Int) {
        if index == 0 {
            self.feedCell?.invalidateLayout()
        } else {
            self.newReportCell?.invalidateLayout()
        }
    }

    override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return appTitles.count
    }

    override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        if indexPath.item == 0{
            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! FeedCell
            cell.homeController = self
            feedCell = cell
            return cell
        }
        else {
            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId2, for: indexPath) as! NewReportCell
            cell.homeController = self
            newReportCell = cell
            return cell
        }
    }

    func pushViewController(controller: UICollectionViewController, animated: Bool) {
        navigationController?.pushViewController(controller, animated: animated)
    }

    func scrollToMenuIndex(menuIndex: Int) {
        let indexPath = IndexPath(item: menuIndex, section: 0)
        currentIndexPath = indexPath
        collectionView?.scrollToItem(at: indexPath, at: [], animated: true)

        setTitleForIndex(index: menuIndex)
        DispatchQueue.main.async {
        }
    }

    func invalidateLayout() {
        self.collectionView?.collectionViewLayout.invalidateLayout()


        DispatchQueue.main.async {
            self.collectionView?.scrollToItem(at: self.currentIndexPath, at: .centeredHorizontally, animated: true)
            self.menuBar.refreshView()
            self.feedCell?.invalidateLayout()
            self.newReportCell?.invalidateLayout()
        }
    }

    override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
        super.viewWillTransition(to: size, with: coordinator)
        invalidateLayout()
    }

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


    private func setupMenuBar() {
        view.addSubview(menuBar)
        menuBar.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
        menuBar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
        menuBar.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
        menuBar.heightAnchor.constraint(equalToConstant: 50).isActive = true
    }

    fileprivate func observeKeyboardNotifications() {
        NotificationCenter.default.addObserver(self, selector: #selector(keyboardShow), name: .UIKeyboardWillShow, object: nil)

        NotificationCenter.default.addObserver(self, selector: #selector(keyboardHide), name: .UIKeyboardWillHide, object: nil)
    }

    @objc func keyboardShow() {
        if(UIDevice.current.orientation == UIDeviceOrientation.landscapeLeft || UIDevice.current.orientation == UIDeviceOrientation.landscapeRight){
            UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
                self.view.frame = CGRect(x: 0, y: -150, width: self.view.frame.width, height: self.view.frame.height)
            }, completion: nil)
        }
        else {
            UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
                self.view.frame = CGRect(x: 0, y: -50, width: self.view.frame.width, height: self.view.frame.height)
            }, completion: nil)
        }
        print("keyboard shown")
    }

    @objc func keyboardHide() {
        UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
            self.view.frame = CGRect(x: 0, y: 0, width: self.view.frame.width, height: self.view.frame.height)
        }, completion: nil)
        print("keyboard hide")
    }

}

layout crashes when keyboard appears

Yupp
  • 315
  • 3
  • 18
  • Try using IQKeyboardManager? – sandpat Aug 21 '18 at 12:48
  • you should rather use content-inset, here is the right approach: https://stackoverflow.com/questions/23988866/how-do-i-resize-views-when-keyboard-pops-up-using-auto-layout/23992055#23992055 – holex Aug 21 '18 at 16:34

3 Answers3

1
func keyboardWillShow(notification: NSNotification) {            
    if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue() {
        self.view.frame.origin.y -= keyboardSize.height
    }            
}

func keyboardWillHide(notification: NSNotification) {
    if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue() {
        self.view.frame.origin.y += keyboardSize.height
    }

try this two functions it's working in my project and it won't broke ur layout

Mahesh Dangar
  • 806
  • 5
  • 11
  • there is still a bug... when I selected a textField and then select another one without closing the keyboard the view moves up. – Yupp Aug 21 '18 at 13:03
  • @Yupp Store the initial frame of the view after it is set up in viewDidLoad, then change the frame in the notification only if the frame is the same. – Rakesha Shastri Aug 21 '18 at 13:49
  • @Yupp yes it will move up because keyboard will take it's area so view needs to be chnage. – Mahesh Dangar Aug 22 '18 at 03:50
0

To manage this I recommend you to use IQKeyboardManagerSwift this will handle your keyboard and text fields.

Nimantha
  • 6,405
  • 6
  • 28
  • 69
Amit gupta
  • 553
  • 3
  • 10
0

I figured out how to fix the problem. I combined the answer from Mahesh Dangar with the answer from Eduardo Irias (ios - How to get the height of Keyboard?). This will take into account safe areas and avoid some issues when one of the textfields has a pickerView as inputView. The view will adjust to the height of the keyboard.

var lastKeyboardHeight = CGFloat(0)

fileprivate func observeKeyboardNotifications() {
    NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: .UIKeyboardWillShow, object: nil)

    NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: .UIKeyboardWillHide, object: nil)
}

@objc func keyboardWillShow(notification: NSNotification) {
    guard let keyboardFrame = notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue else {
        return
    }

    let keyboardHeight: CGFloat
    if #available(iOS 11.0, *) {
        keyboardHeight = keyboardFrame.cgRectValue.height - self.view.safeAreaInsets.bottom
    } else {
        keyboardHeight = keyboardFrame.cgRectValue.height
    }
    if lastKeyboardHeight != keyboardHeight {
        self.view.frame.origin.y += lastKeyboardHeight
    }
    else {
        return
    }
    self.view.frame.origin.y -= keyboardHeight
    lastKeyboardHeight = keyboardHeight
}

@objc func keyboardWillHide(notification: NSNotification) {
    guard let keyboardFrame = notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue else {
        return
    }

    let keyboardHeight: CGFloat
    if #available(iOS 11.0, *) {
        keyboardHeight = keyboardFrame.cgRectValue.height - self.view.safeAreaInsets.bottom
    } else {
        keyboardHeight = keyboardFrame.cgRectValue.height
    }
    self.view.frame.origin.y += keyboardHeight
    lastKeyboardHeight = 0
}
Yupp
  • 315
  • 3
  • 18