Як втратити маржу / прокладку в UITextView?


486

У мене є UITextViewдодаток для iOS, який відображає велику кількість тексту.

Потім я підкачую цей текст, використовуючи параметр зміщення границі UITextView.

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

Тому я задаю питання: чи можна видалити прокладки, що оточують вміст UITextView?

З нетерпінням чекаємо Ваших відповідей!


9
Зауважте, що цьому QA майже десять років! Маючи 100 000+ переглядів, оскільки це одна з найглупіших проблем в iOS . Тільки FTR я поставив у нинішньому 2017 році досить просте / звичайне / прийняте рішення як відповідь.
Fattie

1
Я все ще отримую оновлення з цього приводу, написавши жорсткий спосіб вирішення в 2009 році, коли IOS 3.0 щойно вийшов. Я щойно відредагував відповідь, щоб чітко сказати, що минули роки, і проігнорувати прийнятий статус.
Майкл

Відповіді:


383

Актуальний для 2019 року

Це одна з найрозумніших помилок в iOS.

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

Ось клас:

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

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

  1. Рішення працює належним чином у раскадровці

  2. Рішення працює належним чином під час виконання

Ось і все, ви закінчили.

Взагалі, це повинно бути все, що вам потрібно в більшості випадків .

Навіть якщо ви змінюєте висоту перегляду тексту на льоту , UITextViewFixedзазвичай це все, що вам потрібно.

(Поширений приклад зміни висоти на льоту - це зміна її як тип користувача).

Ось зламаний UITextView від Apple ...

скріншот IB з UITextView

Ось UITextViewFixed:

скріншот IB з UITextViewFixed

Зауважте, що, звичайно, потрібно

вимкніть прокруткуВключено в Інспекторі!

Не забудьте вимкнути прокруткуEnabled! :)


Деякі подальші питання

(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 не страждає від проблем, описаних на цій сторінці якості. Насправді причина, з якої ми зазвичай "відмовляємося" і просто використовуємо UITextView, це те, що з UILabel важко працювати. Зокрема, смішно важко просто додати оббивки , правильно, до UILabel. Насправді тут повна дискусія про те, як "нарешті" правильно додати прокладку до UILabel: https://stackoverflow.com/a/58876988/294884 У деяких випадках, якщо ви робите складну компоновку з осередками динамічної висоти, це іноді краще зробити це важким шляхом з UILabel.


1
Я self.textView.isScrollEnabled = falseвсередині viewDidLayoutSubviews()і це теж працювало. Apple просто любить змусити нас стрибати через обручі :)
AnBisw

1
Найкраще рішення для виправлення всіх моїх абсурдних помилок автоматичного розкладу UITextView на моїх клітинах, заголовку та колонтитулі UITableView з динамічною висотою (UITableViewAutomaticDimension). Чудово!
Пітер Крейнц

1
Я опублікував оновлену відповідь на відповідь @ Fattie, яка допомогла мені справді позбутися всіх вставних даних, використовуючи спеціальний трюк включення / відключення translatesAutoresizingMaskIntoConstraints. Тільки за допомогою цієї відповіді я не зміг видалити всі поля (якийсь дивний нижній край протистояв відходу) в ієрархії перегляду за допомогою автоматичного макета. Він також займається розрахунком розмірів перегляду, використовуючи systemLayoutSizeFitting, який раніше повертав недійсний розмір через UITextView
помилку

1
1up для IBDesignable. Я б також підтримав дуже вдало вибраний текст власника місць, якби тільки міг
Тостор

1
У мене були перегляди тексту в таблиці, ті, у яких був один рядок тексту, не правильно обчислили його висоту (авторозкладка). Щоб виправити це, мені довелося також перекрити didMoveToSuperview і налаштування викликів.
Ель Жахливий

791

Для iOS 7.0 я виявив, що трюк contentInset більше не працює. Це код, який я використовував для позбавлення від поля / padding в iOS 7.

Це приводить лівий край тексту до лівого краю контейнера:

textView.textContainer.lineFragmentPadding = 0

Це призводить до вирівнювання верхньої частини тексту з вершиною контейнера

textView.textContainerInset = .zero

Обидві лінії потрібні для повного видалення поля / прокладки.


2
Це працює для мене на два рядки, але до моменту, коли я дістаюсь до 5 рядків, текст стає відрізаним.
livings124

7
Зауважте, що другий рядок також можна записати як: self.descriptionTextView.textContainerInset = UIEdgeInsetsZero;
jessepinho

19
Встановлення lineFragmentPadding0 - це магія, яку я шукав. Я поняття не маю, чому Apple ускладнює лінійку вмісту UITextView з іншими елементами управління.
phatmann

3
Це правильна відповідь. Прокручування по горизонталі з цим рішенням не відбувається.
Майкл

2
lineFragmentPaddingне призначений для зміни поля. Накладки з фрагментів рядка
Мартін Бергер

256

Це рішення було написано в 2009 році, коли вийшов IOS 3.0. Це більше не застосовується.

Я зіткнувся з точно такою самою проблемою, врешті-решт мені довелося перестати користуватися

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

де nameField - це UITextView . Шрифт, яким я випадково користувався, був Helvetica 16 бал. Єдине спеціальне рішення для конкретного розміру поля, який я малював. Це змушує ліве зміщення врівень з лівою стороною, а верхнє зміщення там, де я хочу, щоб це поле було намальовано.

Крім того, це, здається, стосується лише того місця, UITextViewsде ви використовуєте фрагмент за замовчуванням, тобто.

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
UOSextAlignmentLeft застарілий у iOS 7. Використовуйте NSTextAlignmentLeft.
Джорді Крун

