0

There is a view with two UITextFields, and the view moves according to the height of the keyboard. However, moving the View is not ideal if at least one textfield has its isSecureTextEntry property set to true. It is noticeable when moving between textfields. View moves down once and returns instantly. This is because the height of the keyboard changes momentarily. I think it's a matter of showing / hiding QuickTypeBar. So how to eliminate that effect and control the view movement?

Below is all the code. It may not look right in the simulator. Because the QuickType bar is not displayed. This is happening on the iPhone X iOS13.3.1.

Thanks.

enter image description here enter image description here

import UIKit

class ViewController: UIViewController {

    public var boardView: UIView!
    public var usernameTextField: UITextField!
    public var passwordTextField: UITextField!

    public var boardViewBottomAnchorConstraint: NSLayoutConstraint!
    public var boardViewBottomAnchorConstraintConstant: CGFloat = -220
    public var offset: CGFloat = 16.0

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .darkGray

        NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(_:)), name: UIResponder.keyboardWillShowNotification, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(_:)), name: UIResponder.keyboardWillHideNotification, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillChangeFrame(_:)), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)

        var constraints = [NSLayoutConstraint]()

        let boardView = UIView()
        boardView.backgroundColor = .brown
        boardView.layer.cornerRadius = 16.0
        boardView.translatesAutoresizingMaskIntoConstraints = false
        boardViewBottomAnchorConstraint = boardView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: boardViewBottomAnchorConstraintConstant)
        constraints.append(boardView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 16))
        constraints.append(boardViewBottomAnchorConstraint)
        constraints.append(boardView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -16))
        view.addSubview(boardView)
        self.boardView = boardView

        let usernameTextField = UITextField()
        usernameTextField.backgroundColor = .white
        usernameTextField.delegate = self
        usernameTextField.returnKeyType = .next
        usernameTextField.textContentType = .username
        usernameTextField.placeholder = "USERNAME TEXTFIELD"
        usernameTextField.translatesAutoresizingMaskIntoConstraints = false
        constraints.append(usernameTextField.heightAnchor.constraint(equalToConstant: 56))
        constraints.append(usernameTextField.topAnchor.constraint(equalTo: boardView.topAnchor, constant: 100))
        constraints.append(usernameTextField.leftAnchor.constraint(equalTo: boardView.leftAnchor, constant: 24))
        constraints.append(usernameTextField.rightAnchor.constraint(equalTo: boardView.rightAnchor, constant: -24))
        boardView.addSubview(usernameTextField)
        self.usernameTextField = usernameTextField

        let passwordTextField = UITextField()
        passwordTextField.backgroundColor = .white
        passwordTextField.delegate = self
        passwordTextField.returnKeyType = .go
        passwordTextField.textContentType = .password
        passwordTextField.placeholder = "PASSWORD TEXTFIELD"
        passwordTextField.translatesAutoresizingMaskIntoConstraints = false
        constraints.append(passwordTextField.heightAnchor.constraint(equalToConstant: 56))
        constraints.append(passwordTextField.topAnchor.constraint(equalTo: usernameTextField.bottomAnchor, constant: 50))
        constraints.append(passwordTextField.leftAnchor.constraint(equalTo: boardView.leftAnchor, constant: 24))
        constraints.append(passwordTextField.bottomAnchor.constraint(equalTo: boardView.bottomAnchor, constant: -100))
        constraints.append(passwordTextField.rightAnchor.constraint(equalTo: boardView.rightAnchor, constant: -24))
        boardView.addSubview(passwordTextField)
        self.passwordTextField = passwordTextField

        NSLayoutConstraint.activate(constraints)

        let tap = UITapGestureRecognizer(target: view, action: #selector(UIView.endEditing(_:)))
        view.addGestureRecognizer(tap)

        // false is the ideal move. If true, the view moves violently.
        passwordTextField.isSecureTextEntry = true

    }

}

extension ViewController {

    @objc
    public func keyboardWillShow(_ notification: Notification) { print("WILL SHOW")
        updateConstraints(notification)
    }

    @objc
    public func keyboardWillHide(_ notification: Notification) { print("WILL HIDE")
        boardViewBottomAnchorConstraint.constant = boardViewBottomAnchorConstraintConstant
        view.layoutIfNeeded()
    }

