UIButton qui redimensionne pour s'adapter à son titleLabel

J'ai un UIButton que j'ajoute à la vue de mon contrôleur de vue dans un storyboard. J'ajoute des contraintes de centrage pour le positionner et des contraintes d'espace pour limiter sa largeur. Dans le code, j'Ajoute:

self.button.titleLabel.numberOfLines = 0;
self.button.titleLabel.lineBreakMode = NSLineBreakByWordWrapping;
[self.button setTitle:@"A real real real real real real real real long long name." forState:UIControlStateNormal];
self.button.backgroundColor = [UIColor redColor];
self.button.titleLabel.backgroundColor = [UIColor blueColor];

Le résultat est indiqué ci-dessous:

entrez la description de l'image ici

Je veux que le bouton soit à la taille de son contenu. Comment puis-je faire cela?

J'ai essayé

[self.button sizeToFit];

Et j'ai essayé de définir les priorités des contraintes de serrage de contenu et de résistance à la compression sur requis.

J'ai j'ai également essayé de définir explicitement contentEdgeInsets et titleEdgeInsets sur UIEdgeInsetsZero et d'appeler invalidateIntrinsicContentSize.

J'ai également remarqué que si je place des caractères de nouvelle ligne dans la chaîne de titre, le bouton semble se redimensionner pour s'adapter à son contenu.

Je cours sur Xcode 6 et iOS 8 sur le simulateur iPhone 6.

25
demandé sur shim 2014-12-03 00:30:17

11 réponses

J'ai eu à travailler, mais vous devez utiliser un bouton personnalisé, pas un type de système. Donnez au Bouton les contraintes de largeur et de hauteur, et faites un IBOutlet à la contrainte de hauteur (heightCon dans mon code) afin que vous puissiez l'ajuster dans le code.

- (void)viewDidLoad {
    [super viewDidLoad];
    self.button.titleLabel.numberOfLines = 0;
    self.button.titleLabel.lineBreakMode = NSLineBreakByWordWrapping;
    [self.button setTitle:@"A real real real real real real real real long long name." forState:UIControlStateNormal];
    [self.button addTarget:self action:@selector(doStuff:) forControlEvents:UIControlEventTouchUpInside];
    self.button.backgroundColor = [UIColor redColor];
    self.button.titleLabel.backgroundColor = [UIColor blueColor];
    [self.button layoutIfNeeded]; // need this to update the button's titleLabel's size
    self.heightCon.constant = self.button.titleLabel.frame.size.height;
}

Après L'Édition:

J'ai trouvé que vous pouvez aussi le faire plus simplement, et avec un bouton système Si vous faites une sous-classe, et utilisez ce code,

@implementation RDButton

-(CGSize)intrinsicContentSize {
    return CGSizeMake(self.frame.size.width, self.titleLabel.frame.size.height);
}

La méthode intrinsicContentSize remplacée est appelée lorsque vous définissez intitulé. Vous ne devez pas définir de contrainte de hauteur dans ce cas.

17
répondu rdelmar 2014-12-03 05:05:13

