以编程方式设置文本时阻止 UITextView 跳转

发布于 2024-07-19 11:29:39 字数 1245 浏览 4 评论 0原文

我必须更新滚动 UITextView 中的少量文本。 我只会在光标当前所在的位置插入一个字符,并且只需按导航栏上的按钮即可执行此操作。

我的问题是,每当我调用文本视图的 setText 方法时,它都会跳到文本的底部。 我尝试过使用 contentOffset 并重置 selectedRange 但它不起作用! 这是我的例子:

// Remember offset and selection
CGPoint contentOffset = [entryTextView contentOffset];
NSRange selectedRange = [entryTextView selectedRange];
// Update text
entryTextView.text = entryTextView.text;
// Try and reset offset and selection
[entryTextView setContentOffset:contentOffset animated:NO];
[entryTextView setSelectedRange: selectedRange];

有什么方法可以在不进行任何滚动移动的情况下更新文本......就好像他们刚刚在键盘上输入了一些东西一样?

编辑:

我尝试使用 textViewDidChange: delegate 方法,但它仍然没有向上滚动到原始位置。

- (void)textViewDidChange:(UITextView *)textView {
    if (self.programChanged) {
        [textView setSelectedRange:self.selectedRange];
        [textView setContentOffset:self.contentOffset animated:NO];
        self.programChanged = NO;
    }
}

- (void)changeButtonPressed:(id)sender {
    // Remember position
    self.programChanged = YES;
    self.contentOffset = [entryTextView contentOffset];
    self.selectedRange = [entryTextView selectedRange];
    // Update text
    entryTextView.text = entryTextView.text;
}

I have to update a small amount of text in a scrolling UITextView. I'll only be inserting a character where the cursor currently is, and I'll be doing this on a press of a button on my navigation bar.

My problem is that whenever I call the setText method of the text view, it jumps to the bottom of the text. I've tried using contentOffset and resetting the selectedRange but it doesn't work! Here's my example:

// Remember offset and selection
CGPoint contentOffset = [entryTextView contentOffset];
NSRange selectedRange = [entryTextView selectedRange];
// Update text
entryTextView.text = entryTextView.text;
// Try and reset offset and selection
[entryTextView setContentOffset:contentOffset animated:NO];
[entryTextView setSelectedRange: selectedRange];

Is there any way you can update the text without any scroll movement at all... as if they'd just typed something on the keyboard?

Edit:

I've tried using the textViewDidChange: delegate method but it's still not scrolling up to the original location.

- (void)textViewDidChange:(UITextView *)textView {
    if (self.programChanged) {
        [textView setSelectedRange:self.selectedRange];
        [textView setContentOffset:self.contentOffset animated:NO];
        self.programChanged = NO;
    }
}

