Как сделать прокрутку UIScrollView в позицию курсора UITextView? - PullRequest
8 голосов
/ 11 мая 2011

У меня есть вид, который похож на приложение заметок - то есть печатать на листе бумаги с подкладкой. Чтобы одновременно выполнить прокрутку текста и бумаги, я отключил прокрутку UITextView и вместо этого поместил и мой UITextView, и мой UIImageView в UIScrollView.

Единственная проблема с этим состоит в том, что, когда пользователь печатает, текст исчезает под клавиатурой, потому что, очевидно, UIScrollView не знает, чтобы прокрутить до позиции курсора.

Есть ли какой-нибудь простой способ получить позицию курсора и сказать UIScrollView, чтобы она прокручивалась там?

--- EDIT ---

Начиная с чего-то похожего здесь (где кто-то пытался сделать что-то подобное с UITableView), мне удалось создать растущий редактируемый UITextView с фиксированным фоном, который почти прокручивает отлично. Единственные проблемы сейчас:

  1. При перемещении текста вверх по тексту появляется небольшое колебание.
  2. Если пользователь скрывает клавиатуру, выбирает текст в нижней части экрана, а затем снова показывает клавиатуру, ему нужно набрать пару букв, прежде чем текст снова станет видимым - он не прокручивается сразу.
  3. Когда пользователь прячет клавиатуру, анимация, когда рамка прокрутки заполняет экран, выглядит не совсем правильно.

Вот код - я был бы очень признателен, если кто-нибудь может уточнить его дальше ...

#import "NoteEditViewController.h"
#import "RLWideLabelTableCell.h"

@implementation NoteEditViewController
@synthesize keyboardSize;
@synthesize keyboardHideDuration;
@synthesize scrollView;
@synthesize noteTextView;

//
// Dealloc and all that stuff
//
- (void)loadView
{
    [super loadView];
    UIScrollView *aScrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds];
    self.scrollView = aScrollView; [aScrollView release];
    self.scrollView.contentSize = CGSizeMake(self.view.frame.size.width, noteTextView.frame.size.height);
    [self.view addSubview:scrollView];
}

- (void)viewDidLoad
{   
    [super viewDidLoad];

    // Get notified when keyboard is shown. Don't need notification when hidden because we are
    // using textViewDidEndEditing so we can start animating before the keyboard disappears.
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(keyboardWasShown:)
                                                 name:UIKeyboardDidShowNotification object:nil];

    // Add the Done button so we can test dismissal of the keyboard    
    UIBarButtonItem *doneButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone 
        target:self
        action:@selector(doneButton:)];
    self.navigationItem.rightBarButtonItem = doneButton; [doneButton release];

    // Add the background image that will scroll with the text
    CGRect noteImageFrame = CGRectMake(self.view.bounds.origin.x, 
                                       noteTitleImageFrame.size.height, 
                                       self.view.bounds.size.width, 500);    

    UIView *backgroundPattern = [[UIView alloc] initWithFrame:noteImageFrame];
    backgroundPattern.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"Notepaper-iPhone-Line"]];
    [self.scrollView addSubview:backgroundPattern];
    [self.view sendSubviewToBack:backgroundPattern];
    [backgroundPattern release];

    // Add the textView
    CGRect textViewFrame = CGRectMake(noteImageFrame.origin.x+27, 
                                      noteImageFrame.origin.y-3, 
                                      noteImageFrame.size.width-35,
                                      noteImageFrame.size.height);

    RLTextView *textView = [[RLTextView alloc] initWithFrame:textViewFrame];
    self.noteTextView = textView; [textView release];
    self.noteTextView.font = [UIFont fontWithName:@"Cochin" size:21];
    self.noteTextView.backgroundColor = [UIColor clearColor];
    self.noteTextView.delegate = self;
    self.noteTextView.scrollEnabled = NO;
    [self.scrollView addSubview:self.noteTextView];
}

- (void)doneButton:(id)sender
{
    [self.view endEditing:TRUE];
}

// When the keyboard is shown, the UIScrollView's frame shrinks so that it fits in the
// remaining space
- (void)keyboardWasShown:(NSNotification*)aNotification
{
    NSDictionary* info = [aNotification userInfo];
    CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
    float kbHideDuration = [[info objectForKey:UIKeyboardAnimationDurationUserInfoKey] floatValue];
    self.keyboardHideDuration = kbHideDuration;
    self.keyboardSize = kbSize;
    self.scrollView.frame = CGRectMake(self.view.bounds.origin.x, 
                                       self.view.bounds.origin.y, 
                                       self.view.bounds.size.width, 
                                       self.view.bounds.size.height - kbSize.height);    
}

