【问题标题】:iPhone X how to handle View Controller inputAccessoryView?iPhone X 如何处理 View Controller inputAccessoryView?
【发布时间】:2018-02-27 05:00:42
【问题描述】:

我有一个消息应用程序,它具有典型的 UI 设计,即全屏表格视图底部的文本字段。我将该文本字段设置为视图控制器的 inputAccessoryView 并调用 ViewController.becomeFirstResponder() 以使该字段显示在屏幕底部。

我知道这是 Apple 推荐的完成此 UI 结构的方法,它在“经典”设备上完美运行,但是当我在 iPhone X 模拟器上测试时,我注意到使用这种方法,文本字段不尊重新的“安全区”。文本字段呈现在主屏幕指示器下方的屏幕最底部。

我查看了 HIG 文档,但没有发现关于视图控制器上的 inputAccessoryView 的任何有用信息。

这很困难,因为使用这种方法我实际上并没有直接控制任何约束,我只是设置inputAccessoryView 并让视图控制器从那里处理 UI。所以我不能只将场限制在新的安全区域。

有没有人找到这方面的好文档或知道在 iPhone X 上运行良好的替代方法?

【问题讨论】:

  • 这看起来像是 API 中的一个错误。您应该提交错误报告。作为一种解决方法,您可以在 iPhone X 上运行时更改附件视图以在底部留出额外空间。
  • 您的文本字段是在工具栏还是 uiview 中?我假设您将包含文本字段和相机/发送按钮的工具栏或 uiview 分配给 inputAccessoryView。如果是这样,我会按照 dasdom 的建议进行操作,并在将其分配给 iPhone X 的 inputAccessoryView 之前增加该工具栏或 uiview 的高度
  • 在 viewWillAppear 中你可以输入 print(inputAccessoryView?.safeAreaInsets) 并且会看到所有的 safeAreaInsets 都是 0。不幸的是这是只读的,不能修改。
  • 您是否通过代码或情节提要完成布局?我找到了一种通过代码使其工作的方法。只需将底部设置为 safeLayoutGuide.bottomAnchor。

标签: ios iphone inputaccessoryview iphone-x


【解决方案1】:

我只是将安全区域添加到 inputAccessoryView(Xcode 上的复选框)。并更改底部空间约束等于安全区域底部而不是 inputAccessoryView 根视图底部。

Constraint

And result

