Come perdere margine / riempimento in UITextView?


488

Ho un UITextViewnella mia applicazione iOS, che visualizza una grande quantità di testo.

Sto quindi impaginando questo testo usando il parametro margine di offset di UITextView.

Il mio problema è che il riempimento del UITextViewconfonde i miei calcoli in quanto sembra essere diverso a seconda della dimensione del carattere e del carattere tipografico che uso.

Pertanto, pongo la domanda: è possibile rimuovere l'imbottitura che circonda il contenuto di UITextView?

Non vedo l'ora di ascoltare le tue risposte!


9
Nota che questo QA ha quasi dieci anni! Con oltre 100.000 visualizzazioni, poiché è uno dei problemi più stupidi di iOS . Proprio FTR ho inserito la seguente soluzione 2017, ragionevolmente semplice / normale / accettata, come risposta.
Fattie,

1
Ho ancora aggiornamenti su questo, dopo aver scritto una soluzione temporanea nel 2009, quando IOS 3.0 era appena stato rilasciato. Ho appena modificato la risposta per affermare chiaramente che sono anni non aggiornati e per ignorare lo stato accettato.
Michael,

Risposte:


384

Aggiornato per il 2019

È uno dei bug più sciocchi in iOS.

La classe qui indicata UITextViewFixedè generalmente la soluzione più ragionevole.

Ecco la classe:

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

Non dimenticare di disattivare scrollEnabled in Inspector!

  1. La soluzione funziona correttamente nello storyboard

  2. La soluzione funziona correttamente in fase di esecuzione

Ecco fatto, il gioco è fatto.

In generale, dovrebbe essere tutto ciò che serve nella maggior parte dei casi .

Anche se stai modificando l'altezza della vista testo al volo , di UITextViewFixedsolito fa tutto ciò che ti serve.

(Un esempio comune di modifica dell'altezza al volo, è la modifica man mano che l'utente digita.)

Ecco il UITextView rotto da Apple ...

screenshot di IB con UITextView

Ecco qui UITextViewFixed:

screenshot di IB con UITextViewFixed

Si noti che ovviamente è necessario

disattiva scrollEnabled in Inspector!

Non dimenticare di disattivare scrollEnabled! :)


Qualche ulteriore problema

(1) In alcuni casi insoliti - ad esempio, alcuni casi di tavoli con altezze di cella flessibili e che cambiano dinamicamente - Apple fa una cosa bizzarra: aggiungono spazio extra nella parte inferiore . No davvero! Questa dovrebbe essere una delle cose più esasperanti di iOS.

Ecco una "soluzione rapida" da aggiungere a quanto sopra che di solito aiuta con quella follia.

...
        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) A volte, per risolvere l'ennesimo pasticcio di Apple, devi aggiungere questo:

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

(3) Probabilmente dovremmo aggiungere:

contentInset = UIEdgeInsets.zero

subito dopo .lineFragmentPadding = 0in UITextViewFixed.

Tuttavia ... credici o no ... semplicemente non funziona nell'attuale iOS! (Controllato nel 2019.) Potrebbe essere necessario aggiungere quella linea in futuro.

Il fatto che UITextViewsi rompa in iOS è una delle cose più strane in tutto il mobile computing. Dieci anni di questa domanda e non è stato ancora risolto!

Infine, ecco un suggerimento in qualche modo simile per Campo di testo : https://stackoverflow.com/a/43099816/294884

Suggerimento completamente casuale: come aggiungere il "..." alla fine

Spesso stai usando un UITextView "come un UILabel". Quindi vuoi che tronci il testo usando i puntini di sospensione "..."

In tal caso, aggiungi una terza riga di codice nel "setup":

 textContainer.lineBreakMode = .byTruncatingTail

Consiglio pratico se si desidera un'altezza pari a zero, quando non è presente alcun testo

