I want to center the text vertically inside a big UITextView
that fills the whole screen - so that when there's little of text, say a couple of words, it is centered by height.
It's not a question about centering the text (a property that can be found in IB) but about putting the text vertically right in the middle of UITextView
if the text is short, so there are no blank areas in the UITextView
.
Can this be done?

- 20,030
- 7
- 43
- 238

- 11,995
- 20
- 81
- 120
19 Answers
First add an observer for the contentSize
key value of the UITextView
when the view is loaded:
- (void) viewDidLoad {
[textField addObserver:self forKeyPath:@"contentSize" options:(NSKeyValueObservingOptionNew) context:NULL];
[super viewDidLoad];
}
Then add this method to adjust the contentOffset
every time the contentSize
value changes:
-(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};
}

- 20,427
- 11
- 57
- 70

- 3,138
- 1
- 20
- 21
-
3This works great for me until the UITextView is filled with text and starts to scroll. Then the view bounces for each new character. I put your code in an if to check if content is higher than view: if (([myView bounds].size.height - [myView contentSize].height) > 0) – audub Mar 10 '13 at 13:17
-
3This solution worked great for me; however, while testing my app I noticed that I'd get a crash if I called this code 3-4 times. Looks like you also want to remove the observer. I added this and it fixed the crash: - (void)viewWillDisappear:(BOOL)animated { [textField removeObserver:self forKeyPath:@"contentSize"]; } I found this solution thanks to this post: http://stackoverflow.com/a/7467414/81008 – Skyler Jul 14 '13 at 22:31
-
9UITextView (and other UIKit classes) are not guaranteed to be KVO compliant. Relying on this could cause your code to break in future iOS versions. See this post by a member of the UIKit development team: http://stackoverflow.com/a/6051404/357641 – Greg Aug 28 '13 at 17:48
-
I Prefer using `NSStringFromSelector(@selector(contentSize))` instead of just `@"contentSize"`. And don't forget to put the `removeObserver:forKeyPath:` to a try-catch statement! – Laszlo Jun 17 '15 at 08:52
-
with autolayout you have to use let size = tv.sizeThatFits(CGSizeMake(CGRectGetWidth(tv.bounds), CGFloat(MAXFLOAT))) instead of [tv contentSize] – Ievgen Aug 14 '15 at 19:40
-
9This appears to be broken in iOS 9.0.2. However, a workaround exists. Instead of setting the contentOffset, we can set the contentInset instead (Just change the last line of code in the observeValueForKeyPath method to): ` [tv setContentInset:UIEdgeInsetsMake(topCorrect,0,0,0)];` Note: If we KVO the contentOffset, iOS 9 sets it to 0 at the last moment, overriding any changes to contentOffset. – s.ka Oct 25 '15 at 21:58
-
Same code is not working in UITableViewCell - (void)awakeFromNib { [self.textViewLarge addObserver:self forKeyPath:@"contentSize" options:(NSKeyValueObservingOptionNew) context:NULL]; [self.textView1 addObserver:self forKeyPath:@"contentSize" options:(NSKeyValueObservingOptionNew) context:NULL]; [self.textView2 addObserver:self forKeyPath:@"contentSize" options:(NSKeyValueObservingOptionNew) context:NULL]; [super awakeFromNib]; } – DJtiwari May 24 '16 at 10:58
-
It is so frustrating that this cannot be achieved directly in the Storyboard – MathMax Aug 04 '20 at 16:41
Because UIKit is not KVO compliant, I decided to implement this as a subclass of UITextView
which updates whenever the contentSize
changes.
It's a slightly modified version of Carlos's answer which sets the contentInset
instead of the contentOffset
. In addition to being compatible with iOS 9, it also seems to be less buggy on iOS 8.4.
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)
}
}
}
-
Instead of updating the `contentInset`, I try resizing the textView by changing the constraints whenever the above `contentSize didSet` gets called and that also works. – Yuchen Feb 07 '16 at 04:26
-
4Working fine for iOS 9.2. Just wanted to point out that scrolling MUST be enabled for this to work. If you want to ensure that programmatically (probably safer than expecting someone to set it correctly in the storyboard), just call `scrollEnabled = true` before calculating the `topCorrection` and, in case you still want to disable users to scroll, you will also need `userInteractionEnabled = false`. Also, a minor for copy-pasters: Xcode will require an `init` method in your subclassed UITextView. – Gobe Feb 16 '16 at 20:30
-
One should take into account `layoutMargins` and write `var topCorrection = (bounds.size.height - contentSize.height * zoomScale + layoutMargins.top + layoutMargins.bottom) / 2.0` – malex Dec 01 '20 at 05:22
If you don't want to use KVO you can also manually adjust offset with exporting this code to a function like this :
-(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);
}
and calling it in
-(void)textViewDidChange:(UITextView *)textView{
[self adjustContentSize:textView];
}
and every time you edit the text in the code. Don't forget to set the controller as the delegate
Swift 3 version:
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)
}

