web-dev-qa-db-fra.com

Faites défiler le texte erratique UITextView vers le bas dans iOS 7

Le code suivant fonctionnera correctement sous iOS <7.0. Sous iOS 7, le défilement sera saccadé et irrégulier pendant la mise à jour de UITextView. Je ne sais pas s'il s'agit d'un bogue dans iOS 7 ou si je fais quelque chose de mal. 

TestController.h

//TODO: Add UITextView in storyboard and tie to textView outlet

#define MAX_TEXT_VIEW_CHARACTERS 1000
@interface TestController : UIViewController  {
    NSMutableString *_outputText;
    NSTimer *_outputTimer;
}

@property (strong, nonatomic) IBOutlet UITextView *textView;

@end

TestController.m

@implementation TestController
@synthesize textView;

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    _outputText = [NSMutableString stringWithCapacity:MAX_TEXT_VIEW_CHARACTERS];
    _outputTimer =  [NSTimer scheduledTimerWithTimeInterval:0.01 target:self selector:@selector(outputLine:) userInfo:nil repeats:YES];
}

-(void)outputLine:(NSTimer *) theTimer {
    static int i = 0;
    //Run this 100 times
    if (i > 99) {
        [_outputTimer invalidate];
        return;
    }
    [self outputToScreen:[NSString stringWithFormat:@"Some string %d\r", ++i]];
}

-(void)outputToScreen:(NSString *)str {
    if (!str || !str.length) return;  //Nothing to output

    NSInteger outputTextSize = _outputText.length;
    [_outputText appendString:str];
    if (outputTextSize > MAX_TEXT_VIEW_CHARACTERS)
        [_outputText deleteCharactersInRange:NSMakeRange(0, outputTextSize - MAX_TEXT_VIEW_CHARACTERS)];
    self.textView.text = _outputText;

    [self scrollOutputToBottom];
}

-(void)scrollOutputToBottom {
    CGPoint p = [textView contentOffset];
    [textView setContentOffset:p animated:NO];
    [textView scrollRangeToVisible:NSMakeRange([textView.text length], 0)];
}

@end
24
Mikt25

C'est évidemment un bug d'iOS 7. Voici une solution de contournement jusqu'à ce que Apple le corrige. La solution de contournement consiste essentiellement à instancier une UITextView en créant une NSTextStorage et NSLayoutManager à partir de zéro. Apple doit avoir oublié d'initialiser quelque chose dans la méthode d'initialisation UITextView. J'ai déposé un rapport de bogue et j'espère que vous aussi.

// ios7 bug fix
// check if the device is running iOS 7.0 or later
NSString *reqSysVer = @"7.0";
NSString *currSysVer = [[UIDevice currentDevice] systemVersion];
BOOL osVersionSupported = ([currSysVer compare:reqSysVer  options:NSNumericSearch] != NSOrderedAscending);

if (osVersionSupported) {
    NSTextStorage* textStorage = [[NSTextStorage alloc] init];
    NSLayoutManager* layoutManager = [NSLayoutManager new];
    [textStorage addLayoutManager:layoutManager];
    NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:self.view.bounds.size];
    [layoutManager addTextContainer:textContainer];
    yourTextView = [[UITextView alloc] initWithFrame:someFrameForYourTextView
                                       textContainer:textContainer];
    // if using ARC, remove these 3 lines
    [textContainer release];
    [layoutManager release];
    [textStorage release];
}
else {
    yourTextView = [[UITextView alloc] initWithFrame:someFrameForYourTextView];
}
27
RawMean

Cela fonctionne pour moi dans iOS7.

-(void) scrollToBottom {
  [textView scrollRangeToVisible:NSMakeRange([textView.text length], 0)];
  [textView setScrollEnabled:NO];
  [textView setScrollEnabled:YES];
}
55
dklt

Il y a deux problèmes dans iOS 7 qui pourraient expliquer votre problème:

  • Le contentOffset n'est pas toujours à jour dans iOS 7.
  • scrollRangeToVisible: ne fera pas défiler une ligne vide à la fin de la vue texte.

La solution pourrait être:

-(void)scrollOutputToBottom {
    CGRect caretRect = [textView caretRectForPosition:textView.endOfDocument];
    [textView scrollRectToVisible:caretRect animated:NO];
}
4
davidisdk

Essaye ça:

// Don't forget to set textView's delegate 
-(void)textViewDidChangeSelection:(UITextView *)textView {
    [textView scrollRangeToVisible:NSMakeRange([textView.text length], 0)];
}
2
Allen

Pour ceux qui utilisent Swift, je poste ici la même réponse que RawMean (merci encore!) . Comme je l'ai écrit (décembre 2014), le problème existe toujours dans iOS 8.1 et sa solution fonctionne parfaitement ...

var textView: UITextView!
var textStorage: NSTextStorage!
var layoutManager: NSLayoutManager!
var textContainer: NSTextContainer!


override func viewDidLoad() {
    textStorage = NSTextStorage()
    layoutManager = NSLayoutManager()
    textStorage.addLayoutManager(layoutManager)

    let newTextViewRect = view.bounds
    let containerSize = CGSize(width: newTextViewRect.width, height: CGFloat.max)

    textContainer = NSTextContainer(size: containerSize)
    layoutManager.addTextContainer(textContainer)

    textView = UITextView(frame: newTextViewRect, textContainer: textContainer)

    textView.delegate = self
    view.addSubview(textView)

}

override func viewDidLayoutSubviews() {
    textView.frame = view.bounds
}

et j'ai utilisé la méthode scrollRangeToVisible pour faire défiler doucement vers le bas lorsque le texte est ajouté ...

let length = countElements(textView.text)
let range:NSRange = NSMakeRange(length - 1, 1)
textView.scrollRangeToVisible(range)
1
Steve S.

Ça marche pour moi. Référence: UITextView setText ne doit pas sauter en haut dans ios8

self.textView.layoutManager.allowsNonContiguousLayout = NO;
self.textView.text = fileContent;
if(fileContent.length > 1)
{
    NSRange range = NSMakeRange(self.textView.text.length - 1, 1);
    [self.textView scrollRangeToVisible:range];
}
0
leo5th

Swift 2.0 - IOS 8

Ceci est fondamentalement une version Swift 2.0 de la réponse de dklt above. Auparavant, j'utilisais la même méthode sans les 2 lignes de scrollEnabled. La plupart du temps cela fonctionne bien. Cependant, lorsque scrollToBottom() est appelé successivement à peu près au même moment, cela ne fonctionne pas parfois. 

Les 2 lignes de scrollEnabled n'ont pas beaucoup de sens, mais après les avoir ajoutées, la méthode fonctionne uniformément

Remarque: j'ai essayé de placer les 2 lignes de scrollEnabled dans différentes positions avant ou après la scrollRangeTovisible, comme suggéré dans les commentaires de la réponse de dklt ... 

Seule la solution originale de dklt fonctionne pour moi. Le reste ne le fait pas.

func scrollToBottom()
{
    let range:NSRange = NSMakeRange(self.textView.text.characters.count - 1, 1)

    self.textView.scrollRangeToVisible(range)
    self.textView.scrollEnabled = false
    self.textView.scrollEnabled = true
}
0
interceptwind

En règle générale, setScrollEnabled = YES doit être défini avant que layoutSubviews ne soit appelé. Cela a fonctionné pour moi.

0
Shilpi