在UITextView中垂直居中放置文本


68

我想将文本垂直居中放置在UITextView可填充整个屏幕的大文本内部-这样,当文本很少时,说几个字,就以高度为中心。这不是关于文本居中(在IB中可以找到的属性)的问题,而是关于如果文本较短则将文本垂直居中放置的问题,因此。能做到吗?提前致谢!UITextView UITextView

Answers:


61

首先,为视图加载时的contentSize键值添加观察者UITextView

- (void) viewDidLoad {
     [textField addObserver:self forKeyPath:@"contentSize" options:(NSKeyValueObservingOptionNew) context:NULL];
     [super viewDidLoad];
}

然后添加此方法以在contentOffset每次contentSize值更改时进行调整:

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
     UITextView *tv = object;
     CGFloat topCorrect = ([tv bounds].size.height - [tv contentSize].height * [tv zoomScale])/2.0;
     topCorrect = ( topCorrect < 0.0 ? 0.0 : topCorrect );
     tv.contentOffset = (CGPoint){.x = 0, .y = -topCorrect};
}

3
在UITextView填满文本并开始滚动之前,这对我非常有用。然后,每个新角色的视图都会反弹。我将代码放在if中,以检查内容是否高于视图:if(([[myView bounds] .size.height-[myView contentSize] .height)> 0)
audub 2013年

3
此解决方案对我来说非常有效;但是,在测试我的应用程序时,我注意到如果我将此代码调用3-4次,将会崩溃。看来您也想删除观察者。我添加了它,它修复了崩溃:-(void)viewWillDisappear:(BOOL)animated {[textField removeObserver:self forKeyPath:@“ contentSize”]; }由于这篇文章,我找到了这个解决方案:stackoverflow.com/a/7467414/81008
Skyler

9
UITextView(和其他UIKit类)不保证符合KVO。依赖于此可能导致您的代码在将来的iOS版本中中断。请参阅UIKit开发团队成员的帖子:stackoverflow.com/a/6051404/357641
Greg

我更喜欢使用NSStringFromSelector(@selector(contentSize))而不是@"contentSize"。并且不要忘记将removeObserver:forKeyPath:try-catch语句放进去!
Laszlo 2015年

9
这在iOS 9.0.2中似乎已被破坏。但是,存在解决方法。除了设置contentOffset之外,我们还可以设置contentInset(只需将observeValueForKeyPath方法中的最后一行代码更改为):`[tv setContentInset:UIEdgeInsetsMake(topCorrect,0,0,0)];`注意:如果我们使用KVO iOS 9会在最后一刻将contentOffset设置为0,覆盖对contentOffset的所有更改。
s.ka 2015年

54

因为UIKit不兼容KVO,所以我决定将其实现为一个子类,UITextView该子类在contentSize更改时会更新。

它是Carlos答案的稍作修改的版本,它设置了contentInset而不是contentOffset。除了与iOS 9兼容之外,它在iOS 8.4上的bug也似乎更少。

class VerticallyCenteredTextView: UITextView {
    override var contentSize: CGSize {
        didSet {
            var topCorrection = (bounds.size.height - contentSize.height * zoomScale) / 2.0
            topCorrection = max(0, topCorrection)
            contentInset = UIEdgeInsets(top: topCorrection, left: 0, bottom: 0, right: 0)
        }
    }
}

contentInset我没有更新,而是尝试通过contentSize didSet调用上述约束并且也可以通过更改约束来调整textView的大小。
Yuchen Zhong

4
适用于iOS 9.2。只是想指出,必须启用滚动才能使其正常工作。如果要以编程方式确保执行此操作(可能比期望某人在情节提要中正确设置要安全得多),请scrollEnabled = true在计算之前调用topCorrection,如果仍然要禁止用户滚动,则还需要userInteractionEnabled = false。另外,它是复制粘贴的辅助工具:Xcode将init在您的子类UITextView中需要一个方法。
Gobe

每个人都应该考虑到layoutMargins和写var topCorrection = (bounds.size.height - contentSize.height * zoomScale + layoutMargins.top + layoutMargins.bottom) / 2.0
malex

25

如果您不想使用KVO,也可以通过将以下代码导出到如下函数来手动调整偏移量:

-(void)adjustContentSize:(UITextView*)tv{
    CGFloat deadSpace = ([tv bounds].size.height - [tv contentSize].height);
    CGFloat inset = MAX(0, deadSpace/2.0);
    tv.contentInset = UIEdgeInsetsMake(inset, tv.contentInset.left, inset, tv.contentInset.right);
}  

并调用它

-(void)textViewDidChange:(UITextView *)textView{
    [self adjustContentSize:textView];
}

并且每次您在代码中编辑文本。不要忘记将控制器设置为委托

Swift 3版本:

func adjustContentSize(tv: UITextView){
    let deadSpace = tv.bounds.size.height - tv.contentSize.height
    let inset = max(0, deadSpace/2.0)
    tv.contentInset = UIEdgeInsetsMake(inset, tv.contentInset.left, inset, tv.contentInset.right)
}

func textViewDidChange(_ textView: UITextView) {
    self.adjustContentSize(tv: textView)
}

如图所示,该方法有效,但是在单元格出现时我无法初始化文本视图。仅当我将委托textView参数传递到adjustContentSize(_ :)时才有效,而如果我传递IBOutlet变量名称则不起作用。知道为什么会这样吗?
lifewithelliott

1
这是因为一旦视图具有自动布局约束重绘了视图,带有x和y位置的内容(例如大小,插入项)就有效,因此您可以在控制器中的viewDidLayoutSubviews函数之后调用adjustContentSize,它应该可以工作
Beninho85,2014年

在工作时甚至如何使用它?抱歉,如果我要问一个愚蠢的问题。新的迅速。
Shyam

@Shyam TextviewDidChange是UITextfield的委托。如果您不知道这是什么,我建议您看一下委托模式,因为它已在iOS的许多场景中使用。基本上,您必须将委托设置为当前的视图控制器(yourtextfield.delegate = self)并实现所需的功能(在这种情况下为
textViewDidChange

@ Beninho85,谢谢您的时间。是的,有很多代表。好吧,我的问题应该已经更详细了。我在中使用占位符UITextView。这坚持自动布局。因此,我想在所有视图中将占位符垂直居中。但是,如果用户输入了文本,则占位符将被替换,并且对齐方式应还原为使用整个可用区域。垂直对齐仅适用于占位符。是否有意义?
Shyam

20

对于iOS 9.0.2。我们将需要设置contentInset。如果我们对contentOffset进行KVO,iOS 9.0.2会在最后一刻将其设置为0,从而覆盖对contentOffset的更改。

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    UITextView *tv = object;
    CGFloat topCorrect = ([tv bounds].size.height - [tv     contentSize].height * [tv zoomScale])/2.0;
    topCorrect = ( topCorrect < 0.0 ? 0.0 : topCorrect );
    [tv setContentInset:UIEdgeInsetsMake(topCorrect,0,0,0)];
}

- (void) viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:NO];
    [questionTextView addObserver:self forKeyPath:@"contentSize" options:(NSKeyValueObservingOptionNew) context:NULL];
}

