Como perder margem / preenchimento no UITextView?


487

Eu tenho um UITextViewno meu aplicativo iOS, que exibe uma grande quantidade de texto.

Então, estou paginando este texto usando o parâmetro offset margin do UITextView.

Meu problema é que o preenchimento do UITextViewé confuso nos meus cálculos, pois parece ser diferente, dependendo do tamanho da fonte e do tipo de letra que eu uso.

Portanto, faço a pergunta: É possível remover o preenchimento ao redor do conteúdo do UITextView?

Ansiosos para ouvir suas respostas!


9
Observe que esse controle de qualidade tem quase dez anos! Com mais de 100.000 visualizações, já que é um dos problemas mais estúpidos do iOS . Apenas FTR, coloquei na atual, 2017, solução razoavelmente simples / usual / aceita abaixo como resposta.
Fattie 27/03

1
Ainda recebo atualizações sobre isso, depois de escrever uma solução codificada em 2009 quando o IOS 3.0 acabou de ser lançado. Acabei de editar a resposta para declarar claramente que estão desatualizados e ignorar o status aceito.
Michael

Respostas:


383

Atualizado para 2019

É um dos erros mais bobos do iOS.

A classe dada aqui UITextViewFixedé geralmente a solução mais razoável em geral.

Aqui está a classe:

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

Não se esqueça de desativar o scrollEnabled no Inspetor!

  1. A solução funciona corretamente no storyboard

  2. A solução funciona corretamente em tempo de execução

É isso aí, você terminou.

Em geral, isso deve ser tudo o que você precisa na maioria dos casos .

Mesmo se você estiver alterando a altura da exibição de texto em tempo real , UITextViewFixedgeralmente faz tudo o que precisa.

(Um exemplo comum de alterar a altura em tempo real é alterá-la conforme o usuário digita.)

Aqui está o UITextView quebrado da Apple ...

captura de tela do IB com UITextView

Aqui está UITextViewFixed:

captura de tela do IB com UITextViewFixed

Note que é claro que você deve

desative o scrollEnabled no Inspetor!

Não se esqueça de desativar o scrollEnabled! :)


Algumas questões adicionais

(1) Em alguns casos incomuns - por exemplo, em mesas com alturas de células flexíveis e dinamicamente variáveis ​​- a Apple faz uma coisa bizarra: adicionam espaço extra na parte inferior . Não mesmo! Isso teria que ser uma das coisas mais irritantes no iOS.

Aqui está uma "solução rápida" a ser adicionada acima, que geralmente ajuda com essa loucura.

...
        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) Às vezes, para corrigir outra bagunça sutil da Apple, você deve adicionar o seguinte:

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

(3) Indiscutivelmente, devemos acrescentar:

contentInset = UIEdgeInsets.zero

logo depois de .lineFragmentPadding = 0entrar UITextViewFixed.

No entanto ... acredite ou não ... simplesmente não funciona no iOS atual! (Verificado em 2019.) Pode ser necessário adicionar essa linha no futuro.

O fato de UITextViewser quebrado no iOS é uma das coisas mais estranhas em toda a computação móvel. Aniversário de dez anos desta pergunta e ainda não foi corrigido!

Finalmente, aqui está uma dica um pouco semelhante para o campo de texto : https://stackoverflow.com/a/43099816/294884

Dica completamente aleatória: como adicionar o "..." no final

Muitas vezes você está usando um UITextView "como um UILabel". Então, você deseja que ele trunque o texto usando as reticências "..."

Nesse caso, adicione uma terceira linha de código na "instalação":

 textContainer.lineBreakMode = .byTruncatingTail

Dica prática, se você quiser altura zero, quando não houver texto

Geralmente, você usa uma exibição de texto para exibir apenas o texto. Portanto, o usuário não pode editar nada. Você usa as linhas "0" para significar que a exibição do texto mudará automaticamente de altura, dependendo de quantas linhas de texto.

Isso é ótimo, mas se não houver texto , infelizmente, você terá a mesma altura como se houvesse uma linha de texto ! A exibição de texto nunca "desaparece".

insira a descrição da imagem aqui

Se você quiser "desaparecer", basta adicionar este

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
}

insira a descrição da imagem aqui

(Eu fiz '1' para ficar claro o que está acontecendo, '0' está bom.)

E o UILabel?

