web-dev-qa-db-ja.com

iPhone X View Controller inputAccessoryViewの処理方法

全画面のテーブルビューの下部にテキストフィールドの典型的なUIデザインを持つメッセージングアプリがあります。そのテキストフィールドをView ControllerのinputAccessoryViewに設定し、ViewController.becomeFirstResponder()を呼び出して、画面の下部にフィールドを表示します。

これがAppleこのUI構造を実現する推奨方法であり、「クラシック」デバイスで完全に機能することを理解していますが、iPhone Xシミュレーターでテストすると、このアプローチの使用、テキストフィールド新しい「安全な領域」を尊重しません。テキストフィールドは、ホーム画面インジケータの下の画面の一番下に表示されます。

HIGドキュメントを調べましたが、View ControllerのinputAccessoryViewに関して有用なものは見つかりませんでした。

このアプローチを使用すると、実際には制約を直接制御できず、inputAccessoryViewを設定して、そこからUIをView Controllerに処理させるだけなので、難しいです。そのため、フィールドを新しい安全な領域に制限することはできません。

誰かがこれに関する優れたドキュメントを見つけましたか、iPhone Xでうまく機能する代替アプローチを知っていますか?

enter image description here

16
LOP_Luke

inputAccessoryViewおよびiPhone Xのセーフエリア

  • キーボードが表示されていない場合、inputAccessoryViewは画面の一番下に固定されています。それを回避する方法はありません。これは意図した動作だと思います。

  • layoutMarginsGuideとして設定されたビューのsafeAreaLayoutGuide(iOS 9+)およびinputAccessoryView(iOS 11)プロパティは両方ともセーフエリアを尊重します。つまり、iPhone Xの場合:

    • キーボードが表示されていない場合、bottomAnchorはホームボタン領域を占めます
    • キーボードが表示されている場合、bottomAnchorinputAccessoryViewの下部にあるため、キーボードの上に無駄なスペースはありません。

作業例:

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

結果はこちら

28
Alexandre Bintz

これは、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

25
ahbou

Xibで、デザインのbottomで正しい制約を見つけ、SuperviewではなくSafe Areaにアイテムを設定します。

Beforeenter image description here

修正enter image description here

Afterenter image description here

4
Vlad

これを修正するために 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()
    }
    

がんばろう!

3
Jeff

JSQMessagesInputToolbarの拡張機能を1つ追加するだけです

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

3
ERbittuu

IOSのバグのようです。それにはrdarの問題があります: inputAccessoryViewsは、iPhone Xの外部キーボードとの安全な領域の挿入を尊重する必要があります

これは、iPhone Xが起動するiOSアップデートで修正されるはずです。

2
Marat Saytakov

インセットがiOSによって自動的にガイドされるまで、簡単な回避策は、コンテナビューでアクセサリをラップし、ウィンドウのセーフエリアインセットに合わせてアクセサリビューとコンテナビューの間に下部スペースの制約を設定することです。

注:もちろん、この回避策は、iOSの更新がアクセサリビューの下部の間隔を修正するときに、下部からアクセサリビューの間隔を2倍にすることができます。

例えば。

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

コードから(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 hereenter image description here

1
Vlad

この場合、nibファイルを介してカスタムビューがすでにロードされています。

このような便利なコンストラクタを追加:

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

およびintrinsicContentSize:をオーバーライドする

override var intrinsicContentSize: CGSize {
    return .zero
}

nibで、最初の下部の制約(安全な領域の上にとどまるビューの)safeAreaに設定し、 superviewの2つ目は、より低いpriorityで、古いiOSでは満足できるようになっています。

0
Jakub Truhlář