我分别将0,0和0用作左,下和右边缘插图。确保针对您的用例也进行计算。


1
谢谢!,它可以工作,但必须使用setTextContainerInset而不是setContentInset
nnarayann

11

您可以直接设置它而仅受约束:

我添加了3个约束,以在约束中垂直和水平对齐文本,如下所示:

在此处输入图片说明

  1. 使高度0并添加大于
  2. 将垂直对齐添加到父约束
  3. 将水平对齐添加到父约束

在此处输入图片说明


1
如果您禁用UITextView上的滚动,则效果很好
Ryan Romanchuk

2
这对我有用,但是在xcode中添加这些约束会抛出一个含糊的错误。为了使其静音,我添加了以250为优先级的等于0的顶部和底部约束。
rgkobashi

9

这是将UITextView内容垂直居中的扩展:

extension UITextView {

    func centerVertically() {
        let fittingSize = CGSize(width: bounds.width, height: CGFloat.max)
        let size = sizeThatFits(fittingSize)
        let topOffset = (bounds.size.height - size.height * zoomScale) / 2
        let positiveTopOffset = max(0, topOffset)
        contentOffset.y = -positiveTopOffset
    }

}

我想在布局视图之后是否需要调用此方法?
杰克T.18年

最大值应在0到topOffset之间计算,因为如果contentSize与textView大小相同,则1可能会导致从底部截断文本。
schinj '18

如果有换行符
则会

7

我刚刚在Swift 3中创建了一个自定义的垂直居中文本视图:

