UITextView contentSize changes y NSLayoutManager en iOS7

El problema: UITextView cambia silenciosamente su contentSize en algunas situaciones.

El caso más simple textView con text grande y keyboard. Simplemente agregue Salida de UITextView y configure - viewDidLoad como:

 - (void)viewDidLoad { [super viewDidLoad]; // expand default "Lorem..." _textView.text = [NSString stringWithFormat:@"1%@\n\n2%@\n\n3%@\n\n4%@\n\n5", _textView.text, _textView.text, _textView.text, _textView.text]; _textView.keyboardDismissMode = UIScrollViewKeyboardDismissModeInteractive; _textView.contentInset = UIEdgeInsetsMake(0, 0, 216, 0); } 

Ahora mostrar y ocultar el keyboard provocará saltos de text en algunos casos.

He encontrado la razón para saltar por subclass UITextView . El único método en mi subclass es:

 - (void)setContentSize:(CGSize)contentSize { NSLog(@"CS: %@", NSStringFromCGSize(contentSize)); [super setContentSize:contentSize]; } 

Y muestra que contentSize contrae y se expande en ocultar keyboard. Algo como esto:

 013-09-16 14:40:27.305 textView-bug2[11087:a0b] CS: {320, 651} 2013-09-16 14:40:27.313 textView-bug2[11087:a0b] CS: {320, 885} 2013-09-16 14:40:27.318 textView-bug2[11087:a0b] CS: {320, 902} 

Parece que el comportamiento de UITextView se modificó mucho en iOS7. Y algunas cosas están rotas ahora.

Descubriendo más lejos, he encontrado que la nueva propiedad layoutManager de my textView también cambia. Hay alguna información interesante en el logging ahora:

 2013-09-16 14:41:59.352 textView-bug2[11115:a0b] CS: {320, 668} <NSLayoutManager: 0x899e800> 1 containers, text backing has 2129 characters Currently holding 2129 glyphs. Glyph tree contents: 2129 characters, 2129 glyphs, 3 nodes, 96 node bytes, 5440 storage bytes, 5536 total bytes, 2.60 bytes per character, 2.60 bytes per glyph Layout tree contents: 2129 characters, 2129 glyphs, 532 laid glyphs, 13 laid line fragments, 4 nodes, 128 node bytes, 1048 storage bytes, 1176 total bytes, 0.55 bytes per character, 0.55 bytes per glyph, 40.92 laid glyphs per laid line fragment, 90.46 bytes per laid line fragment 

Y la siguiente línea con contentSize = {320, 885} contiene Layout tree contents: ..., 2127 laid glyphs, 51 laid line fragments . Por lo tanto, parece que algún tipo de reproducción automática intenta volver a distribuir textView en la presentación del keyboard y cambia contentSize incluso si el layout aún no está terminado. Y se ejecuta incluso si mi textView no cambia entre mostrar / ocultar keyboard.

La pregunta es: ¿cómo evitar los cambios de contentSize?

Parece que el problema está en layoutManager pnetworkingeterminado de UITextView . He decidido subclass y veo, dónde y por qué se reinicia el layout. Pero la simple creación de NSLayoutManager con la configuration pnetworkingeterminada resolvió el problema.

Aquí hay un código (no perfecto) de mi proyecto de demostración (ver en cuestión). El _textView allí era una salida, así que lo eliminé de Superview. Este código se coloca en - viewDidLoad :

 NSTextStorage* textStorage = [[NSTextStorage alloc] initWithString:_textView.text]; NSLayoutManager* layoutManager = [NSLayoutManager new]; [textStorage addLayoutManager:layoutManager]; _textContainer = [[NSTextContainer alloc] initWithSize:self.view.bounds.size]; [layoutManager addTextContainer:_textContainer]; [_textView removeFromSuperview]; // remove original textView _textView = [[MyTextView alloc] initWithFrame:self.view.bounds textContainer:_textContainer]; [self.view addSubview:_textView]; 

MyTextView aquí es una subclass de UITextView , consulte la pregunta para más detalles.

Para más información ver:

  • Acerca del event handling text en iOS
  • Uso del kit de text para dibujar y administrar text
  • NSLayoutManager Class Reference para iOS

Encontré una situación similar a la de las urs. El mío se muestra con un error diferente, pero debido a la misma razón: la propiedad contentSize se modifica silenciosamente por iOS7 incorrectamente. Así es como trabajo a su alnetworkingedor. Es un poco una solución fea. Siempre que necesito usar textView.contentSize, lo calculo yo mismo.

 -(CGSize)sizeOfText:(NSString *)textToMesure widthOfTextView:(CGFloat)width withFont:(UIFont*)font { CGSize size = [textToMesure sizeWithFont:font constrainedToSize:CGSizeMake(width-20.0, FLT_MAX) lineBreakMode:NSLineBreakByWordWrapping]; return size; } 

entonces puede llamar a esta function para get el tamaño:

 CGSize cont_size = [self sizeOfText:self.text widthOfTextView:self.frame.size.width withFont:[UIFont systemFontOfSize:15]]; 

entonces, no haga lo siguiente:

 self.contentSize = cont_size;// it causes iOS halt occasionally. 

Entonces, simplemente use cont_size directamente. Creo que es un error en iOS7 por ahora. Con suerte, Apple lo solucionará pronto. Espero que esto sea útil.

Parece el error en iOS7. En el momento del text de input, el comportamiento del área de contenido está cableado en iOS7, funciona bien con una versión más baja de iOS7.

A continuación, agregué el método delegado de UITextView para resolver este problema:

 - (void)textViewDidChange:(UITextView *)textView { CGRect line = [textView caretRectForPosition: textView.selectedTextRange.start]; CGFloat overflow = line.origin.y + line.size.height - ( textView.contentOffset.y + textView.bounds.size.height - textView.contentInset.bottom - textView.contentInset.top ); if ( overflow > 0 ) { // We are at the bottom of the visible text and introduced a line feed, scroll down (iOS 7 does not do it) // Scroll caret to visible area CGPoint offset = textView.contentOffset; offset.y += overflow + 7; // leave 7 pixels margin // Cannot animate with setContentOffset:animated: or caret will not appear [UIView animateWithDuration:.2 animations:^{ [textView setContentOffset:offset]; }]; }