UPDATE这似乎是与IOS 7只有一个问题。一个伟大的解决方法已添加到接受的答案。
我已经创建了一个包含一个UITextView和的UILabel包含TextView的,即我的控制标题的自定义控件。我的控件自动改变大小适应的TextView和标题。在这之前我改变TextView的大小以适合文本。这工作最佳。
我已经添加功能,因此TextView的自动滚动到最后一行。或者说,这至少是我正在努力。它只要在最后一行包含什么,但空的文本工作正常。如果文本是空的,它滚落下来,所以你只能看到光标所在的一半。
我究竟做错了什么?
所以,你可以把它理解更好,我已经取得了一些图片:
这是我键入单词和做一些换行。 (仍不足以使其滚动)
而我做一个换行符。 (按回车键)把光标是如何减半看起来接近。这是问题!
我已经做下一张图像,你可以看到什么我的预期。
与其他问题的答案:
解决的办法是把它添加到文本视图委托:
- (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];
}];
}
}
这就是我对我目前的项目用于调整一个UITextView:
- (void)textViewDidChange:(UITextView *)textView {
CGRect frame = textView.frame;
frame.size.height = textView.contentSize.height;
textView.frame = frame;
}
它的工作原理对我非常好。如果你想创建光标和实际文本框之间的一个小“边界”,你可以随时添加几个像素的高度。像这样:
frame.size.height = textView.contentSize.height+14;
在接受答案的解决方法是不可用的。
说有1000个字的TextView的和最后一个字符是“\ n”。如果编辑的TextView的第一线,hasSuffix:@"\n"
将返回YES
和TextView的将立即滚动到文档的底部。
或者,启动一个空白的TextView并键入一个单词,然后按回车键。文本将滚动至底部。
============ ============ ============ ============
Te| Text | Text
|
Text
|
============ ============ ============ ============
也许这是一个更好的解决办法,但它并不完美。它会检查是否插入符是否低于最大点,然后滚动到最大点,如果它是:
-(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];
});
}
尝试使用
textView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
textView.autoresizingSubviews = YES;
它解决了这个问题对我来说iOS7。
在iOS10在我的自动调整大小UITextView中的关键对我来说是
// 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];
}
全班
#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
在斯威夫特3
设置基准出口及TextView中的代表
class ViewController: UIViewController , UITextViewDelegate{
@IBOutlet var txtViewRef: UITextView!
在viewDidLoad中设置委托,并通知改变键盘边框或隐藏键盘
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)
}
创造一个我们正在键盘的框架和变化的内容和滚动指示的插图功能updateTextView和滚动的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)
}
我有同样的问题,但内的UITableView约UITextView的,所以经过一番调查中,我没有发现任何“简单”的方式来解决它,所以基于公认的答案,我会创造完美的工作液(也应该在里面工作UICollectionView,UIScrollView中这个扩展中评论了一些变化)。
因此,对于重复使用容易它需要对UIKit的基础上增加一些扩展:
extension UITextView {
func scrollToCursor(animated: Bool = false, verticalInset: CGFloat = 8) {
guard let selectedTextRange = selectedTextRange else { return }
var cursorRect = caretRect(for: selectedTextRange.start)
// NOTE: can't point UIScrollView, coz on iOS 10 closest view will be UITableWrapperView
// to extend functionality for UICollectionView or plain UIScrollView it's better to search them one by one
let scrollView = findParent(of: UITableView.self) ?? self
cursorRect = convert(cursorRect, to: scrollView)
if cursorRect.origin.x.isInfinite || cursorRect.origin.y.isInfinite {
return
}
let bottomOverflow = cursorRect.maxY - (scrollView.contentOffset.y + scrollView.bounds.height - scrollView.contentInset.bottom - scrollView.contentInset.top)
if bottomOverflow > 0 {
let offset = CGPoint(x: scrollView.contentOffset.x, y: scrollView.contentOffset.y + bottomOverflow + verticalInset)
scrollView.setContentOffset(offset, animated: animated)
return
}
let topOverflow = scrollView.contentOffset.y - cursorRect.minY
if topOverflow > 0 {
let offset = CGPoint(x: scrollView.contentOffset.x, y: scrollView.contentOffset.y - topOverflow - verticalInset)
scrollView.setContentOffset(offset, animated: animated)
}
}
}
UIView的:
extension UIView {
func findParent<Parent: UIView>(of parentType: Parent.Type) -> Parent? {
return superview?.findNext(of: parentType)
}
private func findNext<Parent: UIView>(of parentType: Parent.Type) -> Parent? {
if let res = self as? Parent {
return res
}
return superview?.findNext(of: parentType)
}
}
因此,对UITextViewDelegate,当文字被更改,叫你需要(可能是内部的调度队列主要异步块 - 我使用ReactiveSwift回调此):
textView.scrollToCursor()
如果你想添加上的光标位置的变化(在屏幕的顶部)向上移动需要调用textViewDidChangeSelection
委托的方法,在此方法中(与当然的选择长度检查)。
我试图把你的textViewDidChange:
片段,如:
if([textView.text hasSuffix:@"\n"])
[self.textView setContentOffset:CGPointMake(0,INT_MAX) animated:YES];
这是不是真的干净,我朝找到了一些更好的东西的工作,但现在它的工作原理:d
更新:由于这是只发生在iOS 7(测试版5,现在是)一个错误,你可以使用此代码的解决方法:
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];
});
}
然后,在iOS 6中,你可以选择的延迟设置为0.0或使用该块的只是内容。
使用斯威夫特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)
})
}
我用下面的代码在textViewDidChange:
方法,它似乎运作良好。
- (void)textViewDidChange:(UITextView *)textView {
CGPoint bottomOffset = CGPointMake(0, self.theTextView.contentSize.height - self.theTextView.bounds.size.height);
[self.theTextView setContentOffset:bottomOffset animated:YES];
}
这似乎进一步小幅滚动的UITextView使光标不切断。
使用Xamarin当Accepted answer / MonoTouch中的样子
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);
});
}
};
维克的回答以下修改为我工作得很好:
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];
});
}
}
我发现,如果你把在viewWillAppear中以下,这将解决这个那个的UITextView似乎已经在测试版的几个其他问题:
[self.textView.layoutManager ensureLayoutForTextContainer:self.textView.textContainer];
有没有人提起了错误,以苹果,关于这个问题?这感觉就像一个很明显的错误,很容易繁殖。如果没有人响应,则我将提交雷达与测试项目。
我认为最好的方法是确定实际的光标位置,看是否需要滚动出现。
- (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);
}
}
上面的代码将确定是否插入符号是在该文本的结尾。如果不是,它不会滚动。如果是(不管最后一个字符是什么),这将决定正确的偏移量滚动到,然后进行滚动。