Ao exibir apenas o texto, o UILabel tem muitas vantagens sobre o UITextView. O UILabel não sofre dos problemas descritos nesta página de controle de qualidade. De fato, a razão pela qual todos "desistimos" e apenas usamos o UITextView é que é difícil trabalhar com o UILabel. Em particular, é ridiculamente difícil adicionar apenas preenchimento , corretamente, ao UILabel. De fato, aqui está uma discussão completa sobre como "finalmente" adicionar preenchimento corretamente ao UILabel: https://stackoverflow.com/a/58876988/294884 Em alguns casos, se você estiver fazendo um layout difícil com células dinâmicas de altura, às vezes é melhor fazê-lo da maneira mais difícil com o UILabel.


1
eu fiz por self.textView.isScrollEnabled = falsedentro viewDidLayoutSubviews()e que funcionou também. A Apple só gosta de fazer-nos saltar através de aros :)
AnBisw

1
A melhor solução para corrigir todos os meus erros absurdos de autolayout do UITextView nas células, cabeçalho e rodapé do UITableView com altura dinâmica (UITableViewAutomaticDimension). Ótimo!
precisa

1
Publiquei uma resposta atualizada na resposta da @ Fattie, que me ajudou a me livrar de todas as inserções usando o truque especial de ativar / desativar translatesAutoresizingMaskIntoConstraints. Com essa resposta, não consegui remover todas as margens (uma margem inferior estranha resistiu a desaparecer) em uma hierarquia de exibição usando o layout automático. Ele também lida com o cálculo de tamanhos de vista usando systemLayoutSizeFitting, que anteriormente retornou um tamanho inválido devido o buggyUITextView
Fab1n

1
1up para IBDesignable. Eu upvote para o lugar texto do suporte muito bem escolhido, bem como, se eu pudesse
Toastor

1
Eu tinha visões de texto na tabela, aquelas que tinham uma única linha de texto não calculavam sua altura corretamente (layout automático). Para corrigi-lo, tive que substituir didMoveToSuperview e chamar a instalação também.
El Horrible

791

Para o iOS 7.0, descobri que o truque contentInset não funciona mais. Este é o código que eu usei para me livrar da margem / preenchimento no iOS 7.

Isso traz a borda esquerda do texto para a borda esquerda do contêiner:

textView.textContainer.lineFragmentPadding = 0

Isso faz com que a parte superior do texto fique alinhada com a parte superior do contêiner

textView.textContainerInset = .zero

Ambas as linhas são necessárias para remover completamente a margem / preenchimento.


2
Isso está funcionando para mim por duas linhas, mas quando chego a 5 linhas, o texto está sendo cortado.
precisa saber é o seguinte

7
Observe que a segunda linha também pode ser escrita como: self.descriptionTextView.textContainerInset = UIEdgeInsetsZero;
jessepinho 22/11/2013

19
Definir lineFragmentPaddingcomo 0 é a mágica que eu estava procurando. Não sei por que a Apple torna tão difícil alinhar o conteúdo do UITextView com outros controles.
Phatmann # 9/14

3
Essa é a resposta correta. A rolagem horizontal não acontece com esta solução.
Michael

2
lineFragmentPaddingnão se destina a modificar margens. A partir dos docs Linha fragmento de preenchimento não é projetado para expressar margens do texto. Em vez disso, você deve usar inserções em sua exibição de texto, ajustar os atributos da margem do parágrafo ou alterar a posição da exibição de texto em sua superview.
Martin Berger

256

Esta solução alternativa foi escrita em 2009 quando o IOS 3.0 foi lançado. Não se aplica mais.

Encontrei exatamente o mesmo problema, no final tive que terminar usando

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

onde nameField é a UITextView. A fonte que eu estava usando era Helvetica 16 pontos. É apenas uma solução personalizada para o tamanho de campo específico que eu estava desenhando. Isso faz com que o deslocamento esquerdo fique nivelado com o lado esquerdo, e o deslocamento superior, onde eu quero, para a caixa ser atraída.

Além disso, isso parece se aplicar apenas ao UITextViewslocal em que você está usando o alinhamento padrão, ou seja.

nameField.textAlignment = NSTextAlignmentLeft;

Alinhar à direita, por exemplo, e o efeito UIEdgeInsetsMakeparece não ter nenhum impacto na borda direita.

No mínimo, o uso da propriedade .contentInset permite colocar seus campos nas posições "corretas" e acomodar os desvios sem compensar o seu UITextViews.


1
Sim, ele funciona no iOS 7. Verifique se o UIEdgeInset está definido com os valores corretos. Pode ser diferente de UIEdgeInsetsMake (-4, -8,0,0).
app_ 18/09/2013

15
No IOS 7, achei UIEdgeInsetsMake (0, -4,0, -4) funcionou melhor.
Racura

2
Sim, esses são números de exemplo. Eu declarei "É apenas uma solução personalizada para o tamanho de campo específico que eu estava desenhando". Principalmente, eu estava tentando ilustrar que você tem que brincar com os números da sua situação de layout exclusiva.
Michael