- 3,273
- 27
- 22
-
this works as shown, but I can not initialize the text view upon cell appearing. Only works if I pass the delegates textView argument into the adjustContentSize(_:), not if I pass in the IBOutlet variable name. Any idea why this may be the case? – lifewithelliott Mar 15 '17 at 02:55
-
1It's because things with x and y position like size, insets, are valid once the view has redrawn the view with autolayout constraint, so you can call adjustContentSize after the viewDidLayoutSubviews function in your controller and it should work – Beninho85 Mar 15 '17 at 14:39
-
How to even use this, while working? Sorry, if I'm asking a dumb question. New to swift. – Shyam Aug 14 '17 at 11:09
-
@Shyam TextviewDidChange is a delegate of UITextfield. If you don't know what is it, I suggest you to take a look at the delegate pattern because it's used in many scenarios in iOS. Basically, you have to set the delegate as your current view controller ( yourtextfield.delegate = self) and implement required functions (textViewDidChange in this case) – Beninho85 Aug 15 '17 at 19:40
-
@Beninho85, thanks for your time. Yeah, got a hold of delegates. Well, my question should have been more detailed. I use a placeholder in a `UITextView`. And this adheres to auto layout. So, I would like to centre the placeholder vertically, in all views. But, if text is entered by the user, the placeholder is replaced, and the alignment should revert back to use the full area available. The vertical alignment is only for the placeholder. Does it make sense? – Shyam Aug 16 '17 at 04:23
-
Yes, it's because my solution uses the Content Offset property of the textfield to adjust the current text to the middle of the UITextview each time the user types something. If you want to center the placeholder only, just call adjustContentSize function once after setting your placeholder text. I'm not sure about the behaviour after that, but normally you don't need the delegate because it's used only for adjust text when typed. If it doesn't work, just try to reset the offset at 0 or something like that in the textviewdidchange property. – Beninho85 Aug 16 '17 at 15:05
-
For iOS 9.0.2. we'll need to set the contentInset instead. If we KVO the contentOffset, iOS 9.0.2 sets it to 0 at the last moment, overriding the changes to 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];
}
I used 0,0, and 0 for the left,bottom and right edge insets respectively. Make sure to calculate those as well for your use case.

- 498
- 5
- 9
-
1Thanks!, it works but had to use `setTextContainerInset` instead of `setContentInset` – nnarayann Feb 18 '16 at 12:34
It is the simple task using NSLayoutManager
to get real text size of the 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)
}
}
Don't use contentSize
and contentInset
in your final calculations.

- 9,874
- 3
- 56
- 77
-
This works great. Make sure you set correct class on the xib identity inspector. https://d.pr/i/hLQe9C – Nafiz Aug 19 '21 at 05:22
Here's a UITextView
extension that centers content vertically:
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
}
}

- 31,030
- 13
- 103
- 118
-
Does this need to be called after the view has been laid out, I imagine? – Jake T. Mar 29 '18 at 21:54
-
The max should be calculated between 0 and topOffset because 1 might cause the clipping of text from bottom in case the contentSize is same as the textView size. – schinj Apr 03 '18 at 09:32
-
You can set it up directly with only constraints:
There are 3 constraints i added to align text vertically and horizontally in constraints as below :
- Make height 0 and add constraints greater than
- Add vertically align to parent constraints
- Add horizontally align to parent constraints
-
1
-
2This worked for me, however adding these constraints xcode will throw an error that there is some ambiguity. To silence it I added top and bottom constraints equal to 0 with 250 as priority. – rgkobashi Dec 26 '18 at 06:56
I just created a custom vertically centered text view in 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)
}
}
}
Ref: https://geek-is-stupid.github.io/2017-05-15-how-to-center-text-vertically-in-a-uitextview/