【讨论】:

    【解决方案2】:

    似乎这是一个 iOS 错误,并且有一个 rdar 问题:inputAccessoryViews should respect safe area inset with external keyboard on iPhone X

    我想这应该会在 iPhone X 推出时在 iOS 更新中修复。

    【讨论】:

      【解决方案3】:

      这是 iPhone X 上 inputAccessoryViews 的一般问题。 inputAccessoryView 会忽略其窗口的 safeAreaLayoutGuides。

      要修复它,当视图移动到其窗口时,我们必须在您的类中手动添加约束:

      override func didMoveToWindow() {
          super.didMoveToWindow()
          if #available(iOS 11.0, *) {
              if let window = self.window {
                  self.bottomAnchor.constraintLessThanOrEqualToSystemSpacingBelow(window.safeAreaLayoutGuide.bottomAnchor, multiplier: 1.0).isActive = true
              }
          }
      }
      

      PS:这里的self指的是inputAccessoryView。

      我在这里详细写过:http://ahbou.org/post/165762292157/iphone-x-inputaccessoryview-fix

      【讨论】:

      • 这是最好的答案,应该被接受,而不是更复杂的答案。
      • 开箱即用,应该是公认的答案。谢谢@ahbou
      • 这个答案非常好,是最简单的方法,但由于某种原因,它会导致 iPhone 6 和 iPhone 6 Plus 设备出现问题,如果您尝试展示将视图控制器放到具有此 inputAccessoryView 的屏幕上,屏幕将在呈现视图控制器之前黑屏片刻。这发生在 UIAlertControllers、UIActivityViewControllers 和任何其他 UIViewController 子类中。
      • @SoroushKhanlou 你在使用 UIToolBar 吗?我在使用它时看到了这种情况。我转而使用普通的 UIView,因为 iOS 11 破坏了 UIToolBar inputAccessoryViews。如果您正在使用它,则需要在添加子视图后立即调用 layoutIfNeeded()。
      • 不知何故,我得到“无法同时满足约束条件”。错误:/你也有它还是我的错误?它仍然有效:D
      【解决方案4】:

      在 iOS 自动引导安全插入之前,简单的解决方法是将附件包装在容器视图中,并在附件视图和容器视图之间设置底部空间约束以匹配窗口的安全区域插入。

      注意:当然,当 iOS 更新修复附件视图的底部间距时,此解决方法可以使附件视图与底部的间距加倍。

      例如

      - (void) didMoveToWindow {
          [super didMoveToWindow];
          if (@available(iOS 11.0, *)) {
              self.bottomSpaceConstraint.constant = self.window.safeAreaInsets.bottom;
          }
      }
      

      【讨论】:

        【解决方案5】:

        inputAccessoryView 和 iPhone X 上的安全区域

        • 当键盘不可见时,inputAccessoryView 固定在屏幕的最底部。没有办法解决这个问题,我认为这是有意的行为。

        • 视图的 layoutMarginsGuide (iOS 9+) 和 safeAreaLayoutGuide (iOS 11) 属性设置为 inputAccessoryView 都尊重安全区域,即在 iPhone X 上:

          • 当键盘不可见时,bottomAnchor 占主页按钮区域
          • 当显示键盘时,bottomAnchor 位于inputAccessoryView 的底部,因此不会在键盘上方留下无用空间

        工作示例:

        import UIKit
        
        class ViewController: UIViewController {
        
            override var canBecomeFirstResponder: Bool { return true }
        
            var _inputAccessoryView: UIView!
        
            override var inputAccessoryView: UIView? {
        
                if _inputAccessoryView == nil {
        
                    _inputAccessoryView = CustomView()
                    _inputAccessoryView.backgroundColor = UIColor.groupTableViewBackground
        
                    let textField = UITextField()
                    textField.borderStyle = .roundedRect
        
                    _inputAccessoryView.addSubview(textField)
        
                    _inputAccessoryView.autoresizingMask = .flexibleHeight
        
                    textField.translatesAutoresizingMaskIntoConstraints = false
        
                    textField.leadingAnchor.constraint(
                        equalTo: _inputAccessoryView.leadingAnchor,
                        constant: 8
                    ).isActive = true
        
                    textField.trailingAnchor.constraint(
                        equalTo: _inputAccessoryView.trailingAnchor,
                        constant: -8
                    ).isActive = true
        
                    textField.topAnchor.constraint(
                        equalTo: _inputAccessoryView.topAnchor,
                        constant: 8
                    ).isActive = true
        
                    // this is the important part :
        
                    textField.bottomAnchor.constraint(
                        equalTo: _inputAccessoryView.layoutMarginsGuide.bottomAnchor,
                        constant: -8
                    ).isActive = true
                }
        
                return _inputAccessoryView
            }
        
            override func loadView() {
        
                let tableView = UITableView()
                tableView.keyboardDismissMode = .interactive
        
                view = tableView
            }
        }
        
        class CustomView: UIView {
        
            // this is needed so that the inputAccesoryView is properly sized from the auto layout constraints
            // actual value is not important
        
            override var intrinsicContentSize: CGSize {
                return CGSize.zero
            }
        }
        

        See the result here

        【讨论】:

        • 完成这项工作的关键是在输入附件视图中添加一个 SUBVIEW,然后将该视图的底部约束到安全区域。
        • 不错的一个。 intrinsicContentSize 是我的魔力。甚至不必更改 translatesAutoresizingMask... 属性。
        • 好吧,在数百次失败之后,唯一对我有用的是将intrinsicContentSize 设置为CGSizeMake(UIViewNoIntrinsicMetric, 0.0f); 或者简单地从UIToolbar 继承而不是简单的UIView。我不知道哪种方法是正确的,但流行的JSQMessagesViewController 使用第二种方法,它只是继承自UIToolbar
        • Apple 是否有任何文件可以参考这个this is needed so that the inputAccesoryView is properly sized from the auto layout constraints
        【解决方案6】:

        我刚刚创建了一个名为 SafeAreaInputAccessoryViewWrapperView 的快速 CocoaPod 来解决这个问题。它还使用自动布局约束动态设置包装视图的高度,因此您不必手动设置框架。支持 iOS 9+。

        使用方法如下:

        1. 使用 SafeAreaInputAccessoryViewWrapperView(for:) 包装任何 UIView/UIButton/UILabel/etc:

          SafeAreaInputAccessoryViewWrapperView(for: button)
          
        2. 在您的班级某处存储对此的引用:

          let button = UIButton(type: .system)
          
          lazy var wrappedButton: SafeAreaInputAccessoryViewWrapperView = {
              return SafeAreaInputAccessoryViewWrapperView(for: button)
          }()
          
        3. 返回inputAccessoryView中的引用:

          override var inputAccessoryView: UIView? {
              return wrappedButton
          }
          
        4. (可选)始终显示inputAccessoryView,即使键盘已关闭:

          override var canBecomeFirstResponder: Bool {
              return true
          }
          
          override func viewDidLoad() {
              super.viewDidLoad()
              becomeFirstResponder()
          }
          

        祝你好运!

        【讨论】:

          【解决方案7】:

          在 Xib 中,在设计的底部找到一个正确的约束,并将 item 设置为 Safe Area 而不是 Superview

          之前

          修复

          之后

          【讨论】:

          • @Akshay 下面此页面上的示例代码。用 iPhone X 的“绿色背景”截图查看我的答案。
          【解决方案8】:

          -- 对于那些正在使用 JSQMessagesViewController 库的人--

          我提出一个基于 JSQ 最新develop 分支提交的固定分叉。

          它正在使用didMoveToWindow 解决方案(我相信来自@jki?)。不理想,但值得在等待 Apple 对inputAccessoryView 的安全区域布局指南附件或任何其他更好的解决方案的答复时尝试。

          您可以将其添加到您的 Podfile 中,替换之前的 JSQ 行:

          pod 'JSQMessagesViewController', :git => 'https://github.com/Tulleb/JSQMessagesViewController.git', :branch => 'develop', :inhibit_warnings => true
          

          【讨论】:

            【解决方案9】:

            来自代码 (Swift 4)。想法——监控layoutMarginsDidChange事件并调整intrinsicContentSize

            public final class AutoSuggestionView: UIView {
            
               private lazy var tableView = UITableView(frame: CGRect(), style: .plain)
               private var bottomConstraint: NSLayoutConstraint?
               var streetSuggestions = [String]() {
                  didSet {
                     if streetSuggestions != oldValue {
                        updateUI()
                     }
                  }
               }
               var handleSelected: ((String) -> Void)?
            
               public override func initializeView() {
                  addSubview(tableView)
                  setupUI()
                  setupLayout()
                  // ...
                  updateUI()
               }
            
               public override var intrinsicContentSize: CGSize {
                  let size = super.intrinsicContentSize
                  let numRowsToShow = 3
                  let suggestionsHeight = tableView.rowHeight * CGFloat(min(numRowsToShow, tableView.numberOfRows(inSection: 0)))
                  //! Explicitly used constraint instead of layoutMargins
                  return CGSize(width: size.width,
                                height: suggestionsHeight + (bottomConstraint?.constant ?? 0))
               }
            
               public override func layoutMarginsDidChange() {
                  super.layoutMarginsDidChange()
                  bottomConstraint?.constant = layoutMargins.bottom
                  invalidateIntrinsicContentSize()
               }
            }
            
            extension AutoSuggestionView {
            
               private func updateUI() {
                  backgroundColor = streetSuggestions.isEmpty ? .clear : .white
                  invalidateIntrinsicContentSize()
                  tableView.reloadData()
               }
            
               private func setupLayout() {
            
                  let constraint0 = trailingAnchor.constraint(equalTo: tableView.trailingAnchor)
                  let constraint1 = tableView.leadingAnchor.constraint(equalTo: leadingAnchor)
                  let constraint2 = tableView.topAnchor.constraint(equalTo: topAnchor)
                  //! Used bottomAnchor instead of layoutMarginGuide.bottomAnchor
                  let constraint3 = bottomAnchor.constraint(equalTo: tableView.bottomAnchor)
                  bottomConstraint = constraint3
                  NSLayoutConstraint.activate([constraint0, constraint1, constraint2, constraint3])
               }
            }
            

            用法:

            let autoSuggestionView = AutoSuggestionView()
            // ...
            textField.inputAccessoryView = autoSuggestionView
            

            结果:

            【讨论】:

              【解决方案10】:

              如果您已经通过 nib 文件加载了自定义视图。

              像这样添加一个方便的构造函数:

              convenience init() {
                  self.init(frame: .zero)
                  autoresizingMask = .flexibleHeight
              }
              

              并覆盖intrinsicContentSize:

              override var intrinsicContentSize: CGSize {
                  return .zero
              }
              

              nib 中,将第一个底部约束(应保持在安全区域上方的视图) 设置为safeArea,第二个设置为superviewpriority 较低,所以它在旧版 iOS 上可以满足。

              【讨论】:

              • 虽然您的答案不是非常精确,但覆盖intrinsicContentSize 可能是覆盖iPhone X InputView(而不是InputAccessoryView)的人的关键,例如,用于显示照片选择器键盘。
              【解决方案11】:

              只需为 JSQMessagesInputToolbar 添加一个扩展

              extension JSQMessagesInputToolbar {
                  override open func didMoveToWindow() {
                      super.didMoveToWindow()
                      if #available(iOS 11.0, *) {
                          if self.window?.safeAreaLayoutGuide != nil {
                          self.bottomAnchor.constraintLessThanOrEqualToSystemSpacingBelow((self.window?.safeAreaLayoutGuide.bottomAnchor)!,
                                                                                          multiplier: 1.0).isActive = true
                          }
                      }
                   }
              }
              

              重复:jsqmessageviewcontroller ios11 toolbar

              【讨论】:

                【解决方案12】:

                我刚刚创建了一个支持 iPhone X 的project on Github。它尊重新的安全区域布局指南。使用:

                autoresizingMask = [.flexibleHeight]
                

                截图:

                【讨论】:

                • 你能解释一下如何使用这段代码,而不是仅仅分享链接吗?
                • 创建一个不遵循安全区域布局指南的自定义视图并在其init方法中添加autoresizingMask = [.flexibleHeight],在子视图中添加另一个遵循安全区域布局的视图。正常添加其他视图。查看项目以调整 textview 大小
                【解决方案13】:

                最简单的答案(只需一行代码)

                只需使用继承自 UIToolbar 的自定义视图而不是 UIView 作为您的 inputAccessoryView

                别忘了将自定义视图的自动调整大小掩码设置为UIViewAutoresizingFlexibleHeight

                就是这样。稍后谢谢我。

                【讨论】:

                  【解决方案14】:

                  没有变通办法对我有用的解决方案:

                  我使用UIInputViewController 来提供输入附件视图,方法是在“主”视图控制器中覆盖inputAccessoryViewController 属性而不是inputAccessoryView

                  UIInputViewControllerinputView 设置为我的自定义输入视图(UIInputView 的子类)。

                  实际上对我有用的是将我的UIInputViewallowsSelfSizing 属性设置为true。输入视图内的约束使用安全区域并以定义总视图高度的方式设置(类似于自动调整表格视图单元格的大小)。

                  【讨论】:

                    【解决方案15】:

                    经过大量研究和试验,这似乎是尝试将UIToolbar 与文本输入的inputAccessoryView 一起使用 的有效解决方案。 (大多数现有的解决方案是使用固定的附件视图,而不是将其分配给文本视图(并在键盘关闭时将其隐藏)。)

                    代码的灵感来自https://stackoverflow.com/a/46510833/2603230。基本上,我们首先创建一个具有工具栏子视图的自定义视图:

                    class CustomInputAccessoryWithToolbarView: UIView {
                        public var toolbar: UIToolbar!
                    
                        override init(frame: CGRect) {
                            super.init(frame: frame)
                    
                            // https://stackoverflow.com/a/58524360/2603230
                            toolbar = UIToolbar(frame: frame)
                    
                            // Below is adopted from https://stackoverflow.com/a/46510833/2603230
                            self.addSubview(toolbar)
                    
                            self.autoresizingMask = .flexibleHeight
                    
                            toolbar.translatesAutoresizingMaskIntoConstraints = false
                            toolbar.leadingAnchor.constraint(
                                equalTo: self.leadingAnchor,
                                constant: 0
                            ).isActive = true
                            toolbar.trailingAnchor.constraint(
                                equalTo: self.trailingAnchor,
                                constant: 0
                            ).isActive = true
                            toolbar.topAnchor.constraint(
                                equalTo: self.topAnchor,
                                constant: 0
                            ).isActive = true
                            // This is the important part:
                            if #available(iOS 11.0, *) {
                                toolbar.bottomAnchor.constraint(
                                    equalTo: self.safeAreaLayoutGuide.bottomAnchor,
                                    constant: 0
                                ).isActive = true
                            } else {
                                toolbar.bottomAnchor.constraint(
                                    equalTo: self.layoutMarginsGuide.bottomAnchor,
                                    constant: 0
                                ).isActive = true
                            }
                        }
                    
                        required init?(coder: NSCoder) {
                            fatalError("init(coder:) has not been implemented")
                        }
                    
                        // https://stackoverflow.com/a/46510833/2603230
                        // This is needed so that the inputAccesoryView is properly sized from the auto layout constraints.
                        // Actual value is not important.
                        override var intrinsicContentSize: CGSize {
                            return CGSize.zero
                        }
                    }
                    

                    然后您可以将其设置为inputAccessoryView 以正常输入文本:(您应该指定帧大小以避免在UIToolbar with UIBarButtonItem LayoutConstraint issue 中看到警告)

                    let myAccessoryView = CustomInputAccessoryWithToolbarView(frame: CGRect(x: 0, y: 0, width: self.view.frame.size.width, height: 44))
                    textView.inputAccessoryView = myAccessoryView
                    

                    当你想与工具栏交互时(例如,设置工具栏上的项目),你可以简单地参考toolbar变量:

                    myAccessoryView.toolbar.setItems(myToolbarItems, animated: true)
                    

                    演示:(在模拟器中使用硬件键盘/Command+K)

                    之前:

                    之后:

                    【讨论】:

                      猜你喜欢
                      • 1970-01-01
                      • 1970-01-01
                      • 2011-05-31
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2011-01-16
                      • 1970-01-01
                      • 1970-01-01
                      相关资源
                      最近更新 更多