class VerticallyCenteredTextView: UITextView {
    override var contentSize: CGSize {
        didSet {
            var topCorrection = (bounds.size.height - contentSize.height * zoomScale) / 2.0
            topCorrection = max(0, topCorrection)
            contentInset = UIEdgeInsets(top: topCorrection, left: 0, bottom: 0, right: 0)
        }
    }
}

参考:https : //geek-is-stupid.github.io/2017-05-15-how-to-center-text-vertically-in-a-uitextview/


无效。...此回调在每个屏幕出现时被调用5-6次,并且每次计算都不相同....一点都不好,请不要使用
Radu

这是一个非常优雅的解决方案。它非常适合SwiftUI中的UIViewRepresentable,该UIViewResponseable响应MagnificationGesture动态更改字体大小。太棒了!
codewithfeeling

6

我有一个TextView,我使用与自动布局与设置lineFragmentPaddingtextContainerInset为零。在我的情况下,以上解决方案均无效。但是,这对我有用。经过iOS 9测试

@interface VerticallyCenteredTextView : UITextView
@end

@implementation VerticallyCenteredTextView

-(void)layoutSubviews{
    [self recenter];
}

-(void)recenter{
    // using self.contentSize doesn't work correctly, have to calculate content size
    CGSize contentSize = [self sizeThatFits:CGSizeMake(self.bounds.size.width, CGFLOAT_MAX)];
    CGFloat topCorrection = (self.bounds.size.height - contentSize.height * self.zoomScale) / 2.0;
    self.contentOffset = CGPointMake(0, -topCorrection);
}

@end

谢谢,这就是我的需要。
iAleksandr '18 -10-9

@ user3777977只需在此处复制您的答案。添加到卡洛斯的答案,以防万一您的电视中的文本大于电视尺寸,而您不需要更新文本,因此请更改此代码: tv.contentOffset = (CGPoint){.x = 0, .y = -topCorrect}; 改为: if ([tv contentSize].height < [tv bounds].size.height) { tv.contentOffset = (CGPoint){.x = 0, .y = -topCorrect}; }
iWill

5
func alignTextVerticalInTextView(textView :UITextView) {

    let size = textView.sizeThatFits(CGSizeMake(CGRectGetWidth(textView.bounds), CGFloat(MAXFLOAT)))

    var topoffset = (textView.bounds.size.height - size.height * textView.zoomScale) / 2.0
    topoffset = topoffset < 0.0 ? 0.0 : topoffset

    textView.contentOffset = CGPointMake(0, -topoffset)
}

5

斯威夫特3:

override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()

        textField.frame = self.view.bounds

        var topCorrect : CGFloat = (self.view.frame.height / 2) - (textField.contentSize.height / 2)
        topCorrect = topCorrect < 0.0 ? 0.0 : topCorrect
        textField.contentInset = UIEdgeInsetsMake(topCorrect,0,0,0)

    }

4

我也有这个问题,我有一个解决它UITableViewCellUITextView。我在自定义UITableViewCell子类property中创建了方法statusTextView

- (void)centerTextInTextView
{
    CGFloat topCorrect = ([self.statusTextView bounds].size.height - [self.statusTextView contentSize].height * [self.statusTextView zoomScale])/2.0;
    topCorrect = ( topCorrect < 0.0 ? 0.0 : topCorrect );
    self.statusTextView.contentOffset = (CGPoint){ .x = 0, .y = -topCorrect };

并在方法中调用此方法:

- (void)textViewDidBeginEditing:(UITextView *)textView
- (void)textViewDidEndEditing:(UITextView *)textView
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

此解决方案对我没有任何问题,您可以尝试一下。


2

添加到Carlos答案中,以防万一您的电视中的文本比电视大,而您不需要更新文本,请更改此代码:

tv.contentOffset = (CGPoint){.x = 0, .y = -topCorrect};

对此:

if ([tv contentSize].height < [tv bounds].size.height) {
     tv.contentOffset = (CGPoint){.x = 0, .y = -topCorrect};
}

2

自动布局解决方案:

  1. 创建一个UIView充当UITextView的容器。
  2. 添加以下约束:
    • TextView:将前导空间对齐到:Container
    • TextView:将尾随空间对齐到:Container
    • TextView:将中心Y对齐到:容器
    • TextView:等于高度:容器,关系:≤

1
不幸的是,它对我不起作用。即使这四个约束条件还不够。我可以通过某种方式将textField设置为“粘贴”文本内容吗?
ingaham'3

如果将容器与层次结构中的文本视图置于同一级别,会发生什么情况?-因此,它不是充当实际的容器,而只是位于同一位置的占位符。
Etan 2015年

该解决方案还需要自动调整大小文本视图- stackoverflow.com/questions/16868117/...
梅德

这个解决方案更好!
光明的未来

1

您可以尝试以下代码,而不必强制要求观察者。当视图解除分配时,观察者有时会抛出错误。您可以将此代码保存在viewDidLoad,viewWillAppear或viewDidAppear中的任何位置。

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    dispatch_async(dispatch_get_main_queue(), ^(void) {
        UITextView *tv = txtviewDesc;
        CGFloat topCorrect = ([tv bounds].size.height - [tv contentSize].height * [tv zoomScale])/2.0;
        topCorrect = ( topCorrect < 0.0 ? 0.0 : topCorrect );
        tv.contentOffset = (CGPoint){.x = 0, .y = -topCorrect};
    });
});