- (void)changeButtonPressed:(id)sender {
    // Remember position
    self.programChanged = YES;
    self.contentOffset = [entryTextView contentOffset];
    self.selectedRange = [entryTextView selectedRange];
    // Update text
    entryTextView.text = entryTextView.text;
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(13

潦草背影 2024-07-26 11:29:40

以下两种解决方案在 iOS 8.0 上不适用于我。

textView.scrollEnabled = NO;
[textView.setText: text];
textView.scrollEnabled = YES;

CGPoint offset = textView.contentOffset;
[textView.setText: text];
[textView setContentOffset:offset];

设置了一个委托给textview来监视滚动事件,并注意到在我执行恢复偏移量的操作后,偏移量再次重置为0。 因此,我改为使用主操作队列来确保我的恢复操作在“重置为 0”选项之后发生。

这是我的适用于 iOS 8.0 的解决方案。

CGPoint offset = self.textView.contentOffset;
self.textView.attributedText = replace;
[[NSOperationQueue mainQueue] addOperationWithBlock: ^{
    [self.textView setContentOffset: offset];
}];

The following two solutions don't work for me on iOS 8.0.

textView.scrollEnabled = NO;
[textView.setText: text];
textView.scrollEnabled = YES;

and

CGPoint offset = textView.contentOffset;
[textView.setText: text];
[textView setContentOffset:offset];

I setup a delegate to the textview to monitor the scroll event, and noticed that after my operation to restore the offset, the offset is reset to 0 again. So I instead use the main operation queue to make sure my restore operation happens after the "reset to 0" option.

Here's my solution that works for iOS 8.0.

CGPoint offset = self.textView.contentOffset;
self.textView.attributedText = replace;
[[NSOperationQueue mainQueue] addOperationWithBlock: ^{
    [self.textView setContentOffset: offset];
}];
ま昔日黯然 2024-07-26 11:29:40

为了编辑 UITextView 的文本,您需要更新它的 textStorage 字段:

[_textView.textStorage beginEditing];

NSRange replace = NSMakeRange(10, 2); //enter your editing range
[_textView.textStorage replaceCharactersInRange:replace withString:@"ha ha$ "];

//if you want to edit the attributes
NSRange attributeRange = NSMakeRange(10, 5); //enter your editing attribute range
[_textView.textStorage addAttribute:NSBackgroundColorAttributeName value:[UIColor greenColor] range:attributeRange];

[_textView.textStorage endEditing];

祝你好运

In order to edit the text of a UITextView, you need to update it's textStorage field:

[_textView.textStorage beginEditing];

NSRange replace = NSMakeRange(10, 2); //enter your editing range
[_textView.textStorage replaceCharactersInRange:replace withString:@"ha ha$ "];

//if you want to edit the attributes
NSRange attributeRange = NSMakeRange(10, 5); //enter your editing attribute range
[_textView.textStorage addAttribute:NSBackgroundColorAttributeName value:[UIColor greenColor] range:attributeRange];

[_textView.textStorage endEditing];

Good luck

深巷少女 2024-07-26 11:29:40

建议的解决方案都不适合我。 -setContentOffset:animated: 由 -setText: 触发 3 次,并带有动画 YES 和结束的 contentOffset(减去 UITextView 的默认 8pt 边距)。 我将 -setText: 包装在guard:

textView.contentOffsetAnimatedCallsDisabled = YES;
textView.text = text;
textView.contentOffsetAnimatedCallsDisabled = NO;

中的 UITextView 子类中 -setContentOffset:animated: 放在

if (contentOffsetAnimatedCallsDisabled) return; // early return

其他逻辑中。 不要忘记超级电话。 这有效。

拉斐尔

No of the suggested solutions worked for me. -setContentOffset:animated: gets triggered by -setText: 3 times with animated YES and a contentOffset of the end (minus the default 8pt margin of a UITextView). I wrapped the -setText: in a guard:

textView.contentOffsetAnimatedCallsDisabled = YES;
textView.text = text;
textView.contentOffsetAnimatedCallsDisabled = NO;

In a UITextView subclass in -setContentOffset:animated: put

if (contentOffsetAnimatedCallsDisabled) return; // early return

among your other logic. Don’t forget the super call. This works.

Raphael

关于从前 2024-07-26 11:29:40

在 iOS 7 中。在 UITextView 中存在 sizeThatFits 和换行符的错误,我发现有效的解决方案是通过禁用滚动来包装它。 像这样:

textView.scrollEnabled = NO;
CGSize newSize = [textView sizeThatFits:CGSizeMake(fixedWidth, MAXFLOAT)];
textView.scrollEnabled = YES;

奇怪的跳跃已被修复。

in iOS 7. There seams to be a bug with sizeThatFits and having linebreaks in your UITextView the solution I found that works is to wrap it by disabling scrolling. Like this:

textView.scrollEnabled = NO;
CGSize newSize = [textView sizeThatFits:CGSizeMake(fixedWidth, MAXFLOAT)];
textView.scrollEnabled = YES;

and weird jumping has been fixed.

分开我的手 2024-07-26 11:29:40

我在 IOS9 中遇到了类似(如果不是相同)的问题。 将某些文本的特征更改为粗体会导致视图将所选内容滚动到视线之外。 我通过在setSelectedRange之后添加对scrollRangeToVisible的调用来对此进行排序:

    [self setSelectedRange:range];
    [self scrollRangeToVisible:range];

I hit a a similar, if not the same, problem in IOS9. Changing the characteristics of some text to, say, BOLD caused the view to scroll the selection out of sight. I sorted this by adding a call to scrollRangeToVisible after the setSelectedRange:

    [self setSelectedRange:range];
    [self scrollRangeToVisible:range];
燕归巢 2024-07-26 11:29:40

看看 UITextViewDelegate,我相信 textViewDidChangeSelection 方法可以让您做您需要的事情。

Take a look at the UITextViewDelegate, I believe the textViewDidChangeSelection method may allow you to do what you need.

怪异←思 2024-07-26 11:29:40

老问题,但我对 iOS 7 应用程序也有同样的问题。 需要在运行循环后稍微更改 contentOffset。 这是一个快速的想法。

self.clueString = [self.level clueText];
CGPoint point = self.clueText.contentOffset;
self.clueText.attributedText = self.clueString;
double delayInSeconds = 0.001; // after the run loop update
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
    [self.clueText setContentOffset:point animated:NO]; 
});

Old question but I had the same issue with iOS 7 app. Requires changing the contentOffset a little bit after the run loop. Here is a quick idea.

self.clueString = [self.level clueText];
CGPoint point = self.clueText.contentOffset;
self.clueText.attributedText = self.clueString;
double delayInSeconds = 0.001; // after the run loop update
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
    [self.clueText setContentOffset:point animated:NO]; 
});
鲸落 2024-07-26 11:29:40

最后试试这个,在 iOS 10 上检查过

let offset = textView.contentOffset
textView.attributedText = newValue
OperationQueue.main.addOperation {
    self.textView.setContentOffset(offset, animated: false)
}

Finally try this, checked on iOS 10

let offset = textView.contentOffset
textView.attributedText = newValue
OperationQueue.main.addOperation {
    self.textView.setContentOffset(offset, animated: false)
}
一直在等你来 2024-07-26 11:29:40