3
UITextAlignmentLeft foi descontinuado no iOS 7. Use NSTextAlignmentLeft.
Jordi Kroon

7
Não entendo por que as pessoas votaram positivamente em uma resposta que utiliza valores codificados. É muito provável que ocorra em versões futuras do iOS e é apenas uma péssima idéia.
Ldoogy 12/08/16

77

Com base em algumas das boas respostas já fornecidas, aqui está uma solução baseada no Storyboard / Interface Builder que funciona no iOS 7.0 ou superior

Defina os Atributos de tempo de execução definidos pelo usuário do UITextView para as seguintes chaves:

textContainer.lineFragmentPadding
textContainerInset

Construtor de interface


4
Este é claramente a melhor resposta, especialmente se você precisa de uma solução apenas de XIB
yano

16
Copiar / colar: textContainer.lineFragmentPadding | textContainerInset
Luca De Angelis

3
Isto é o que faz uma bela resposta - fácil e funciona bem, mesmo depois de 3 anos :)
Shai Mishali


41

Definitivamente, evitaria respostas com valores codificados, pois as margens reais podem mudar com as configurações de tamanho de fonte do usuário etc.

Aqui está a resposta do @ user1687195, escrita sem modificar o textContainer.lineFragmentPadding(porque os documentos afirmam que esse não é o uso pretendido).

Isso funciona muito bem para o iOS 7 e posterior.

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

Este é efetivamente o mesmo resultado, apenas um pouco mais limpo, pois não usa mal a propriedade lineFragmentPadding.


Tentei outra solução com base nesta resposta e funciona muito bem. As soluções são:self.textView.textContainer.lineFragmentPadding = 0;
Bakyt Abdrasulov

1
@BakytAbdrasulov, leia minha resposta. Enquanto sua solução funciona, ela não está de acordo com os documentos (consulte o link na minha resposta). lineFragmentPaddingnão se destina a controlar as margens. É por isso que você deveria usar textContainerInset.
Ldoogy 23/01

21

Solução Storyboard ou Interface Builder usando Atributos de Tempo de Execução Definidos pelo Usuário :

As capturas de tela são do iOS 7.1 e iOS 6.1 com contentInset = {{-10, -5}, {0, 0}}.

Atributos de tempo de execução definidos pelo usuário

resultado


1
Trabalhou para mim em iOS 7.
Kubilay

Apenas para atributos de tempo de execução definidos pelo usuário - incrível!
hris.to

16

Todas essas respostas abordam a questão do título, mas eu queria propor algumas soluções para os problemas apresentados no corpo da pergunta do OP.

Tamanho do conteúdo do texto

Uma maneira rápida de calcular o tamanho do texto dentro de UITextViewé usar o NSLayoutManager:

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

Isso fornece o conteúdo rolável total, que pode ser maior que o UITextViewquadro do. Achei isso muito mais preciso do que, textView.contentSizepois calcula quanto espaço o texto ocupa. Por exemplo, dado um vazio 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)

Altura da linha

UIFontpossui uma propriedade que permite rapidamente obter a altura da linha para a fonte especificada. Assim, você pode encontrar rapidamente a altura da linha do texto UITextViewcom:

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

Cálculo do tamanho do texto visível

Determinar a quantidade de texto realmente visível é importante para manipular um efeito de "paginação". UITextViewtem uma propriedade chamada textContainerInsetque na verdade é uma margem entre o real UITextView.framee o próprio texto. Para calcular a altura real do quadro visível, você pode executar os seguintes cálculos:

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

Determinando o tamanho da paginação

Por fim, agora que você tem o tamanho do texto visível e o conteúdo, pode determinar rapidamente quais devem ser suas compensações subtraindo o valor textHeightde 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

Usando todos esses métodos, eu não toquei em minhas inserções e pude ir para o cursor ou para qualquer lugar do texto que desejar.


12

você pode usar a textContainerInsetpropriedade de UITextView:

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

(superior, esquerda, inferior, direita)


1
Eu me pergunto por que isso não é atribuído como a melhor resposta aqui. O textContainerInset realmente satisfez minhas necessidades.
KoreanXcodeWorker

Atualizar o valor inferior da textContainerInsetpropriedade não está funcionando para mim quando eu quero alterá-lo para um novo valor - consulte stackoverflow.com/questions/19422578/… .
Evan R

@ MaggiePhillips Não é a melhor resposta, porque os valores codificados não podem ser a maneira certa de fazer isso e, em alguns casos, são garantidos que falhem. Você precisa levar o lineFragmentPadding em consideração.
Ldoogy 21/05

11