7
Я не розумію, чому люди підтримують відповідь, яка використовує жорстко закодовані значення. ДУЖЕ, ймовірно, зламається в майбутніх версіях iOS, і це просто неприємна ідея.
ldoogy

77

Виправляючи деякі добрі відповіді, вже подані, ось суто рішення на основі Storyboard / Interface Builder, яке працює в iOS 7.0+

Встановіть визначені користувачем атрибути часу виконання UITextView для таких клавіш:

textContainer.lineFragmentPadding
textContainerInset

Інтерфейс Builder


4
Це, безумовно, найкраща відповідь, особливо якщо вам потрібно лише рішення
XIB

16
Копіювати / вставляти: textContainer.lineFragmentPadding | textContainerInset
Лука Де Анджеліс

3
Це те, що дає гарну відповідь - легко і добре працює навіть через 3 роки :)
Шай Мішалі


41

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

Ось відповідь @ user1687195, написана без зміни textContainer.lineFragmentPadding(оскільки в документах зазначено, що це не призначене використання).

Це чудово підходить для iOS 7 та новіших версій.

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

Це фактично той самий результат, лише трохи чистіший, оскільки він не зловживає властивістю lineFragmentPadding.


Я спробував інше рішення, засноване на цій відповіді, і воно чудово працює. Рішення:self.textView.textContainer.lineFragmentPadding = 0;
Бакит Абдрасулов

1
@BakytAbdrasulov, будь ласка, прочитайте мою відповідь. Хоча ваше рішення працює, воно не відповідає документам (див. Посилання у моїй відповіді). lineFragmentPaddingне призначений для контролю над полями. Ось чому ви повинні використовувати textContainerInset.
ldoogy

20

Рішення для розробників розкадрувань або інтерфейсу, використовуючи визначені користувачем атрибути виконання :

Знімки екрана є iOS 7.1 та iOS 6.1 с contentInset = {{-10, -5}, {0, 0}}.

Визначені користувачем атрибути виконання

вихід


1
Працював для мене в iOS 7.
kubilay

Вгору лише для визначених користувачем атрибутів часу виконання - приголомшливо!
hris.to

16

Усі ці відповіді стосуються заголовного питання, але я хотів запропонувати деякі рішення проблем, представлених в тілі питання ОП.

Розмір текстового вмісту

Швидкий спосіб обчислити розмір тексту всередині UITextView: використання NSLayoutManager:

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

Це дає загальний вміст прокрутки, який може бути більшим, ніж UITextViewкадр 's. Я вважав, що це набагато точніше, ніж 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

Користуючись усіма цими методами, я не торкався своїх вставки і мені вдалося перейти до карети чи кудись у тексті, який я хочу.


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 Simulator
Simon Warta

10

Останній Swift:

self.textView.textContainerInset = .init(top: -2, left: 0, bottom: 0, right: 0)
self.textView.textContainer.lineFragmentPadding = 0

Чудова відповідь. Ця відповідь видаляє додаткову верхню вставку. 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 .

Тож я читаю тут на ТАК, що включення та відключення translatesAutoresizingMaskIntoConstraintsдопомагає при встановленні кадру / межі вручну між дзвінками.

На щастя, це працює не тільки з налаштуванням кадру, але і з двома лініями, setup()просоченими між нимиtranslatesAutoresizingMaskIntoConstraints дзвінками!

Це, наприклад, дуже корисно при обчисленні кадру перегляду за допомогою параметра systemLayoutSizeFittingaUIView . Повертає правильний розмір (який раніше він не робив)!

Як і в згадуваній оригінальній відповіді:
Не забудьте вимкнути прокрутку, включену в Інспекторі!
Це рішення працює належним чином у раскадровці, а також під час виконання.

Ось так, тепер ти справді зробив!


5

Для швидкого 4, Xcode 9

Використовуйте наступну функцію, щоб змінити поле / прокладку тексту в UITextView

public func UIEdgeInsetsMake (_ вгорі: CGFloat, _ зліва: CGFloat, _ внизу: CGFloat, _ праворуч: CGFloat) -> UIEdgeInsets

тому в цьому випадку є

 self.textView?.textContainerInset = UIEdgeInsetsMake(0, 0, 0, 0)

3

Виконуючи вставне рішення, я все ще мав прокладки з правого боку і знизу. Також вирівнювання тексту викликало проблеми. Єдиний впевнений спосіб пожежі, який я знайшов, - це помістити текстовий вигляд всередині іншого виду, який вирізаний до меж.


3

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

Примітка. Інтерфейс Builder все ще відображатиме старий запас, але ваш додаток працюватиме, як очікувалося.

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);
   }
}

1

Для мене (iOS 11 та Xcode 9.4.1) те, що магічним чином спрацювало, було налаштувати властивість textView.font на UIFont.preferred(forTextStyle:UIFontTextStyle) стиль, а також першу відповідь, як згадував @Fattie. Але відповідь @Fattie не спрацювала, поки я не встановив властивість textView.font else UITextView продовжує поводитися помилково.


1

У випадку, якщо хтось шукає останню версію Swift, тоді нижче код добре працює з Xcode 10.2 та Swift 4.2

yourTextView.textContainerInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)

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);

Чомусь я цього ще не зрозумів, курсор все ще не зосереджений до початку введення тексту, але текст центрирується одразу, коли я починаю вводити текст.


0

Якщо ви хочете встановити рядок HTML і уникнути нижньої прокладки, переконайтеся, що ви не використовуєте теги блоків, тобто div, p.

У моєму випадку це було причиною. Ви можете легко перевірити це, замінивши входження тегів блоків на, наприклад, тег span.


0

Для SwiftUI

Якщо ви створюєте свій власний TextView за допомогою UIViewRepresentableта хочете контролювати прокладку, у своїй makeUIViewфункції просто виконайте:

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

або що завгодно.


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.