iPhone X如何处理View Controller inputAccessoryView?

时间:2017-09-18 15:23:55

标签: ios iphone inputaccessoryview iphone-x

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

我理解这是苹果推荐的实现这种UI结构的方式,它在“经典”设备上运行得很好但是当我在iPhone X模拟器上测试时,我注意到使用这种方法,文本字段不尊重新的“安全区“。文本字段显示在主屏幕指示器下方屏幕的最底部。

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

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

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

enter image description here

15 个答案:

答案 0 :(得分:33)

inputAccessoryView和iPhone X上的安全区域

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

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

    • 当键盘不可见时,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

答案 1 :(得分:27)

这是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

答案 2 :(得分:4)

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

<强>之前enter image description here

<强>修正enter image description here

<强>后enter image description here

答案 3 :(得分:3)

我刚刚创建了一个名为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()
    }
    
  5. 祝你好运!

答案 4 :(得分:3)

只需为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

答案 5 :(得分:2)

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

我想这应该在iOS更新时修复,当iPhone X出现时。

答案 6 :(得分:2)

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

注意:当iOS更新修复附件视图的底部间距时,这种解决方法当然会使配件视图间距从底部加倍。

E.g。

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

答案 7 :(得分:1)

来自代码(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

结果:

enter image description here enter image description here

答案 8 :(得分:1)

经过大量研究和试验,这似乎是尝试将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)

之前: the toolbar obstructs the home indicator

之后: the toolbar does not obstruct the home indicator

答案 9 :(得分:0)

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

添加如下的便利构造函数:

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

并覆盖intrinsicContentSize

override var intrinsicContentSize: CGSize {
    return .zero
}
  

nib中设置第一个底部约束(应保持在安全区域之上的视图)safeArea,将第二个约束设置为superview降低priority,以便在较旧的iOS上满足。

答案 10 :(得分:0)

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

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

也不要忘记将自定义视图的自动调整大小蒙版设置为UIViewAutoresizingFlexibleHeight

就是这样。待会儿谢谢我。

答案 11 :(得分:0)

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

我正在使用UIInputViewController通过覆盖inputAccessoryViewController属性而不是“主”视图控制器中的inputAccessoryView提供输入附件视图。

UIInputViewController的{​​{1}}设置为我的自定义输入视图(inputView的子类)。

实际上,对我来说,技巧是将我的UIInputView的{​​{1}}属性设置为allowsSelfSizing。输入视图内的约束使用安全区域,并以定义总视图高度的方式进行设置(类似于自动调整表格视图单元格的大小)。

答案 12 :(得分:-1)

- 对于那些使用JSQMessagesViewController lib的人 -

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

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

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

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

答案 13 :(得分:-1)

我刚刚创建了一个project on Github,它支持iPhoneX。它遵循新的安全区域布局指南。使用:

autoresizingMask = [.flexibleHeight]

屏幕截图:

screenshot

答案 14 :(得分:-3)

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

Constraint

And result