【问题标题】:How to make textFields stay in place when keyboard moves up? Swift键盘向上移动时如何使文本字段保持原位?迅速
【发布时间】:2017-03-03 22:52:11
【问题描述】:

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

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

在此尝试之前,我将按钮固定在 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

触发viewDidLoad时,按钮不显示

开始输入 textFields 时

When keyboard is hidden

 Desired result

【问题讨论】:

    标签: xcode uistoryboard


    【解决方案1】:

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

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

    下面的方法除了在布局底部添加 50 或其他任何东西来保存滚动视图的视图。下面的方法也将有助于进行编辑

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

    添加这个相等的高度后,我可以继续故事板。我设置了文本字段。底部文本字段您可能希望或可能不希望将其固定到底部,但如果您确实将其设为 >= yourNumber。

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

    显然这与初始图像略有冲突,但您所要做的就是增加底部布局指南的空间,只需确保您的按钮被添加到主视图而不是持有滚动视图的视图。现在将您的按钮连接到 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
    

    现在又迈出了一步。我们必须处理按钮的向上移动。您可以将底部约束子类化来处理它,而不是仅仅杀死这个控制器并在其中进行更多处理。 (请确保不要在底部添加顶部约束。)这是要放入项目的约束。

    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

    【讨论】:

    • 这是一个非常全面的答案。太感谢了。从昨天开始,我开始寻找替代方法,我发现使用 tableView 消除了使用约束和滚动的大部分复杂性,因为 UITableView 具有内置功能。但是,我被困在一个步骤中,因为我必须遍历附加在静态单元格上的 textFields。请看我的问题:stackoverflow.com/questions/42587867
    【解决方案2】:

    我认为只有当它们被键盘覆盖时才应该向上移动字段,就像我几天前做的那样:

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

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

    【讨论】:

    • 非常感谢您的回答。但是,在阅读了有关 stackoverflow 的更多信息后,我决定将 UITableView 与静态单元格一起使用,因为它内置了滚动和键盘管理功能。我被困在循环文本字段中。 stackoverflow.com/questions/42587867
    猜你喜欢
    • 2020-08-08
    • 1970-01-01
    • 1970-01-01
    • 2020-02-15
    • 1970-01-01
    • 2017-01-29
    • 2016-07-07
    • 2016-04-13
    • 2014-10-30
    相关资源
    最近更新 更多