Как потерять поля / отступы в UITextView?

488

У меня есть UITextViewв моем приложении iOS, которое отображает большое количество текста.

Затем я разбил этот текст на страницы с помощью параметра смещения поля UITextView.

Моя проблема в том, что отступы UITextViewзапутывают мои расчеты, так как они кажутся разными в зависимости от размера шрифта и используемого шрифта.

Поэтому я задаю вопрос: возможно ли удалить отступы, окружающие содержимое UITextView?

С нетерпением ждем ваших ответов!

sniurkst
источник
9
Обратите внимание, что этому QA почти десять лет! С более чем 100 000 просмотров, так как это одна из самых глупых проблем в iOS . Просто FTR я положил в текущем, 2017 году, достаточно простое / обычное / принятое решение ниже в качестве ответа.
Толстяк
1
Я все еще получаю обновления об этом, написав жестко закодированный обходной путь в 2009 году, когда только что вышла IOS 3.0. Я только что отредактировал ответ, чтобы четко заявить, что он устарел, и игнорировать принятый статус.
Майкл

Ответы:

384

Обновленный на 2019 год

Это одна из самых глупых ошибок в iOS.

Класс, данный здесь, UITextViewFixedобычно является наиболее разумным решением в целом.

Вот класс:

@IBDesignable class UITextViewFixed: UITextView {
    override func layoutSubviews() {
        super.layoutSubviews()
        setup()
    }
    func setup() {
        textContainerInset = UIEdgeInsets.zero
        textContainer.lineFragmentPadding = 0
    }
}

Не забудьте отключить scrollEnabled в Инспекторе!

  1. Решение работает правильно в раскадровке

  2. Решение работает правильно во время выполнения

Вот и все, вы сделали.

В общем, это должно быть все, что вам нужно в большинстве случаев .

Даже если вы изменяете высоту текстового представления на лету , UITextViewFixedобычно делает все, что вам нужно.

(Типичным примером изменения высоты на лету является изменение ее по типу пользователя.)

Вот сломанный UITextView от Apple ...

скриншот IB с UITextView

Вот UITextViewFixed:

снимок экрана IB с UITextViewFixed

Обратите внимание, что, конечно, вы должны

отключите scrollEnabled в Инспекторе!

Не забудьте отключить scrollEnabled! :)


Некоторые дальнейшие проблемы

(1) В некоторых необычных случаях - например, в некоторых случаях таблиц с гибкими, динамически изменяющимися высотами ячеек - Apple делает странную вещь: они добавляют дополнительное пространство внизу . Нет, правда! Это должно быть одной из самых ярких вещей в iOS.

Вот «быстрое решение», которое можно добавить к вышесказанному, что обычно помогает с этим безумием.

...
        textContainerInset = UIEdgeInsets.zero
        textContainer.lineFragmentPadding = 0

        // this is not ideal, but you can sometimes use this
        // to fix the "extra space at the bottom" insanity
        var b = bounds
        let h = sizeThatFits(CGSize(
           width: bounds.size.width,
           height: CGFloat.greatestFiniteMagnitude)
       ).height
       b.size.height = h
       bounds = b
 ...

(2) Иногда, чтобы исправить еще одну тонкую ошибку Apple, вы должны добавить это:

override func setContentOffset(_ contentOffset: CGPoint, animated: Bool) {
    super.setContentOffset(contentOffset, animated: false)
}

(3) Возможно, мы должны добавить:

contentInset = UIEdgeInsets.zero

только после того, как .lineFragmentPadding = 0в UITextViewFixed.

Однако ... верьте или нет ... это просто не работает в текущей iOS! (Проверено в 2019.) Возможно, потребуется добавить эту строку в будущем.

Тот факт, что UITextViewэто не работает в iOS, является одной из самых странных вещей во всех мобильных вычислениях. Десятилетие этого вопроса, и он до сих пор не решен!

Наконец, вот несколько похожий совет для текстового поля : https://stackoverflow.com/a/43099816/294884

Совершенно случайный совет: как добавить «...» в конец

Часто вы используете UITextView "как UILabel". Таким образом, вы хотите усечь текст с помощью многоточия "..."

Если это так, добавьте третью строку кода в «настройку»:

 textContainer.lineBreakMode = .byTruncatingTail

Полезный совет, если вы хотите нулевую высоту, когда нет текста вообще

Часто вы используете текстовое представление только для отображения текста. Таким образом, пользователь не может ничего редактировать. Вы используете строки "0", чтобы обозначить, что текстовое представление автоматически изменит высоту в зависимости от того, сколько строк текста.

