Comment perdre la marge / le remplissage dans UITextView?

488

J'ai un UITextViewdans mon application iOS, qui affiche une grande quantité de texte.

Je recherche ensuite ce texte en utilisant le paramètre de marge de décalage du UITextView.

Mon problème est que le remplissage du UITextViewest déroutant mes calculs car il semble être différent selon la taille de police et la police que j'utilise.

Par conséquent, je pose la question: est-il possible de supprimer le rembourrage entourant le contenu du UITextView?

Au plaisir d'entendre vos réponses!

sniurkst
la source
9
Notez que ce QA a presque dix ans! Avec plus de 100 000 vues, car c'est l'un des problèmes les plus stupides d'iOS . Juste FTR j'ai mis la solution actuelle, 2017, raisonnablement simple / habituelle / acceptée ci-dessous comme réponse.
Fattie
1
Je reçois toujours des mises à jour à ce sujet, après avoir écrit une solution de contournement codée en dur en 2009 lorsque IOS 3.0 venait de sortir. Je viens de modifier la réponse pour indiquer clairement qu'elle est obsolète et ignorer le statut accepté.
Michael

Réponses:

384

À jour pour 2019

C'est l'un des bogues les plus stupides d'iOS.

La classe donnée ici UITextViewFixedest généralement la solution la plus raisonnable.

Voici la classe:

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

N'oubliez pas de désactiver scrollEnabled dans l'inspecteur!

  1. La solution fonctionne correctement dans le storyboard

  2. La solution fonctionne correctement lors de l'exécution

Voilà, vous avez terminé.

En général, cela devrait être tout ce dont vous avez besoin dans la plupart des cas .

Même si vous modifiez la hauteur de la vue texte à la volée , fait UITextViewFixedgénéralement tout ce dont vous avez besoin.

(Un exemple courant de modification de la hauteur à la volée est de la modifier au fur et à mesure que l'utilisateur tape.)

Voici le UITextView cassé d'Apple ...

capture d'écran d'IB avec UITextView

Voici UITextViewFixed:

capture d'écran d'IB avec UITextViewFixed

Notez bien sûr que vous devez

désactiver scrollEnabled dans l'inspecteur!

N'oubliez pas de désactiver scrollEnabled! :)


Quelques autres problèmes

(1) Dans certains cas inhabituels - par exemple, certains cas de tables avec des hauteurs de cellule flexibles et changeantes dynamiquement - Apple fait une chose bizarre: ils ajoutent de l'espace supplémentaire en bas . Pas vraiment! Cela devrait être l'une des choses les plus exaspérantes d'iOS.

Voici une "solution miracle" à ajouter à ce qui précède qui aide généralement à cette folie.

...
        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) Parfois, pour corriger un autre subtil désordre d'Apple, vous devez ajouter ceci:

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

(3) Sans doute, nous devrions ajouter:

contentInset = UIEdgeInsets.zero

juste après .lineFragmentPadding = 0dans UITextViewFixed.

Cependant ... croyez ou non ... cela ne fonctionne tout simplement pas dans iOS actuel! (Vérifié en 2019.) Il peut être nécessaire d'ajouter cette ligne à l'avenir.

Le fait que cela UITextViewsoit cassé dans iOS est l'une des choses les plus étranges de tout l'informatique mobile. Dixième anniversaire de cette question et ce n'est toujours pas résolu!

Enfin, voici une astuce quelque peu similaire pour Text Field : https://stackoverflow.com/a/43099816/294884

Astuce complètement aléatoire: comment ajouter le "..." à la fin

Souvent, vous utilisez un UITextView "comme un UILabel". Vous voulez donc qu'il tronque le texte en utilisant des points de suspension "..."

Si oui, ajoutez une troisième ligne de code dans le "setup":

 textContainer.lineBreakMode = .byTruncatingTail

Astuce pratique si vous voulez une hauteur nulle, quand, il n'y a pas de texte du tout

Souvent, vous utilisez une vue texte pour n'afficher que du texte. Ainsi, l'utilisateur ne peut rien modifier. Vous utilisez les lignes "0" pour signifier que la vue du texte changera automatiquement de hauteur en fonction du nombre de lignes de texte.

