How to make textFields stay in place when keyboard moves up? Swift

混江龙づ霸主 提交于 2020-01-11 04:05:09

问题


I have created a form with 4 fields and one button. The view hierarchy looks like this: Main UIVIew, View (renamed contentView), on top of contentView I have 4 fields and 1 programmatically created button.

  1. When viewDidLoad is triggered, the button is not scrolled up so it can be visible in contentView.
  2. When starting to type in textFields, the textFields are scrolled up, outside of the viewable area.
  3. When firstResponder resigns (keyboard hidden) I cannot scroll the contentView. I will list the pictures screenshots in the order specified above.

Prior to this attempt, I pined the button on the ViewController's view, assigned the button's bottom constraint to a variable and when keyboardDidShow, I was adding the keyboard size to the bottom constraint, thus shooting the button up above the keyboard. However a stackoverflower said this method was prone to bugs: Move button when keyboard appears swift

I have followed this tutorial, but I don’t get same result. https://spin.atomicobject.com/2014/03/05/uiscrollview-autolayout-ios/
Given that Iphone has different screen sizes, please advise the best approach.

class EleventhViewController: UIViewController, UITextFieldDelegate {

 @IBOutlet weak var fullName: UITextField!
 @IBOutlet weak var flatNumber: UITextField!
 @IBOutlet weak var streetAddress: UITextField!
 @IBOutlet weak var phoneNumber: UITextField!
 @IBOutlet weak var contentView: UIView!
 @IBOutlet weak var scrollView: UIScrollView!
 var nextButtonOutlet:UIButton!

override func viewDidLoad() {
      super.viewDidLoad()
    //called whenever keyboard is shown/hidden
 registerForKeyboardNotifications()   

     //when identifies single or multiple taps, call DismissKeyboard
 var tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: "DismissKeyboard")
    contentView.addGestureRecognizer(tap)

//create button programmatically
    var button = UIButton(type: UIButtonType.custom) as UIButton
      button = UIButton(frame: CGRect(x: 0, y: 637, width: 375, height: 50))
      button.titleLabel?.textColor = UIColor.white
      button.backgroundColor = UIColor(colorLiteralRed: 117/255, green: 232/255, blue: 0, alpha: 1)
      button.setTitle("Next", for: .normal)
      button.addTarget(self, action: #selector(EleventhViewController.nextButton), for: .touchUpInside)
       self.contentView.addSubview(button)
         self.nextButtonOutlet = button

//disable scroll bouncing
   scrollView.bounces = false

    self.fullName.delegate = self
     self.flatNumber.delegate = self
      self.streetAddress.delegate = self
         self.phoneNumber.delegate = self
 }


   //Call this function when the tap is recognized.
     func DismissKeyboard(){
       contentView.endEditing(true)
  }



     // Stop Editing on Return Key Tap. 
     func textFieldShouldReturn(_ textField: UITextField) -> Bool {
       textField.resignFirstResponder()
        return true
  }


 weak var activeField: UITextField?
 func keyboardDidShow(_ notification: Notification) {

       //when a textfield is edited lift the button above the keyboard
      if let activeField = self.activeField,let keyboardSize =      
       (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as?  
            NSValue)?.cgRectValue {
        let contentInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 
             keyboardSize.height, right: 0.0)
               self.scrollView.contentInset = contentInsets

  var aRect = self.view.frame
     aRect.size.height -= keyboardSize.size.height

      if !aRect.contains(nextButtonOutlet.frame.origin) {
          self.scrollView.scrollRectToVisible(nextButtonOutlet.frame, animated: true)
    }
 }


  func keyboardWillHide(_ notification: Notification) {

     let contentInsets = UIEdgeInsets.zero
      self.scrollView.contentInset = contentInsets
          self.scrollView.scrollIndicatorInsets = contentInsets
}

    //Keep track of which textfield is being edited to make sure the field is visible when keyboard pops up
 func textFieldDidBeginEditing(_ textField: UITextField) {
        self.activeField = textField
 }

  func textFieldDidEndEditing(_ textField: UITextField, reason: UITextFieldDidEndEditingReason) {  
    self.activeField = nil
}

    //register for keyboard notifications
      func registerForKeyboardNotifications() {

       NotificationCenter.default.addObserver(self, selector: 
      #selector(keyboardDidShow), 
        name: NSNotification.Name.UIKeyboardDidShow, object: nil)

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

 //remove keyBoard observers
  func deregisterFromKeyboardNotifications() {
    NotificationCenter.default.removeObserver(self, name: 
           NSNotification.Name.UIKeyboardDidShow, object: nil)

    NotificationCenter.default.removeObserver(self, name: 
         NSNotification.Name.UIKeyboardWillHide, object: nil)
    }

override func viewDidDisappear(_ animated: Bool) {
    super.viewDidDisappear(true)
    //deregister keyboard notifications
         deregisterFromKeyboardNotifications()
     }
 } //end of class