Para o iOS 10, a seguinte linha funciona para a remoção de preenchimento superior e inferior.

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

Xcode 8.2.1. ainda é o mesmo problema mencionado nesta resposta. Minha solução foi editar os valores como UIEdgeInsets (superior: 0, esquerda: -4,0, inferior: 0, direita: -4,0).
Darkwonder

UIEdgeInset.zerofunciona usando o XCode 8.3 e o iOS 10.3 Simulator
Simon Warta 31/03

10

Swift mais recente:

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

Ótima resposta. Esta resposta remove a inserção superior adicional. textView.textContainerInset = UIEdgeInsets.zero não remove 2 pixels da parte superior.
Korgx9

10

Aqui está uma versão atualizada da resposta muito útil de Fattie. Ele adiciona duas linhas importantes que me ajudaram a fazer o layout funcionar no iOS 10 e 11 (e provavelmente nas mais baixas também):

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

As linhas importantes são as duas translatesAutoresizingMaskIntoConstraints = <true/false>declarações!

Surpreendentemente, isso remove todas as margens em todas as minhas circunstâncias!

Embora textViewnão seja o primeiro a responder, pode acontecer que haja uma margem inferior estranha que não possa ser resolvida usando o sizeThatFitsmétodo mencionado na resposta aceita.

Ao tocar no textView de repente, a margem inferior estranha desapareceu e tudo parecia como deveria, mas apenas assim que o textView chegou firstResponder.

Então, li aqui no SO que ativar e desativar translatesAutoresizingMaskIntoConstraintsajuda ao definir o quadro / limites manualmente entre as chamadas.

Felizmente, isso não funciona apenas com a configuração de quadro, mas com as 2 linhas de setup()sanduíche entre as duas translatesAutoresizingMaskIntoConstraintschamadas!

Isso, por exemplo, é muito útil ao calcular o quadro de uma vista usando systemLayoutSizeFittingaUIView . Devolve o tamanho correto (o que anteriormente não acontecia)!

Como na resposta original mencionada:
Não se esqueça de desativar o scrollEnabled no Inspector!
Essa solução funciona corretamente no storyboard, bem como no tempo de execução.

É isso aí, agora você está realmente pronto!


5

Para o swift 4, Xcode 9

Use a seguinte função para alterar a margem / preenchimento do texto no UITextView

função pública UIEdgeInsetsMake (_ superior: CGFloat, _ esquerda: CGFloat, _ inferior: CGFloat, _ direita: CGFloat) -> UIEdgeInsets

então, neste caso, é

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

3

Fazendo a solução inserida, eu ainda tinha preenchimento no lado direito e no fundo. O alinhamento de texto também estava causando problemas. O único caminho certo que encontrei foi colocar a exibição de texto dentro de outra exibição cortada aos limites.


3

Aqui está uma pequena extensão fácil que removerá a margem padrão da Apple de todas as visualizações de texto em seu aplicativo.

Nota: O Interface Builder ainda mostrará a margem antiga, mas seu aplicativo funcionará conforme o esperado.

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

Para mim (iOS 11 e Xcode 9.4.1), o que funcionou magicamente foi configurar a propriedade textView.font para UIFont.preferred(forTextStyle:UIFontTextStyle) estilizar e também a primeira resposta, como mencionado por @Fattie. Mas a resposta @Fattie não funcionou até eu definir a propriedade textView.font, caso contrário o UITextView continua se comportando de maneira irregular.


1

Caso alguém que esteja procurando pela versão mais recente do Swift, o código abaixo esteja funcionando bem com o Xcode 10.2 e o Swift 4.2

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

0

Eu encontrei mais uma abordagem, obtendo visualização com texto das subvisões do UITextView e configurando-o no método layoutSubview de uma subclasse:

- (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

A rolagem textView também afeta a posição do texto e faz com que pareça não centralizado verticalmente. Consegui centralizar o texto na visualização desativando a rolagem e definindo a inserção superior como 0:

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

Por alguma razão, ainda não descobri, o cursor ainda não está centralizado antes do início da digitação, mas o texto é centralizado imediatamente quando começo a digitar.


0

Caso deseje definir uma string HTML e evitar o preenchimento inferior, verifique se você não está usando tags de bloco, por exemplo, div, p.

No meu caso, esse foi o motivo. Você pode testá-lo facilmente substituindo ocorrências de tags de bloco por tag de extensão.


0

Para SwiftUI

Se você está criando seu próprio TextView usando UIViewRepresentablee deseja controlar o preenchimento, em sua makeUIViewfunção, basta:

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

ou o que você quiser.


-4
[firstNameTextField setContentVerticalAlignment:UIControlContentVerticalAlignmentCenter];
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.