Spesso si utilizza una vista di testo per visualizzare solo il testo. Quindi, l'utente non può effettivamente modificare nulla. Usa le righe "0" per indicare che la vista del testo cambierà automaticamente altezza in base al numero di righe di testo.

È fantastico, ma se non c'è alcun testo, sfortunatamente si ottiene la stessa altezza di una riga di testo !! La vista di testo non "va mai via".

inserisci qui la descrizione dell'immagine

Se vuoi che "vada via", aggiungi questo

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
}

inserisci qui la descrizione dell'immagine

(L'ho fatto '1', quindi è chiaro cosa sta succedendo, '0' va bene.)

Che dire di UILabel?

Quando si visualizza solo testo, UILabel ha molti vantaggi rispetto a UITextView. UILabel non soffre dei problemi descritti in questa pagina di QA. In effetti, il motivo per cui di solito ci "arrendiamo" e usiamo UITextView è che UILabel è difficile da lavorare. In particolare è ridicolmente difficile aggiungere solo l' imbottitura , correttamente, a UILabel. In effetti, ecco una discussione completa su come "finalmente" aggiungere correttamente il riempimento a UILabel: https://stackoverflow.com/a/58876988/294884 In alcuni casi, se si sta eseguendo un layout difficile con celle ad altezza dinamica, a volte è meglio farlo nel modo più duro con UILabel.


1
l'ho fatto self.textView.isScrollEnabled = falsedentro viewDidLayoutSubviews()e anche quello ha funzionato. Ad Apple piace solo farci saltare attraverso i cerchi :)
AnBisw,

1
La migliore soluzione per correggere tutti i miei assurdi bug di autolayout UITextView su celle, intestazione e piè di pagina UITableView con altezza dinamica (UITableViewAutomaticDimension). Grande!
Peter Kreinz,

1
Ho pubblicato una risposta aggiornata alla risposta di @ Fattie che mi ha aiutato a liberarmi davvero di tutte le inserzioni usando il trucco speciale di abilitazione / disabilitazione translatesAutoresizingMaskIntoConstraints. Con questa sola risposta non sono stato in grado di rimuovere tutti i margini (alcuni strani margini inferiori hanno resistito ad andare via) in una gerarchia di viste usando il layout automatico. Si occupa anche del calcolo delle dimensioni della vista utilizzando systemLayoutSizeFitting, che in precedenza aveva restituito una dimensione non valida a causa del buggyUITextView
Fab1n

1
1up per IBDesignable. Vorrei votare anche per il testo del
segnaposto

1
Avevo viste di testo nella tabella, quelle con una sola riga di testo non calcolavano correttamente la sua altezza (autolayout). Per risolvere il problema, ho dovuto ignorare didMoveToSuperview e chiamare anche l'installazione lì.
El Horrible,

792

Per iOS 7.0, ho scoperto che il trucco contentInset non funziona più. Questo è il codice che ho usato per sbarazzarmi del margine / riempimento in iOS 7.

Questo porta il bordo sinistro del testo al bordo sinistro del contenitore:

textView.textContainer.lineFragmentPadding = 0

Questo fa sì che la parte superiore del testo si allinei con la parte superiore del contenitore

textView.textContainerInset = .zero

Entrambe le linee sono necessarie per rimuovere completamente il margine / riempimento.


2
Questo funziona per me su due righe, ma quando arrivo a 5 righe, il testo viene tagliato.
livings124

7
Si noti che la seconda riga può anche essere scritta come: self.descriptionTextView.textContainerInset = UIEdgeInsetsZero;
jessepinho il

19
L'impostazione lineFragmentPaddingsu 0 è la magia che stavo cercando. Non ho idea del perché Apple rende così difficile allineare i contenuti UITextView con altri controlli.
Phatmann,

3
Questa è la risposta corretta Lo scorrimento orizzontale non avviene con questa soluzione.
Michael,

2
lineFragmentPaddingnon intende modificare i margini. Dalla documentazione L' imbottitura del frammento di riga non è progettata per esprimere margini di testo. Invece, è necessario utilizzare gli inserti nella vista di testo, regolare gli attributi del margine di paragrafo o modificare la posizione della vista di testo all'interno della sua superview.
Martin Berger,

256

Questa soluzione alternativa è stata scritta nel 2009 quando è stato rilasciato IOS 3.0. Non si applica più.

Ho riscontrato lo stesso identico problema, alla fine ho dovuto finire con

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

dove nameField è a UITextView . Il carattere che mi è capitato di usare era Helvetica 16 punti. È solo una soluzione personalizzata per la particolare dimensione del campo che stavo disegnando. Questo fa sì che l'offset sinistro sia allineato con il lato sinistro e l'offset superiore nel punto in cui lo desidero per la casella venga inserito.

Inoltre, questo sembra applicarsi solo al punto in UITextViewscui si utilizza l'aligment predefinito, ad es.

nameField.textAlignment = NSTextAlignmentLeft;

Allinea a destra, ad esempio, e UIEdgeInsetsMake sembra non avere alcun impatto sul bordo destro.

Per lo meno, l'uso della proprietà .contentInset ti consente di posizionare i tuoi campi con le posizioni "corrette" e di compensare le deviazioni senza compensare il tuo UITextViews.


1
Sì, funziona in iOS 7. Assicurati che UIEdgeInset sia impostato sui valori corretti. Potrebbe essere diverso da UIEdgeInsetsMake (-4, -8,0,0).
app_18

15
In IOS 7 ho scoperto che UIEdgeInsetsMake (0, -4,0, -4) ha funzionato al meglio.
Racura,

2
Sì, quelli sono numeri di esempio. Ho dichiarato "È solo una soluzione personalizzata per la dimensione del campo particolare che stavo disegnando". Principalmente stavo cercando di illustrare che devi giocare con i numeri per la tua situazione di layout unica.
Michael,

3
UITextAlignmentLeft è obsoleto in iOS 7. Utilizzare NSTextAlignmentLeft.
Jordi Kroon,

7
Non capisco perché le persone votino una risposta che utilizza valori codificati. È MOLTO probabile che si rompa nelle versioni future di iOS ed è solo una cattiva idea.
ldoogy,

77

Costruendo alcune delle buone risposte già fornite, ecco una soluzione puramente basata su Storyboard / Interface Builder che funziona in iOS 7.0+

Impostare gli attributi di runtime definiti dall'utente di UITextView per le seguenti chiavi:

textContainer.lineFragmentPadding
textContainerInset

Interface Builder


4
Questa è chiaramente la risposta migliore, soprattutto se hai bisogno di una soluzione solo per XIB
yano,

16
Copia / incolla: textContainer.lineFragmentPadding | textContainerInset
Luca De Angelis

3
Questo è ciò che rende una bella risposta - facile e funziona bene, anche dopo 3 anni :)
Shai Mishali,