C'est super, mais s'il n'y a pas de texte du tout, malheureusement, vous obtenez la même hauteur que s'il y a une ligne de texte !! La vue texte ne "disparaît" jamais.

entrez la description de l'image ici

Si vous voulez qu'il "s'en aille", ajoutez simplement ceci

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
}

entrez la description de l'image ici

(Je l'ai fait «1» donc il est clair ce qui se passe, «0» est très bien.)

Et UILabel?

Lors de l'affichage de texte, UILabel présente de nombreux avantages par rapport à UITextView. UILabel ne souffre pas des problèmes décrits sur cette page QA. En effet, la raison pour laquelle nous «abandonnons» habituellement et utilisons simplement UITextView est qu'il est difficile de travailler avec UILabel. En particulier, il est ridiculement difficile de simplement ajouter un rembourrage , correctement, à UILabel. En fait, voici une discussion complète sur la façon d'ajouter "enfin" correctement le remplissage à UILabel: https://stackoverflow.com/a/58876988/294884 Dans certains cas, si vous effectuez une mise en page difficile avec des cellules de hauteur dynamique, il est parfois il vaut mieux le faire à la dure avec UILabel.

Fattie
la source
1
je l'ai fait à l' self.textView.isScrollEnabled = falseintérieur viewDidLayoutSubviews()et cela a fonctionné aussi. Apple aime juste nous faire sauter à travers des cerceaux :)
AnBisw
1
Meilleure solution pour corriger tous mes bugs absurdes de mise en page automatique UITextView sur mes cellules UITableView, en-tête et pied de page avec une hauteur dynamique (UITableViewAutomaticDimension). Génial!
Peter Kreinz
1
J'ai posté une réponse mise à jour à la réponse de @ Fattie qui m'a aidé à vraiment me débarrasser de tous les encarts en utilisant l'astuce spéciale d'activation / désactivation translatesAutoresizingMaskIntoConstraints. Avec cette seule réponse, je n'ai pas pu supprimer toutes les marges (une étrange marge inférieure a résisté à disparaître) dans une hiérarchie de vues utilisant la mise en page automatique. Il traite également du calcul des tailles de vue à l'aide systemLayoutSizeFitting, qui UITextView
renvoyait
1
1up pour IBDesignable. Je voterais aussi pour le texte de l'espace réservé très bien choisi, si seulement je pouvais
Toastor
1
J'avais des vues de texte dans le tableau, celles qui avaient une seule ligne de texte ne calculaient pas correctement sa hauteur (mise en page automatique). Pour y remédier, j'ai dû remplacer didMoveToSuperview et appeler la configuration là-bas également.
El Horrible
792

Pour iOS 7.0, j'ai constaté que l'astuce contentInset ne fonctionne plus. C'est le code que j'ai utilisé pour me débarrasser de la marge / du remplissage dans iOS 7.

Cela amène le bord gauche du texte au bord gauche du conteneur:

textView.textContainer.lineFragmentPadding = 0

Cela provoque l'alignement du haut du texte avec le haut du conteneur

textView.textContainerInset = .zero

Les deux lignes sont nécessaires pour supprimer complètement la marge / le rembourrage.

user1687195
la source
2
Cela fonctionne pour moi pour deux lignes, mais au moment où j'arrive à 5 lignes, le texte est coupé.
livings124
7
Notez que la deuxième ligne peut également s'écrire: self.descriptionTextView.textContainerInset = UIEdgeInsetsZero;
jessepinho
19
Mettre lineFragmentPaddingà 0 est la magie que je recherchais. Je ne sais pas pourquoi Apple rend si difficile d'aligner le contenu UITextView avec d'autres contrôles.
phatmann
3
Ceci est la bonne réponse. Le défilement horizontal ne se produit pas avec cette solution.
Michael
2
lineFragmentPaddingn'est pas destiné à modifier les marges. À partir de la documentation, le remplissage du fragment de ligne n'est pas conçu pour exprimer des marges de texte. Au lieu de cela, vous devez utiliser des encarts sur votre affichage de texte, ajuster les attributs de marge de paragraphe ou modifier la position de l'affichage de texte dans sa vue d'ensemble.
Martin Berger
256

