it-swarm.com.ru

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

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

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

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

425
sniurkst

Правильно в актуальном состоянии на 2019 год

Это одна из самых глупых ошибок в 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 ...

screenshot of IB with UITextView

Вот UITextViewFixed:

screenshot of IB with 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! (Проверено в 2019.) Возможно, потребуется добавить эту строку в будущем.

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

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

248
Fattie

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

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

textView.textContainer.lineFragmentPadding = 0

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

textView.textContainerInset = .zero

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

751
user1687195

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

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

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

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

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

nameField.textAlignment = NSTextAlignmentLeft;

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

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

250
Michael

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

enter image description here

68
azdev

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

44
Engin Kurutepe

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

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

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

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

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

39
ldoogy

Решение Storyboard или Interface Builder, использующее пользовательские атрибуты среды выполнения . Обновление. Добавлены скриншоты iOS7.1 и iOS6.1 с contentInset = {{-10, -5}, {0, 0}} enter image description hereenter image description here

18
Uladzimir

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

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

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

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

15
mikeho

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

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

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

11
Himanshu padia

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

captionTextView.textContainerInset = UIEdgeInsetsMake(0, 0, 0, 0)
8
Anson Yao

Вот обновленная версия очень полезного ответа Fattie .... Он добавляет 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

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

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

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

6
Fab1n

Для Swift 4, Xcode 9

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

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

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

 self.textView?.textContainerInset = UIEdgeInsetsMake(0, 0, 0, 0)
3
Shan Ye

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

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

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

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

2
rp90

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

0
Nikhil Pandey

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

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