41

Eviterei sicuramente qualsiasi risposta che implichi valori codificati, poiché i margini effettivi potrebbero cambiare con le impostazioni della dimensione del carattere dell'utente, ecc.

Ecco la risposta di @ user1687195, scritta senza modificare il textContainer.lineFragmentPadding(poiché i documenti indicano che non è l'uso previsto).

Funziona benissimo per iOS 7 e versioni successive.

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

Questo è effettivamente lo stesso risultato, solo un po 'più pulito in quanto non abusa della proprietà lineFragmentPadding.


Ho provato un'altra soluzione basata su questa risposta e funziona benissimo. La soluzione è:self.textView.textContainer.lineFragmentPadding = 0;
Bakyt Abdrasulov il

1
@BakytAbdrasulov, per favore leggi la mia risposta. Mentre la tua soluzione funziona, non è conforme ai documenti (vedi il link nella mia risposta). lineFragmentPaddingnon è pensato per controllare i margini. Ecco perché dovresti usare textContainerInset.
ldoogy,

21

Soluzione per Storyboard o Interface Builder che utilizza attributi di runtime definiti dall'utente :

Le schermate sono di iOS 7.1 e iOS 6.1 con contentInset = {{-10, -5}, {0, 0}}.

Attributi di runtime definiti dall'utente

produzione


1
Ha funzionato per me su iOS 7.
kubilay

Solo per gli attributi di runtime definiti dall'utente - fantastico!
hris.to,

16

Tutte queste risposte rispondono alla domanda del titolo, ma volevo proporre alcune soluzioni ai problemi presentati nel corpo della domanda del PO.

Dimensione del contenuto del testo

Un modo rapido per calcolare la dimensione del testo all'interno di UITextViewè usare NSLayoutManager:

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

Ciò fornisce il contenuto scorrevole totale, che può essere più grande della UITextViewcornice. Ho trovato che questo è molto più preciso rispetto a textView.contentSizequando calcola in realtà quanto spazio occupa il testo. Ad esempio, dato un vuoto 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)

