Search code examples
swiftuiscrollviewuitextfielduitextfielddelegateuiscrollviewdelegate

Swift Scroll to UITextField via UiscrollView only acts first time bug


im posting my full code here. the problem is when initially loading the app and the viewcontroller. it fully works. tap on the two textfields and the scrollview pushes up and keyboard is below the current textfield. but then if tapping out of the text field.. moving the the view up and retaping on the textfield it doesnt do it anymore. Also, if going back via nav controller and then loading again this viewcontroller, it wont do anything. it doesnt scroll anymore..( doesnt push the textfield up and keyboard goes below it anymore ) ...

import UIKit
import Parse
import Alamofire
import SwiftyJSON

class VCreservacion: UIViewController,UITextFieldDelegate,UIScrollViewDelegate {

    var SUCURSALID = 0
    var EMP_NOMBRE = ""
    var DIRECCION = ""
    var PROVINCIA = ""
    var RESTID = 20556

    @IBOutlet var lbl_empresa: UILabel!
    @IBOutlet var lbl_direccion: UILabel!
    @IBOutlet var lbl_step: UILabel!
    @IBOutlet var cantidadView: UIView!
    @IBOutlet var datePicker: UIDatePicker!
    @IBOutlet var btn_reservar: UIButton!
    @IBOutlet var stackView: UIStackView!
    @IBOutlet var scrollView: UIScrollView!
    @IBOutlet var txtComentario: UITextField!
    @IBOutlet weak var txtCelular: UITextField!
    var activeField: UITextField?

    var steps = 2