Cette solution de contournement a été écrite en 2009 lors de la sortie d'IOS 3.0. Cela ne s'applique plus.

J'ai rencontré exactement le même problème, à la fin j'ai dû finir par utiliser

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

où nameField est un UITextView. La police que j'utilisais était Helvetica 16 points. Ce n'est qu'une solution personnalisée pour la taille de champ particulière que je dessinais. Cela fait que le décalage gauche affleure le côté gauche et le décalage supérieur où je le veux pour la boîte son dessin.

De plus, cela ne semble s'appliquer qu'à l' UITextViewsendroit où vous utilisez l'alignement par défaut, c'est-à-dire.

nameField.textAlignment = NSTextAlignmentLeft;

Alignez à droite par exemple et le UIEdgeInsetsMakesemble ne pas avoir d'impact du tout sur le bord droit.

À tout le moins, l'utilisation de la propriété .contentInset vous permet de placer vos champs avec les positions "correctes" et de tenir compte des écarts sans compenser votre UITextViews.

Michael
la source
1
Oui, cela fonctionne dans iOS 7. Assurez-vous que UIEdgeInset est défini sur les bonnes valeurs. Il peut être différent de UIEdgeInsetsMake (-4, -8,0,0).
app_
15
Dans IOS 7, j'ai trouvé que UIEdgeInsetsMake (0, -4,0, -4) fonctionnait le mieux.
Racura
2
Oui, ce sont des exemples de chiffres. J'ai déclaré: «Ce n'est qu'une solution personnalisée pour la taille de champ particulière que je dessinais». J'essayais principalement d'illustrer que vous devez jouer avec les nombres pour votre situation de mise en page unique.
Michael
3
UITextAlignmentLeft est déconseillé dans iOS 7. Utilisez NSTextAlignmentLeft.
Jordi Kroon
7
Je ne comprends pas pourquoi les gens votent pour une réponse qui utilise des valeurs codées en dur. Il est TRÈS susceptible de se casser dans les futures versions d'iOS et c'est juste une mauvaise idée.
ldoogy
77

En s'appuyant sur certaines des bonnes réponses déjà données, voici une solution purement basée sur Storyboard / Interface Builder qui fonctionne dans iOS 7.0+

Définissez les attributs d' exécution définis par l'utilisateur de UITextView pour les clés suivantes:

textContainer.lineFragmentPadding
textContainerInset

Générateur d'interface

azdev
la source
4
C'est clairement la meilleure réponse, surtout si vous avez besoin d'une solution XIB uniquement
yano
16
Copier / coller: textContainer.lineFragmentPadding | textContainerInset
Luca De Angelis
3
C'est ce qui fait une belle réponse - facile et fonctionne bien, même après 3 ans :)
Shai Mishali
46

Sur iOS 5 UIEdgeInsetsMake(-8,-8,-8,-8);semble très bien fonctionner.

Engin Kurutepe
la source
41

J'éviterais certainement toute réponse impliquant des valeurs codées en dur, car les marges réelles peuvent changer avec les paramètres de taille de police de l'utilisateur, etc.

Voici la réponse de @ user1687195, écrite sans modifier le textContainer.lineFragmentPadding(car les documents indiquent que ce n'est pas l'usage prévu).

Cela fonctionne très bien pour iOS 7 et versions ultérieures.

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

C'est effectivement le même résultat, juste un peu plus propre en ce qu'il n'utilise pas la propriété lineFragmentPadding de manière abusive.

ldoogy
la source
J'ai essayé une autre solution basée sur cette réponse et cela fonctionne très bien. Les solutions sont:self.textView.textContainer.lineFragmentPadding = 0;
Bakyt Abdrasulov
1
@BakytAbdrasulov, veuillez lire ma réponse. Bien que votre solution fonctionne, elle n'est pas conforme aux documents (voir le lien dans ma réponse). lineFragmentPaddingn'est pas censé contrôler les marges. Voilà pourquoi vous êtes censé utilisertextContainerInset .
ldoogy
21

