键盘向上移动时如何使textFields保持原位?迅速

时间:2017-03-01 22:47:55

标签: xcode uistoryboard

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

  1. 触发viewDidLoad时,按钮不会向上滚动,因此可以在contentView中显示。
  2. 当开始输入textFields时,textFields在可视区域之外向上滚动。
  3. 当firstResponder辞职(键盘隐藏)时,我无法滚动contentView。 我将按照上面指定的顺序列出图片截图。
  4. 在此尝试之前,我在ViewController的视图上按下按钮,将按钮的底部约束分配给变量,当keyboardDidShow时,我将键盘大小添加到底部约束,从而拍摄键盘上方的按钮。然而,stackoverflower说这种方法很容易出错:Move button when keyboard appears swift

    我已经按照本教程,但我没有得到相同的结果。 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
    

    enter image description here       触发viewDidLoad时,按钮不显示 When viewDidLoad is triggered

    开始输入textFields时

    When starting to type in textFields

    When keyboard is hidden
    

    When Keyboard is hidden

     Desired result
    

    Desired result

2 个答案:

答案 0 :(得分:3)

我认为如果它们被键盘覆盖,你应该只将字段向上移动,就像我几天前做的那样:

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

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

答案 1 :(得分:3)

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

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

这些方法在下面工作,除了为布局底部添加50或其他任何内容以保存滚动视图。此外,下面的方法将有助于编辑

自动布局: 首先,对于仅在页面上占用的表单,我首先要在故事板上添加一个视图,然后在顶部布局指南中添加(按钮所需的任何空间),左右。然后我将ScrollView(将scrollview固定到该视图)添加到刚刚添加的视图中。接下来,我将内容视图添加到scrollview。现在我把它固定到scrollview上。您会看到autolayout仍然不满意。那么为什么第一个视图以及如何解决这个问题。我从contentView拖动到持有scrollview的视图,并选择相等的高度和相等的宽度。现在你不会有自动布局尖叫着你。注意:这适用于要填充第一个视图大小但允许其滚动以避开键盘的内容。见图片

Complaining

Adding Equal Heights

添加相同的高度后,我可以继续使用故事板。我设置了文本字段。您可能想要也可能不想将它固定在底部的底部文本字段,但如果您确实将其设为&gt; = yourNumber。

编辑: 现在将您的NEXT按钮添加到视图下方的故事板中。按钮必须固定在主视图的底部,并用0固定在键盘上。它现在看起来像这样。

ButtonAdded

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

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

最后的代码。其中一些你需要替换你的文本字段变量,因为我循环通过子视图来设置委托。我还在向上滚动中添加了填充。您应该将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
    }

}

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

Button