Swift 4.x version de Réponse de Kubba {[5]:

Besoin de mettre à jour saut de ligne comme Clip/WordWrap/ dans Interface builder aux boutons correspondants.

class ResizableButton: UIButton {
    override var intrinsicContentSize: CGSize {
       let labelSize = titleLabel?.sizeThatFits(CGSize(width: frame.width, height: .greatestFiniteMagnitude)) ?? .zero
       let desiredButtonSize = CGSize(width: labelSize.width + titleEdgeInsets.left + titleEdgeInsets.right, height: labelSize.height + titleEdgeInsets.top + titleEdgeInsets.bottom)

       return desiredButtonSize
    }
}
16
répondu A. Buksha 2018-07-21 07:56:37

J'ai le même problème. Cela n'arrive que si UIButton titleLabel a plus d'une ligne. Est - ce un bug dans UIKit?

Ma solution rapide:

class ResizableButton: UIButton {    
    override var intrinsicContentSize: CGSize {
        let labelSize = titleLabel?.sizeThatFits(CGSize(width: frame.size.width, height: CGFloat.greatestFiniteMagnitude)) ?? .zero
        let desiredButtonSize = CGSize(width: labelSize.width + titleEdgeInsets.left + titleEdgeInsets.right, height: labelSize.height + titleEdgeInsets.top + titleEdgeInsets.bottom)

        return desiredButtonSize
    }
}
11
répondu Kubba 2018-03-05 06:53:04

J'ai lutté avec cela pendant un moment et j'ai fini par le faire fonctionner en sous-classant UIButton et en ajoutant ces deux fonctions

class GoalsButton: UIButton {

    override var intrinsicContentSize: CGSize {
        return self.titleLabel!.intrinsicContentSize
    }

    // Whever the button is changed or needs to layout subviews,
    override func layoutSubviews() {
        super.layoutSubviews()
        titleLabel?.preferredMaxLayoutWidth = self.titleLabel!.frame.size.width
    }
}
3
répondu Lord Fresh 2017-08-10 15:57:54

Je suis loin de mon ordinateur, donc je ne peux pas ajouter le code pour le moment, mais j'ai déjà trouvé une solution de contournement.

Ce que vous pouvez faire est de créer un UILabel et ajouter un UITapGestureRecognizer à l'étiquette. Faites ce que vous voulez pour l'action du bouton en manipulant l'événement tap. Et assurez-vous également d'activer les interactions utilisateur sur le UILabel.

Cette étiquette se comporte désormais comme un bouton de redimensionnement automatique.

2
répondu Vinay Jain 2014-12-03 03:58:16

Ce que je suggère est de calculer la largeur du texte, et calculer le cadre par vous-même. Ce n'est pas compliqué de toute façon, obtenez d'abord la largeur du texte:

[NSString sizeWithFont:font];

Faites une opération de mod et vous trouverez facilement le nombre de lignes pour le texte.

Note Cette méthode est pour pré iOS7, pour iOS 7 et après vous voudrez peut-être essayer

[NSString sizeWithAttributes:aDictionary];
1
répondu Xiangdong 2014-12-03 04:22:16

[auto.bouton sizeToFit] devrait fonctionner si votre Autolayout est désactivé. Si vous devez utiliser autolayout, d'autres suggestions (calcul de la largeur de la ligne), etc., sont plus appropriées.

1
répondu Aamir 2014-12-05 07:52:13

J'ai eu le même problème avec UIButton avec du texte multiliné, et il y avait aussi une image. J'ai utilisé sizeThatFits: pour calculer la taille mais il a calculé une mauvaise hauteur.

Je ne l'ai pas fait UIButtonTypeCustom, au lieu de cela j'ai appelé sizeThatFits: sur le bouton titleLabel avec une taille avec une largeur plus petite (en raison de l'image dans le bouton):

CGSize buttonSize = [button sizeThatFits:CGSizeMake(maxWidth, maxHeight)];
CGSize labelSize = [button.titleLabel sizeThatFits:CGSizeMake(maxWidth - offset, maxHeight)]; // offset for image
buttonSize.height = labelSize.height;
buttonFrame.size = buttonSize;

Et puis j'ai utilisé la hauteur de cette taille pour définir correctement le cadre du bouton, et cela a fonctionné:)

Peut-être qu'ils ont un bug dans le dimensionnement interne de UIButton.

1
répondu BSevo 2016-07-01 08:20:40

Remplacez le - (CGSize)intrinsicContentSize dans UIButton personnalisé comme indiqué ci-dessous.

Objectif-C:

-(CGSize)intrinsicContentSize {
    CGSize titleLabelIntrinsicSize = [self.titleLabel intrinsicContentSize];
    return CGSizeMake(titleLabelIntrinsicSize.width + self.contentEdgeInsets.left + self.contentEdgeInsets.right, titleLabelIntrinsicSize.height + self.contentEdgeInsets.top + self.contentEdgeInsets.bottom);
}

Swfit :

override var intrinsicContentSize: CGSize {
        get {
            if let thisSize = self.titleLabel?.intrinsicContentSize {
                return CGSize(width: thisSize.width + self.contentEdgeInsets.left + self.contentEdgeInsets.right, height: thisSize.height + self.contentEdgeInsets.top + self.contentEdgeInsets.bottom)
            }
            return super.intrinsicContentSize
        }
    }
0
répondu Bhavesh Patel 2017-07-31 07:39:09

J'ai dû invalider la taille du contenu intrinsèque lorsque les vues ont été disposées, puis calculer la hauteur du bouton pour la propriété intrinsicContentSize.

Voici le code dans Swift3 / Xcode 9

override func layoutSubviews() {
    self.invalidateIntrinsicContentSize()
    super.layoutSubviews()
}


override var intrinsicContentSize: CGSize {
    return CGSize(width: self.frame.size.width, height: titleLabel!.frame.size.height + contentEdgeInsets.top + contentEdgeInsets.bottom)
}
0
répondu Maria 2018-04-04 15:14:50
class SFResizableButton: UIButton {
  override var intrinsicContentSize: CGSize {
    get {
      var labelSize = CGSize.zero
        if let text = titleLabel?.text, let font = titleLabel?.font {
          labelSize.width = text.width(constrained: .greatestFiniteMagnitude, font: font)
        } else if let att = titleLabel?.attributedText {
          labelSize.width = att.width(constrained: .greatestFiniteMagnitude)
        }
      if let imageView = imageView {
        labelSize.width = labelSize.width + imageView.frame.width
      }
      let desiredButtonSize = CGSize(width: ceil(labelSize.width) + titleEdgeInsets.left + titleEdgeInsets.right + imageEdgeInsets.left + imageEdgeInsets.right, height: labelSize.height + titleEdgeInsets.top + titleEdgeInsets.bottom + imageEdgeInsets.top + imageEdgeInsets.bottom)

        return desiredButtonSize
    }
  }
}
extesion String {
  func width(constrained height: CGFloat, font: UIFont) -> CGFloat {
    let constraintRect = CGSize(width: .greatestFiniteMagnitude, height: height)
      let boundingBox = (self as NSString).boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [NSFontAttributeName: font], context: nil)

      return boundingBox.width
  }
}

extension NSAttributedString {
  func width(constrained height: CGFloat) -> CGFloat {
    let constraintRect = CGSize(width: .greatestFiniteMagnitude, height: height)
      let boundingBox = boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, context: nil)

      return boundingBox.width
  }
}
-1
répondu looseyi 2017-02-10 15:17:01