- 8,530
- 5
- 41
- 34
-
Didn't work....this callback gets called 5-6 times per one screen appearance, and the calculations are not identical each time....it's not good at all please don't use – Radu Jun 15 '18 at 06:01
-
This is a really elegant solution. It worked perfectly for a UIViewRepresentable in SwiftUI that responds to a MagnificationGesture changing font size dynamically. Bravo! – codewithfeeling Oct 08 '19 at 10:20
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)
}

- 1,596
- 16
- 13
I have a textview that I'm using with autolayout and with setting the lineFragmentPadding
and textContainerInset
to zero. None of the solutions above worked in my situation. However, this works for me. Tested with 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

- 9,058
- 2
- 15
- 15
-
-
@user3777977 Just copy your answer here. Add to Carlos answer, just in case you have text in tv bigger then tv size you don't need to recenter text, so change this code: `tv.contentOffset = (CGPoint){.x = 0, .y = -topCorrect};` to this: `if ([tv contentSize].height < [tv bounds].size.height) { tv.contentOffset = (CGPoint){.x = 0, .y = -topCorrect}; } ` – iWill Aug 02 '19 at 06:52
I have also this problem and I solved it with a UITableViewCell
with UITextView
. I created method in a custom UITableViewCell
subclass, 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 };
And call this method in methods:
- (void)textViewDidBeginEditing:(UITextView *)textView
- (void)textViewDidEndEditing:(UITextView *)textView
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
This solution worked for me with no issues, you can try it.

- 6,792
- 7
- 41
- 81

- 79
- 1
- 5
Swift 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)
}

- 673
- 6
- 22
Add to Carlos answer, just in case you have text in tv bigger then tv size you don't need to recenter text, so change this code:
tv.contentOffset = (CGPoint){.x = 0, .y = -topCorrect};
to this:
if ([tv contentSize].height < [tv bounds].size.height) {
tv.contentOffset = (CGPoint){.x = 0, .y = -topCorrect};
}

- 29
- 1
Auto-layout solution:
- Create a UIView that acts as a container for the UITextView.
- Add the following constraints:
- TextView: Align leading space to: Container
- TextView: Align trailing space to: Container
- TextView: Align center Y to: Container
- TextView: Equal Height to: Container, Relation: ≤

- 17,014
- 17
- 89
- 148
-
1Unfortunately it does not work for me. Even these four constraints are not enough. Can I somehow set the textField to 'stick around' the text content? – Balazs Nemeth Mar 14 '15 at 18:13
-
What happens if you put the container on the same level as the text view in the hierarchy? - So, instead of it acting as an actual container, it's just a placeholder located at the same location. – Etan Mar 14 '15 at 19:10
-
This solution also requires auto-resizeable text view - http://stackoverflow.com/questions/16868117/uitextview-that-expands-to-text-using-auto-layout – Dmitry Nov 24 '16 at 16:33
-
You can try below code, no observer mandatorily required. observer throws error sometimes when view deallocates. You can keep this code in viewDidLoad, viewWillAppear or in viewDidAppear anywhere.
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};
});
});

- 6,362
- 1
- 36
- 52
-
I tried to remove the observer too in viewWillDisappear & viewDidDisappear, then also it's crashing sometimes. – Kiran Jasvanee Jul 27 '15 at 12:35
-
Rather than setting observer, this process is simple and flexible to keep anywhere. – Kiran Jasvanee Jul 27 '15 at 12:38
-
You can remove the observer in the `dealloc` method, `-(void)dealloc {}` – sbarow Jul 27 '15 at 12:50
-
-
The dealloc method is allowed in ARC projects, just don't call `[super dealloc];` – sbarow Jul 27 '15 at 13:07
-
I did it like this: first of all, I embedded the UITextView in an UIView (this should work for mac OS too). Then I pinned all four sides of the external UIView to the sides of its container, giving it a shape and size similar or equal to that of the UITextView. Thus I had a proper container for the UITextView. Then I pinned the left and right borders of the UITextView to the sides of the UIView, and gave the UITextView a height. Finally, I centered the UITextView vertically in the UIView. Bingo :) now the UITextView is vertically centered in the UIView, hence text inside the UITextView is vertically centered too.

- 51
- 1
- 3
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

- 68,471
- 58
- 283
- 421
I fixed this problem by creating extension to center height vertically.
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
}
}

- 981
- 11
- 20
Solution for iOS10 in RubyMotion:
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

- 1,049
- 11
- 13