我也试图在viewWillDisappear和viewDidDisappear中删除观察者,然后有时也会崩溃。
基兰·雅斯瓦妮

无需设置观察者,此过程既简单又灵活,可以保存在任何地方。
基兰·雅斯瓦妮

您可以在dealloc方法中删除观察者-(void)dealloc {}
sbarow's

dealloc在ARC项目中不再被允许。:)
Kiran Jasvanee 2015年

ARC项目中允许使用dealloc方法,只是不要调用[super dealloc];
sbarow

0

我这样做是这样的:首先,我将UITextView嵌入到UIView中(这也适用于mac OS)。然后,我将外部UIView的所有四个侧面固定到其容器的侧面,使其形状和大小与UITextView相似或相等。因此,我有一个适合UITextView的容器。然后,我将UITextView的左右边框固定到UIView的侧面,并给UITextView设置一个高度。最后,我将UITextView垂直居中于UIView中。Bingo :)现在,UITextView在UIView中垂直居中,因此,UITextView内部的文本也垂直居中。


0

这是NSLayoutManager用于获取实际文本大小的简单任务NSTextContainer

class VerticallyCenteredTextView: UITextView {

    override func layoutSubviews() {
        super.layoutSubviews()
    
        let rect = layoutManager.usedRect(for: textContainer)
        let topInset = (bounds.size.height - rect.height) / 2.0
        textContainerInset.top = max(0, topInset)
    }
}

在最终计算中不要使用contentSizecontentInset


-1

UITextView + VerticalAlignment.h

//  UITextView+VerticalAlignment.h
//  (c) The Internet 2015
#import <UIKit/UIKit.h>

@interface UITextView (VerticalAlignment)

- (void)alignToVerticalCenter;
- (void)disableAlignment;

@end

UITextView + VerticalAlignment.m

#import "UITextView+VerticalAlignment.h"

@implementation UITextView (VerticalAlignment)

- (void)alignToVerticalCenter {
    [self addObserver:self forKeyPath:@"contentSize" options:NSKeyValueObservingOptionNew context:NULL];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    UITextView *tv = object;
    CGFloat topCorrect = ([tv bounds].size.height - [tv contentSize].height * [tv zoomScale])/2.0;
    topCorrect = ( topCorrect < 0.0 ? 0.0 : topCorrect );
    tv.contentOffset = (CGPoint){.x = 0, .y = -topCorrect};
}

- (void)disableAlignment {
    [self removeObserver:self forKeyPath:@"contentSize"];
}
@end

-1

我通过创建垂直扩展到中心高度的方法来解决此问题。

SWIFT 5:

extension UITextView {
    func centerContentVertically() {
        let fitSize = CGSize(width: bounds.width, height: CGFloat.greatestFiniteMagnitude)
        let size = sizeThatFits(fitSize)
        let heightOffset = (bounds.size.height - size.height * zoomScale) / 2
        let positiveTopOffset = max(0, heightOffset)
        contentOffset.y = -positiveTopOffset
    }
}

-3

RubyMotion中针对iOS10的解决方案:

class VerticallyCenteredTextView < UITextView

    def init
        super
    end

    def layoutSubviews
        self.recenter
    end

    def recenter
        contentSize = self.sizeThatFits(CGSizeMake(self.bounds.size.width, Float::MAX))
        topCorrection = (self.bounds.size.height - contentSize.height * self.zoomScale) / 2.0;
        topCorrection = 0 if topCorrection < 0
        self.contentInset = UIEdgeInsetsMake(topCorrection,  0, 0, 0)
    end

end
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.