ACTUALIZACIÓN
Esto parece ser un problema con IOS 7. Una gran solución ha sido añadido a aceptado la respuesta.

He creado un control personalizado que contiene un UITextView y UILabel que contiene el título de la textview es decir de mi control.
Mi control cambia automáticamente de tamaño para adaptarse a la textview y el título. Antes de que esto suceda puedo cambiar el tamaño de la textview para ajustar el texto.
Esto funciona de manera óptima.

He añadido funcionalidad, de modo que el textview se desplaza automáticamente a la última línea. O eso es al menos lo estoy intentando. Funciona bien siempre y cuando la última línea contiene nada, pero de texto vacío. Si el texto está vacío, tira hacia abajo de modo que sólo se puede ver alrededor de la mitad del cursor.

¿Qué estoy haciendo mal?

Así que usted puede entender mejor que he hecho algunas imágenes:

Esto me está escribiendo una palabra y hacer algunos puedes incluir varias líneas. (Todavía no es suficiente para hacer scroll)

Cómo hacer un UITextView de desplazamiento mientras se escribe/edición

Y la hago un salto de línea. (pulsar enter) cierre de la Mirada en cómo el cursor se reduce a la mitad. Este es el problema!

Cómo hacer un UITextView de desplazamiento mientras se escribe/edición

Me han hecho la siguiente imagen para que pueda ver exactamente lo que me esperaba.

Cómo hacer un UITextView de desplazamiento mientras se escribe/edición

  • puedes subir tu simple proyecto de Xcode en algún lugar?
  • Sí, un segundo, subir a github
  • Código fuente del proyecto, sumado a la pregunta
  • Gracias, he añadido una respuesta
  • Parece que esto es todavía un problema en iOS 7.0.3.
InformationsquelleAutor chrs | 2013-08-06

