Центрировать текст по вертикали в UITextView
Я хочу центрировать текст вертикально внутри большого UITextView
он заполняет весь экран - поэтому, когда текста мало, скажем пару слов, он центрируется по высоте. Речь идет не о центрировании текста (свойство, которое можно найти в IB), а о том, чтобы поместить текст вертикально прямо в середину UITextView
если текст короткий, то в UITextView
, Можно ли это сделать? Заранее спасибо!
19 ответов
Сначала добавьте наблюдателя для 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};
}
Поскольку UIKit не является KVO-совместимым, я решил реализовать это как подкласс UITextView
который обновляется всякий раз, когда contentSize
изменения.
Это слегка измененная версия ответа Карлоса, которая устанавливает contentInset
вместо contentOffset
, В дополнение к совместимости с iOS 9, он также менее глючит на 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)
}
}
}
Если вы не хотите использовать 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)
}
Для iOS 9.0.2. вместо этого нам нужно установить contentInset. Если мы используем KVO для contentOffset, 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 для левой, нижней и правой кромок соответственно. Обязательно рассчитайте их и для вашего варианта использования.
Это простая задача с использованием
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)
}
}
Не используйте
contentSize
а также
contentInset
в ваших окончательных расчетах.
Вот 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
}
}
Вы можете установить его напрямую, только с ограничениями:
Есть 3 ограничения, которые я добавил, чтобы выровнять текст по вертикали и горизонтали в ограничениях, как показано ниже:
- Сделайте высоту 0 и добавьте ограничения больше
- Добавить выравнивание по вертикали к родительским ограничениям
- Добавить выравнивание по горизонтали к родительским ограничениям
Я только что создал пользовательский текст по вертикали в 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)
}
}
}
ссылка: http://geek-is-stupid.github.io/blog/2017/03/14/how-to-center-text-vertically-in-a-uitextview/
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)
}
У меня есть textview, который я использую с autolayout и с настройкой lineFragmentPadding
а также textContainerInset
в ноль. Ни одно из приведенных выше решений не сработало в моей ситуации. Тем не менее, это работает для меня. Протестировано с 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
Свифт 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)
}
У меня также есть эта проблема, и я решил ее с UITableViewCell
с UITextView
, Я создал метод в кастом UITableViewCell
подкласс, собственность 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
Это решение работало для меня без проблем, вы можете попробовать его.
Решение для автоматической разметки:
- Создайте UIView, который действует как контейнер для UITextView.
- Добавьте следующие ограничения:
- TextView: выравнивание начального пространства по: контейнеру
- TextView: выравнивание конечного пространства по: контейнеру
- TextView: выравнивание по центру Y: контейнер
- TextView: равная высота: контейнер, отношение: ≤
Добавьте к ответу Карлоса, на тот случай, если у вас текст в телевизоре больше, чем размер телевизора, вам не нужно вводить текст заново, поэтому измените этот код:
tv.contentOffset = (CGPoint){.x = 0, .y = -topCorrect};
к этому:
if ([tv contentSize].height < [tv bounds].size.height) {
tv.contentOffset = (CGPoint){.x = 0, .y = -topCorrect};
}
Вы можете попробовать код ниже, наблюдатель не обязательно. наблюдатель иногда выдает ошибку, когда вид освобождается. Вы можете сохранить этот код в 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};
});
});
Я сделал это так: во-первых, я встроил UITextView в UIView (это должно работать и для Mac OS). Затем я прикрепил все четыре стороны внешнего UIView к сторонам его контейнера, придав ему форму и размер, аналогичные или равные UITextView. Таким образом, у меня был правильный контейнер для UITextView. Затем я прикрепил левую и правую границы UITextView к сторонам UIView и дал UITextView высоту. Наконец, я центрировал UITextView вертикально в UIView. Бинго:) теперь UITextView вертикально центрирован в UIView, следовательно, текст внутри UITextView также центрирован вертикально.
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
Я исправил эту проблему, создав вертикальное расширение до высоты центра.
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
}
}
Решение для iOS10 в 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