   view Hierachy

When viewDidLoad is triggered,button does not show

When starting to type in textFields

When keyboard is hidden

 Desired result


回答1:


So I think you are close and I am not sure if it is a code issue or an autolayout issue. My guess is you are getting complaints about your scrollview not know the content size so I will cover both.

EDIT: Also you button will have to be below the view container that I first add and will need to be handled separately from the scrollview. Do not put it in the scrollview.

The methods work below except add 50 or whatever to the layout bottom for the view that will hold the scrollview. Also the method below will help make edits

Autolayout: First for forms that will only take up on page I like to start by adding a view to the storyboard and pin in top layout guide, (whatever space you need for button), left and right. Then I add my ScrollView(pin the scrollview to that view) to the view just added.Then next I add my content view to the scrollview. Now I pin this to the scrollview. You will see that autolayout is still not happy. So why the first view and how to fix this. I drag from the contentView to the view holding the scrollview and choose equal heights and equal widths. Now you will not have auto layout screaming at you. Note:This works for content you want to fill the first view size but allow it to scroll to avoid the keyboard. See images

After adding this equal heights I can continue with the storyboard. I set the textfields up. The bottom textfield you may or may not want to pin it to the bottom but if you do make it >= yourNumber.

EDIT: Now add your NEXT Button to the storyboard below the view that is holding everything. The button has to be pinned to the bottom of the main view with a 0 to stick to the keyboard. It will now look like this.

Obviously this conflicts slightly with the initial images but all you have to do is increase the space to the bottom layout guide just make sure your button is added to the main view and not the view holding the scrollview. Now connect your button to the controller in an iboutlet. we will need it.

Next make sure you have the right keyboard in your simulator. **Not using hardware keyboard

Finally the code. some of it you would need to substitute your textfield variables as I looped through the subviews to set the delegate. I also added padding to the scroll up. You should move your deRegister to deint(). See my code and finally you might want to scroll the scroll view on keyboard will appear instead of did appear but I did not alter this.

import UIKit

class ViewController: UIViewController,UITextFieldDelegate {

//added in storyboard. removed the code
@IBOutlet weak var nextButton: UIButton!

@IBOutlet weak var scrollView: UIScrollView!
@IBOutlet weak var contentView: UIView!
    override func viewDidLoad() {
        super.viewDidLoad()
        //called whenever keyboard is shown/hidden

        registerForKeyboardNotifications()

        //when identifies single or multiple taps, call DismissKeyboard
        var tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: "DismissKeyboard")
        contentView.addGestureRecognizer(tap)

        //disable scroll bouncing
        scrollView.bounces = false

        //replace with your textfields
        for subs in self.contentView.subviews{
            if subs is UITextField{
                print("setting")
                (subs as! UITextField).delegate = self
            }
        }
    }


    //Call this function when the tap is recognized.
    func DismissKeyboard(){
        contentView.endEditing(true)
    }



    // Stop Editing on Return Key Tap.
    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        textField.resignFirstResponder()
        return true
    }

    //edited for next button
    weak var activeField: UITextField?
    func keyboardDidShow(_ notification: Notification) {

        //when a textfield is edited lift the button above the keyboard
        if let activeField = self.activeField,let keyboardSize =
            (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as?
                NSValue)?.cgRectValue {

            //20 in insets and offset is just padding
            let contentInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom:
                keyboardSize.height + 20 + nextButton.bounds.height, right: 0.0)
            self.scrollView.contentInset = contentInsets

            var aRect = self.view.frame
            aRect.size.height -= keyboardSize.height


            let bottomPoint = CGPoint(x: activeField.frame.origin.x, y:activeField.frame.origin.y)

            if aRect.contains(bottomPoint){
                let scrollPoint = CGPoint(x: 0.0, y: bottomPoint.y - keyboardSize.height - 20 - nextButton.bounds.height)
                scrollView.setContentOffset(scrollPoint, animated: true)
            }

        }

    }
        func keyboardWillHide(_ notification: Notification) {

            let contentInsets = UIEdgeInsets.zero
            self.scrollView.contentInset = contentInsets
            self.scrollView.scrollIndicatorInsets = contentInsets
        }

        //Keep track of which textfield is being edited to make sure the field is visible when keyboard pops up
        func textFieldDidBeginEditing(_ textField: UITextField) {
            self.activeField = textField
        }


        func textFieldDidEndEditing(_ textField: UITextField, reason: UITextFieldDidEndEditingReason) {
            self.activeField = nil
        }

        //register for keyboard notifications
        func registerForKeyboardNotifications() {

            NotificationCenter.default.addObserver(self, selector:
                #selector(keyboardDidShow),
                                                   name: NSNotification.Name.UIKeyboardDidShow, object: nil)

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

        //remove keyBoard observers
        func deregisterFromKeyboardNotifications() {
            NotificationCenter.default.removeObserver(self, name: 
                NSNotification.Name.UIKeyboardDidShow, object: nil)

            NotificationCenter.default.removeObserver(self, name: 
                NSNotification.Name.UIKeyboardWillHide, object: nil)
        }



        deinit {
            //deregister keyboard notifications
            deregisterFromKeyboardNotifications()
        }
} //end of class

