UITextField Jumps Above UIImageView When Keyboard Appears

70 Views Asked by At

The problem involves the layout behavior of a UITextField which within an UIImageView. And that UIImageView is within a UIScrollView. The goal is to have the text field positioned on top of the image view, and when the keyboard appears, the text field should remain in place without jumping over the image view.

However, when the keyboard is displayed, instead of staying in its original position on top of the image view, the text field moves upward, passing over the image view. Now this issue goes away if you dismiss the keyboard. I've tried moving the whole scrollview when we know the keyboard is being shown but that didn't seem to work, or I wasn't able to implement it correctly. Any Help would be great! thx!

import UIKit
import NotificationCenter
import Foundation

class ZoomImage: UIViewController {
    var x: CGFloat = 100
    var y: CGFloat = -400
    var keyboardHeight: CGFloat = 0.0
    
    private let textField: UITextField = {
        let ut = UITextField()
        ut.textColor = .white
        ut.placeholder = "Testing"
        ut.backgroundColor = .none
        ut.frame = CGRect(x: 20, y: 20, width: 200, height: 30)
        return ut
    }()
    
    // create scrollView
    private let scrollView: UIScrollView = {
        let sv = UIScrollView()
        sv.backgroundColor = .blue
        return sv
    }()
    
    // create imageView
    private var imageView: UIImageView = {
        let v = UIImageView()
        v.isUserInteractionEnabled = true
        v.backgroundColor = .red
        return v
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        registerObservers()
        setup()
    }
    
    func registerObservers() {
        NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(notification:)),
                                               name: UIResponder.keyboardWillShowNotification,
                                               object: nil)
    }
    
    @objc func keyboardWillShow(notification: NSNotification) {
        if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
            keyboardHeight = keyboardSize.size.height
        }
    }
    
    deinit {
        // Unsubscribe from keyboard notifications
        NotificationCenter.default.removeObserver(self)
    }
    
    func setup() {
        // Allows us to have this view be the delegate of the textfield
        self.textField.delegate = self
        // Allows us to have this view be the delegate of the scrollview
        scrollView.delegate = self
        // add Scrollview to view
        self.view.addSubview(scrollView)
        
        // Defining imageView
        let image = UIImage(named: "test.png")!
        imageView.image = image
        imageView.contentMode = .scaleAspectFit
        
        // add UIImageView to scroll view
        scrollView.addSubview(imageView)
        imageView.addSubview(textField)
        
        imageView.translatesAutoresizingMaskIntoConstraints = false
        textField.translatesAutoresizingMaskIntoConstraints = false
        scrollView.translatesAutoresizingMaskIntoConstraints = false
        
        // respect safe-area
        let safeG = view.safeAreaLayoutGuide
        
        // Zoom range
        scrollView.minimumZoomScale = 1.0
        scrollView.maximumZoomScale = 10.0
        
        // constraints
        NSLayoutConstraint.activate([
            scrollView.frameLayoutGuide.leadingAnchor.constraint(equalTo: safeG.leadingAnchor, constant: 0),
            scrollView.frameLayoutGuide.trailingAnchor.constraint(equalTo: safeG.trailingAnchor, constant: 0),
            scrollView.frameLayoutGuide.bottomAnchor.constraint(equalTo: safeG.bottomAnchor, constant: 0),
            scrollView.frameLayoutGuide.topAnchor.constraint(equalTo: safeG.topAnchor, constant: 0),

            imageView.leadingAnchor.constraint(equalTo: self.scrollView.contentLayoutGuide.leadingAnchor, constant: 0),
            imageView.trailingAnchor.constraint(equalTo: self.scrollView.contentLayoutGuide.trailingAnchor, constant: 0),
            imageView.bottomAnchor.constraint(equalTo: self.scrollView.contentLayoutGuide.bottomAnchor, constant: 0),
            imageView.topAnchor.constraint(equalTo: self.scrollView.contentLayoutGuide.topAnchor, constant: 0),

            imageView.widthAnchor.constraint(equalTo: scrollView.frameLayoutGuide.widthAnchor),
            imageView.heightAnchor.constraint(equalTo: scrollView.frameLayoutGuide.heightAnchor),

            textField.leadingAnchor.constraint(equalTo: imageView.leadingAnchor, constant: self.x),
            textField.bottomAnchor.constraint(equalTo: imageView.bottomAnchor, constant: self.y),
        ])
        
    }
}


// MARK: - Extensions
extension ZoomImage:  UIScrollViewDelegate {
    func viewForZooming(in scrollView: UIScrollView) -> UIView? {
        return imageView
    }
}

extension ZoomImage: UITextFieldDelegate {
    
    func textFieldDidBeginEditing(_ textField: UITextField) {
            print("MOVE VIEW Down")
            if self.scrollView.frame.origin.y == 0{
                self.scrollView.frame.origin.y -= keyboardHeight
            }
    }
    
    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        print("TextField should return method called")
        textField.resignFirstResponder();
        return true;
    }
}
1

There are 1 best solutions below

3
Chetan A On

It seems like the issue is related to adjusting the scrollView frame when the keyboard is shown. Instead of modifying the frame directly, you should adjust the content inset of the scrollView to accommodate the keyboard without changing the frame of its subviews.

class ZoomImage: UIViewController {
    // ... (your existing code)

    override func viewDidLoad() {
        super.viewDidLoad()
        registerObservers()
        setup()
    }

    func registerObservers() {
        NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(notification:)),
                                               name: UIResponder.keyboardWillShowNotification,
                                               object: nil)
        
        NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(notification:)),
                                               name: UIResponder.keyboardWillHideNotification,
                                               object: nil)
    }

    @objc func keyboardWillShow(notification: NSNotification) {
        if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
            keyboardHeight = keyboardSize.height
            adjustScrollViewContentInset(showing: true)
        }
    }

    @objc func keyboardWillHide(notification: NSNotification) {
        adjustScrollViewContentInset(showing: false)
    }

    func adjustScrollViewContentInset(showing: Bool) {
        var contentInset = scrollView.contentInset
        contentInset.bottom = showing ? keyboardHeight : 0
        scrollView.contentInset = contentInset
        scrollView.scrollIndicatorInsets = contentInset
    }

    // ... (your existing code)
}