15 Comentarios

  1. 54

    Problemas con otras respuestas:

    • cuando sólo escaneo para «\n», si escribe una línea de texto que excede el ancho de la vista de texto, a continuación, el desplazamiento no se produce.
    • cuando siempre la configuración de contentOffset en textViewDidChange:, si edita el medio del texto que usted no desea desplazarse a la parte inferior.

    La solución es añadir esto a la vista de texto delegado:

    - (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];
            }];
        }
    }
    • El solucionó el problema con la última línea, pero hace extraño desplazamiento al intentar editar cerca de la parte superior de un documento largo.
    • Hay un problema con esta solución. 1. Agregar las filas de texto para llenar vista de texto. 2. Deja el cursor en la línea en blanco de la última fila. 3. Desplazarse a la parte superior a través de contacto. 4. Escriba un carácter. Error: El contenido de la desviación es demasiado grande. Espera: ser sexy como en otros casos.
  2. 8

    Traté de poner en su textViewDidChange: un fragmento como:

    if([textView.text hasSuffix:@"\n"])
        [self.textView setContentOffset:CGPointMake(0,INT_MAX) animated:YES];

    No es muy limpio, estoy trabajando para encontrar algunos de los mejores cosas, pero por ahora funciona 😀

    ACTUALIZACIÓN:
    Dado que este es un error que sólo ocurre en iOS 7 Beta 5, por ahora), usted puede hacer una solución con este código:

    if([textView.text hasSuffix:@"\n"]) { 
        double delayInSeconds = 0.2; 
        dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); 
        dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ 
            CGPoint bottomOffset = CGPointMake(0, self.textView.contentSize.height - self.textView.bounds.size.height); 
            [self.textView setContentOffset:bottomOffset animated:YES]; 
        }); 
    }

    Luego, en iOS 6 puede elegir cualquiera de ellos para establecer el retraso a 0.0 o utilizar el contenido del bloque.

    • Funciona, pero es como usted menciona un poco sucio. Pero Gracias por el esfuerzo. Primera «solución» en este momento
    • Otra cosa que puedes hacer, tal vez más «limpios», es: if([textView.text hasSuffix:@"\n"]) { double delayInSeconds = 0.2; dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ CGPoint bottomOffset = CGPointMake(0, self.textView.contentSize.height - self.textView.bounds.size.height); [self.textView setContentOffset:bottomOffset animated:YES]; }); }
    • el problema es que en textViewDidChange: la contentSize aún no está actualizado a la real, pero a la una de la anterior edición. También puede ser un error temporal con iOS 7
    • El último comentario es también lo que yo creo que por ahora, ya que todos los demás piensa que estoy completamente perdido jeje. Estoy tratando de descargar el 6.1 simulador y ver si se ejecuta sin problemas.
    • Sólo he probado en iOS 6.1 y funciona bien, incluso la configuración de la demora de 0.0
    • El «normal» enfoque o el hack?
    • El «normal» y el «hack»
    • OK. Puedes actualizar tu respuesta por lo que es un error, y que ha encontrado una solución? A continuación, te voy a dar la recompensa 😉
    • Respuesta actualizada para que sea más fácil de leer

  3. 4

    He utilizado el siguiente código en la textViewDidChange: método y pareció funcionar bien.

    - (void)textViewDidChange:(UITextView *)textView {
        CGPoint bottomOffset = CGPointMake(0, self.theTextView.contentSize.height - self.theTextView.bounds.size.height);
        [self.theTextView setContentOffset:bottomOffset animated:YES];
    }

    Esto parece desplazar la UITextView un poco más para que el cursor no se corten.

    • No sé por qué. Pero no funcionó para mí 🙁 Actualizado mi Post
    • Esto hizo que funcionaba mejor para mí en iOS10
    • Para Swift 4 func textViewDidChange(_ textView: UITextView) { switch textView { case reasonsText: vamos a bottomOffset = CGPoint(x: 0, y: textView.contentSize.altura – textView.límites.tamaño.altura) reasonsText.setContentOffset(bottomOffset, animados: false) default: break } }
  4. 4

    Uso De Swift 3 :-

    let line : CGRect = textView.caretRect(for: (textView.selectedTextRange?.start)!)
        print("line = \(line)")
    
        let overFlow = line.origin.y + line.size.height - (textView.contentOffset.y + textView.bounds.size.height - textView.contentInset.bottom - textView.contentInset.top)
    
        print("\n OverFlow = \(overFlow)")
    
        if (0 < overFlow)
        {
            //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
    
            var offSet : CGPoint = textView.contentOffset
    
            print("offSet = \(offSet)")
    
            //leave 7 pixels margin
            offSet.y += (overFlow + 7)
    
            //Cannot animate with setContentOffset:animated: or caret will not appear
    
            UIView.animate(withDuration: 0.3, animations: {
                textView.setContentOffset(offSet, animated: true)
            })
        }
  5. 3

    Aceptado respuesta cuando usando Xamarin/Monotouch se verá como

            textView.Changed += (object sender, EventArgs e) =>
            {
    
                var line = textView.GetCaretRectForPosition(textView.SelectedTextRange.start);
                var overflow = line.Top + line.Height -
                               (textView.ContentOffset.Y
                               + textView.Bounds.Size.Height
                               - textView.ContentInset.Bottom
                               - textView.ContentInset.Top);
                if (overflow > 0)
                {
                    var offset = textView.ContentOffset;
                    offset = new PointF(offset.X, offset.Y + overflow + 7);
                    UIView.Animate(0.2f, () =>
                        {
                            textView.SetContentOffset(offset, false);
                        });
                }
            };
  6. 2

    La siguiente modificación de Vik respuesta trabajado muy bien para mí:

    if([_textView.text hasSuffix:@"\n"])
    {
        if (_textView.contentSize.height - _textView.bounds.size.height > -30)
        {
            double delayInSeconds = 0.2;
            dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
            dispatch_after(popTime, dispatch_get_main_queue(), ^(void)
            {
                CGPoint bottomOffset = CGPointMake(0, _textView.contentSize.height - _textView.bounds.size.height);
                [_textView setContentOffset:bottomOffset animated:YES];
            });
        }
    }
    • Agradable ver a una real solución para este error. ¿Cómo Apple pierda esta??
  7. 1

    He encontrado que si usted pone el siguiente en viewWillAppear, se va a resolver este y algunos otros temas que UITextView parece tener en la beta:


    [self.textView.layoutManager ensureLayoutForTextContainer:self.textView.textContainer];

    • Esto no parece hacer nada por mí.
    • Esto hizo que el UITextView oculto detrás de mi teclado y el tipo de vencido el propósito. Yo puedo imaginar que esta trabajando junto a algo para compensar oculta el teclado O si el UITextView es en la parte superior de la pantalla y por lo tanto no es probable que se oculta.
  8. 1

    Alguien ha presentado un error de apple con respecto a este problema? Esto se siente como un obvio error que es muy fácil de reproducir. Si nadie responde, a continuación, voy a presentar un radar con un proyecto de prueba.

    • hice un archivo de radar.
  9. 1

    Creo que la mejor manera es para determinar la posición del cursor para ver si el desplazamiento que debe producirse.

    - (void)textViewDidChange:(UITextView *)textView {
        //check to see if the cursor is at the end of the text
        if (textView.text.length == textView.selectedRange.location) {
            //find the caret position
            CGRect caret = [textView caretRectForPosition:textView.selectedTextRange.start];
    
            //determine the height of the visible text window
            UIEdgeInsets textInsets = textView.textContainerInset;
            CGFloat textViewHeight = textView.frame.size.height - textInsets.top - textInsets.bottom;
            //need to subtract the textViewHeight to correctly get the offset
            //that represents the top of the text window above the cursor
            textView.contentOffset = CGPointMake(textView.contentOffset.x, caret.origin.y - textViewHeight);
        }
    }

    El código anterior determinará si el cursor está al final del texto. Si no, no va a desplazarse. Si es que (independientemente de lo que el último carácter es), va a determinar el correcto desplazamiento para desplazarse y, a continuación, realizar el desplazamiento.

  10. 0

    Esto es lo que he usado en mi proyecto actual para cambiar el tamaño de un UITextView:

    - (void)textViewDidChange:(UITextView *)textView {
        CGRect frame = textView.frame;
        frame.size.height = textView.contentSize.height;
        textView.frame = frame;    
    }

    Funciona muy bien para mí. Si desea crear un poco de «frontera» entre el cursor y el cuadro de texto real, siempre se puede añadir un par de píxeles a la altura. Así:

        frame.size.height = textView.contentSize.height+14;
    • No tengo ningún problema en cambiar el tamaño de la vista de texto.
    • Oh, no he entendido tu pregunta. Usted sólo quiere corregir el error, donde sólo la mitad del cursor está visible si la última línea está vacía correcta?
    • Cierto – ¿has visto el vídeo?
  11. 0

    La solución en la aceptó respuesta es inutilizable.

    Decir que hay que 1000 palabras en el textView y el carácter final es «\n». Si edita la primera línea de la textView, hasSuffix:@"\n" volverá YES y el textView inmediatamente desplácese hasta la parte inferior del documento.

    O, comenzar con un espacio en blanco textView y escriba una palabra, a continuación, presione retorno. El texto se desplaza a la parte inferior.

    ============  ============   ============   ============
     Te|           Text |         Text           
                                  |
    
    
                                                 Text
                                                 |
    ============  ============   ============   ============

    Tal vez esta es una solución mejor, pero no es perfecto. Comprueba si el cursor está por debajo de un punto máximo, luego se desplaza hasta el punto máximo si es:

    -(void)textViewDidChange:(UITextView *)textView {
    
        //Get caret frame
        UITextPosition *caret = [textView positionFromPosition:textView.beginningOfDocument offset:textView.selectedRange.location];
        CGRect caretFrame     = [textView caretRectForPosition:caret];
    
        //Get absolute y position of caret in textView
        float absCaretY       = caretFrame.origin.y - textView.contentOffset.y;
    
        //Set a max y for the caret (in this case the textView is resized to avoid the keyboard and an arbitrary padding is added)
        float maxCaretY       = textView.frame.size.height - 70;
    
        //Get how far below the maxY the caret is
        float overflow        = absCaretY - maxCaretY;
    
        //No need to scroll if the caret is above the maxY
        if (overflow < 0)
            return;
    
        //Need to add a delay for this to work
        double delayInSeconds = 0.2;
        dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
        dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
    
            //Scroll to the maxCaretY
            CGPoint contentOffset = CGPointMake(0, textView.contentOffset.y + overflow);
            [textView setContentOffset:contentOffset animated:YES];
        });
    }
    • Esta solución hace que la vista de texto para desplazarse por segunda vez pasado el final, después de pegar un gran bloque de texto. @davidisdk la solución no parece tener este problema.
  12. 0

    Trate de usar

       textView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
       textView.autoresizingSubviews = YES;

    Resuelve el problema para mí para iOS7.

  13. 0

    Sobre iOS10 en mi autosizing UITextView la clave para mí fue

    //my method called on text change
    
    - (void)updateLayout {
    
        [self invalidateIntrinsicContentSize];
    
        [UIView animateWithDuration:0.33 animations:^{
    
            [self.superview layoutIfNeeded];
    
            CGPoint bottomOffset = CGPointMake(0, self.contentSize.height - self.bounds.size.height);
            [self setContentOffset:bottomOffset animated:NO];
    
        } completion:nil];
    
    }

    Toda la clase

    #import "AutosizeTextView.h"
    @implementation AutosizeTextView
    - (instancetype)initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {
    [self setup];
    }
    return self;
    }
    - (void)awakeFromNib {
    [super awakeFromNib];
    [self setup];
    }
    - (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UITextViewTextDidChangeNotification object:self];
    }
    - (void)setText:(NSString *)text {
    [super setText:text];
    [self updateLayout];
    }
    - (CGSize)intrinsicContentSize {
    CGRect textRect = [self.layoutManager usedRectForTextContainer:self.textContainer];
    CGFloat height = textRect.size.height + self.textContainerInset.top + self.textContainerInset.bottom;
    return CGSizeMake(UIViewNoIntrinsicMetric, height);
    }
    ////////////////////////////////////////////////////////////////////////
    #pragma mark - Private
    ////////////////////////////////////////////////////////////////////////
    - (void)setup {
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textDidChangeNotification:) name:UITextViewTextDidChangeNotification object:self];
    self.textContainer.lineFragmentPadding = 0;
    self.textContainerInset = UIEdgeInsetsMake(4, 4, 4, 4);
    }
    - (void)updateLayout {
    [self invalidateIntrinsicContentSize];
    [UIView animateWithDuration:0.33 animations:^{
    [self.superview layoutIfNeeded];
    CGPoint bottomOffset = CGPointMake(0, self.contentSize.height - self.bounds.size.height);
    [self setContentOffset:bottomOffset animated:NO];
    } completion:nil];
    }
    ////////////////////////////////////////////////////////////////////////
    #pragma mark - Notification
    ////////////////////////////////////////////////////////////////////////
    - (void)textDidChangeNotification:(NSNotification *)notification {
    [self updateLayout];
    }
    @end
  14. 0

    En Swift 3

    Cómo hacer un UITextView de desplazamiento mientras se escribe/edición

    De referencia del conjunto de salida & delegado de textview

    class ViewController: UIViewController , UITextViewDelegate{
    @IBOutlet var txtViewRef: UITextView!

    En viewDidLoad conjunto delegado & Notificación para cambiar KeyboardFrame u Ocultar el teclado

     override func viewDidLoad() {
    super.viewDidLoad()
    txtViewRef.delegate = self
    NotificationCenter.default.addObserver(self, selector: #selector(ViewController.updateTextView(notification:)), name: Notification.Name.UIKeyboardWillChangeFrame, object: nil)
    NotificationCenter.default.addObserver(self, selector: #selector(ViewController.updateTextView(notification:)), name: Notification.Name.UIKeyboardWillHide, object: nil)    
    }

    Crear la Función updateTextView En la que estamos recibiendo el marco de teclado y el cambio de la inserción de contenido y de desplazamiento indicador de desplazamiento y de la textview

    func updateTextView(notification : Notification)
    {
    let userInfo = notification.userInfo!
    let keyboardEndFrameScreenCoordinates = (userInfo[UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
    let keyboardEndFrame = self.view.convert(keyboardEndFrameScreenCoordinates, to: view.window)
    if notification.name == Notification.Name.UIKeyboardWillHide{
    txtViewRef.contentInset = UIEdgeInsets.zero
    }
    else
    {
    txtViewRef.contentInset = UIEdgeInsetsMake(0, 0, keyboardEndFrame.height, 0)
    txtViewRef.scrollIndicatorInsets = txtViewRef.contentInset
    }
    txtViewRef.scrollRangeToVisible(txtViewRef.selectedRange)
    }
  15. 0

    Yo tenía el mismo problema, pero sobre UITextView dentro de UITableView, así que después de un poco de investigación no encontré ningún «fácil» forma de arreglarlo, así que basado en aceptó responder que había creado a la perfección la solución de trabajo (debería funcionar también en el interior de UICollectionView, UIScrollView con algunos cambios comentó en el interior de esta extensión).

    Así de fácil re-uso de es necesario algunas extensiones en la parte superior de UIKit:

    extension UITextView {
    func scrollToCursor(animated: Bool = false, verticalInset: CGFloat = 8) {
    guard let selectedTextRange = selectedTextRange else { return }
    var cursorRect = caretRect(for: selectedTextRange.start)
    //NOTE: can't point UIScrollView, coz on iOS 10 closest view will be UITableWrapperView
    //to extend functionality for UICollectionView or plain UIScrollView it's better to search them one by one
    let scrollView = findParent(of: UITableView.self) ?? self
    cursorRect = convert(cursorRect, to: scrollView)
    if cursorRect.origin.x.isInfinite || cursorRect.origin.y.isInfinite {
    return
    }
    let bottomOverflow = cursorRect.maxY - (scrollView.contentOffset.y + scrollView.bounds.height - scrollView.contentInset.bottom - scrollView.contentInset.top)
    if bottomOverflow > 0 {
    let offset = CGPoint(x: scrollView.contentOffset.x, y: scrollView.contentOffset.y + bottomOverflow + verticalInset)
    scrollView.setContentOffset(offset, animated: animated)
    return
    }
    let topOverflow = scrollView.contentOffset.y - cursorRect.minY
    if topOverflow > 0 {
    let offset = CGPoint(x: scrollView.contentOffset.x, y: scrollView.contentOffset.y - topOverflow - verticalInset)
    scrollView.setContentOffset(offset, animated: animated)
    }
    }
    }

    UIView:

    extension UIView {
    func findParent<Parent: UIView>(of parentType: Parent.Type) -> Parent? {
    return superview?.findNext(of: parentType)
    }
    private func findNext<Parent: UIView>(of parentType: Parent.Type) -> Parent? {
    if let res = self as? Parent {
    return res
    }
    return superview?.findNext(of: parentType)
    }
    }

    Así en UITextViewDelegate, cuando el texto se ha cambiado, llame a donde usted necesita (puede ser dentro de envío de la cola principal async bloque – estoy usando ReactiveSwift de devolución de llamada para esto):

    textView.scrollToCursor()

    Si quieres agregar moviéndose hacia arriba en la posición del cursor de cambio (en la parte superior de la pantalla) necesidad de llamar a este método en el interior de textViewDidChangeSelection delegado del método (con verificación en la selección de la longitud de curso).

Dejar respuesta

Please enter your comment!
Please enter your name here