Altezza della linea

UIFontha una proprietà che ti consente rapidamente di ottenere l'altezza della linea per il carattere specificato. Quindi puoi trovare rapidamente l'altezza della linea del testo UITextViewcon:

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

Calcolo della dimensione del testo visibile

Determinare la quantità di testo effettivamente visibile è importante per gestire un effetto di "paging". UITextViewha una proprietà chiamata textContainerInsetche in realtà è un margine tra l'effettivo UITextView.framee il testo stesso. Per calcolare l'altezza reale della cornice visibile è possibile eseguire i seguenti calcoli:

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

Determinazione della dimensione del paging

Infine, ora che hai la dimensione del testo e il contenuto visibili, puoi determinare rapidamente quali dovrebbero essere i tuoi offset sottraendo il textHeightda 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 tutti questi metodi, non ho toccato le mie inserzioni e sono stato in grado di andare al cursore o ovunque nel testo che desidero.


12

puoi usare la textContainerInsetproprietà di UITextView:

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

(in cima a sinistra in basso a destra)


1
Mi chiedo perché questa non sia assegnata come la migliore risposta qui. TextContainerInset ha davvero soddisfatto le mie esigenze.
KoreanXcodeWorker,

L'aggiornamento del valore inferiore sulla textContainerInsetproprietà non funziona per me quando voglio cambiarlo con un nuovo valore: consultare stackoverflow.com/questions/19422578/… .
Evan R,

@MaggiePhillips Non è la risposta migliore perché i valori hardcoded non possono essere il modo giusto per farlo, e in alcuni casi sono garantiti fallimenti. Devi prendere in considerazione lineFragmentPadding.
ldoogy,

11

Per iOS 10, la seguente riga funziona per la rimozione dell'imbottitura superiore e inferiore.

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

Xcode 8.2.1. sempre lo stesso problema menzionato in questa risposta. La mia soluzione era quella di modificare i valori come UIEdgeInsets (in alto: 0, a sinistra: -4,0, in basso: 0, a destra: -4,0).
Darkwonder,

UIEdgeInset.zerofunziona con XCode 8.3 e iOS 10.3 Simulator
Simon Warta

10

Ultima Swift:

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

Bella risposta. Questa risposta rimuove l'inserzione superiore aggiuntiva. textView.textContainerInset = UIEdgeInsets.zero non rimuove 2 pixel dall'inserto superiore.
korgx9,

10

Ecco una versione aggiornata della risposta molto utile di Fattie. Aggiunge 2 linee importanti che mi hanno aiutato a far funzionare il layout su iOS 10 e 11 (e probabilmente anche su quelli inferiori):

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

Le linee importanti sono le due translatesAutoresizingMaskIntoConstraints = <true/false> affermazioni!