    // MARK: RESERVE ACTION
    @IBAction func ReserveAction(_ sender: UIButton) {
        print("Reservando...")

        // For date formater
        var dateformated = ""
        var dateformated2 = ""
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
        //formatter.timeZone = NSTimeZone(forSecondsFromGMT: 0) as TimeZone!


        dateformated = formatter.string(from: datePicker.clampedDate)
        dateformated2 = formatter.string(from: datePicker.date)
        print(dateformated)
        print(dateformated2)
        let cart = Cart.sharedInstance

        guard let user = PFUser.current()  else {
            cart.showAlertView("Login", message: "Debes estar logeado para poder reservar.")
            return
        }

        Alamofire.request("URL String", parameters: ["qty": "\(steps)","sucursalid":"\(self.SUCURSALID)","restid":"\(RESTID)","comment":"\(txtComentario.text!)","phone":"\(txtCelular.text!)","datetime":"\(dateformated)","action":"request","userid":"\(user.objectId!)"]).authenticate(usingCredential: cart.credential).responseJSON() {

            response in

            if (response.error != nil ) {

                print(response.error.debugDescription)
                print(response.request)
                cart.showAlertView("Error", message: "there was an error.")

            }

            if(response.result.value != nil) {

                let json = JSON(response.result.value!);

                print(json);


                let success:Bool = json["success"].boolValue
                let error: Bool = json["error"].boolValue

                if(success) {
                    print("con exito")
                    let alert = UIAlertController(title: "Reserva Enviada", message: "Tu reserva ha sido enviada y será revisada por el establecimiento", preferredStyle: .alert)
                    let okAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.default) {
                        UIAlertAction in

                        // Get the previous Controller.
                        let targetController: UIViewController = (self.navigationController?.viewControllers[self.navigationController!.viewControllers.count - 3])!

                        // And go to that Controller
                        self.navigationController?.popToViewController(targetController, animated: true)

                    }

                    alert.addAction(okAction)
                    self.present(alert,animated:true)


                }
            }

        }



    }



    @IBAction func stepperValue(_ sender: UIStepper) {

        self.lbl_step.text = Int(sender.value).description
        steps = Int(sender.value)

    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        registerForKeyboardNotifications()
        self.scrollView.delegate = self
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        deregisterFromKeyboardNotifications()
        self.scrollView.delegate = nil
    }


    // MARK: Viewdidload
    override func viewDidLoad() {
        super.viewDidLoad()
        // enable scroll on scrollview
         self.scrollView.isScrollEnabled = true

        // step label initial value 2
        self.lbl_step.text = "2"

        // Get celular or phone

        if ( Cart.sharedInstance.User_celular != "") {
            txtCelular.text = Cart.sharedInstance.User_celular
        } else {
            txtCelular.text = Cart.sharedInstance.User_phone
        }


        let nearesthour = Date().nearestHour()
        self.datePicker.minimumDate = nearesthour
        self.txtComentario.delegate = self
        self.txtCelular.delegate = self
        self.txtCelular.tag = 20
        self.scrollView.delegate = self


        // tap gesture recognizer

        let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
        self.scrollView.addGestureRecognizer(tap)


        print("el enarest hour es \(nearesthour as Date?) y el date normal es \(Date())")

        self.btn_reservar.layer.cornerRadius = 10
        self.cantidadView.layer.cornerRadius = 10

        self.lbl_empresa.text = EMP_NOMBRE
        self.lbl_direccion.text = DIRECCION

        /*
        print("VC Reservacion")
        print("SUCURSAL \(SUCURSALID) ")
        print("EMP NOMBRE " + EMP_NOMBRE)
        print("DIRECCION " + DIRECCION)
        */


    }

    // MARK: TEXTFIELD STUFF

    func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
        self.activeField = textField
        return true
    }
    /*
    func textFieldDidBeginEditing(_ textField: UITextField) {
        self.activeField = textField
    }
    */


    func textFieldDidEndEditing(_ textField: UITextField) {

        activeField = nil

    }




    func registerForKeyboardNotifications(){
        //Adding notifies on keyboard appearing
        NotificationCenter.default.addObserver(self, selector: #selector(keyboardWasShown(notification:)), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillBeHidden(notification:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
    }

    func deregisterFromKeyboardNotifications(){
        //Removing notifies on keyboard appearing
        NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIKeyboardWillShow, object: nil)
        NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIKeyboardWillHide, object: nil)
    }

    @objc  func keyboardWasShown(notification: NSNotification){
        //Need to calculate keyboard exact size due to Apple suggestions

        print(" Keyboaard shown")


        var info = notification.userInfo!
        let keyboardSize = (info[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue.size
        print(" el keyboardsize is \(keyboardSize)")
        let contentInsets : UIEdgeInsets = UIEdgeInsetsMake(0.0, 0.0, keyboardSize!.height + 80, 0.0)

        self.scrollView.contentInset = contentInsets
        self.scrollView.scrollIndicatorInsets = contentInsets

        var aRect : CGRect = self.view.frame
        print("VIEW COMPLETE FRAME IS \(aRect)")
        print("KEYBOARD FRAME HEIGHT \(keyboardSize!.height)")
        aRect.size.height -= keyboardSize!.height
        print("FRAME MENOS KEYBOARD ES \(aRect)")

        print("SCROLLVIEW CONTENT \(self.scrollView.contentSize)")
        if let activeField = self.activeField {

            print("ACTIVEFIELD FRAME ORIGIN \(activeField.frame.origin) ")
            print("Active field is textfield tag is \(activeField.tag)")

            // if (!aRect.contains(activeField.frame.origin)){
            print("arect Does Not contains activeField")
            self.scrollView.scrollRectToVisible(activeField.frame, animated: true)
            print("TEXTFIELD FRAME ES \(activeField.frame)")
            print(" SCROLLVIEW CONTENT \(self.scrollView.contentSize)")
            //}
        }


    }

    @objc  func keyboardWillBeHidden(notification: NSNotification){
        //Once keyboard disappears, restore original positions
        var info = notification.userInfo!
        let keyboardSize = (info[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue.size

        let contentInsets : UIEdgeInsets = UIEdgeInsetsMake(0.0, 0.0, -keyboardSize!.height, 0.0)
        self.scrollView.contentInset = contentInsets
        self.scrollView.scrollIndicatorInsets = contentInsets
        self.activeField = nil
        self.view.endEditing(true)
        self.scrollView.isScrollEnabled = false

    }


    @objc func handleTap(_ sender: UITapGestureRecognizer) {
        self.view.endEditing(true)
        print("Tap")

    }


    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        self.view.endEditing(true)
        return false
    }


    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        scrollView.contentSize = CGSize(width: view.frame.width, height: view.frame.height)
        print(" SCROLLVIEW CONTENT AFTER SUBVIEWS \(self.scrollView.contentSize)")
    }


    /*  // NOT WORKING BECAUSE OF UISCROLLVIEW IN PLACE, MUST USE UITAPGESTURE RECOGNIZER
     override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
     self.view.endEditing(true)
     self.activeField?.resignFirstResponder()
     }
     */


}

Solution

  • I had the same problem. Apparently there is a change in getting the size of the keyboard. I changed UIKeyboardFrameBeginUserInfoKey to UIKeyboardFrameEndUserInfoKey and got it to work again.

    Here is my exact code for moving the view when the textfield is pressed

    @objc func keyboardWasShown(notification: NSNotification){
        self.scrollView.isScrollEnabled = true
        var info = notification.userInfo!
        let keyboardSize = (info[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue.size
        let contentInsets = UIEdgeInsetsMake(0.0, 0.0, keyboardSize!.height, 0.0)
    
        self.scrollView.contentInset = contentInsets
        self.scrollView.scrollIndicatorInsets = contentInsets
    
        var aRect : CGRect = self.view.frame
        guard let kbHeight = keyboardSize?.height else{return}
        aRect.size.height -= kbHeight
        if let activeField = self.activeTextField {
            if (!aRect.contains(activeField.frame.origin)){
                self.scrollView.scrollRectToVisible(activeField.frame, animated: true)
            }
        }
    }