Это здорово, но если текста нет вообще, то, к сожалению, вы получите ту же высоту, как если бы была одна строка текста !! Текстовое представление никогда не «уходит».

введите описание изображения здесь

Если вы хотите, чтобы он "ушел", просто добавьте это

override var intrinsicContentSize: CGSize {
    var i = super.intrinsicContentSize
    print("for \(text) size will be \(i)")
    if text == "" { i.height = 1.0 }
    print("   but we changed it to \(i)")
    return i
}

введите описание изображения здесь

(Я сделал это «1», поэтому ясно, что происходит, «0» - это хорошо.)

А как насчет UILabel?

Просто отображая текст, UILabel имеет много преимуществ по сравнению с UITextView. UILabel не страдает от проблем, описанных на этой странице QA. Действительно, причина, по которой мы все обычно «сдаемся» и просто используем UITextView, заключается в том, что с UILabel трудно работать. В частности, до смешного трудно просто добавить отступы в UILabel. На самом деле, здесь подробно обсуждается, как «наконец» правильно добавить заполнение в UILabel: https://stackoverflow.com/a/58876988/294884 В некоторых случаях, если вы делаете сложный макет с динамическими ячейками высоты, иногда лучше сделать это нелегко с UILabel.

Fattie
источник
1
Я сделал self.textView.isScrollEnabled = falseвнутри, viewDidLayoutSubviews()и это тоже сработало. Apple просто нравится заставлять нас прыгать через обручи :)
AnBisw
1
Лучшее решение для исправления всех моих абсурдных ошибок автоматического размещения UITextView в моих ячейках, верхнем и нижнем колонтитулах UITableView с динамической высотой (UITableViewAutomaticDimension). Большой!
Питер Крейнз
1
Я опубликовал обновленный ответ на ответ @ Fattie, который помог мне действительно избавиться от всех вставок, используя специальный прием включения / выключения translatesAutoresizingMaskIntoConstraints. Только с этим ответом я не смог удалить все поля (какой-то странный нижний отступ, чтобы не уходить) в иерархии представления, используя автоматическое расположение. Он также имеет дело с вычислением размеров представлений systemLayoutSizeFitting, которые ранее возвращали неверный размер из-за ошибкиUITextView
Fab1n
1
1up для IBDesignable. Я бы также поддержал очень хорошо выбранный текст заполнителя, если бы я только мог
Тостор
1
У меня были текстовые представления в таблице, те, у которых была одна строка текста, неправильно вычисляли его высоту (autolayout). Чтобы это исправить, мне пришлось переопределить didMoveToSuperview и вызвать там настройку.
Эль Ужасный
792

Для iOS 7.0 я обнаружил, что трюк contentInset больше не работает. Это код, который я использовал, чтобы избавиться от полей / отступов в iOS 7.

Это приводит левый край текста к левому краю контейнера:

textView.textContainer.lineFragmentPadding = 0

Это приводит к тому, что верх текста выравнивается по верху контейнера.

textView.textContainerInset = .zero

Обе строки необходимы для полного удаления полей / отступов.

user1687195
источник
2
Это работает для меня на две строки, но к тому времени, когда я получаю 5 строк, текст обрезается.
livings124
7
Обратите внимание, что вторая строка также может быть записана как: self.descriptionTextView.textContainerInset = UIEdgeInsetsZero;
jessepinho
19
Установка lineFragmentPaddingв 0 это магия, которую я искал. Я понятия не имею, почему Apple так сложно выровнять контент UITextView с другими элементами управления.
phatmann
3
Это правильный ответ. Горизонтальная прокрутка не происходит с этим решением.
Майкл
2
lineFragmentPaddingне предназначен для изменения полей. Из документов Заполнение фрагмента строки не предназначено для выражения текстовых полей. Вместо этого вы должны использовать вставки в текстовом представлении, настроить атрибуты полей абзаца или изменить положение текстового представления в его суперпредставлении.
Мартин Бергер
256

Этот обходной путь был написан в 2009 году, когда была выпущена IOS 3.0. Это больше не применяется.

Я столкнулся с точно такой же проблемой, в конце концов мне пришлось закончить с помощью

nameField.contentInset = UIEdgeInsetsMake(-4,-8,0,0);

где nameField является UITextView. Я использовал шрифт Helvetica 16 пунктов. Это единственное нестандартное решение для конкретного размера поля, которое я рисовал. Это делает левое смещение заподлицо с левой стороной, а верхнее смещение там, где я хочу, для поля, в котором оно нарисовано.

