iPhone X how to handle View Controller inputAccessoryView?

前端 未结 15 2644
长情又很酷
长情又很酷 2020-11-30 00:43

I have a messaging app that has the typical UI design of a text field at the bottom of a full screen table view. I am setting that text field to be the view controller\'s

相关标签:
15条回答
  • 2020-11-30 01:12

    Until safe are insets are guided by iOS automatically, simple workaround would be to wrap your accessory in container view and set bottom space constraint between accesory view and container view to match safe area insets of window.

    Note: Of course this workaround can double your accessory view spacing from bottom when iOS update fixes bottom spacing for accessory views.

    E.g.

    - (void) didMoveToWindow {
        [super didMoveToWindow];
        if (@available(iOS 11.0, *)) {
            self.bottomSpaceConstraint.constant = self.window.safeAreaInsets.bottom;
        }
    }
    
    0 讨论(0)
  • 2020-11-30 01:15

    I just created a quick CocoaPod called SafeAreaInputAccessoryViewWrapperView to fix this. It also dynamically sets the wrapped view's height using autolayout constraints so you don't have to manually set the frame. Supports iOS 9+.

    Here's how to use it:

    1. Wrap any UIView/UIButton/UILabel/etc using SafeAreaInputAccessoryViewWrapperView(for:):

      SafeAreaInputAccessoryViewWrapperView(for: button)
      
    2. Store a reference to this somewhere in your class:

      let button = UIButton(type: .system)
      
      lazy var wrappedButton: SafeAreaInputAccessoryViewWrapperView = {
          return SafeAreaInputAccessoryViewWrapperView(for: button)
      }()
      
    3. Return the reference in inputAccessoryView:

      override var inputAccessoryView: UIView? {
          return wrappedButton
      }
      
    4. (Optional) Always show the inputAccessoryView, even when the keyboard is closed:

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

    Good luck!

    0 讨论(0)
  • 2020-11-30 01:17

    After much research and trials, this seems to be a working solution for trying to use UIToolbar with a text input's inputAccessoryView. (Most of the existing solutions are for using fixed accessory view instead of assigning it to a text view (and hiding it when the keyboard is closed).)

    The code is inspired by https://stackoverflow.com/a/46510833/2603230. Basically, we first create a custom view that has a toolbar subview:

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

    Then you can set it as an inputAccessoryView for a text input normally: (You should specify the frame size to avoid the warnings seen in 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
    

    When you want to interact with the toolbar (e.g., set items on the toolbar), you can simply refer to the toolbar variable:

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

    Demo: (with hardware keyboard / Command+K in simulator)

    Before:

    After:

    0 讨论(0)
  • 2020-11-30 01:18

    inputAccessoryView and safe area on iPhone X

    • when the keyboard is not visible, the inputAccessoryView is pinned on the very bottom of the screen. There is no way around that and I think this is intended behavior.

    • the layoutMarginsGuide (iOS 9+) and safeAreaLayoutGuide (iOS 11) properties of the view set as inputAccessoryView both respect the safe area, i.e on iPhone X :

      • when the keyboard is not visible, the bottomAnchor accounts for the home button area
      • when the keyboard is shown, the bottomAnchor is at the bottom of the inputAccessoryView, so that it leaves no useless space above the keyboard

    Working example :

    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

    0 讨论(0)
  • 2020-11-30 01:20

    In the case you already have a custom view loaded via nib file.

    Add a convenience constructor like this:

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

    and override intrinsicContentSize:

    override var intrinsicContentSize: CGSize {
        return .zero
    }
    

    In the nib set the first bottom constraint (of views that should stay above the safe area) to safeArea and the second one to superview with lower priority so it can be satisfied on older iOS.

    0 讨论(0)
  • 2020-11-30 01:20

    I just created a project on Github with support for iPhone X. It respects the new safe area layout guide. Use:

    autoresizingMask = [.flexibleHeight]
    

    Screenshot:

    0 讨论(0)
提交回复
热议问题