Comment faire défiler UITextView en tapant/éditant
UPDATE Cela semble être un problème avec IOS 7 seulement. Une excellente solution de contournement a été ajoutée à la réponse acceptée.
j'ai créé un contrôle personnalisé qui contient UITextView et UILabel qui contient le titre du textview c'est à dire mon contrôle. Mon contrôle change automatiquement la taille pour adapter le textview et le titre. Avant que cela n'arrive, je change la taille du textview pour l'adapter au texte. Cela fonctionne de manière optimale.
j'ai ajouté la fonctionnalité de sorte que le textview défile automatiquement vers la dernière ligne. Ou c'est du moins ce que j'essaie. Cela fonctionne très bien tant que la dernière ligne contient autre chose que du texte vide. Si le texte est vide, il roule vers le bas de sorte que vous ne pouvez voir environ la moitié du curseur.
Qu'est-ce que je fais de mal?
pour que vous puissiez mieux le comprendre j'ai fait quelques images:
C'est moi en train de taper un mot et de faire des linebreaks. (Pas encore assez pour le faire faites défiler jusqu')
Et la je fais un saut de ligne. (en appuyant sur entrée) regardez de près comment le curseur est divisé en deux. Telle est la question!
j'ai fait la prochaine image pour que vous puissiez voir exactement ce à quoi je m'attendais.
14 réponses
problèmes avec d'autres réponses:
- lorsque vous scannez seulement pour "\n", Si vous tapez une ligne de texte qui dépasse la largeur de la vue de texte, alors le défilement ne se produira pas.
- quand toujours contentOffset dans textViewDidChange: si vous modifiez le milieu du texte que vous ne voulez pas faire défiler vers le bas.
La solution est d'ajouter à l'affichage de texte délégué:
- (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];
}];
}
}
j'ai essayé de mettre dans votre textViewDidChange:
un extrait de la forme:
if([textView.text hasSuffix:@"\n"])
[self.textView setContentOffset:CGPointMake(0,INT_MAX) animated:YES];
ce n'est pas vraiment propre, je travaille à trouver de meilleures choses, mais pour l'instant ça marche :D
UPDATE: Comme il s'agit d'un bug qui ne se produit que sur iOS 7 (Beta 5, pour l'instant), vous pouvez faire un contournement avec ce code:
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];
});
}
Puis, sur iOS 6, vous pouvez choisir soit de régler le délai à 0,0 ou d'utiliser le contenu du bloc.
Utilisation 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)
})
}
j'ai utilisé le code suivant dans la balise textViewDidChange:
méthode et ça semble bien fonctionner.
- (void)textViewDidChange:(UITextView *)textView {
CGPoint bottomOffset = CGPointMake(0, self.theTextView.contentSize.height - self.theTextView.bounds.size.height);
[self.theTextView setContentOffset:bottomOffset animated:YES];
}
cela semble faire défiler l'UITextView légèrement plus loin pour que votre curseur ne soit pas coupé.
réponse acceptée lors de L'utilisation de Xamarin / Monotouch ressemblera à
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);
});
}
};
La suite de la modification de Vik réponse a bien fonctionné pour moi:
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];
});
}
}
j'ai trouvé que si vous placez le code suivant dans viewWillAppear, il permettra de résoudre cela, et quelques autres questions qui UITextView semble avoir dans les bêtas:
[self.textView.layoutManager ensureLayoutForTextContainer:self.textView.textContainer];
Quelqu'un a-t-il signalé un bogue à apple concernant cette question? Cela ressemble à un bug assez évident qui est très facile à reproduire. Si personne ne répond, je vais déposer un radar avec un projet d'essai.
je pense que le meilleur moyen est de déterminer la position réelle du curseur pour voir si le défilement doit se produire.
- (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);
}
}
le code ci-dessus va déterminer si le signe d'insertion est à la fin du texte. Si ce n'est pas le cas, ça ne défilera pas. Si c'est le cas (indépendamment de ce qu'est le dernier caractère), il va déterminer le déport correct à faire défiler et puis effectuer le défilement.
- (void)textViewDidChange:(UITextView *)textView {
CGRect frame = textView.frame;
frame.size.height = textView.contentSize.height;
textView.frame = frame;
}
Cela fonctionne très bien pour moi. Si vous voulez créer une petite "bordure" entre le curseur et la zone de texte réelle, vous pouvez toujours ajouter quelques pixels à la hauteur. Comme ceci:
frame.size.height = textView.contentSize.height+14;
la solution dans la réponse acceptée est inutilisable.
disons qu'il y a 1000 mots dans le textView et que le caractère final est "\n". Si vous éditez la première ligne de textView,hasSuffix:@"\n"
retour YES
et le textView défile immédiatement vers le bas du document.
ou, commencez avec un textView vide et tapez un mot, puis appuyez sur retour. Le texte défile vers le bas.
============ ============ ============ ============
Te| Text | Text
|
Text
|
============ ============ ============ ============
c'est Peut-être une meilleure solution, mais ce n'est pas parfait. Il vérifie si le signe d'insertion est en dessous d'un point maximum, puis passe au point maximum s'il est:
-(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];
});
}
Essayez d'utiliser
textView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
textView.autoresizingSubviews = YES;
Il a résolu le problème pour moi pour iOS7.
sur iOS10 dans mon UITextView autosizing la clé pour moi était
// 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];
}
toute la classe
#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
Dans Swift 3
Définir la référence de sortie & délégué de textview
class ViewController: UIViewController , UITextViewDelegate{
@IBOutlet var txtViewRef: UITextView!
Dans le viewDidLoad ensemble de délégué et de Notification de changement de KeyboardFrame ou Masquer le clavier
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)
}
Créer une Fonction updateTextView Dans lesquels nous sommes le cadre du clavier et de l'évolution de l'encart de contenu et de défilement de l'indicateur de défilement et 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)
}