Uitextview as Inputaccessoryview Doesn't Render Text Until After Animation

Strange delay displaying text in input accessory view during push animation.

I have come up with the solution which works on both iOS 8 and 9. Also it address retain cycle issue presented in iOS 9 which prevent view controller from being deallocated when use inputaccessoryview. Check github project for more details.

With lots of experimentation I have found quite hacky solution but works like a charm. Just subclass your implemantation accessory view from AccessoryView listed below.

class AccessoryView: UITextField {
override var canBecomeFirstResponder: Bool {
return true
}

override func awakeFromNib() {
super.awakeFromNib()
disableShowingKeyboard()
hideCursor()
}
}

extension AccessoryView {

private func disableShowingKeyboard() {
inputView = UIView()
}

private func hideCursor() {
tintColor = UIColor.clear
}

override func accessibilityActivate() -> Bool {
return false
}

override var isEditing: Bool {
return false
}

override func caretRect(for position: UITextPosition) -> CGRect {
return .zero
}

override func selectionRects(for range: UITextRange) -> [UITextSelectionRect] {
return []
}

override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
if action == #selector(UIResponder.copy(_:)) || action == #selector(UIResponder.selectAll(_:)) || action == #selector(UIResponder.paste(_:)){
return false
}

return super.canPerformAction(action, withSender: sender)
}

override func addGestureRecognizer(_ gestureRecognizer: UIGestureRecognizer) {
if gestureRecognizer is UILongPressGestureRecognizer {
gestureRecognizer.isEnabled = false
}

super.addGestureRecognizer(gestureRecognizer)
}
}

extension AccessoryView {
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
for view in subviews {
let _point = self.convert(point, to: view)
if !view.isHidden && view.isUserInteractionEnabled && view.alpha > 0.01 && view.point(inside: _point, with: event) {
if let _view = view.hitTest(_point, with: event){
return _view
}
}
}

return super.hitTest(point, with: event)
}
}


UITextView's not displaying content during push animation when is in inputAccessoryView

So I will stick with the solution where the inputAccessoryView is stored until next appearance.

UITextView in InputAccessoryView not becoming First Responder (Keyboard does not show up initially)

If you want the text view to become active, and the keyboard to show, as soon as the view appears, use:

override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
textView.becomeFirstResponder()
}

If you want the text view to be visible at the bottom, and become active / show the keyboard when the textview is tapped, take a look at this answer:

https://stackoverflow.com/a/61508928/6257435


Edit

If you want to push a view controller onto the navigation stack, and have the keyboard showing with a custom input accessory view, containing a text view, and give it the focus...

Add a hidden text field to the controller. In viewDidLoad tell that text field to use the custom input accessory view and tell it to become first responder.

Then, in viewDidAppear tell the text view in the custom input accessory view to become the first responder:

class TestViewController: UIViewController {

var hiddenTF = UITextField()

override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white

// set the text field to hidden
hiddenTF.isHidden = true
// add it to the view
view.addSubview(hiddenTF)

// tell hidden text field to use custom input accessory view
hiddenTF.inputAccessoryView = keyboardAccessory

// tell it to become first responder
hiddenTF.becomeFirstResponder()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// tell the textView (in the custom input accessory view)
// to become first responder
textView.becomeFirstResponder()
}

var textView: UITextView = {
let view = UITextView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .yellow
return view
}()

lazy var keyboardAccessory: UIToolbar = {
let inputAccessory = UIToolbar(frame: .init(x: 0, y: 0, width: 0, height: 100))
inputAccessory.addSubview(textView)
NSLayoutConstraint.activate([
textView.centerXAnchor.constraint(equalTo: inputAccessory.centerXAnchor),
textView.centerYAnchor.constraint(equalTo: inputAccessory.centerYAnchor),
textView.widthAnchor.constraint(equalToConstant: 200),
textView.heightAnchor.constraint(equalToConstant: 50)
])
inputAccessory.backgroundColor = .gray
return inputAccessory
}()

}

UIButton title not set until view appear

Just add...

inputAccessoryView?.snapshotView(afterScreenUpdates: true)

in your viewDidLoad of the ViewController.

Btw this issue appeared also to others. I found the solution here: https://stackoverflow.com/a/36548797/8932493

Make UITextView parent be its own inputAccessoryView

A much easier solution is to make your input field the input accessory view of your view controller:

- (BOOL)canBecomeFirstResponder
{
return YES;
}

- (UIView *)inputAccessoryView
{
return self.yourInputField;
}

The view will be on screen at the bottom of the screen and when it becomes first responder in response to a user tapping it, the keyboard will be presented. The view will be animated such that it remains immediately above the keyboard.

Hiding/Showing Input Accessory View

I don't know if this is really what you're going for, but give it a try.

Tapping anywhere in the view will show/hide the input accessory view:

class TestInputViewController: UIViewController {

//in viewcontroller
lazy var inputContainerView: CustomInputAccessoryView = {
let frame = CGRect(x: 0, y: 0, width: self.view.frame.width, height: 60)
let customInputAccessoryView = CustomInputAccessoryView(frame: frame)
return customInputAccessoryView
}()

override var canBecomeFirstResponder: Bool {
return true
}

//onLoad
override var inputAccessoryView: UIView? {
get { return inputContainerView }
}

override func viewDidLoad() {
super.viewDidLoad()

// so we can see the frame
inputContainerView.backgroundColor = .blue

// tap anywhere in view to show / hide input accessory view
let g = UITapGestureRecognizer(target: self, action: #selector(didTap(sender:)))
view.addGestureRecognizer(g)
}

override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
}

@objc func didTap(sender: UITapGestureRecognizer) {
if self.isFirstResponder {
resignFirstResponder()
} else {
becomeFirstResponder()
}
}

}

class CustomInputAccessoryView: UIView {

let customTextView: CustomInputTextView = {
let tv = CustomInputTextView()
tv.isScrollEnabled = false
return tv
}()

override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor.white
autoresizingMask = .flexibleHeight

addSubview(customTextView)
customTextView.translatesAutoresizingMaskIntoConstraints = false
customTextView.topAnchor.constraint(equalTo: topAnchor, constant: 12).isActive = true
customTextView.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor, constant: -8).isActive = true
customTextView.leftAnchor.constraint(equalTo: leftAnchor, constant: 10).isActive = true
customTextView.rightAnchor.constraint(equalTo: rightAnchor, constant: -10).isActive = true
customTextView.heightAnchor.constraint(greaterThanOrEqualToConstant: frame.height - 20).isActive = true
}

required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

override var intrinsicContentSize: CGSize {
return .zero
}
}

class CustomInputTextView: UITextView {
override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
}

override var canResignFirstResponder: Bool {
return true
}

override var canBecomeFirstResponder: Bool {
return true
}

required init?(coder aDecoder: NSCoder) {
fatalError("init coder has not been implemented")
}
}

Not related to showing / hiding, but a few of your constraints were wrong, causing the text view to be misplaced.

AccessoryView on TextView without Keyboard?

inputAccessoryView is for a view to come up above the standard apple virtual keyboard. If you don't allow the on-screen keyboard to appear, then the inputAccessoryView will not appear either.

If you ONLY want a custom keyboard to show, and NOT the standard on-screen keyboard, you should use inputView instead of inputAccessoryView. See the difference between inputView and inputAccessoryView here:
http://developer.apple.com/iphone/library/documentation/uikit/reference/UITextView_Class/Reference/UITextView.html



Related Topics



Leave a reply



Submit