Кроме того, это, кажется, относится только к тому, UITextViewsгде вы используете aligment по умолчанию, т.е.

nameField.textAlignment = NSTextAlignmentLeft;

Например UIEdgeInsetsMake, выровняйте по правому краю, и кажется, что оно никак не влияет на правый край.

По крайней мере, использование свойства .contentInset позволяет размещать поля с «правильными» позициями и учитывать отклонения, не компенсируя ваши UITextViews.

Майкл
источник
1
Да, он работает в iOS 7. Убедитесь, что для UIEdgeInset заданы правильные значения. Может отличаться от UIEdgeInsetsMake (-4, -8,0,0).
app_
15
В IOS 7 я обнаружил, что UIEdgeInsetsMake (0, -4,0, -4) работает лучше всего.
Ракура
2
Да, это примеры номеров. Я сказал: «Это только индивидуальное решение для конкретного размера поля, которое я рисовал». В основном я пытался проиллюстрировать, что вы должны играть с числами для вашей уникальной ситуации макета.
Майкл
3
UITextAlignmentLeft устарела в iOS 7. Используйте NSTextAlignmentLeft.
Хорди Крон
7
Я не понимаю, почему люди высказывают ответ, который использует жестко закодированные ценности. Это ОЧЕНЬ вероятно сломается в будущих версиях iOS, и это просто плохая идея.
ldoogy
77

Опираясь на некоторые хорошие ответы, которые уже даны, мы предлагаем решение на основе исключительно Storyboard / Interface Builder, которое работает в iOS 7.0+.

Установите пользовательские атрибуты времени выполнения UITextView для следующих ключей:

textContainer.lineFragmentPadding
textContainerInset

Интерфейсный Разработчик

azdev
источник
4
Это, безусловно, лучший ответ, особенно если вам нужно решение только для
XIB
16
Копировать / вставить: textContainer.lineFragmentPadding | textContainerInset
Лука Де Анжелис
3
Это то, что делает красивый ответ - легко и хорошо работает, даже после 3 лет :)
Shai Mishali
46

На iOS 5 UIEdgeInsetsMake(-8,-8,-8,-8);вроде бы отлично работает.

Энгин Курутепе
источник
41

Я бы определенно избегал любых ответов, связанных с жестко заданными значениями, поскольку фактические поля могут изменяться в зависимости от настроек размера шрифта пользователя и т. Д.

Вот ответ @ user1687195, написанный без изменения textContainer.lineFragmentPadding(поскольку в документе указано, что это не является предполагаемым использованием).

Это прекрасно работает для iOS 7 и более поздних версий.

self.textView.textContainerInset = UIEdgeInsetsMake(
                                      0, 
                                      -self.textView.textContainer.lineFragmentPadding, 
                                      0, 
                                      -self.textView.textContainer.lineFragmentPadding);

Это фактически тот же результат, только немного чище, поскольку он не использует свойство lineFragmentPadding.

ldoogy
источник
Я попробовал другое решение, основанное на этом ответе, и оно прекрасно работает. Решения:self.textView.textContainer.lineFragmentPadding = 0;
Бакыт Абдрасулов
1
@ BakytAbdrasulov, пожалуйста, прочитайте мой ответ. Хотя ваше решение работает, оно не соответствует документации (см. Ссылку в моем ответе). lineFragmentPaddingне предназначен для контроля наценок. Вот почему вы должны использовать textContainerInset.
ldoogy
21

Решение Storyboard или Interface Builder с использованием пользовательских атрибутов времени выполнения :

Скриншоты из iOS 7.1 и iOS 6.1 с contentInset = {{-10, -5}, {0, 0}}.

Определяемые пользователем атрибуты времени выполнения

вывод

Владимир
источник
1
Работал у меня в iOS 7.
Кубилай
Только для пользовательских атрибутов времени выполнения - потрясающе!
hris.to
16

Все эти ответы касаются заглавного вопроса, но я хотел предложить некоторые решения проблем, представленных в основной части вопроса ОП.

Размер текстового содержимого

Быстрый способ рассчитать размер текста внутри UITextView- это использовать NSLayoutManager:

UITextView *textView;
CGSize textSize = [textView usedRectForTextContainer:textView.textContainer].size;

Это дает общий прокручиваемый контент, который может быть больше, чем UITextViewфрейм. Я обнаружил, что это гораздо точнее, чем textView.contentSizeвычисление того, сколько места занимает текст. Например, дано пустое UITextView:

