Cambiando el marco de una entradaAccessoryView en iOS 8
objective-c ios8 (9)
Lurker largo tiempo - primer cartel de tiempo!
Estoy teniendo un problema al recrear una barra con un UITextView
como lo hace WhatsApp.
Estoy usando una subclase UIView
personalizada, y la UIView
perezosamente en:
- (UIView *)inputAccessoryView
y devolviendo SÍ a:
- (BOOL)canBecomeFirstResponder
Ahora, quiero cambiar el tamaño de inputAccessoryView
cuando el UITextView
crezca de tamaño. En iOS 7, simplemente cambiaría el tamaño del marco de dicha vista, y no su origen, y luego llamaría a reloadInputViews
y funcionaría: la vista se reloadInputViews
hacia arriba para que sea completamente visible por encima del teclado.
En iOS 8, sin embargo, esto no funciona. La única forma de hacerlo funcionar es cambiar el origen del marco a un valor negativo. Esto estaría bien, excepto que crea algunos errores extraños: por ejemplo, UIView
vuelve al marco "original" al ingresar cualquier texto.
¿Hay algo que este olvidando? Estoy bastante seguro de que WhatsApp utiliza inputAccessoryView
debido a la forma en que descartan el teclado al arrastrar, solo en la última versión de la aplicación.
¡Por favor avísame si puedes ayudarme! O si hay alguna prueba que me gustaría que corriera!
¡Gracias! :)
Por cierto, aquí está el código que estoy usando para actualizar la altura de la vista UIV personalizada llamada composeBar
:
// ComposeBar frame size
CGRect frame = self.composeBar.frame;
frame.size.height += heightDifference;
frame.origin.y -= heightDifference;
self.composeBar.frame = frame;
[self.composeBar.textView reloadInputViews]; // Tried with this
[self reloadInputViews]; // and this
Edición: el código fuente completo está disponible en https://github.com/manuelmenzella/SocketChat-iOS
100% funcional y una solución muy simple es enumerar todas las restricciones y establecer un nuevo valor de altura. Aquí hay un código C # (xamarin):
foreach (var constraint in inputAccessoryView.Constraints)
{
if (constraint.FirstAttribute == NSLayoutAttribute.Height)
{
constraint.Constant = newHeight;
}
}
Aquí hay una solución completa e independiente (gracias a @JohnnyC y @ JoãoNunes por señalarme la dirección correcta, @stigi por explaining cómo animar los cambios de contenido intrinsicContent
):
class InputAccessoryView: UIView {
// InputAccessoryView is instantiated from nib, but it''s not a requirement
override func awakeFromNib() {
super.awakeFromNib()
autoresizingMask = .FlexibleHeight
}
override func intrinsicContentSize() -> CGSize {
let exactHeight = // calculate exact height of your view here
return CGSize(width: UIViewNoIntrinsicMetric, height: exactHeight)
}
func somethingDidHappen() {
// invalidate intrinsic content size, animate superview layout
UIView.animateWithDuration(0.2) {
self.invalidateIntrinsicContentSize()
self.superview?.setNeedsLayout()
self.superview?.layoutIfNeeded()
}
}
}
Desafortunadamente, iOS8 agrega una restricción de altura privada a inputAccessoryView, y esta restricción no es pública.
Recomiendo volver a crear la vista de accesorios cuando debería cambiar su marco, y llamar a reloadInputViews para que se instale el nuevo.
Esto es lo que hago, y funciona como se espera.
El problema es que en iOS 8, se instala automáticamente una NSLayoutConstraint que establece la altura de inputAccessoryView igual a su altura de marco inicial. Para solucionar el problema de diseño, debe actualizar esa restricción a la altura deseada y luego instruir a su inputAccessoryView para que se establezca.
- (void)changeInputAccessoryView:(UIView *)inputAccessoryView toHeight:(CGFloat)height {
for (NSLayoutConstraint *constraint in [inputAccessoryView constraints]) {
if (constraint.firstAttribute == NSLayoutAttributeHeight) {
constraint.constant = height;
[inputAccessoryView layoutIfNeeded];
break;
}
}
}
He estado golpeando mi cabeza contra la pared en este por bastante tiempo, ya que el comportamiento cambió de iOS 7 a iOS 8. Probé todo, hasta que la solución más obvia de todas me funcionó:
inputAccessoryView.autoresizingMask = UIViewAutoresizingFlexibleHeight;
duh
Para resumir la answer de JohnnyC: establezca la máscara de autoresizingMask
automático de autoresizingMask
a .flexibleHeight
, calcule su autoresizingMask
de .flexibleHeight
intrinsicContentSize
y deje que el marco haga el resto.
Código completo, actualizado para Swift 3:
class InputAccessoryView: UIView, UITextViewDelegate {
let textView = UITextView()
override var intrinsicContentSize: CGSize {
// Calculate intrinsicContentSize that will fit all the text
let textSize = textView.sizeThatFits(CGSize(width: textView.bounds.width, height: CGFloat.greatestFiniteMagnitude))
return CGSize(width: bounds.width, height: textSize.height)
}
override init(frame: CGRect) {
super.init(frame: frame)
// This is required to make the view grow vertically
autoresizingMask = .flexibleHeight
// Setup textView as needed
addSubview(textView)
textView.translatesAutoresizingMaskIntoConstraints = false
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[textView]|", options: [], metrics: nil, views: ["textView": textView]))
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[textView]|", options: [], metrics: nil, views: ["textView": textView]))
textView.delegate = self
// Disabling textView scrolling prevents some undesired effects,
// like incorrect contentOffset when adding new line,
// and makes the textView behave similar to Apple''s Messages app
textView.isScrollEnabled = false
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: UITextViewDelegate
func textViewDidChange(_ textView: UITextView) {
// Re-calculate intrinsicContentSize when text changes
invalidateIntrinsicContentSize()
}
}
Para solucionar este problema, utilicé inputAccessoryView.autoresizingMask = UIViewAutoresizingFlexibleHeight;
Pero, por supuesto, esto provocó que mi vista de texto colapsara. Entonces, agregar una restricción a la barra de herramientas y actualizarla cuando sea necesario, o agregar la restricción a la vista de texto y actualizarla funcionó para mí.
Sí, iOS8 agrega una restricción de altura privada al inputAccessoryView.
Teniendo en cuenta que recrear todo el inputAccessoryView y reemplazar el antiguo puede ser una operación realmente costosa, puede eliminar las restricciones antes de volver a cargar las vistas de entrada
[inputAccessoryView removeConstraints:[inputAccessoryView constraints]];
[textView reloadInputViews];
Solo otra solución
primero, obtenga inputAccessoryView y establezca nil
UIView *inputAccessoryView = yourTextView.inputAccessoryView;
yourTextView.inputAccessoryView = nil;
[yourTextView reloadInputViews];
a continuación, establecer el marco y el diseño
inputAccessoryView.frame = XXX
[inputAccessoryView setNeedsLayout];
[inputAccessoryView layoutIfNeeded];
Último conjunto nuevo inputAccessoryView de nuevo y volver a cargar
yourTextView.inputAccessoryView = inputAccessoryView;
[yourTextView reloadInputViews];