27

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

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

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

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

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

在此处输入图像描述

4

15 回答 15

50

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

在这里查看结果

于 2017-10-01T09:03:06.940 回答
49

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

于 2017-09-27T08:28:14.337 回答
7

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

之前在此处输入图像描述

修复在此处输入图像描述

之后在此处输入图像描述

于 2017-11-01T20:56:19.463 回答
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工具栏

于 2018-03-13T12:37:37.847 回答
3

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

以下是如何使用它:

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

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

祝你好运!

于 2017-10-05T18:55:01.320 回答
2

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

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

例如

- (void) didMoveToWindow {
    [super didMoveToWindow];
    if (@available(iOS 11.0, *)) {
        self.bottomSpaceConstraint.constant = self.window.safeAreaInsets.bottom;
    }
}
于 2017-09-28T09:21:52.020 回答
2

似乎这是一个 iOS 错误,并且有一个 rdar 问题:inputAccessoryViews 应该尊重 iPhone X 上的外部键盘插入的安全区域

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

于 2017-09-22T12:16:14.730 回答
2

经过大量研究和试验,这似乎是尝试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 中看到带有 UIBarButtonItem LayoutConstraint 问题的警告)

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)

前: 工具栏挡住了主页指示器

后: 工具栏不会阻碍主页指示器

于 2020-09-09T04:57:21.337 回答
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

结果:

在此处输入图像描述 在此处输入图像描述

于 2017-12-01T15:59:48.923 回答
1

我刚刚在 Github上创建了一个支持 iPhone X 的项目。它尊重新的安全区域布局指南。利用:

autoresizingMask = [.flexibleHeight]

截屏:

截屏

于 2018-12-26T05:09:26.773 回答
0

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

我通过覆盖属性而不是“主”视图控制器UIInputViewController来提供输入附件视图。inputAccessoryViewControllerinputAccessoryView

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

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

于 2019-08-28T14:36:29.913 回答
0

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

添加一个方便的构造函数,如下所示:

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

并覆盖intrinsicContentSize

override var intrinsicContentSize: CGSize {
    return .zero
}

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

于 2017-12-18T21:21:29.390 回答
0

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

只需使用继承自UIToolbar而不是UIView作为您的inputAccessoryView.

另外不要忘记将该自定义视图的自动调整大小掩码设置为UIViewAutoresizingFlexibleHeight.

而已。晚点再谢我。

于 2019-07-04T22:15:50.173 回答
-1

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

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

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

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

pod 'JSQMessagesViewController', :git => 'https://github.com/Tulleb/JSQMessagesViewController.git', :branch => 'develop', :inhibit_warnings => true
于 2017-11-08T09:56:13.790 回答
-3

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

约束

结果

于 2017-09-21T07:48:40.317 回答