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
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];
}
Cela fonctionne pour moi dans iOS7.
-(void) scrollToBottom {
[textView scrollRangeToVisible:NSMakeRange([textView.text length], 0)];
[textView setScrollEnabled:NO];
[textView setScrollEnabled:YES];
}
Il y a deux problèmes dans iOS 7 qui pourraient expliquer votre problème:
La solution pourrait être:
-(void)scrollOutputToBottom {
CGRect caretRect = [textView caretRectForPosition:textView.endOfDocument];
[textView scrollRectToVisible:caretRect animated:NO];
}
Essaye ça:
// Don't forget to set textView's delegate
-(void)textViewDidChangeSelection:(UITextView *)textView {
[textView scrollRangeToVisible:NSMakeRange([textView.text length], 0)];
}
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)
Ç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];
}
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
}
En règle générale, setScrollEnabled = YES doit être défini avant que layoutSubviews ne soit appelé. Cela a fonctionné pour moi.