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

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

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

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

25 ответов

Решение

На 2018 год

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

Класс UITextViewFixed это обычно разумное решение.

Вот класс:

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

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

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

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

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

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

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

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

Вот сломанный 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 "space at 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! Может быть необходимо добавить это в будущем.

Дело в том, что UITextView полностью сломан в iOS - одна из самых странных вещей во всех мобильных вычислениях.

Наконец, вот несколько похожий совет для текстовогополя: /questions/17427348/ustanovite-maksimalnuyu-dlinu-simvola-uitextfield-v-swift/17427361#17427361

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

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

textView.textContainer.lineFragmentPadding = 0

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

textView.textContainerInset = .zero

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

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

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

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

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

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

nameField.textAlignment = NSTextAlignmentLeft;

Выровняйте направо, например, и UIEdgeInsetsMake кажется, не оказывает никакого влияния на правый край вообще.

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

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

На iOS 5 UIEdgeInsetsMake(-8,-8,-8,-8); Кажется, работает отлично.

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

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

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

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

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

Решение Storyboard или Interface Builder с использованием пользовательских атрибутов времени выполнения.Обновить. Добавлены скриншоты iOS7.1 и iOS6.1 с contentInset = {{-10, -5}, {0, 0}}

Ты можешь использовать textContainerInset собственностью UITextView:

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

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

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

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

Быстрый способ рассчитать размер текста внутри 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

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

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

@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 звонки!

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

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

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

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

self.textView.textContainerInset = .init(сверху: -2, слева: 0, снизу: 0, справа: 0) self.textView.textContainer.lineFragmentPadding = 0

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

captionTextView.textContainerInset = UIEdgeInsetsMake(0, 0, 0, 0)

Для swift 4, Xcode 9

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

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

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

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

Для SwiftUI

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

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

или как хотите.

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

Вот небольшое простое расширение, которое удалит марку 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);
   }
}

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

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

yourTextView.textContainerInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 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);
}

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

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

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

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

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

Для тех, кто приходит к этому из-за синтаксического анализа HTML в UITextView, просто удалите новую строку, введенную закрывающим тегом p "/ n". Я предполагаю, что вы работаете с NSMutableAttributedString:

      if let lastCharacter = attributedString.string.last, lastCharacter == "\n" {
        attributedString.deleteCharacters(in: NSRange(location:(attributedString.length) - 1,length:1))
    }

Вам нужно установить inset и lineFragmentPadding на didMoveToSuperView

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

Для Swift 11.7

Написание следующего кода сработало для меня.

          textView.textContainer.lineFragmentPadding = CGFloat(0.0)
    textView.textContainerInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
    textView.contentInset = UIEdgeInsets(top: 0,left: 0,bottom: 0,right: 0)

textContainerInset : можно установить размер текстового контейнера и представления, а также поведение изменения размера так, чтобы вставка не могла поддерживаться точно, хотя текстовая система пытается сохранить вставку везде, где это возможно. В любом случае textContainerOrigin и размер текстового контейнера имеют решающее значение в отношении местоположения текстового контейнера в представлении. Сам текст может иметь дополнительную вставку внутри текстового контейнера, заданного методом lineFragmentPadding NSTextContainer.

[firstNameTextField setContentVerticalAlignment:UIControlContentVerticalAlignmentCenter];
Другие вопросы по тегам