全画面のテーブルビューの下部にテキストフィールドの典型的なUIデザインを持つメッセージングアプリがあります。そのテキストフィールドをView ControllerのinputAccessoryView
に設定し、ViewController.becomeFirstResponder()
を呼び出して、画面の下部にフィールドを表示します。
これがAppleこのUI構造を実現する推奨方法であり、「クラシック」デバイスで完全に機能することを理解していますが、iPhone Xシミュレーターでテストすると、このアプローチの使用、テキストフィールド新しい「安全な領域」を尊重しません。テキストフィールドは、ホーム画面インジケータの下の画面の一番下に表示されます。
HIGドキュメントを調べましたが、View ControllerのinputAccessoryView
に関して有用なものは見つかりませんでした。
このアプローチを使用すると、実際には制約を直接制御できず、inputAccessoryView
を設定して、そこからUIをView Controllerに処理させるだけなので、難しいです。そのため、フィールドを新しい安全な領域に制限することはできません。
誰かがこれに関する優れたドキュメントを見つけましたか、iPhone Xでうまく機能する代替アプローチを知っていますか?
inputAccessoryView
およびiPhone Xのセーフエリアキーボードが表示されていない場合、inputAccessoryView
は画面の一番下に固定されています。それを回避する方法はありません。これは意図した動作だと思います。
layoutMarginsGuide
として設定されたビューのsafeAreaLayoutGuide
(iOS 9+)およびinputAccessoryView
(iOS 11)プロパティは両方ともセーフエリアを尊重します。つまり、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
}
}
これは、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
これを修正するために SafeAreaInputAccessoryViewWrapperView という簡単なCocoaPodを作成しました。また、自動レイアウト制約を使用してラップされたビューの高さを動的に設定するため、フレームを手動で設定する必要がありません。 iOS 9以降をサポートします。
使用方法は次のとおりです。
SafeAreaInputAccessoryViewWrapperView(for:)
を使用して、UIView/UIButton/UILabel/etcをラップします。
SafeAreaInputAccessoryViewWrapperView(for: button)
これへの参照をクラスのどこかに保存します。
let button = UIButton(type: .system)
lazy var wrappedButton: SafeAreaInputAccessoryViewWrapperView = {
return SafeAreaInputAccessoryViewWrapperView(for: button)
}()
inputAccessoryView
で参照を返します。
override var inputAccessoryView: UIView? {
return wrappedButton
}
(オプション)キーボードが閉じている場合でも、常にinputAccessoryView
を表示します。
override var canBecomeFirstResponder: Bool {
return true
}
override func viewDidLoad() {
super.viewDidLoad()
becomeFirstResponder()
}
がんばろう!
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
}
}
}
}
IOSのバグのようです。それにはrdarの問題があります: inputAccessoryViewsは、iPhone Xの外部キーボードとの安全な領域の挿入を尊重する必要があります
これは、iPhone Xが起動するiOSアップデートで修正されるはずです。
インセットがiOSによって自動的にガイドされるまで、簡単な回避策は、コンテナビューでアクセサリをラップし、ウィンドウのセーフエリアインセットに合わせてアクセサリビューとコンテナビューの間に下部スペースの制約を設定することです。
注:もちろん、この回避策は、iOSの更新がアクセサリビューの下部の間隔を修正するときに、下部からアクセサリビューの間隔を2倍にすることができます。
例えば。
- (void) didMoveToWindow {
[super didMoveToWindow];
if (@available(iOS 11.0, *)) {
self.bottomSpaceConstraint.constant = self.window.safeAreaInsets.bottom;
}
}
コードから(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
結果:
この場合、nibファイルを介してカスタムビューがすでにロードされています。
このような便利なコンストラクタを追加:
convenience init() {
self.init(frame: .zero)
autoresizingMask = .flexibleHeight
}
およびintrinsicContentSize
:をオーバーライドする
override var intrinsicContentSize: CGSize {
return .zero
}
nib
で、最初の下部の制約(安全な領域の上にとどまるビューの)をsafeArea
に設定し、superview
の2つ目は、より低いpriority
で、古いiOSでは満足できるようになっています。