Storyboard ou solution Interface Builder utilisant des attributs d'exécution définis par l' utilisateur :

Les captures d'écran sont d'iOS 7.1 et iOS 6.1 avec contentInset = {{-10, -5}, {0, 0}} .

Attributs d'exécution définis par l'utilisateur

production

Uladzimir
la source
1
A travaillé pour moi dans iOS 7.
kubilay
Juste pour les attributs d'exécution définis par l'utilisateur - génial!
hris.to
16

Toutes ces réponses répondent à la question du titre, mais je voulais proposer des solutions aux problèmes présentés dans le corps de la question du PO.

Taille du contenu du texte

Un moyen rapide de calculer la taille du texte à l'intérieur du UITextViewest d'utiliser NSLayoutManager:

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

Cela donne le contenu défilant total, qui peut être plus grand que le UITextViewcadre du. J'ai trouvé que c'était beaucoup plus précis que textView.contentSizecela car il calcule réellement la place que prend le texte. Par exemple, étant donné un vide 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)

Hauteur de la ligne

UIFontpossède une propriété qui vous permet d'obtenir rapidement la hauteur de ligne pour la police donnée. Ainsi, vous pouvez trouver rapidement la hauteur de ligne du texte dans votre UITextViewavec:

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

Calcul de la taille du texte visible

Déterminer la quantité de texte réellement visible est important pour gérer un effet de «pagination». UITextViewa une propriété appelée textContainerInsetqui est en fait une marge entre le réel UITextView.frameet le texte lui-même. Pour calculer la hauteur réelle du cadre visible, vous pouvez effectuer les calculs suivants:

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

Détermination de la taille de la pagination

Enfin, maintenant que vous avez la taille du texte et le contenu visibles, vous pouvez rapidement déterminer quels devraient être vos décalages en soustrayant le textHeightdu 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

En utilisant toutes ces méthodes, je n'ai pas touché mes encarts et j'ai pu aller au curseur ou n'importe où dans le texte que je veux.

mikeho
la source
12

vous pouvez utiliser la textContainerInsetpropriété de UITextView:

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

(haut, gauche, bas, droite)

Himanshu padia
la source
1
Je me demande pourquoi ce n'est pas attribué comme la meilleure réponse ici. Le textContainerInset a vraiment satisfait mes besoins.
KoreanXcodeWorker
La mise à jour de la valeur inférieure de la textContainerInsetpropriété ne fonctionne pas pour moi lorsque je souhaite la changer en une nouvelle valeur — voir stackoverflow.com/questions/19422578/… .
Evan R
@MaggiePhillips Ce n'est pas la meilleure réponse car les valeurs codées en dur ne peuvent pas être la bonne façon de le faire, et sont garanties d'échouer dans certains cas. Vous devez prendre en compte lineFragmentPadding.
ldoogy
11

Pour iOS 10, la ligne suivante fonctionne pour la suppression du rembourrage supérieur et inférieur.

captionTextView.textContainerInset = UIEdgeInsetsMake(0, 0, 0, 0)
Anson Yao
la source
Xcode 8.2.1. toujours le même problème que celui mentionné dans cette réponse. Ma solution était de modifier les valeurs en tant que UIEdgeInsets (en haut: 0, à gauche: -4.0, en bas: 0, à droite: -4.0).
Darkwonder
UIEdgeInset.zerofonctionne avec XCode 8.3 et iOS 10.3 Simulator
Simon Warta
10

Dernier Swift:

self.textView.textContainerInset = .init(top: -2, left: 0, bottom: 0, right: 0)
self.textView.textContainer.lineFragmentPadding = 0
Urvish Modi
la source
Très bonne réponse. Cette réponse supprime l'encart supérieur supplémentaire. textView.textContainerInset = UIEdgeInsets.zero ne supprime pas 2 pixels de l'encart supérieur.
korgx9
10

Voici une version mise à jour de la réponse très utile de Fattie. Il ajoute 2 lignes importantes qui m'ont aidé à faire fonctionner la mise en page sur iOS 10 et 11 (et probablement aussi sur les plus basses):

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

