键盘向上移动时如何使文本字段保持原位? Swift

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

我创建了一个包含 4 个字段和一个按钮的表单。视图层次结构如下所示:Main UIVIew,View(重命名为 contentView),在 contentView 之上我有 4 个字段和 1 个以编程方式创建的按钮。

  1. 触发 viewDidLoad 时,按钮不会向上滚动,因此可以在 contentView 中看到。
  2. 开始在文本字段中键入时,文本字段会向上滚动到可视区域之外。
  3. 当 firstResponder 退出时(隐藏键盘),我无法滚动 contentView。 我会按照上面指定的顺序列出图片截图。

在此尝试之前,我将按钮固定在 ViewController 的视图上,将按钮的底部约束分配给一个变量,当 keyboardDidShow 时,我将键盘大小添加到底部约束,从而拍摄键盘上方的按钮。然而,Whosebuger 说这种方法容易出错:

我已经按照本教程进行操作,但没有得到相同的结果。 https://spin.atomicobject.com/2014/03/05/uiscrollview-autolayout-ios/
鉴于 Iphone 有不同的屏幕尺寸,请告知最佳方法。

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

触发 viewDidLoad 时,按钮不显示

开始在文本字段中输入时

When keyboard is hidden

 Desired result

我认为你应该只在键盘覆盖的情况下将字段向上移动,就像我几天前做的一样:

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

基本上你只需要为键盘处理计时添加逻辑,如果键盘框架超过文本字段框架,你应该处理它。 希望对你有帮助。

所以我认为你很接近,我不确定这是代码问题还是自动布局问题。我的猜测是您收到关于您的滚动视图不知道内容大小的投诉,所以我将涵盖两者。

编辑: 此外,您的按钮必须位于我首先添加的视图容器下方,并且需要与滚动视图分开处理。不要放在滚动视图中。

除了将 50 或其他内容添加到将容纳滚动视图的视图的布局底部之外,这些方法在下面起作用。此外,以下方法将有助于进行编辑

自动布局: 首先,对于只占用页面的表单,我喜欢先在故事板中添加一个视图,然后在顶部布局指南中固定(无论 space 你需要什么按钮),左右。然后我将我的 ScrollView(将滚动视图固定到该视图)添加到视图 added.Then 接下来我将我的内容视图添加到滚动视图。现在我将其固定到滚动视图。你会看到自动布局仍然不快乐。那么为什么第一个视图以及如何解决这个问题。我从 contentView 拖动到持有 scrollview 的视图并选择相等的高度和相等的宽度。现在你不会有自动布局对你尖叫。 Note:This 适用于您想要填充第一个视图大小但允许它滚动以避免键盘的内容。查看图片

添加这个等高后我可以继续故事板。我设置了文本字段。底部的文本字段你可能想也可能不想将它固定在底部,但如果你确实让它 >= yourNumber.

编辑: 现在将您的 NEXT 按钮添加到包含所有内容的视图下方的情节提要中。该按钮必须固定在主视图的底部,并带有 0 以固定在键盘上。它现在看起来像这样。

显然这与初始图像略有冲突,但您所要做的就是将 space 增加到底部布局指南,只需确保将按钮添加到主视图而不是包含滚动视图的视图.现在将您的按钮连接到 iboutlet 中的控制器。我们需要它。

接下来确保您的模拟器中有正确的键盘。 **不使用硬件键盘

最后是代码。其中一些你需要替换你的文本字段变量,因为我循环遍历子视图来设置委托。我还在向上滚动中添加了填充。你应该将你的 deRegister 移动到 deint()。查看我的代码,最后你可能想要滚动键盘上的滚动视图将出现而不是出现但我没有改变这一点。

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

现在还有一步。我们必须处理按钮的上移。您可以将 bottomconstraint 子类化来处理它,而不是仅仅杀死这个控制器并在其中放置更多处理。 (只要确保不要将顶部约束添加到底部即可。)这是要放入项目中的约束。

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
    }

}

将其添加到项目中的文件中。然后你所要做的就是在故事板中将按钮的底部约束更改为这个子类。只要确保没有最高约束。持有滚动视图的视图需要对主视图有一个底部约束,而不是对按钮有足够 space 的按钮。 运行 项目和享受。如果这个解释不够,请参阅 link 测试项目。 https://www.dropbox.com/s/ir5x324mvhhne64/ScrollView.zip?dl=0