2009-12-29 2 views
10

Я использую UITextView для грубой репликации текстового поля SMS над клавиатурой. Я использую UITextView вместо поля, чтобы он мог расширяться с несколькими строками.Как переместить предложение по корректировке UITextView над текстом

Проблема заключается в том, что в моем UITextView предложения по коррекции вызывают ниже текст, в результате чего они частично скрываются от клавиатуры.

В приложении SMS предложения появляются над текстом. Размещение не является свойством UITextView или UITextInputTraits.

Есть идеи, как воспроизвести это поведение? Благодаря!

+0

Только два варианта: 1) используя частные методы или 2) перемещение вашего UITextView достаточно высоко, чтобы ваш корректирующий пузырь не был закрыт. – bentford

ответ

8

Проблема заключается в том, что клавиатура выполнена в виде отдельного UIWindow, а чем как представление в основном UIWindow, поэтому компоновка с ним сложна. Вот некоторые указатели в правильном направлении:

  • Hunt через -windows собственности приложения, чтобы найти частный UITextEffectsWindow окна и выяснить его структуру. Это клавиатура
  • Охота через подсмотры TextView, чтобы найти частный вид UIAutocorrectInlinePrompt. Это автокорректный пузырь.
  • Переместите это представление в отдельный вид оболочки (добавленный в TextView), а затем переместите это представление оболочки так, чтобы оно было выше указанного окна клавиатуры.

Вы увидите два упоминания о «частных» выше. Это несет все соответствующие оговорки. Я понятия не имею, почему Apple разрешила эту проблему сохраняться, когда даже их приложения должны были ее обойти.

+1

Интересно, происходит ли перехват subviews без явного обращения к частным классам, считающимся нарушением? Я имею в виду, я предполагаю, что он расширяет UIView правильно? Что делать, если я просто случайно искал дерево UIViews и изменил местоположение одного из них? Не кажется мне «частным», но, возможно, Apple не согласна ... – DougW

+0

Transversing subviews полностью основан на общедоступных API. Но независимо от того, что вы собираетесь завершить, полагаясь на недокументированное поведение. Главная опасность здесь заключается в том, что обновление может сломать вас, поэтому в производственном коде я бы хотел изящно потерпеть неудачу. Я бы не стал считать, что ни один из этих частных подклассов не будет сильно стабильным, так как они оба немного напуганы, и фанковые вещи съедят, скорее всего, перепроектированные Apple. Верьте или нет, Apple не делает вещи частными просто для того, чтобы быть средними :) Очень часто они - то, что Apple планирует реорганизовать. Возьмите его за то, что он стоит. –

+0

Случайно у вас есть образец кода? Мне интересно, если вы использовали метод swizzling или то, что вы могли бы сделать для его реализации ... – marcc

0

Если нижняя часть вашего UITextView очищает клавиатуру, вы должны просто изменить размер своего UITextView, чтобы быть достаточно высоким, чтобы видеть исправления. Сами исправления не отображаются вне рамок UITextView.

Если вы хотите подражать тому, что получаете в приложении SMS (исправления выше), вам, вероятно, придется сворачивать самостоятельно.

-2

Убедитесь, что делегат диспетчера вашего вида прослушивает уведомление, когда клавиатура появляется, чтобы изменить размер UITextView, чтобы клавиатура не скрывала UITextView. Тогда ваша коррекция не будет закрыта клавиатурой. См:

http://www.iphonedevsdk.com/forum/iphone-sdk-development/12641-uitextview-scroll-while-editing.html

Вот копия кода с этой страницы в случае, если первоначальная ссылка сломана:

// the amount of vertical shift upwards keep the Notes text view visible as the keyboard appears 
#define kOFFSET_FOR_KEYBOARD     140.0 

// the duration of the animation for the view shift 
#define kVerticalOffsetAnimationDuration  0.50 

- (IBAction)textFieldDoneEditing:(id)sender 
{ 
    [sender resignFirstResponder]; 
} 

- (IBAction)backgroundClick:(id)sender 
{ 
    [latitudeField resignFirstResponder]; 
    [longitudeField resignFirstResponder]; 
    [notesField resignFirstResponder]; 

    if (viewShifted) 
    { 
     [UIView beginAnimations:nil context:NULL]; 
     [UIView setAnimationDuration:kVerticalOffsetAnimationDuration]; 

     CGRect rect = self.view.frame; 
     rect.origin.y += kOFFSET_FOR_KEYBOARD; 
     rect.size.height -= kOFFSET_FOR_KEYBOARD; 
     self.view.frame = rect; 

     [UIView commitAnimations]; 

     viewShifted = FALSE; 
    }  
} 

- (BOOL)textViewShouldBeginEditing:(UITextView *)textView 
{ 
    if (!viewShifted) {  // don't shift if it's already shifted 

     [UIView beginAnimations:nil context:NULL]; 
     [UIView setAnimationDuration:kVerticalOffsetAnimationDuration]; 

     CGRect rect = self.view.frame;  
     rect.origin.y -= kOFFSET_FOR_KEYBOARD; 
     rect.size.height += kOFFSET_FOR_KEYBOARD; 
     self.view.frame = rect; 

     [UIView commitAnimations]; 

     viewShifted = TRUE; 
    } 
    return YES; 
} 
3

Выполняя поиск UIAutocorrectInlinePrompt в переопределенном или swizzled layoutSubViews, можно изменить макет коррекции так, чтобы он отображался выше. Вы можете сделать это, не вызывая каких-либо частных API-интерфейсов, ища подвид-представления определенных классов, расположенных так, как вы ожидали бы их. В этом примере показано, что такое представление, которое проверяет, что исправление уже не над текстом и перемещает исправление выше, и рисует его в окне, так что оно не ограничено самим UITextView. Очевидно, что если яблоко изменит базовую реализацию, это не приведет к коррекции. Добавьте это в свою переопределенную или swizzled реализацию layoutSubViews.