不是那么优雅的解决方案 - 但它有效,所以谁在乎:

- (IBAction)changeTextProgrammaticaly{
     myTextView.text = @"Some text";
     [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(rewindOffset) userInfo:nil repeats:NO];
}

- (void)rewindOffset{
    [myTextView setContentOffset:CGPointMake(0,0) animated: NO];
}

Not so elegant solution- but it works so who cares:

- (IBAction)changeTextProgrammaticaly{
     myTextView.text = @"Some text";
     [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(rewindOffset) userInfo:nil repeats:NO];
}

- (void)rewindOffset{
    [myTextView setContentOffset:CGPointMake(0,0) animated: NO];
}
森林很绿却致人迷途 2024-07-26 11:29:40

我找到了一个在 iOS 6 和 7(可能还有更早版本)中可靠工作的解决方案。 在 UITextView 的子类中,执行以下操作:

@interface MyTextView ()
@property (nonatomic) BOOL needToResetScrollPosition;
@end

@implementation MyTextView

- (void)setText:(NSString *)text
{
    [super setText:text];
    self.needToResetScrollPosition = YES;
}

- (void)layoutSubviews
{
    [super layoutSubviews];

    if (self.needToResetScrollPosition) {
        self.contentOffset = CGPointMake(0, 0);
        self.needToResetScrollPosition = NO;
    }
}

其他答案在 iOS 7 中均不起作用,因为它将在显示时调整滚动偏移。

I found a solution that works reliably in iOS 6 and 7 (and probably earlier versions). In a subclass of UITextView, do the following:

@interface MyTextView ()
@property (nonatomic) BOOL needToResetScrollPosition;
@end

@implementation MyTextView

- (void)setText:(NSString *)text
{
    [super setText:text];
    self.needToResetScrollPosition = YES;
}

- (void)layoutSubviews
{
    [super layoutSubviews];

    if (self.needToResetScrollPosition) {
        self.contentOffset = CGPointMake(0, 0);
        self.needToResetScrollPosition = NO;
    }
}

None of the other answers work in iOS 7 because it will adjust the scroll offsets at display time.

说好的呢 2024-07-26 11:29:39

如果您使用iPhone 3.0或更高版本,您可以解决这个问题:

textView.scrollEnabled = NO;

//You should know where the cursor will be(if you update your text by appending/inserting/deleting you can know the selected range) so keep it in a NSRange variable.

Then update text:
textView.text = yourText;

textView.scrollEnabled = YES;
textView.selectedRange = range;//you keep before

现在应该可以工作了(不再跳跃)

问候
梅厄·阿萨亚格

If you use iPhone 3.0 or later, you can solve this problem:

textView.scrollEnabled = NO;

//You should know where the cursor will be(if you update your text by appending/inserting/deleting you can know the selected range) so keep it in a NSRange variable.

Then update text:
textView.text = yourText;

textView.scrollEnabled = YES;
textView.selectedRange = range;//you keep before

It should work now (no more jumping)

Regards
Meir Assayag

抱猫软卧 2024-07-26 11:29:39

根据梅尔的建议,这里的代码以编程方式删除选择(是的,我知道有一个选择菜单按钮也可以这样做,但我正在做一些有点时髦的事情),而无需滚动文本视图。

NSRange selectedRange = textView.selectedRange;
textView.scrollEnabled = NO;
// I'm deleting text. Replace this line with whatever insertions/changes you want
textView.text = [textView.text
                stringByReplacingCharactersInRange:selectedRange withString:@""];
selectedRange.length = 0;
// If you're inserting text, you might want to increment selectedRange.location to be
// after the text you inserted
textView.selectedRange = selectedRange;
textView.scrollEnabled = YES;

Building on Meir's suggestion, here's code that removes the selection programmatically (yes I know there's a selection menu button that does it too, but I'm doing something a bit funky) without scrolling the text view.

NSRange selectedRange = textView.selectedRange;
textView.scrollEnabled = NO;
// I'm deleting text. Replace this line with whatever insertions/changes you want
textView.text = [textView.text
                stringByReplacingCharactersInRange:selectedRange withString:@""];
selectedRange.length = 0;
// If you're inserting text, you might want to increment selectedRange.location to be
// after the text you inserted
textView.selectedRange = selectedRange;
textView.scrollEnabled = YES;
雨落星ぅ辰 2024-07-26 11:29:39

此决定适用于 iOS 8:

let offset = textView.contentOffset
textView.text = newText
textView.layoutIfNeeded()
textView.setContentOffset(offset, animated: false)

有必要准确调用 setContentOffset:animated: 因为只有这样才会取消动画。 textView.contentOffset = offset 不会取消动画,也没有帮助。

This decision works for iOS 8:

let offset = textView.contentOffset
textView.text = newText
textView.layoutIfNeeded()
textView.setContentOffset(offset, animated: false)

It is necessary to call exactly setContentOffset:animated: because only this will cancel animation. textView.contentOffset = offset will not cancel the animation and will not help.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文