Ciò rimuove sorprendentemente tutti i margini in tutte le mie circostanze!

Sebbene il textViewnon sia il primo risponditore, potrebbe accadere che ci sia uno strano margine inferiore che non è stato possibile risolvere usandosizeThatFits metodo menzionato nella risposta accettata.

Quando si tocca il textView all'improvviso lo strano margine inferiore è scomparso e tutto sembrava come dovrebbe, ma non appena il textView ha ottenuto firstResponder .

Quindi ho letto qui su SO che abilitare e disabilitare translatesAutoresizingMaskIntoConstraintsaiuta quando si imposta il frame / limiti manualmente tra le chiamate.

Fortunatamente questo non funziona solo con l'impostazione del frame ma con le 2 linee di setup()sandwich tra le due translatesAutoresizingMaskIntoConstraintschiamate!

Questo ad esempio è molto utile quando si calcola il frame di una vista usando systemLayoutSizeFittingsu aUIView . Restituisce la dimensione corretta (cosa che prima non aveva)!

Come nella risposta originale menzionata:
non dimenticare di disattivare scrollEnabled in Inspector!
Tale soluzione funziona correttamente nello storyboard e in fase di esecuzione.

Ecco fatto, ora hai davvero finito!


5

Per swift 4, Xcode 9

Utilizzare la seguente funzione per modificare il margine / riempimento del testo in UITextView

funzioni pubbliche UIEdgeInsetsMake (_ top: CGFloat, _ left: CGFloat, _ bottom: CGFloat, _ right: CGFloat) -> UIEdgeInsets

così in questo caso è

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

3

Facendo la soluzione inserto avevo ancora imbottitura sul lato destro e inferiore. Anche l'allineamento del testo stava causando problemi. L'unico modo sicuro che ho trovato è stato quello di mettere la vista di testo all'interno di un'altra vista che è tagliata al limite.


3

Ecco una semplice piccola estensione che rimuoverà il margine predefinito di Apple da ogni visualizzazione di testo nella tua app.

Nota: Interface Builder mostrerà comunque il vecchio margine, ma l'app funzionerà come previsto.

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

Per me (iOS 11 e Xcode 9.4.1) ciò che ha funzionato magicamente è stato impostare la proprietà textView.font in base allo UIFont.preferred(forTextStyle:UIFontTextStyle) stile e anche la prima risposta menzionata da @Fattie. Ma la risposta @Fattie non ha funzionato finché non ho impostato la proprietà textView.font altrimenti UITextView continua a comportarsi in modo irregolare.


1

Nel caso in cui chiunque cerchi l'ultima versione di Swift, il codice sottostante funziona bene con Xcode 10.2 e Swift 4.2

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

0

Ho trovato un altro approccio, ottenendo la visualizzazione con il testo dalle sottoview di UITextView e impostandolo nel metodo layoutSubview di una sottoclasse:

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

Lo scorrimento di textView influisce anche sulla posizione del testo e lo fa sembrare non centrato verticalmente. Sono riuscito a centrare il testo nella vista disabilitando lo scorrimento e impostando l'inserzione superiore su 0:

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

Per qualche motivo non l'ho ancora capito, il cursore non è ancora centrato prima dell'inizio della digitazione, ma il testo si centra immediatamente quando inizio a scrivere.


0

Nel caso in cui si desideri impostare una stringa HTML ed evitare il riempimento in basso, assicurarsi di non utilizzare tag di blocco, ad esempio div, p.

Nel mio caso questa era la ragione. Puoi facilmente provarlo sostituendo le occorrenze di tag block con tag span.


0

Per SwiftUI

Se stai creando il tuo TextView usando UIViewRepresentablee vuoi controllare il riempimento, nella tua makeUIViewfunzione, fai semplicemente:

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

o qualunque cosa tu voglia.


-4
[firstNameTextField setContentVerticalAlignment:UIControlContentVerticalAlignmentCenter];
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.