- (void) moveSpellingCorrection { 
for (UIView *view in self.subviews) 
{ 
    if ([[[view class] description] isEqualToString:@"UIAutocorrectInlinePrompt"]) 
    { 
    UIView *correctionShadowView = nil; // [view correctionShadowView]; 

    for (UIView *subview in view.subviews) 
    { 
    if ([[[subview class] description] isEqualToString:@"UIAutocorrectShadowView"]) 
    { 
    correctionShadowView = subview; 
    break; 
    } 
    } 

    if (correctionShadowView) 
    { 
    UIView *typedTextView = nil; //[view typedTextView]; 
    UIView *correctionView = nil; //[view correctionView]; 

    for (UIView *subview in view.subviews) 
    { 
    if ([[[subview class] description] isEqualToString:@"UIAutocorrectTextView"]) 
    { 
     if (CGRectContainsRect(correctionShadowView.frame,subview.frame)) 
     { 
     correctionView = subview; 
     } 
     else 
     { 
     typedTextView = subview; 
     } 
    } 

    } 
    if (correctionView && typedTextView) 
    { 

    CGRect textRect = [typedTextView frame]; 
    CGRect correctionRect = [correctionView frame]; 
    if (textRect.origin.y < correctionRect.origin.y) 
    { 
     CGAffineTransform moveUp = CGAffineTransformMakeTranslation(0,-50.0); 
     [correctionView setTransform: moveUp]; 
     [correctionShadowView setTransform: moveUp]; 

     CGRect windowPos = [self convertRect: view.frame toView: nil ]; 
     [view removeFromSuperview]; 
     [self.window addSubview: view]; 
     view.frame = windowPos; 
    } 

    } 
    } 

    } 

} 
} 
+0

Спасибо за код. Это определенно интересно, но я согласен с некоторыми другими комментариями, что это слишком непредсказуемо для общего использования. – DougW

+1

Для этого требуется исправление - он не учитывает ориентацию интерфейса. – Eiko

+0

Кажется, что это работает нормально, но повторное воспроизведение заставило автокорректное представление перемещаться в очень низкую позицию y после того, как второй ключ нажат (то есть когда его содержимое будет расширено). Любая идея почему? –

0

Поставив ниже метод, adjustAutocorrectPromptView в layoutSubviews работал для меня в портретной и альбомной. У меня есть категория, которая предоставляет нижний и верхний методы просмотра, но вы получаете эту идею.

NSArray * subviewsWithDescription(UIView *view, NSString *description) 
{ 
    return [view.subviews filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:[NSString stringWithFormat:@"class.description == '%@'", description]]]; 
} 

- (void) adjustAutocorrectPromptView; 
{ 
    UIView *autocorrectPromptView = [subviewsWithDescription(self, @"UIAutocorrectInlinePrompt") lastObject]; 

    if (! autocorrectPromptView) 
    { 
     return; 
    } 

    UIView *correctionShadowView = [subviewsWithDescription(autocorrectPromptView, @"UIAutocorrectShadowView") lastObject]; 

    if (! correctionShadowView) 
    { 
     return; 
    } 

    UIView *typedTextView = nil; //[view typedTextView]; 
    UIView *correctionView = nil; //[view correctionView]; 

    for (UIView *subview in subviewsWithDescription(autocorrectPromptView, @"UIAutocorrectTextView")) 
    { 
     if (CGRectContainsRect(correctionShadowView.frame,subview.frame)) 
     { 
      correctionView = subview; 
     } 
     else 
     { 
      typedTextView = subview; 
     } 
    } 

    if (correctionView && typedTextView) 
    { 
     if (typedTextView.top < correctionView.top) 
     { 
      correctionView.bottom = typedTextView.top; 
      correctionShadowView.center = correctionView.center; 
     } 
    } 
} 
1

На самом деле делает

textview.scrollEnabled = NO; 

установит пузырь поверх текста ... Проблема заключается в том, что вы теряете прокрутки, в моем случае это не было проблемой из-за havinng в текстовое поле только для входных целей с ограничением символов

+0

+1 в сочетании с contentSize вы можете отключить/включить прокрутку, когда это необходимо. – EsbenB

+0

Это не делает это для меня. iOS9.3.1 работает на симуляторе iPhone 6s, я все еще получаю текст автозамены ниже. –

+0

Да, это было сделано с тревогой в iOS7, мне нужно было бы проверить это решение, если оно по-прежнему работает на новых устройствах –

1

На самом деле клавиатура просто использует результат - [UITextInput textInputView], чтобы определить, куда помещать окно коррекции (и спросить, поддерживает ли ваше изображение коррекцию). Так что все, что вам нужно сделать, это:

- (UIView *)textInputView { 
    for (UIWindow *window in [UIApplication sharedApplication].windows) { 
    if ([window isKindOfClass:NSClassFromString(@"UITextEffectsWindow")] && 
     window != self.window) { 
     return window; 
    } 
    } 

    // Fallback just in case the UITextEffectsWindow has not yet been created. 
    return self; 
} 

Обратите внимание, что вы, вероятно, также необходимо обновить - [UITextInput firstRectForRange:] использовать систему оконного/устройства координат, так что вы можете сделать это:

- (CGRect)firstRectForRange:(CoreTextTokenTextRange *)range { 
    CGRect firstRect = [self firstRectForRangeInternal:range]; 

    return [self convertRect:firstRect toView:[self textInputView]]; 
} 

(В приведенном выше контексте self является классом, который реализует UITextInput).