Now one more step. We have to handle the move up of the button. Instead of just killing this controller and putting more handling in it you can subclass the bottomconstraint to handle it. (Just make sure not to add a top constraint to the bottom.) Here is a constraint to drop in your project.

import UIKit

class AvoidingConstraint: NSLayoutConstraint {

    private var offset : CGFloat = 0
    private var keyboardVisibleHeight : CGFloat = 0

    override public func awakeFromNib() {
        super.awakeFromNib()

        offset = constant

        NotificationCenter.default.addObserver(self, selector: #selector(AvoidingConstraint.keyboardWillShowNotification(_:)), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(AvoidingConstraint.keyboardWillHideNotification(_:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
    }

    deinit {
        NotificationCenter.default.removeObserver(self)
    }

    // MARK: Notification

    func keyboardWillShowNotification(_ notification: Notification) {
        if let userInfo = notification.userInfo {
            if let frameValue = userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue {
                let frame = frameValue.cgRectValue
                keyboardVisibleHeight = frame.size.height
            }

            self.updateConstant()
            switch (userInfo[UIKeyboardAnimationDurationUserInfoKey] as? NSNumber, userInfo[UIKeyboardAnimationCurveUserInfoKey] as? NSNumber) {
            case let (.some(duration), .some(curve)):

                let options = UIViewAnimationOptions(rawValue: curve.uintValue)

                UIView.animate(
                    withDuration: TimeInterval(duration.doubleValue),
                    delay: 0,
                    options: options,
                    animations: {
                        UIApplication.shared.keyWindow?.layoutIfNeeded()
                        return
                }, completion: { finished in
                })
            default:

                break
            }

        }

    }

    func keyboardWillHideNotification(_ notification: NSNotification) {
        keyboardVisibleHeight = 0
        self.updateConstant()

        if let userInfo = notification.userInfo {

            switch (userInfo[UIKeyboardAnimationDurationUserInfoKey] as? NSNumber, userInfo[UIKeyboardAnimationCurveUserInfoKey] as? NSNumber) {
            case let (.some(duration), .some(curve)):

                let options = UIViewAnimationOptions(rawValue: curve.uintValue)

                UIView.animate(
                    withDuration: TimeInterval(duration.doubleValue),
                    delay: 0,
                    options: options,
                    animations: {
                        UIApplication.shared.keyWindow?.layoutIfNeeded()
                        return
                }, completion: { finished in
                })
            default:
                break
            }
        }
    }

    func updateConstant() {
        self.constant = offset + keyboardVisibleHeight
    }

}

Add this to a file in your project. Then all you have to do is on the bottom constraint for your button in story board change it to this subclass. Just make sure there is no top constraint. The view holding the scrollview needs to have a bottom constraint to the main view and not the button with enough space for the button. Run the project and enjoy. See link for test project if this explanation is not enough. https://www.dropbox.com/s/ir5x324mvhhne64/ScrollView.zip?dl=0




回答2:


I think you should only move the fields up if they are covered by the keyboard, something like what i cooked up a couple of days ago:

let keyboardScreenEndFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue ?? NSValue()).cgRectValue
let keyboardViewEndFrame = view.convert(keyboardScreenEndFrame, from: view.window)
UIView.animate(withDuration: 0.2) {
    if notification.name == Notification.Name.UIKeyboardWillHide {
        self.view.frame = CGRect(x: 0, y: 0, width: self.view.width, height: self.view.height)
    } else {
        let offset = (self.view.frame.size.height - self.activeField.frame.maxY) - keyboardViewEndFrame.height
        if offset < 0 {
            self.view.frame = CGRect(x: 0, y:  offset, width: self.view.width, height: self.view.height)
        } else {
            self.view.frame = CGRect(x: 0, y: 0, width: self.view.width, height: self.view.height)
        }
    }
}

Basically you just need to add logic for keyboard handling timing, and you should handle it if keyboard frame goes over textfield frame. Hope it helps.



来源:https://stackoverflow.com/questions/42543593/how-to-make-textfields-stay-in-place-when-keyboard-moves-up-swift

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!