    @objc
    public func keyboardWillChangeFrame(_ notification: Notification) { print("WILL CHANGE FRAME")
        updateConstraints(notification)
    }

    private func updateConstraints(_ notification: Notification) {
        guard
            let toFrame = notification.userInfo?[UIApplication.keyboardFrameEndUserInfoKey] as? CGRect,
            let fromFrame = notification.userInfo?[UIApplication.keyboardFrameBeginUserInfoKey] as? CGRect else { return }
        print(fromFrame); print(toFrame);
        let constant = -toFrame.size.height - offset
        boardViewBottomAnchorConstraint.constant = constant
        view.layoutIfNeeded()
    }

}

extension ViewController: UITextFieldDelegate {

    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        if textField == usernameTextField {
            passwordTextField.becomeFirstResponder()
        } else {
            passwordTextField.resignFirstResponder()
        }
        return true
    }

}
  • try to use https://github.com/hackiftekhar/IQKeyboardManager i use it for such case – M Ohamed Zead Apr 09 '20 at 15:44
  • you want to only move the view if the keyboard is active. moving the view everytime the keyboard appears will give you the current result. Try moving the view only if the keyboard is active. If the keyboard is active and a different view is selected, make changes to how the view moves based on what view is selected. Only show the keyboard when a textfield is active – Julian Silvestri Apr 09 '20 at 15:47
  • @JulianSilvestri Thank you. How do I determine if the keyboard is active? I don't know what state active means. –  Apr 09 '20 at 16:10
  • @MOhamedZead I tried that library but it doesn't seem to solve this problem. Libraries also make things complicated. –  Apr 20 '20 at 16:58

2 Answers2

0
if UIApplication.shared.isKeyboardPresented {
     print("Keyboard presented")
} else { 
     print("Keyboard is not presented")
}

Use this code in combination with your code. Determine when the keyboad is active and do your actions accordingly

edit ----

YOu need to ctrl + drag from each of your text fields into your code and create a @IBAction

@IBAction func textField1() {
    //when a user taps on text field 1. this code block is triggered. 
    if UIApplication.shared.isKeyboardPresented {
        print("Keyboard presented")
        //if the keyboard is already presented do whatever other action is required 
        //inside here the keyboard is already active
    } else { 
        print("Keyboard is not presented")
        //if the keyboard is not currently active do any required action here
        //Inside here the keyboards will become active 
    }
}

For all your other textfields and all other views you need to do the same thing and customize your code based on the actions

EDI ----

Below is how you can achieve the same functionality without using the interface builder

let textField = UITextField()
self.textField.addTarget(self, action: #selector(targetFunc), for: .touchUpInside)

@objc func targetFunc() {
    // same actions as above in here
}
Julian Silvestri
  • 1,970
  • 1
  • 15
  • 33
  • Does this mean I decide when to be active? –  Apr 09 '20 at 16:54
  • @Diricawl this detects when teh keyboard is active – Julian Silvestri Apr 09 '20 at 16:55
  • understood. But I don't know where to use it. Can you show me where to use it in the question code? Thanks. –  Apr 09 '20 at 17:15
  • I don't use Interface Builder, so I added code to "textFieldShouldBeginEditing" to achieve this. –  Apr 09 '20 at 19:01
  • @Diricawl you dont need to use interface builder. you can still use ibaction by typing in the function. then add a target to your textfield in viewDidLoad, make that target your ibaction – Julian Silvestri Apr 09 '20 at 19:09
  • Thank you. ".TouchUpInside" cannot be used in UITextField. In this case, you can use ".editingDidBegin" instead, but I'm not sure how it differs from UITextField delegate method. There is some discussion [here](https://stackoverflow.com/a/32261288/9383160). –  Apr 09 '20 at 20:31
0

try using github.com/hackiftekhar/IQKeyboardManager from cocoapods. inside documentation you have to add few lines in appdelegate class and you are good to go. IQKeyboardmanager is avaiable both through cocoapods and IOS swift dependancy manager.

Drokez
  • 9
  • 2
  • It's a nice library that might solve this problem, but I think it complicates things. I'm not familiar with this library and I can't find any code to solve this directly. –  Apr 20 '20 at 15:54