// When the user presses 'done' the UIScrollView expands to the size of its superview
// again, as the keyboard disappears.
- (void)textViewDidEndEditing:(UITextView *)textView
{
    [UIScrollView animateWithDuration:keyboardHideDuration animations:^{self.scrollView.frame = self.view.bounds;}];
}

// This method needs to get called whenever there is a change of cursor position in the text box
// That means both textViewDidChange: and textViewDidChangeSelection:
- (void)scrollToCursor
{
    // if there is a selection cursor…
    if(noteTextView.selectedRange.location != NSNotFound) {
        NSLog(@"selectedRange: %d %d", noteTextView.selectedRange.location, noteTextView.selectedRange.length);

        // work out how big the text view would be if the text only went up to the cursor
        NSRange range;
        range.location = noteTextView.selectedRange.location;
        range.length = noteTextView.text.length - range.location;
        NSString *string = [noteTextView.text stringByReplacingCharactersInRange:range withString:@""];
        CGSize size = [string sizeWithFont:noteTextView.font constrainedToSize:noteTextView.bounds.size lineBreakMode:UILineBreakModeWordWrap];

        // work out where that position would be relative to the textView's frame
        CGRect viewRect = noteTextView.frame;  
        int scrollHeight = viewRect.origin.y + size.height;
        CGRect finalRect = CGRectMake(1, scrollHeight, 1, 1);

        // scroll to it
        [self.scrollView scrollRectToVisible:finalRect animated:YES];
    }
}

// Whenever the text changes, the textView's size is updated (so it grows as more text
// is added), and it also scrolls to the cursor.
- (void)textViewDidChange:(UITextView *)textView
{
    noteTextView.frame = CGRectMake(noteTextView.frame.origin.x, 
                                    noteTextView.frame.origin.y, 
                                    noteTextView.frame.size.width, 
                                    noteTextView.contentSize.height);
    self.scrollView.contentSize = CGSizeMake(self.scrollView.contentSize.width, 
                                             noteTextView.frame.size.height+200);
    [self scrollToCursor];
}

// The textView scrolls to the cursor whenever the user changes the selection point.
- (void)textViewDidChangeSelection:(UITextView *)aTextView 
{
    [self scrollToCursor];
}

// PROBLEM - the textView does not scroll until the user starts typing - just selecting
// it is not enough. 
- (void)textViewDidBeginEditing:(UITextView *)textView
{
    [self scrollToCursor];
}

Ответы [ 3 ]

5 голосов
/ 12 мая 2011

Здорово, что вы нашли мой пост об этом, рад, что это было полезно!

Я полагаю, что вы, возможно, не видите нижней строки из-за этой строки:

CGRect finalRect = CGRectMake(1, scrollHeight, 1, 1);

Высоздание 1x1 точечного поля.Одна строка текста может иметь размер около 20 или 30 точек (в зависимости от размера шрифта).Так что если вы прокручиваете эту точку до видимой, она может показывать только самый верхний пиксель нижней строки - делая нижнюю линию фактически невидимой!Если вы сделаете finalRect немного выше, чтобы он занимал всю строку, он может работать лучше:

CGRect finalRect = CGRectMake(1, scrollHeight, 1, 30);

Кроме того, вы можете вызывать свой код scrollRectToVisible несколько раз за раз, что может вызвать «дрожание».В моем коде я запускаю только scrollRectToVisible из textViewDidChangeSelection и изменяю размер UITextView (если необходимо) в textViewDidChange.UIScrollView (и по наследству UITableView) имеет встроенную поддержку для прокрутки активно выбранного элемента, чтобы быть видимым, что в моем тестировании хорошо работало при простом изменении размера UITextView во время набора текста (но не при выборе определенной точки внутри касанием).

1 голос
/ 11 мая 2011

Не совсем ответ на ваш вопрос, но вот другой подход к фоновому трюку с заметками: http://www.cocoanetics.com/2010/03/stuff-you-learn-from-reverse-engineering-notes-app/

Я использовал его, и он хорошо работает.

1 голос
/ 11 мая 2011

Нет простого способа найти координаты экрана для любого текста или курсора в UITextView.

Что вам нужно сделать, это зарегистрироваться на UIKeyboardWillShowNotification и UIKeyboardWillShowNotification. А в обратных вызовах вы настраиваете size или contentInsets UIScrollView для настройки размера клавиатуры.

Размер клавиатуры и даже продолжительность анимации указаны в уведомлениях userInfo, так что вы можете сделать это в хорошем анимационном стиле.

Более подробную информацию и пример кода вы найдете здесь: http://developer.apple.com/library/ios/#documentation/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/KeyboardManagement/KeyboardManagement.html

...