textView.frame.size = (width=246, height=50)
textSize = (width=10, height=16.701999999999998)
textView.contentSize = (width=246, height=33)
textView.textContainerInset = (top=8, left=0, bottom=8, right=0)

Высота линии

UIFontимеет свойство, которое позволяет быстро получить высоту строки для данного шрифта. Таким образом, вы можете быстро найти высоту строки текста в вашем UITextView:

UITextView *textView;
CGFloat lineHeight = textView.font.lineHeight;

Расчет видимого размера текста

Определение объема фактически видимого текста важно для обработки эффекта «подкачки». UITextViewимеет свойство с именем, textContainerInsetкоторое на самом деле является границей между фактическим UITextView.frameи самим текстом. Для расчета реальной высоты видимой рамки вы можете выполнить следующие расчеты:

UITextView *textView;
CGFloat textViewHeight = textView.frame.size.height;
UIEdgeInsets textInsets = textView.textContainerInset;
CGFloat textHeight = textViewHeight - textInsets.top - textInsets.bottom;

Определение размера подкачки

Наконец, теперь, когда у вас есть видимый размер текста и содержание, вы можете быстро определить, какими должны быть ваши смещения, вычитая textHeightиз textSize:

// where n is the page number you want
CGFloat pageOffsetY = textSize - textHeight * (n - 1);
textView.contentOffset = CGPointMake(textView.contentOffset.x, pageOffsetY);

// examples
CGFloat page1Offset = 0;
CGFloat page2Offset = textSize - textHeight
CGFloat page3Offset = textSize - textHeight * 2

Используя все эти методы, я не трогал свои вкладыши и мог перейти к каретке или куда угодно в тексте, который я хочу.

mikeho
источник
12

Вы можете использовать textContainerInsetсвойство UITextView:

textView.textContainerInset = UIEdgeInsetsMake (10, 10, 10, 10);

(верхний левый нижний правый)

Химаншу Падия
источник
1
Интересно, почему это не назначено лучшим ответом здесь. TextContainerInset действительно удовлетворил мои потребности.
KoreanXcodeWorker
Обновление нижнего значения textContainerInsetсвойства не работает для меня, когда я хочу изменить его на новое значение - см. Stackoverflow.com/questions/19422578/… .
Эван Р
@MaggiePhillips Это не лучший ответ, потому что жестко закодированные значения не могут быть правильным способом сделать это, и в некоторых случаях гарантированно потерпят неудачу. Вы должны принять во внимание lineFragmentPadding.
ldoogy
11

Для iOS 10 следующая строка работает для удаления верхнего и нижнего отступов.

captionTextView.textContainerInset = UIEdgeInsetsMake(0, 0, 0, 0)
Ансон Яо
источник
Xcode 8.2.1. все та же проблема, что упоминается в этом ответе. Моим решением было редактировать значения как UIEdgeInsets (верх: 0, слева: -4.0, снизу: 0, справа: -4.0).
Darkwonder
UIEdgeInset.zeroработает с использованием симулятора XCode 8.3 и iOS 10.3
Simon Warta
10

Последний Свифт:

self.textView.textContainerInset = .init(top: -2, left: 0, bottom: 0, right: 0)
self.textView.textContainer.lineFragmentPadding = 0
Урвиш Моди
источник
Отличный ответ. Этот ответ удаляет дополнительную вкладку Top. textView.textContainerInset = UIEdgeInsets.zero не удаляет 2 пикселя из верхней вставки.
korgx9
10

Вот обновленная версия очень полезного ответа Фэтти. Он добавляет 2 важные строки, которые помогли мне заставить работать макет на iOS 10 и 11 (и, вероятно, на более низких):

@IBDesignable class UITextViewFixed: UITextView {
    override func layoutSubviews() {
        super.layoutSubviews()
        setup()
    }
    func setup() {
        translatesAutoresizingMaskIntoConstraints = true
        textContainerInset = UIEdgeInsets.zero
        textContainer.lineFragmentPadding = 0
        translatesAutoresizingMaskIntoConstraints = false
    }
}

Важные строки - это два translatesAutoresizingMaskIntoConstraints = <true/false>утверждения!

Это удивительно удаляет все поля при всех моих обстоятельствах!

Хотя textViewреспондент не первый, может случиться так, что есть какой-то странный нижний предел, который не может быть решен с помощью sizeThatFitsметода, который упомянут в принятом ответе.

При нажатии на textView внезапно исчезло странное нижнее поле, и все выглядело так, как должно, но только после того, как textView получил firstResponder .

