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!

sniurkst
fonte
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.

Fattie
fonte
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.

user1687195
fonte
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.

Michael
fonte
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

azdev
fonte
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
46

No iOS 5 UIEdgeInsetsMake(-8,-8,-8,-8);parece funcionar muito bem.

Engin Kurutepe
fonte
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.

ldoogy
fonte
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

Uladzimir
fonte
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.

mikeho
fonte
12

você pode usar a textContainerInsetpropriedade de UITextView:

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

(superior, esquerda, inferior, direita)

Himanshu padia
fonte
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)
Anson Yao
fonte
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
Urvish Modi
fonte
Ó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!

Fab1n
fonte
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)
Shan Ye
fonte
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.

rp90
fonte
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);
   }
}
Cal S
fonte
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.

Nikhil Pandey
fonte
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)
Bhavin_m
fonte
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);
}
Nikita
fonte
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.

Alex
fonte
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.

Dawid Płatek
fonte
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.

P. Ent
fonte
-4
[firstNameTextField setContentVerticalAlignment:UIControlContentVerticalAlignmentCenter];
to0nice4eva
fonte