Les lignes importantes sont les deux translatesAutoresizingMaskIntoConstraints = <true/false>déclarations!

Cela supprime étonnamment toutes les marges dans toutes mes circonstances!

Bien que ce textViewne soit pas le premier répondant, il peut arriver qu'il y ait une marge inférieure étrange qui n'a pas pu être résolue en utilisant la sizeThatFitsméthode mentionnée dans la réponse acceptée.

En tapant dans le textView soudainement, l'étrange marge inférieure a disparu et tout ressemblait à ce qu'il devrait, mais seulement dès que le textView a été firstResponder .

Je lis donc ici sur SO que l'activation et la désactivation translatesAutoresizingMaskIntoConstraintsaident à définir manuellement le cadre / les limites entre les appels.

Heureusement, cela fonctionne non seulement avec le réglage du cadre, mais avec les 2 lignes de setup() sandwich entre les deux translatesAutoresizingMaskIntoConstraintsappels!

Ceci est par exemple très utile lors du calcul du cadre d'une vue à l' aide systemLayoutSizeFittingd'unUIView . Rend la bonne taille (ce qui n'était pas le cas auparavant)!

Comme dans la réponse originale mentionnée:
N'oubliez pas de désactiver scrollEnabled dans l'inspecteur!
Cette solution fonctionne correctement dans le storyboard, ainsi qu'au moment de l'exécution.

Voilà, maintenant vous avez vraiment terminé!

Fab1n
la source
5

Pour swift 4, Xcode 9

Utilisez la fonction suivante peut modifier la marge / remplissage du texte dans UITextView

fonction publique UIEdgeInsetsMake (_ haut: CGFloat, _ gauche: CGFloat, _ bas: CGFloat, _ droite: CGFloat) -> UIEdgeInsets

dans ce cas,

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

En faisant la solution d'encart, j'avais encore un rembourrage sur le côté droit et le bas. L'alignement du texte causait également des problèmes. Le seul moyen sûr que j'ai trouvé était de mettre la vue texte dans une autre vue qui est limitée.

rp90
la source
3

Voici une petite extension simple qui supprimera la marge par défaut d'Apple de chaque affichage de texte dans votre application.

Remarque: Interface Builder affichera toujours l'ancienne marge, mais votre application fonctionnera comme prévu.

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
la source
1

Pour moi (iOS 11 et Xcode 9.4.1), ce qui fonctionnait comme par magie, c'était la configuration de la propriété textView.font pour le UIFont.preferred(forTextStyle:UIFontTextStyle) style et également la première réponse mentionnée par @Fattie. Mais la réponse @Fattie n'a pas fonctionné tant que je n'ai pas défini la propriété textView.font, sinon UITextView continue de se comporter de manière irrégulière.

Nikhil Pandey
la source
1

Si quelqu'un cherche la dernière version de Swift, le code ci-dessous fonctionne correctement avec Xcode 10.2 et Swift 4.2

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

J'ai trouvé une autre approche, obtenir la vue avec du texte des sous-vues d'UITextView et le configurer dans la méthode layoutSubview d'une sous-classe:

- (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
la source
0

Le défilement textView affecte également la position du texte et donne l'impression qu'il n'est pas centré verticalement. J'ai réussi à centrer le texte dans la vue en désactivant le défilement et en définissant l'encart supérieur à 0:

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

Pour une raison quelconque, je ne l'ai pas encore compris, le curseur n'est toujours pas centré avant le début de la frappe, mais le texte se centre immédiatement lorsque je commence à taper.

Alex
la source
0

Si vous souhaitez définir une chaîne HTML et éviter le remplissage inférieur, assurez-vous que vous n'utilisez pas de balises de bloc, c'est-à-dire div, p.

Dans mon cas, c'était la raison. Vous pouvez facilement le tester en remplaçant les occurrences des balises de bloc par la balise ie span.

Dawid Płatek
la source
0

Pour SwiftUI

Si vous créez votre propre TextView en utilisant UIViewRepresentableet que vous souhaitez contrôler le remplissage, dans votre makeUIViewfonction, faites simplement:

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

ou tout ce que vous voulez.

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