Итак, я прочитал здесь о SO, что включение и отключение translatesAutoresizingMaskIntoConstraintsдействительно помогает при ручной настройке фрейма / границ между вызовами.

К счастью, это работает не только с настройкой кадра, но и с двумя линиями, setup()расположенными между двумя translatesAutoresizingMaskIntoConstraintsвызовами!

Это, например, очень полезно при расчете фрейма вида с использованием systemLayoutSizeFittingaUIView . Возвращает правильный размер (чего раньше не было)!

Как упоминалось в оригинальном ответе:
не забудьте отключить scrollEnabled в Инспекторе!
Это решение работает правильно как в раскадровке, так и во время выполнения.

Вот и все, теперь вы действительно сделали!

Fab1n
источник
5

Для swift 4, Xcode 9

Используйте следующую функцию, чтобы изменить поле / отступ текста в UITextView.

public func UIEdgeInsetsMake (_ вверху: CGFloat, _ слева: CGFloat, _ внизу: CGFloat, _ справа: CGFloat) -> UIEdgeInsets

так что в этом случае

 self.textView?.textContainerInset = UIEdgeInsetsMake(0, 0, 0, 0)
Шань Йе
источник
3

Делая решение для врезки, у меня все еще была прокладка справа и снизу. Также выравнивание текста вызывало проблемы. Единственный надежный способ, который я нашел, - это поместить текстовое представление в другое представление, обрезанное до границ.

RP90
источник
3

Вот небольшое простое расширение, которое удалит поля Apple по умолчанию из каждого текстового представления в вашем приложении.

Примечание. Интерфейсный конструктор по-прежнему будет отображать старое поле, но ваше приложение будет работать так, как ожидается.

extension UITextView {

   open override func awakeFromNib() {
      super.awakeFromNib();
      removeMargins();
   }

   /** Removes the Apple textview margins. */
   public func removeMargins() {
      self.contentInset = UIEdgeInsetsMake(
         0, -textContainer.lineFragmentPadding,
         0, -textContainer.lineFragmentPadding);
   }
}
Cal S
источник
1

Для меня (iOS 11 и Xcode 9.4.1) волшебным образом было настроить свойство textView.font для UIFont.preferred(forTextStyle:UIFontTextStyle) стиля, а также первый ответ, упомянутый @Fattie. Но ответ @Fattie не сработал, пока я не установил свойство textView.font, иначе UITextView продолжает работать беспорядочно.

Никхил Пандей
источник
1

Если кто-то ищет последнюю версию Swift, то приведенный ниже код работает нормально с Xcode 10.2 и Swift 4.2

yourTextView.textContainerInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
Bhavin_m
источник
0

Я нашел еще один подход: получить представление с текстом из подпредставлений UITextView и настроить его в методе layoutSubview подкласса:

- (void)layoutSubviews {
    [super layoutSubviews];

    const int textViewIndex = 1;
    UIView *textView = [self.subviews objectAtIndex:textViewIndex];
    textView.frame = CGRectMake(
                                 kStatusViewContentOffset,
                                 0.0f,
                                 self.bounds.size.width - (2.0f * kStatusViewContentOffset),
                                 self.bounds.size.height - kStatusViewContentOffset);
}
Никита
источник
0

Прокрутка textView также влияет на положение текста и делает его не центрированным по вертикали. Мне удалось отцентрировать текст в представлении, отключив прокрутку и установив верхнюю вставку в 0:

    textView.scrollEnabled = NO;
    textView.textContainerInset = UIEdgeInsetsMake(0, textView.textContainerInset.left, textView.textContainerInset.bottom, textView.textContainerInset.right);

По какой-то причине я еще не понял, курсор все еще не центрирован до начала набора, но текст центрируется сразу же, как я начинаю печатать.

Alex
источник
0

Если вы хотите установить строку HTML и избежать нижнего заполнения, убедитесь, что вы не используете теги блока, т.е. div, p.

В моем случае это было причиной. Вы можете легко проверить это, заменив вхождения тегов блока, например, тегом span.

Давид Платек
источник
0

Для SwiftUI

Если вы делаете свой собственный TextView, используя UIViewRepresentableи хотите управлять заполнением, в своей makeUIViewфункции просто выполните:

uiTextView.textContainerInset = UIEdgeInsets(top: 10, left: 18, bottom: 0, right: 18)

или что хочешь.

П. Энт
источник
-4
[firstNameTextField setContentVerticalAlignment:UIControlContentVerticalAlignmentCenter];
to0nice4eva
источник