9

I have a horizontal UICollectionViewController where each cell contains a UITextView at the bottom of the cell. When I tap inside the UITextView, while the keyboard is appearing, the CollectionView's height is reduced 260 points (which I notice is the height of the keyboard) and then increases 130 points, so the final height is 130 points less than desired.

Do you have any idea why the frame changes in this manner?

I've included the most relevant parts below, or you can find the test project here: https://github.com/johntiror/testAutomaticPush/tree/master

UIViewController (simply launches 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) { }
  }
}

CollectionViewController:

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
  }
}

enter image description here

Thanks very much

mmd1080
  • 1,812
  • 2
  • 17
  • 29
Stefano Giacone
  • 2,016
  • 3
  • 27
  • 50

5 Answers5

3

First I have to give my two cents that storyboards are great :)

You may not want to go with CollectionViewController for this use case. If you decide to use it, I've also posted another answer. Here's the quickest way to move your CollectionView to ViewController. This solves your problem but doesn't account for autolayout.

1) Replace these lines in ViewController:

let fsPicVC = CollectionViewController(collectionViewLayout: layout)
self.present(fsPicVC, animated: true) { }

with

let collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout)
collectionView.register(CollectionViewCell.self, forCellWithReuseIdentifier: "Cell")
collectionView.dataSource = self
view.addSubview(collectionView)

2) Add this to the very bottom of ViewController (outside the 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
  }
}

Finally, you can delete CollectionViewController, as it has now been replaced.

PS You also probably want to 1) extend ViewController to conform to UICollectionViewDelegateFlowLayout and 2) make collectionView global.

mmd1080
  • 1,812
  • 2
  • 17
  • 29
  • Hi, I've invested quite some time into autolayout/storyboards. I simply prefer to do everything via code. Can you please point me to what am I missing? Why UICollectionViewController isn't going to work here? – Stefano Giacone May 03 '17 at 08:54
  • My mistake, I assumed you weren't understanding, sorry! I thought about this some more and tried a new solution, which I'm going to post separately. I will delete this one if you choose the other. – mmd1080 May 03 '17 at 16:08
  • I tried this and it's working! Before accepting this I want to try it in my actual app, since it's a more complex scenario I need to verify if I can adapt it. Thank you very much! – Stefano Giacone May 03 '17 at 20:53
  • Glad I could help. I guess I'll keep both answers up because I think the other one is also good. There is probably a fairly simple fix for whatever issue you're seeing – mmd1080 May 03 '17 at 22:15
1

If you want to use CollectionViewController, here's a possible solution (if you want to switch to a generic ViewController see my other answer).

Add the following to your CollectionViewController 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)
}
mmd1080
  • 1,812
  • 2
  • 17
  • 29
  • Thanks! Actually I already found a similar workaround. I did the bounty because I was looking for "the correct" solution. I have to say this solution is a better workaround than mine, but still there is a little weird animation and (for some strange reason) it's randomly not working. – Stefano Giacone May 03 '17 at 20:50
1

Demo link : https://github.com/harshilkotecha/UIScrollViewWhenKeyboardAppearInSwift3

when you have multiple textview it is so difficult so best solution ->

step 1 : Give Delegate to UITextFieldDelegate

class ScrollViewController: UIViewController,UITextFieldDelegate {

step 2 :create new IBOutlet but don't connect with any text field in storyboard.

//  get current text box when user Begin editing
    @IBOutlet weak var activeTextField: UITextField?

step 3 : write this two method when user focus on text filed object pass the reference and store in activeTextField.

// get current text field
    func textFieldDidBeginEditing(_ textField: UITextField)
    {
        activeTextField=textField;
    }
    func textFieldDidEndEditing(_ textField: UITextField)
    {
        activeTextField=nil;
    }

step 4 : set Notification in 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)
    }

step 5 : two method for Keyboard appear and disappear.

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)
    }
Sakir Sherasiya
  • 1,562
  • 1
  • 17
  • 31
0

I had a case where I had a view controller with an embedded UICollectionViewController running across the bottom of the screen. When the keyboard would appear, it would change the contentInset of the embedded controller. There was no need to do this in my mind - it was okay for the keyboard to (partially) cover it up. What I ended up doing is resetting the contentInset inside viewWillLayoutSubviews() inside the embedded UICollectionViewController: (Swift 4)

override func viewWillLayoutSubviews() {
    super.viewWillLayoutSubviews()
    collectionView?.contentInset = UIEdgeInsets.zero
}
anorskdev
  • 1,867
  • 1
  • 15
  • 18
-1

What I find unusual about your code is that you are putting your text field inside a cell in your container view. I'm not sure what else you plan to put in that container view... but having your text box in there is a little strange to me.

Now having said that... the problem you are experiencing is this: when the keyboard shows, the system has two competing goals. First it is trying to scroll the content of the collection view so that the text field will stay visible. That gives the large jump. Then it's trying to adjust the size of the collection view to get it out of the way of the keyboard, but because of the flow layout setup it gets really confused. If you look in the log you'll see a lot of:

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.

As the system desperately tries to reconcile the constraints of your collection view with the attempt to keep the text view visible

All in all I would suggest that you put your text view OUTSIDE of the collection view.

Scott Thompson
  • 22,629
  • 4
  • 32
  • 34
  • I noticed and the cause is that the cell's frame move to negative y origin (0.0, -130.0, 375.0, 667.0). Can you please explain why it's weird to put the text box into the cell? I need each cell of the collection view to have a text field, I can't put it outside – Stefano Giacone May 03 '17 at 08:14
  • The keyboard appearing is affecting the flow layout but it has nothing to do with placement of the textView. Try moving it elsewhere and you'll see the same issue. – mmd1080 May 06 '17 at 17:20