【问题标题】:ScrollView and keyboard in SwiftSwift 中的 ScrollView 和键盘
【发布时间】:2014-12-28 15:11:31
【问题描述】:

我开始创建一个简单的 iOS 应用来执行一些操作。

但是当键盘出现时我遇到了一些问题,隐藏了我的一个文本字段。

我认为这是一个常见问题,我做了一些研究,但找不到任何解决我问题的方法。

我想使用 ScrollView 而不是为文本字段设置动画以使其可见。

【问题讨论】:

    标签: ios view swift scroll


    【解决方案1】:

    在 ViewDidLoad 中,注册通知:

    NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name:UIResponder.keyboardWillShowNotification, object: nil)
    NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name:UIResponder.keyboardWillHideNotification, object: nil)
    

    添加以下观察者方法,当键盘出现时自动滚动。

    @objc func keyboardWillShow(notification:NSNotification) {
    
        guard let userInfo = notification.userInfo else { return }
        var keyboardFrame:CGRect = (userInfo[UIResponder.keyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue
        keyboardFrame = self.view.convert(keyboardFrame, from: nil)
    
        var contentInset:UIEdgeInsets = self.scrollView.contentInset
        contentInset.bottom = keyboardFrame.size.height + 20
        scrollView.contentInset = contentInset
    }
    
    @objc func keyboardWillHide(notification:NSNotification) {
    
        let contentInset:UIEdgeInsets = UIEdgeInsets.zero
        scrollView.contentInset = contentInset
    }
    

    【讨论】:

    • 将这些代码与 scrollView 一起使用挽救了我的生命。只需添加 20 contentInset.bottom 即可正确滚动。 contentInset.bottom = keyboardFrame.size.height + 20
    • 完美运行! contentInset 可以在keyboardWillHide中设置为'let'而不是'var' :)
    • 太棒了,非常感谢
    • 第一个 keyboardWillShow 返回正确的键盘高度 (260)。 All next keyboardWillShow 返回键盘高度216。为什么?
    • @BenShabat Apple Docs for NotificationCenter: If your app targets iOS 9.0 and later or macOS 10.11 and later, you don't need to unregister an observer in its deallocation method.
    【解决方案2】:

    swift 3 的最佳答案:

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

    然后:

    func keyboardWillShow(notification:NSNotification){
        //give room at the bottom of the scroll view, so it doesn't cover up anything the user needs to tap
        var userInfo = notification.userInfo!
        var keyboardFrame:CGRect = (userInfo[UIKeyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue
        keyboardFrame = self.view.convert(keyboardFrame, from: nil)
    
        var contentInset:UIEdgeInsets = self.theScrollView.contentInset
        contentInset.bottom = keyboardFrame.size.height
        theScrollView.contentInset = contentInset
    }
    
    func keyboardWillHide(notification:NSNotification){
        let contentInset:UIEdgeInsets = UIEdgeInsets.zero
        theScrollView.contentInset = contentInset
    }
    

    【讨论】:

    • 如果您想使用返回键关闭键盘,也可以保留上面的 textFieldShouldReturn 方法。感谢更新!
    • 别忘了NotificationCenter.default.removeObserver(self)
    • 像魅力一样工作。谢谢丹尼尔。
    • @nikans Apple Docs for NotificationCenter: If your app targets iOS 9.0 and later or macOS 10.11 and later, you don't need to unregister an observer in its deallocation method.
    • 我认为您需要在答案中将UIKeyboardFrameBeginUserInfoKey 替换为UIKeyboardFrameEndUserInfoKey。因为在 willShow 方法中需要结束帧。
    【解决方案3】:

    这是一个完整的解决方案,使用保护和简洁的代码。加上keyboardWillHide 中的正确代码,仅将bottom 重置为0。

    @IBOutlet private weak var scrollView: UIScrollView!
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        registerNotifications()
    }
    
    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        scrollView.contentInset.bottom = 0
    }
    
    private func registerNotifications() {
        NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)
    }
    
    @objc private func keyboardWillShow(notification: NSNotification){
        guard let keyboardFrame = notification.userInfo![UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else { return }
        scrollView.contentInset.bottom = view.convert(keyboardFrame.cgRectValue, from: nil).size.height
    }
    
    @objc private func keyboardWillHide(notification: NSNotification){
        scrollView.contentInset.bottom = 0
    }
    

    【讨论】:

    • 不错的答案。为了更简洁,你可以说“.UIKeyboardWillShow”而不是“NSNotification.Name.UIKeyboardWillShow”。如果您不介意,我更新了您的答案。
    • UIKeyboardFrameBeginUserInfoKey 是错误的键;它应该是UIKeyboardFrameEndUserInfoKey(开始→结束)。此解决方案使键盘的高度为零。
    • 这对我有帮助!谢谢队友:)
    • 很好的答案。我认为使用 viewDidDisappear() 会更安全 - 如果在键盘可见时视图被导航走,则可以想象您可以在隐藏实际发生之前删除通知观察者,因此不会重置您的内容插图。为了更加确定,每当通知观察者被删除时,我也会将 inset 设置为 0。
    • 相当干净的解决方案。此外,keyboardWillShowkeyboardWillHide 可以是私有的。
    【解决方案4】:

    适用于 Swift 4.0

    在 ViewDidLoad 中

    // setup keyboard event
    NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillShow), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
    NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillHide), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
    

    添加以下观察者方法,当键盘出现时自动滚动。

    @objc func keyboardWillShow(notification:NSNotification){
        var userInfo = notification.userInfo!
        var keyboardFrame:CGRect = (userInfo[UIKeyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue
        keyboardFrame = self.view.convert(keyboardFrame, from: nil)
    
        var contentInset:UIEdgeInsets = self.ui_scrollView.contentInset
        contentInset.bottom = keyboardFrame.size.height
        ui_scrollView.contentInset = contentInset
    }
    
    @objc func keyboardWillHide(notification:NSNotification){
    
        let contentInset:UIEdgeInsets = UIEdgeInsets.zero
        ui_scrollView.contentInset = contentInset
    }
    

    【讨论】:

    • 对于 Swift 5,NSNotification.Name.UIKeyboardWillShow 是 UIResponder.keyboardWillShowNotification 并且 UIKeyboardFrameBeginUserInfoKey 是 UIResponder.keyboardFrameBeginUserInfoKey。
    【解决方案5】:

    contentInset 对我不起作用,因为我希望滚动视图一直向上移动到键盘上方。所以我用contentOffset:

    func keyboardWillShow(notification:NSNotification) {
        guard let keyboardFrameValue = notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue else {
            return
        }
        let keyboardFrame = view.convert(keyboardFrameValue.cgRectValue, from: nil)
        scrollView.contentOffset = CGPoint(x:0, y:keyboardFrame.size.height)
    }
    
    func keyboardWillHide(notification:NSNotification) {
        scrollView.contentOffset = .zero
    }
    

    【讨论】:

      【解决方案6】:

      Swift 5仅在TextField被键盘隐藏时调整ScrollView(对于多个TextFields)

      添加/删除观察者:

      override func viewWillAppear(_ animated: Bool) {
          super.viewWillAppear(animated)
          NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
          NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)
      }
      
      override func viewWillDisappear(_ animated: Bool) {
          super.viewWillDisappear(animated)
          NotificationCenter.default.removeObserver(self)
      }
      

      跟踪这些值,以便您回到原来的位置:

      var scrollOffset : CGFloat = 0
      var distance : CGFloat = 0
      

      根据TextField位置调整ScrollView contentOffset:

      @objc func keyboardWillShow(notification: NSNotification) {
          if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
      
              var safeArea = self.view.frame
              safeArea.size.height += scrollView.contentOffset.y
              safeArea.size.height -= keyboardSize.height + (UIScreen.main.bounds.height*0.04) // Adjust buffer to your liking
      
              // determine which UIView was selected and if it is covered by keyboard
      
              let activeField: UIView? = [textFieldA, textViewB, textFieldC].first { $0.isFirstResponder }
              if let activeField = activeField {
                  if safeArea.contains(CGPoint(x: 0, y: activeField.frame.maxY)) {
                      print("No need to Scroll")
                      return
                  } else {
                      distance = activeField.frame.maxY - safeArea.size.height
                      scrollOffset = scrollView.contentOffset.y
                      self.scrollView.setContentOffset(CGPoint(x: 0, y: scrollOffset + distance), animated: true)
                  }
              }
              // prevent scrolling while typing
      
              scrollView.isScrollEnabled = false
          }
      }
      @objc func keyboardWillHide(notification: NSNotification) {
              if distance == 0 {
                  return
              }
              // return to origin scrollOffset
              self.scrollView.setContentOffset(CGPoint(x: 0, y: scrollOffset), animated: true)
              scrollOffset = 0
              distance = 0
              scrollView.isScrollEnabled = true
      }
      

      确保第一次使用 [UIResponder.keyboardFrameEndUserInfoKey] 来获得正确的键盘高度。

      【讨论】:

        【解决方案7】:

        来自 Sudheer Palchuri 的回答,已转换为 Swift 4。

        在 ViewDidLoad 中,注册通知:

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

        然后:

        // MARK: - Keyboard Delegates
        func textFieldShouldReturn(textField: UITextField) -> Bool {
            textField.resignFirstResponder()
            return true
        }
        
        @objc func keyboardWillShow(notification:NSNotification){
        
            var userInfo = notification.userInfo!
            var keyboardFrame:CGRect = (userInfo[UIKeyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue
            keyboardFrame = self.view.convert(keyboardFrame, from: nil)
        
            var contentInset:UIEdgeInsets = self.scrollView.contentInset
            contentInset.bottom = keyboardFrame.size.height
            self.scrollView.contentInset = contentInset
        }
        
        @objc func keyboardWillHide(notification:NSNotification){
        
            let contentInset:UIEdgeInsets = UIEdgeInsets.zero
            self.scrollView.contentInset = contentInset
        }
        

        【讨论】:

          【解决方案8】:

          阅读了你发给我的链接,我找到了让它工作的方法,谢谢!:

          func textFieldDidBeginEditing(textField: UITextField) {            
              if (textField == //your_field) {
                  scrollView.setContentOffset(CGPointMake(0, field_extra.center.y-280), animated: true)
                  callAnimation()
                  viewDidLayoutSubviews()
              }
          }
          
          func textFieldDidEndEditing(textField: UITextField) {    
              if (textField == //your_field){
                  scrollView .setContentOffset(CGPointMake(0, 0), animated: true)
                  viewDidLayoutSubviews()
              }
          }
          

          【讨论】:

            【解决方案9】:

            您可以通过滚动偏移为您的滚动视图设置动画以在键盘外观上以 UITextField 为中心(即,使您的文本字段成为第一响应者)。这里有一些很好的资源可以帮助你入门(这个网站上有很多):

            How programmatically move a UIScrollView to focus in a control above keyboard?

            How to make a UIScrollView auto scroll when a UITextField becomes a first responder

            此外,如果您只是在单元格中使用带有内容的 UITableView,当文本字段成为第一响应者时,UITableViewController 将自动滚动到文本字段单元格(尽管我不确定这是您想要做的) .

            【讨论】:

              【解决方案10】:

              Swift 3 的答案,基于 Daniel Jones 提出的答案,但更安全(感谢守卫)、更简洁且具有一致的滚动指示器插图:

              @objc private func keyboardWillBeShown(notification: NSNotification) {
                  guard let keyboardFrameValue = notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue else { return }
                  let keyboardFrame = view.convert(keyboardFrameValue.cgRectValue, from: nil)
                  scrollView.contentInset.bottom = keyboardFrame.size.height
                  scrollView.scrollIndicatorInsets = scrollView.contentInset
              }
              
              @objc private func keyboardWillBeHidden() {
                  scrollView.contentInset = .zero
                  scrollView.scrollIndicatorInsets = scrollView.contentInset
              }
              

              【讨论】:

              • 我得到 ViewController has no member for self.scrollView
              • @Siddharth 那是因为你需要为你的滚动视图创建 IBOutlet
              【解决方案11】:

              在 Swift4 中,只需添加以下扩展即可。

              extension UIViewController {
              
                 func setupViewResizerOnKeyboardShown() {
                      NotificationCenter.default.addObserver(self,
                                                         selector: #selector(self.keyboardWillShowForResizing),
                                                         name: 
                      Notification.Name.UIKeyboardWillShow,
                                                         object: nil)
                      NotificationCenter.default.addObserver(self,
                                                         selector: #selector(self.keyboardWillHideForResizing),
                                                         name: Notification.Name.UIKeyboardWillHide,
                                                         object: nil)
                      }
              
                 @objc func keyboardWillShowForResizing(notification: Notification) {
                      if let keyboardSize = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue,
                      let window = self.view.window?.frame {
                      // We're not just minusing the kb height from the view height because
                      // the view could already have been resized for the keyboard before
                      self.view.frame = CGRect(x: self.view.frame.origin.x,
                                               y: self.view.frame.origin.y,
                                               width: self.view.frame.width,
                                               height: window.origin.y + window.height - keyboardSize.height)
                    } else {
                      debugPrint("We're showing the keyboard and either the keyboard size or window is nil: panic widely.")
                    }
                }
              
                 @objc func keyboardWillHideForResizing(notification: Notification) {
                   if let keyboardSize = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
                      let viewHeight = self.view.frame.height
                      self.view.frame = CGRect(x: self.view.frame.origin.x,
                                               y: self.view.frame.origin.y,
                                               width: self.view.frame.width,
                                               height: viewHeight + keyboardSize.height)
                  } else {
                      debugPrint("We're about to hide the keyboard and the keyboard size is nil. Now is the rapture.")
                  }
                 }
               }
              

              【讨论】:

                【解决方案12】:

                这是对上面发布的Zachary Probst 解决方案的改进。 我在他的解决方案中遇到了一些问题,并对其进行了修复并进行了一些改进。

                这个版本不需要传入 UITextView 控件列表。它在当前视图中找到第一个响应者。它还处理视图层次结构中任何级别的 UITextView 控件。

                我认为他的 safeArea 计算不太正确,scrollView.contentOffset.y 需要更改符号。如果不滚动它就不会显示。这个固定的增量滚动。这可能来自我所做的其他更改。

                如果用户在键盘启动时跳转到其他 UITextViews,则此方法有效。

                这是我用于一堆 ViewController 的基类。继承的 ViewController 只需要设置 UIScrollViewer 即可激活此代码行为。

                class ThemeAwareViewController: UIViewController
                {
                    var scrollViewForKeyBoard: UIScrollView? = nil
                    var saveOffsetForKeyBoard: CGPoint?
                    
                    func findViewThatIsFirstResponder(view: UIView) -> UIView?
                    {
                        if view.isFirstResponder {
                            return view
                        }
                
                        for subView in view.subviews {
                            if let hit = findViewThatIsFirstResponder(view: subView) {
                                return hit
                            }
                        }
                
                        return nil
                    }
                    
                    @objc func keyboardWillShow(notification: NSNotification) {
                        if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
                            if let scrollView = scrollViewForKeyBoard {
                                
                                var safeArea = self.view.frame
                                safeArea.size.height -= scrollView.contentOffset.y
                                safeArea.size.height -= keyboardSize.height
                                safeArea.size.height -= view.safeAreaInsets.bottom
                                
                                let activeField: UIView? = findViewThatIsFirstResponder(view: view)
                                
                                if let activeField = activeField {
                                    // This line had me stumped for a while (I was passing in .frame)
                                    let activeFrameInView = view.convert(activeField.bounds, from: activeField)
                                    let distance = activeFrameInView.maxY - safeArea.size.height
                                    if saveOffsetForKeyBoard == nil {
                                        saveOffsetForKeyBoard = scrollView.contentOffset
                                    }
                                    scrollView.setContentOffset(CGPoint(x: 0, y: distance), animated: true)
                                }
                            }
                        }
                    }
                    
                    @objc func keyboardWillHide(notification: NSNotification) {
                        guard let restoreOffset = saveOffsetForKeyBoard else {
                            return
                        }
                        if let scrollView = scrollViewForKeyBoard {
                            scrollView.setContentOffset(restoreOffset, animated: true)
                            self.saveOffsetForKeyBoard = nil
                        }
                    }   
                }
                

                【讨论】:

                • 很棒的解决方案,伙计。奇迹般有效。但你真的,真的应该首先提到 NotificationCenter.default.addObserver。也许这就是为什么你仍然没有得到任何喜欢的原因!
                【解决方案13】:
                override func viewDidLoad() {
                    super.viewDidLoad()
                    // Do any additional setup after loading the view, typically from a nib.
                
                    NotificationCenter.default.addObserver(self, selector: #selector(ViewController.keyboardWillShow), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
                
                    NotificationCenter.default.addObserver(self, selector: #selector(ViewController.keyboardWillHide), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
                
                }
                
                func keyboardWillShow(_ notification:Notification) {
                
                    if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
                        tableView.contentInset = UIEdgeInsetsMake(0, 0, keyboardSize.height, 0)
                    }
                }
                func keyboardWillHide(_ notification:Notification) {
                
                    if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
                        tableView.contentInset = UIEdgeInsetsMake(0, 0, 0, 0)
                    }
                }
                

                【讨论】:

                  【解决方案14】:

                  如果有人正在为此解决方案寻找 Objective-C 代码:

                  - (void)keyboardWasShown:(NSNotification *)notification {
                          NSDictionary* info = [notification userInfo];
                          CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
                          UIEdgeInsets contentInsets = baseScrollView.contentInset;
                          contentInsets.bottom = kbSize.height;
                          baseScrollView.contentInset = contentInsets;
                      }
                  
                      - (void)keyboardWillBeHidden:(NSNotification *)notification {
                          UIEdgeInsets contentInsets = UIEdgeInsetsZero;
                          baseScrollView.contentInset = contentInsets;
                          [baseScrollView endEditing:YES];
                      }
                  

                  【讨论】:

                    【解决方案15】:

                    在我的情况下

                    滚动视图 --> 表视图 --> TableViewCell

                    所以我必须获得相对于键盘框架的 y 位置,并检查键盘 y 位置和我的活动字段 y 位置是否相交

                       @objc func keyboardWillShow(_ notification: Foundation.Notification) {
                    
                            var userInfo = notification.userInfo!
                            var keyboardFrame:CGRect = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
                            keyboardFrame = self.view.convert(keyboardFrame, from: nil)
                    
                            var contentInset:UIEdgeInsets = self.scrollView!.contentInset
                            contentInset.bottom = keyboardFrame.size.height
                            let loc = self.activeTextField?.convert(activeTextField!.bounds, to: self.view)
                    
                            if keyboardFrame.origin.y <  loc!.origin.y {
                                self.scrollView?.contentOffset = CGPoint.init(x: (self.scrollView?.contentOffset.x)!, y: loc!.origin.y)
                            }
                    
                            if self.scrollView?.contentInset.bottom == 0 {
                                self.scrollView?.contentInset = contentInset
                            }
                    
                        }
                    

                    【讨论】:

                      猜你喜欢
                      • 1970-01-01
                      • 2019-01-18
                      • 2021-05-31
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2020-12-11
                      • 2018-05-21
                      相关资源
                      最近更新 更多