1
votes

I am trying to get an inputAccessoryView working correctly. Namely, I want to be able to display, in this case, a UIToolbar in two possible states:

  1. Above the keyboard - standard and expected behavior
  2. At the bottom of the screen when the keyboard is dismissed (e.g. command + K in the simulator) - and in such instances, have the bottomAnchor respect the bottom safeAreaLayoutGuide.

I've researched this topic extensively but every suggestion I can find has a bunch of workarounds that don't seem to align with Apple engineering's suggested solution. Based on an openradar ticket, Apple engineering proposed this solution be approached as follows:

It’s your responsibility to respect the input accessory view’s safeAreaInsets. We designed it this way so developers could provide a background view (i.e., see Safari’s Find on Page input accessory view) and lay out the content view with respect to safeAreaInsets. This is fairly straightforward to accomplish. Have a view hierarchy where you have a container view and a content view. The container view can have a background color or a background view that encompasses its entire bounds, and it lays out it’s content view based on safeAreaInsets. If you’re using autolayout, this is as simple as setting the content view’s bottomAnchor to be equal to it’s superview’s safeAreaLayoutGuide.

The link for the above is: http://www.openradar.me/34411433

I have therefore constructed a simple xCode project (iOS App template) that has the following code:

class ViewController: UIViewController {
    
    var field = UITextField()
    var containerView = UIView()
    var contentView = UIView()
    var toolbar = UIToolbar()
    
    override func viewDidLoad() {
        super.viewDidLoad()
    
        // TEXTFIELD
        field = UITextField(frame: CGRect(x: 20, y: 100, width: view.frame.size.width, height: 50))
        field.placeholder = "Enter name..."
        field.backgroundColor = .secondarySystemBackground
        field.inputAccessoryView = containerView
        view.addSubview(field)
        
        // CONTAINER VIEW
        containerView.frame = CGRect(x: 0, y: 0, width: view.frame.size.width, height: 50)
        containerView.backgroundColor = .systemYellow
        containerView.translatesAutoresizingMaskIntoConstraints = false
                
        // CONTENT VIEW
        contentView.frame = CGRect(x: 0, y: 0, width: view.frame.size.width, height: 50)
        contentView.backgroundColor = .systemPink
        contentView.translatesAutoresizingMaskIntoConstraints = false
        containerView.addSubview(contentView)
        
        // TOOLBAR
        toolbar = UIToolbar(frame: CGRect(x: 0, y: 0, width: view.frame.size.width, height: 50))
        let flexibleSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: self, action: nil)
        let doneButton = UIBarButtonItem(title: "Done", style: .done, target: self, action: #selector(didTapDone))
        toolbar.setItems([flexibleSpace, doneButton], animated: true)
        toolbar.backgroundColor = .systemGreen
        toolbar.translatesAutoresizingMaskIntoConstraints = false
        contentView.addSubview(toolbar)
        
        NSLayoutConstraint.activate([
            contentView.topAnchor.constraint(equalTo: containerView.topAnchor),
            contentView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
            contentView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
            contentView.bottomAnchor.constraint(equalTo: contentView.superview!.safeAreaLayoutGuide.bottomAnchor),

            toolbar.topAnchor.constraint(equalTo: contentView.topAnchor),
            toolbar.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
            toolbar.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
            toolbar.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
        ])
    }
    
    
    @objc private func didTapDone() {
        print("done tapped")
    }
}

The result works whilst the keyboard is visible but doesn't once the keyboard is dimissed:

keyboard visible

enter image description here

I've played around with the heights of the various views with mixed results and making the container view frame height larger (e.g. 100), does show the toolbar when the keyboard is collapsed, it also makes the toolbar too tall for when the keyboard is visible.

Clearly I'm making some auto layout constraint issues but I can't work out and would appreciate any feedback that provides a working solution aligned with Apple's recommendation.

Thanks in advance.

2

2 Answers

0
votes

In my case I use the following approach:

import UIKit

extension UIView {
    
    
    
   
    
    func setDimensions(height: CGFloat, width: CGFloat) {
        translatesAutoresizingMaskIntoConstraints = false
        heightAnchor.constraint(equalToConstant: height).isActive = true
        widthAnchor.constraint(equalToConstant: width).isActive = true
    }
    
    func setHeight(_ height: CGFloat) {
        translatesAutoresizingMaskIntoConstraints = false
        heightAnchor.constraint(equalToConstant: height).isActive = true
    }
}



class CustomTextField: UITextField {
    override init(frame: CGRect) {
        super.init(frame: frame)
    }
    
    convenience init(placeholder: String) {
        self.init(frame: .zero)
        configureUI(placeholder: placeholder)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func configureUI(placeholder: String) {
        
        let spacer = UIView()
        spacer.setDimensions(height: 50, width: 12)
        leftView = spacer
        leftViewMode = .always
        
        borderStyle = .none
        textColor = .white
        keyboardAppearance = .dark
        backgroundColor = UIColor(white: 1, alpha: 0.1)
        setHeight(50)
        attributedPlaceholder = NSAttributedString(string: placeholder, attributes: [.foregroundColor: UIColor(white: 1, alpha: 0.75)])
        
        
    }
}

 
0
votes

I was able to achieve the effect by wrapping the toolbar (chat input bar in my case) and constraining it top/right/left + bottom to safe area of the wrapper.

I'll leave an approximate recipe below.

In your view controller:

override var inputAccessoryView: UIView? {
    keyboardHelper
}
    
override var canBecomeFirstResponder: Bool {
    true
}

lazy var keyboardHelper: InputBarWrapper = {
    let wrapper = InputBarWrapper()
    let inputBar = InputBar()
    helper.addSubview(inputBar)
    inputBar.translatesAutoresizingMaskIntoConstraints = false
    NSLayoutConstraint.activate([
        inputBar.topAnchor.constraint(equalTo: helper.topAnchor),
        inputBar.leftAnchor.constraint(equalTo: helper.leftAnchor),
        inputBar.bottomAnchor.constraint(equalTo:
helper.safeAreaLayoutGuide.bottomAnchor),
        inputBar.rightAnchor.constraint(equalTo: helper.rightAnchor),
    ])
    
    return wrapper
}()

Toolbar wrapper subclass:

class InputBarWrapper: UIView {
        
    var desiredHeight: CGFloat = 0 {
        didSet { invalidateIntrinsicContentSize() }
    }
    
    override var intrinsicContentSize: CGSize {
        CGSize(width: 0, height: desiredHeight)
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError()
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame);
        autoresizingMask = .flexibleHeight
        backgroundColor = UIColor.systemGreen.withAlphaComponent(0.2)
    }

}

enter image description here