J'essaie de créer un bouton qui contient du texte sous l'icône (en quelque sorte, comme les boutons de l'application), mais il semble être assez difficile à obtenir. Toutes les idées, comment puis-je obtenir le texte à afficher ci-dessous l'image avec un UIButton
?
Ou vous pouvez simplement utiliser cette catégorie:
@interface UIButton (VerticalLayout)
- (void)centerVerticallyWithPadding:(float)padding;
- (void)centerVertically;
@end
@implementation UIButton (VerticalLayout)
- (void)centerVerticallyWithPadding:(float)padding {
CGSize imageSize = self.imageView.frame.size;
CGSize titleSize = self.titleLabel.frame.size;
CGFloat totalHeight = (imageSize.height + titleSize.height + padding);
self.imageEdgeInsets = UIEdgeInsetsMake(- (totalHeight - imageSize.height),
0.0f,
0.0f,
- titleSize.width);
self.titleEdgeInsets = UIEdgeInsetsMake(0.0f,
- imageSize.width,
- (totalHeight - titleSize.height),
0.0f);
self.contentEdgeInsets = UIEdgeInsetsMake(0.0f,
0.0f,
titleSize.height,
0.0f);
}
- (void)centerVertically {
const CGFloat kDefaultPadding = 6.0f;
[self centerVerticallyWithPadding:kDefaultPadding];
}
@end
extension UIButton {
func centerVertically(padding: CGFloat = 6.0) {
guard
let imageViewSize = self.imageView?.frame.size,
let titleLabelSize = self.titleLabel?.frame.size else {
return
}
let totalHeight = imageViewSize.height + titleLabelSize.height + padding
self.imageEdgeInsets = UIEdgeInsets(
top: -(totalHeight - imageViewSize.height),
left: 0.0,
bottom: 0.0,
right: -titleLabelSize.width
)
self.titleEdgeInsets = UIEdgeInsets(
top: 0.0,
left: -imageViewSize.width,
bottom: -(totalHeight - titleLabelSize.height),
right: 0.0
)
self.contentEdgeInsets = UIEdgeInsets(
top: 0.0,
left: 0.0,
bottom: titleLabelSize.height,
right: 0.0
)
}
}
Dans Xcode, vous pouvez simplement définir l’encadré gauche de titre de bord sur la largeur de l’image en négatif. Cela affichera l'étiquette au centre de l'image.
Pour que l'étiquette apparaisse sous l'image (en quelque sorte comme les boutons de l'application), vous devrez peut-être définir l'encart supérieur du titre de contour sur un nombre positif.
Il s'agit d'un simple bouton de titre centré implémenté dans Swift en remplaçant titleRect(forContentRect:)
et imageRect(forContentRect:)
.]. Il implémente également intrinsicContentSize
à utiliser avec AutoLayout. .
import UIKit
class CenteredButton: UIButton
{
override func titleRect(forContentRect contentRect: CGRect) -> CGRect {
let rect = super.titleRect(forContentRect: contentRect)
return CGRect(x: 0, y: contentRect.height - rect.height + 5,
width: contentRect.width, height: rect.height)
}
override func imageRect(forContentRect contentRect: CGRect) -> CGRect {
let rect = super.imageRect(forContentRect: contentRect)
let titleRect = self.titleRect(forContentRect: contentRect)
return CGRect(x: contentRect.width/2.0 - rect.width/2.0,
y: (contentRect.height - titleRect.height)/2.0 - rect.height/2.0,
width: rect.width, height: rect.height)
}
override var intrinsicContentSize: CGSize {
let size = super.intrinsicContentSize
if let image = imageView?.image {
var labelHeight: CGFloat = 0.0
if let size = titleLabel?.sizeThatFits(CGSize(width: self.contentRect(forBounds: self.bounds).width, height: CGFloat.greatestFiniteMagnitude)) {
labelHeight = size.height
}
return CGSize(width: size.width, height: image.size.height + labelHeight + 5)
}
return size
}
override init(frame: CGRect) {
super.init(frame: frame)
centerTitleLabel()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
centerTitleLabel()
}
private func centerTitleLabel() {
self.titleLabel?.textAlignment = .center
}
}
Regardez ceci excellente réponse dans Swift.
extension UIButton {
func alignImageAndTitleVertically(padding: CGFloat = 6.0) {
let imageSize = self.imageView!.frame.size
let titleSize = self.titleLabel!.frame.size
let totalHeight = imageSize.height + titleSize.height + padding
self.imageEdgeInsets = UIEdgeInsets(
top: -(totalHeight - imageSize.height),
left: 0,
bottom: 0,
right: -titleSize.width
)
self.titleEdgeInsets = UIEdgeInsets(
top: 0,
left: -imageSize.width,
bottom: -(totalHeight - titleSize.height),
right: 0
)
}
}
Sous-classe UIButton
. Remplacez -layoutSubviews
pour déplacer le subviews
intégré dans de nouveaux emplacements:
- (void)layoutSubviews
{
[super layoutSubviews];
CGRect frame = self.imageView.frame;
frame = CGRectMake(truncf((self.bounds.size.width - frame.size.width) / 2), 0.0f, frame.size.width, frame.size.height);
self.imageView.frame = frame;
frame = self.titleLabel.frame;
frame = CGRectMake(truncf((self.bounds.size.width - frame.size.width) / 2), self.bounds.size.height - frame.size.height, frame.size.width, frame.size.height);
self.titleLabel.frame = frame;
}
corrigé l'une des réponses ici:
Swift 3:
class CenteredButton: UIButton
{
override func titleRect(forContentRect contentRect: CGRect) -> CGRect {
let rect = super.titleRect(forContentRect: contentRect)
let imageRect = super.imageRect(forContentRect: contentRect)
return CGRect(x: 0, y: imageRect.maxY + 10,
width: contentRect.width, height: rect.height)
}
override func imageRect(forContentRect contentRect: CGRect) -> CGRect {
let rect = super.imageRect(forContentRect: contentRect)
let titleRect = self.titleRect(forContentRect: contentRect)
return CGRect(x: contentRect.width/2.0 - rect.width/2.0,
y: (contentRect.height - titleRect.height)/2.0 - rect.height/2.0,
width: rect.width, height: rect.height)
}
override init(frame: CGRect) {
super.init(frame: frame)
centerTitleLabel()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
centerTitleLabel()
}
private func centerTitleLabel() {
self.titleLabel?.textAlignment = .center
}
}
Si vous sous-classe UIButton
et override layoutSubviews
, vous pouvez utiliser le texte ci-dessous pour centrer l'image et placer le titre au centre, en dessous:
kTextTopPadding
est une constante que vous devez introduire et qui détermine l'espace entre l'image et le texte situé en dessous.
-(void)layoutSubviews {
[super layoutSubviews];
// Move the image to the top and center it horizontally
CGRect imageFrame = self.imageView.frame;
imageFrame.Origin.y = 0;
imageFrame.Origin.x = (self.frame.size.width / 2) - (imageFrame.size.width / 2);
self.imageView.frame = imageFrame;
// Adjust the label size to fit the text, and move it below the image
CGRect titleLabelFrame = self.titleLabel.frame;
CGSize labelSize = [self.titleLabel.text sizeWithFont:self.titleLabel.font
constrainedToSize:CGSizeMake(self.frame.size.width, CGFLOAT_MAX)
lineBreakMode:NSLineBreakByWordWrapping];
titleLabelFrame.size.width = labelSize.width;
titleLabelFrame.size.height = labelSize.height;
titleLabelFrame.Origin.x = (self.frame.size.width / 2) - (labelSize.width / 2);
titleLabelFrame.Origin.y = self.imageView.frame.Origin.y + self.imageView.frame.size.height + kTextTopPadding;
self.titleLabel.frame = titleLabelFrame;
}
Ceci est une version modifiée de l'excellente réponse de Erik W. Mais au lieu de placer l'image au centre de la vue, elle place l'image et l'étiquette au centre de la vue en tant que groupe.
La différence est:
+-----------+
| ( ) |
| Hello | // Erik W's code
| |
| |
+-----------+
contre
+-----------+
| |
| ( ) | // My modified version
| Hello |
| |
+-----------+
Source ci-dessous:
-(void)layoutSubviews {
[super layoutSubviews];
CGRect titleLabelFrame = self.titleLabel.frame;
CGSize labelSize = [self.titleLabel.text sizeWithFont:self.titleLabel.font constrainedToSize:CGSizeMake(self.frame.size.width, CGFLOAT_MAX) lineBreakMode:NSLineBreakByWordWrapping];
CGRect imageFrame = self.imageView.frame;
CGSize fitBoxSize = (CGSize){.height = labelSize.height + kTextTopPadding + imageFrame.size.height, .width = MAX(imageFrame.size.width, labelSize.width)};
CGRect fitBoxRect = CGRectInset(self.bounds, (self.bounds.size.width - fitBoxSize.width)/2, (self.bounds.size.height - fitBoxSize.height)/2);
imageFrame.Origin.y = fitBoxRect.Origin.y;
imageFrame.Origin.x = CGRectGetMidX(fitBoxRect) - (imageFrame.size.width/2);
self.imageView.frame = imageFrame;
// Adjust the label size to fit the text, and move it below the image
titleLabelFrame.size.width = labelSize.width;
titleLabelFrame.size.height = labelSize.height;
titleLabelFrame.Origin.x = (self.frame.size.width / 2) - (labelSize.width / 2);
titleLabelFrame.Origin.y = fitBoxRect.Origin.y + imageFrame.size.height + kTextTopPadding;
self.titleLabel.frame = titleLabelFrame;
}
FYI: Cela peut casser quand il est combiné avec des animations UIView, puisque layoutSubviews est appelé pendant ces animations.
Refonte de la réponse de icecrystal23.
Swift 3, fonctionne avec des autolayouts, xib, storyboards, peut être animé.
Le bouton dans la réponse originale de icecrystal23 avait un cadre mal calculé. Je pense avoir corrigé ça.
Edit: Mis à jour en Swift 5 et fonctionné dans Interface Builder/Storyboards
import UIKit
@IBDesignable
class VerticalButton: UIButton {
@IBInspectable public var padding: CGFloat = 20.0 {
didSet {
setNeedsLayout()
}
}
override var intrinsicContentSize: CGSize {
let maxSize = CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)
if let titleSize = titleLabel?.sizeThatFits(maxSize), let imageSize = imageView?.sizeThatFits(maxSize) {
let width = ceil(max(imageSize.width, titleSize.width))
let height = ceil(imageSize.height + titleSize.height + padding)
return CGSize(width: width, height: height)
}
return super.intrinsicContentSize
}
override func layoutSubviews() {
if let image = imageView?.image, let title = titleLabel?.attributedText {
let imageSize = image.size
let titleSize = title.size()
titleEdgeInsets = UIEdgeInsets(top: 0.0, left: -imageSize.width, bottom: -(imageSize.height + padding), right: 0.0)
imageEdgeInsets = UIEdgeInsets(top: -(titleSize.height + padding), left: 0.0, bottom: 0.0, right: -titleSize.width)
}
super.layoutSubviews()
}
}
La solution de Dave dans Swift:
override func layoutSubviews() {
super.layoutSubviews()
if let imageView = self.imageView {
imageView.frame.Origin.x = (self.bounds.size.width - imageView.frame.size.width) / 2.0
imageView.frame.Origin.y = 0.0
}
if let titleLabel = self.titleLabel {
titleLabel.frame.Origin.x = (self.bounds.size.width - titleLabel.frame.size.width) / 2.0
titleLabel.frame.Origin.y = self.bounds.size.height - titleLabel.frame.size.height
}
}
Mise à jour de la réponse de Kenny Winker depuis que sizeWithFont était obsolète dans iOS 7.
-(void)layoutSubviews {
[super layoutSubviews];
int kTextTopPadding = 3;
CGRect titleLabelFrame = self.titleLabel.frame;
CGRect labelSize = [self.titleLabel.text boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, CGRectGetHeight(self.bounds)) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:self.titleLabel.font} context:nil];
CGRect imageFrame = self.imageView.frame;
CGSize fitBoxSize = (CGSize){.height = labelSize.size.height + kTextTopPadding + imageFrame.size.height, .width = MAX(imageFrame.size.width, labelSize.size.width)};
CGRect fitBoxRect = CGRectInset(self.bounds, (self.bounds.size.width - fitBoxSize.width)/2, (self.bounds.size.height - fitBoxSize.height)/2);
imageFrame.Origin.y = fitBoxRect.Origin.y;
imageFrame.Origin.x = CGRectGetMidX(fitBoxRect) - (imageFrame.size.width/2);
self.imageView.frame = imageFrame;
// Adjust the label size to fit the text, and move it below the image
titleLabelFrame.size.width = labelSize.size.width;
titleLabelFrame.size.height = labelSize.size.height;
titleLabelFrame.Origin.x = (self.frame.size.width / 2) - (labelSize.size.width / 2);
titleLabelFrame.Origin.y = fitBoxRect.Origin.y + imageFrame.size.height + kTextTopPadding;
self.titleLabel.frame = titleLabelFrame;
}
Sur iOS 11/Swift 4), aucune des réponses ci-dessus n'a vraiment fonctionné pour moi. J'ai trouvé quelques exemples et j'ai mis ma réaction à profit:
extension UIButton {
func centerImageAndButton(_ gap: CGFloat, imageOnTop: Bool) {
guard let imageView = self.currentImage,
let titleLabel = self.titleLabel?.text else { return }
let sign: CGFloat = imageOnTop ? 1 : -1
self.titleEdgeInsets = UIEdgeInsetsMake((imageView.size.height + gap) * sign, -imageView.size.width, 0, 0);
let titleSize = titleLabel.size(withAttributes:[NSAttributedStringKey.font: self.titleLabel!.font!])
self.imageEdgeInsets = UIEdgeInsetsMake(-(titleSize.height + gap) * sign, 0, 0, -titleSize.width)
}
}
J'espère que ça aide quelqu'un.
En utilisant le code de Kenny Winker et Siméon, je crée ce code Swift qui fonctionne pour moi.
import UIKit
@IBDesignable
class TopIconButton: UIButton {
override func layoutSubviews() {
super.layoutSubviews()
let kTextTopPadding:CGFloat = 3.0;
var titleLabelFrame = self.titleLabel!.frame;
let labelSize = titleLabel!.sizeThatFits(CGSizeMake(CGRectGetWidth(self.contentRectForBounds(self.bounds)), CGFloat.max))
var imageFrame = self.imageView!.frame;
let fitBoxSize = CGSizeMake(max(imageFrame.size.width, labelSize.width), labelSize.height + kTextTopPadding + imageFrame.size. height)
let fitBoxRect = CGRectInset(self.bounds, (self.bounds.size.width - fitBoxSize.width)/2, (self.bounds.size.height - fitBoxSize. height)/2);
imageFrame.Origin.y = fitBoxRect.Origin.y;
imageFrame.Origin.x = CGRectGetMidX(fitBoxRect) - (imageFrame.size.width/2);
self.imageView!.frame = imageFrame;
// Adjust the label size to fit the text, and move it below the image
titleLabelFrame.size.width = labelSize.width;
titleLabelFrame.size.height = labelSize.height;
titleLabelFrame.Origin.x = (self.frame.size.width / 2) - (labelSize.width / 2);
titleLabelFrame.Origin.y = fitBoxRect.Origin.y + imageFrame.size.height + kTextTopPadding;
self.titleLabel!.frame = titleLabelFrame;
self.titleLabel!.textAlignment = .Center
}
}
Il vous suffit d'ajuster les trois encarts Edge en fonction de la taille de votre image et de l'intitulé du titre:
button.contentEdgeInsets = UIEdgeInsetsMake(0, 0, titleLabelBounds.height + 4, 0)
button.titleEdgeInsets = UIEdgeInsetsMake(image.size.height + 8, -image.size.width, 0, 0)
button.imageEdgeInsets = UIEdgeInsetsMake(0, 0, 0, -titleLabelBounds.width)
Vous pouvez obtenir les limites de l'étiquette du titre en appelant sizeToFit après avoir défini son texte. L'espacement horizontal devrait fonctionner quelle que soit la taille du texte, de la police de caractères et de l'image, mais je ne connais pas de solution unique permettant de rendre cohérents l'espacement vertical et le contenu du bas.
Voici la réponse de "Bear With Me" en tant que sous-classe dans Swift 2.0
. Pour l'utiliser, il suffit de changer votre classe de boutons dans Interface Builder
à VerticalButton
et l’aperçu sera mis à jour comme par magie.
Je l'ai également mis à jour pour calculer la taille de contenu intrinsèque correcte pour l'autolayout.
import UIKit
@IBDesignable
class VerticalButton: UIButton {
@IBInspectable var padding: CGFloat = 8
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
update()
}
override func layoutSubviews() {
super.layoutSubviews()
update()
}
func update() {
let imageBounds = self.imageView!.bounds
let titleBounds = self.titleLabel!.bounds
let totalHeight = CGRectGetHeight(imageBounds) + padding + CGRectGetHeight(titleBounds)
self.imageEdgeInsets = UIEdgeInsets(
top: -(totalHeight - CGRectGetHeight(imageBounds)),
left: 0,
bottom: 0,
right: -CGRectGetWidth(titleBounds)
)
self.titleEdgeInsets = UIEdgeInsets(
top: 0,
left: -CGRectGetWidth(imageBounds),
bottom: -(totalHeight - CGRectGetHeight(titleBounds)),
right: 0
)
}
override func intrinsicContentSize() -> CGSize {
let imageBounds = self.imageView!.bounds
let titleBounds = self.titleLabel!.bounds
let width = CGRectGetWidth(imageBounds) > CGRectGetWidth(titleBounds) ? CGRectGetWidth(imageBounds) : CGRectGetWidth(titleBounds)
let height = CGRectGetHeight(imageBounds) + padding + CGRectGetHeight(titleBounds)
return CGSizeMake(width, height)
}
}
J'ai trouvé que réponse de Simeon était probablement le meilleur, mais cela me donnait des résultats étranges sur certains boutons et je ne pouvais tout simplement pas comprendre pourquoi. Donc, en utilisant sa réponse comme base, j'ai implémenté mes boutons comme indiqué ci-dessous:
#define PADDING 2.0f
@implementation OOButtonVerticalImageText
-(CGSize) intrinsicContentSize {
CGSize size = [super intrinsicContentSize];
CGFloat labelHeight = 0.0f;
CGSize titleSize = [self.titleLabel sizeThatFits:CGSizeMake([self contentRectForBounds:self.bounds].size.width, CGFLOAT_MAX)];
labelHeight = titleSize.height;
return CGSizeMake(MAX(titleSize.width, self.imageView.image.size.width), self.imageView.image.size.height + labelHeight + PADDING);
}
-(void) layoutSubviews {
[super layoutSubviews];
CGSize titleSize = [self.titleLabel sizeThatFits:CGSizeMake([self contentRectForBounds:self.bounds].size.width, CGFLOAT_MAX)];
self.titleLabel.frame = CGRectMake((self.bounds.size.width - titleSize.width)/2.0f,
self.bounds.size.height - titleSize.height - PADDING,
titleSize.width,
titleSize.height);
CGSize ivSize = self.imageView.frame.size;
self.imageView.frame = CGRectMake((self.bounds.size.width - ivSize.width)/2.0f,
self.titleLabel.frame.Origin.y - ivSize.height - PADDING,
ivSize.width,
ivSize.height);
}
@end
@Tiago je change votre réponse comme ça. Cela fonctionne bien avec toutes les tailles
func alignImageAndTitleVertically(padding: CGFloat = 5.0) {
self.sizeToFit()
let imageSize = self.imageView!.frame.size
let titleSize = self.titleLabel!.frame.size
let totalHeight = imageSize.height + titleSize.height + padding
self.imageEdgeInsets = UIEdgeInsets(
top: -(totalHeight - imageSize.height),
left: 0,
bottom: 0,
right: -titleSize.width
)
self.titleEdgeInsets = UIEdgeInsets(
top: 0,
left: 0,
bottom: -(totalHeight - titleSize.height),
right: titleSize.height
)
}
Utilisez ces deux méthodes:
func titleRect(forContentRect contentRect: CGRect) -> CGRect
func imageRect(forContentRect contentRect: CGRect) -> CGRect
Exemple:
class VerticalButton: UIButton {
override func titleRect(forContentRect contentRect: CGRect) -> CGRect {
let titleRect = super.titleRect(forContentRect: contentRect)
let imageRect = super.imageRect(forContentRect: contentRect)
return CGRect(x: 0,
y: contentRect.height - (contentRect.height - padding - imageRect.size.height - titleRect.size.height) / 2 - titleRect.size.height,
width: contentRect.width,
height: titleRect.height)
}
override func imageRect(forContentRect contentRect: CGRect) -> CGRect {
let imageRect = super.imageRect(forContentRect: contentRect)
let titleRect = self.titleRect(forContentRect: contentRect)
return CGRect(x: contentRect.width/2.0 - imageRect.width/2.0,
y: (contentRect.height - padding - imageRect.size.height - titleRect.size.height) / 2,
width: imageRect.width,
height: imageRect.height)
}
private let padding: CGFloat
init(padding: CGFloat) {
self.padding = padding
super.init(frame: .zero)
self.titleLabel?.textAlignment = .center
}
required init?(coder aDecoder: NSCoder) { fatalError() }
}
extension UIButton {
static func vertical(padding: CGFloat) -> UIButton {
return VerticalButton(padding: padding)
}
}
Et vous pouvez utiliser:
let myButton = UIButton.vertical(padding: 6)
J'ai pris une combinaison de réponses ici et en ai trouvé une qui semble fonctionner pour moi, à Swift. Je n'aime pas que je remplace les encarts, mais ça marche. Je serais ouvert aux suggestions d'améliorations dans les commentaires. Cela semble fonctionner correctement avec sizeToFit()
et avec la mise en page automatique.
import UIKit
/// A button that displays an image centered above the title. This implementation
/// only works when both an image and title are set, and ignores
/// any changes you make to Edge insets.
class CenteredButton: UIButton
{
let padding: CGFloat = 0.0
override func layoutSubviews() {
if imageView?.image != nil && titleLabel?.text != nil {
let imageSize: CGSize = imageView!.image!.size
titleEdgeInsets = UIEdgeInsetsMake(0.0, -imageSize.width, -(imageSize.height + padding), 0.0)
let labelString = NSString(string: titleLabel!.text!)
let titleSize = labelString.sizeWithAttributes([NSFontAttributeName: titleLabel!.font])
imageEdgeInsets = UIEdgeInsetsMake(-(titleSize.height + padding), 0.0, 0.0, -titleSize.width)
let edgeOffset = abs(titleSize.height - imageSize.height) / 2.0;
contentEdgeInsets = UIEdgeInsetsMake(edgeOffset, 0.0, edgeOffset, 0.0)
}
super.layoutSubviews()
}
override func sizeThatFits(size: CGSize) -> CGSize {
let defaultSize = super.sizeThatFits(size)
if let titleSize = titleLabel?.sizeThatFits(size),
let imageSize = imageView?.sizeThatFits(size) {
return CGSize(width: ceil(max(imageSize.width, titleSize.width)), height: ceil(imageSize.height + titleSize.height + padding))
}
return defaultSize
}
override func intrinsicContentSize() -> CGSize {
let size = sizeThatFits(CGSize(width: CGFloat.max, height: CGFloat.max))
return size
}
}
Solution adaptée à la localisation:
Autant de solutions géniales, mais j'aimerais ajouter une note ici pour ceux qui utilisent la localisation.
Vous devez inverser les valeurs EdgeInstets gauche et droite pour que le bouton soit correctement agencé en cas de changement de direction de la langue de LtR à RtL.
En utilisant une solution similaire, je l’implémenterais comme suit:
extension UIButton {
func alignVertical(spacing: CGFloat, lang: String) {
guard let imageSize = self.imageView?.image?.size,
let text = self.titleLabel?.text,
let font = self.titleLabel?.font
else { return }
let labelString = NSString(string: text)
let titleSize = labelString.size(
withAttributes: [NSAttributedString.Key.font: font]
)
var titleLeftInset: CGFloat = -imageSize.width
var titleRigtInset: CGFloat = 0.0
var imageLeftInset: CGFloat = 0.0
var imageRightInset: CGFloat = -titleSize.width
if Locale.current.languageCode! != "en" { // If not Left to Right language
titleLeftInset = 0.0
titleRigtInset = -imageSize.width
imageLeftInset = -titleSize.width
imageRightInset = 0.0
}
self.titleEdgeInsets = UIEdgeInsets(
top: 0.0,
left: titleLeftInset,
bottom: -(imageSize.height + spacing),
right: titleRigtInset
)
self.imageEdgeInsets = UIEdgeInsets(
top: -(titleSize.height + spacing),
left: imageLeftInset,
bottom: 0.0,
right: imageRightInset
)
let edgeOffset = abs(titleSize.height - imageSize.height) / 2.0;
self.contentEdgeInsets = UIEdgeInsets(
top: edgeOffset,
left: 0.0,
bottom: edgeOffset,
right: 0.0
)
}
}
Je pense que l’un des meilleurs moyens de le faire est de sous-classer UIButton et de remplacer certaines méthodes de rendu:
- (void)awakeFromNib
{
[super awakeFromNib];
[self setupSubViews];
}
- (instancetype)init
{
if (self = [super init])
{
[self setupSubViews];
}
return self;
}
- (void)setupSubViews
{
[self.imageView setTranslatesAutoresizingMaskIntoConstraints:NO];
[self addConstraint:[NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.imageView attribute:NSLayoutAttributeCenterX multiplier:1 constant:0]];
[self.titleLabel setTranslatesAutoresizingMaskIntoConstraints:NO];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[imageView][titleLabel]|" options:NSLayoutFormatAlignAllCenterX metrics:nil views:@{@"imageView": self.imageView, @"titleLabel": self.titleLabel}]];
}
- (CGSize)intrinsicContentSize
{
CGSize imageSize = self.imageView.image.size;
CGSize titleSize = [self.titleLabel.text sizeWithAttributes:@{NSFontAttributeName: self.titleLabel.font}];
return CGSizeMake(MAX(imageSize.width, titleSize.width), imageSize.height + titleSize.height);
}
Si vous utilisez polices personnalisées le calcul de la taille de titleLabel ne fonctionnera pas correctement, remplacez-le par
let titleLabelSize = self.titleLabel?.text?.size(withAttributes: [NSAttributedStringKey.font: self.titleLabel!.font!])
Voici ma sous-classe de UIButton
qui résout ce problème:
@implementation MyVerticalButton
@synthesize titleAtBottom; // BOOL property
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.titleAtBottom = YES;
}
return self;
}
- (CGSize)sizeThatFits:(CGSize)size {
self.titleLabel.text = [self titleForState: self.state];
UIEdgeInsets imageInsets = self.imageEdgeInsets;
UIEdgeInsets titleInsets = self.titleEdgeInsets;
CGSize imageSize = [self imageForState: self.state].size;
if (!CGSizeEqualToSize(imageSize, CGSizeZero)) {
imageSize.width += imageInsets.left + imageInsets.right;
imageSize.height += imageInsets.top + imageInsets.bottom;
}
CGSize textSize = [self.titleLabel sizeThatFits: CGSizeMake(size.width - titleInsets.left - titleInsets.right,
size.height -(imageSize.width +
titleInsets.top+titleInsets.bottom))];
if (!CGSizeEqualToSize(textSize, CGSizeZero)) {
textSize.width += titleInsets.left + titleInsets.right;
textSize.height += titleInsets.top + titleInsets.bottom;
}
CGSize result = CGSizeMake(MAX(textSize.width, imageSize.width),
textSize.height + imageSize.height);
return result;
}
- (void)layoutSubviews {
// needed to update all properities of child views:
[super layoutSubviews];
CGRect bounds = self.bounds;
CGRect titleFrame = UIEdgeInsetsInsetRect(bounds, self.titleEdgeInsets);
CGRect imageFrame = UIEdgeInsetsInsetRect(bounds, self.imageEdgeInsets);
if (self.titleAtBottom) {
CGFloat titleHeight = [self.titleLabel sizeThatFits: titleFrame.size].height;
titleFrame.Origin.y = CGRectGetMaxY(titleFrame)-titleHeight;
titleFrame.size.height = titleHeight;
titleFrame = CGRectStandardize(titleFrame);
self.titleLabel.frame = titleFrame;
CGFloat imageBottom = CGRectGetMinY(titleFrame)-(self.titleEdgeInsets.top+self.imageEdgeInsets.bottom);
imageFrame.size.height = imageBottom - CGRectGetMinY(imageFrame);
self.imageView.frame = CGRectStandardize(imageFrame);
} else {
CGFloat titleHeight = [self.titleLabel sizeThatFits: titleFrame.size].height;
titleFrame.size.height = titleHeight;
titleFrame = CGRectStandardize(titleFrame);
self.titleLabel.frame = titleFrame;
CGFloat imageTop = CGRectGetMaxY(titleFrame)+(self.titleEdgeInsets.bottom+self.imageEdgeInsets.top);
imageFrame.size.height = CGRectGetMaxY(imageFrame) - imageTop;
self.imageView.frame = CGRectStandardize(imageFrame);
}
}
- (void)setTitleAtBottom:(BOOL)newTitleAtBottom {
if (titleAtBottom!=newTitleAtBottom) {
titleAtBottom=newTitleAtBottom;
[self setNeedsLayout];
}
}
@end
C'est ça. Fonctionne comme un charme. Un problème peut apparaître si le bouton est trop petit pour contenir le titre et le texte.
@ simeon's solution en Objective-C
#import "CenteredButton.h"
@implementation CenteredButton
- (CGRect)titleRectForContentRect:(CGRect)contentRect
{
CGRect rect = [super titleRectForContentRect: contentRect];
return CGRectMake(0,
contentRect.size.height - rect.size.height - 5,
contentRect.size.width,
rect.size.height);
}
- (CGRect)imageRectForContentRect:(CGRect)contentRect
{
CGRect rect = [super imageRectForContentRect: contentRect];
CGRect titleRect = [self titleRectForContentRect: contentRect];
return CGRectMake(contentRect.size.width / 2.0 - rect.size.width / 2.0,
(contentRect.size.height - titleRect.size.height)/2.0 - rect.size.height/2.0,
rect.size.width,
rect.size.height);
}
- (CGSize)intrinsicContentSize {
CGSize imageSize = [super intrinsicContentSize];
if (self.imageView.image) {
UIImage* image = self.imageView.image;
CGFloat labelHeight = 0.0;
CGSize labelSize = [self.titleLabel sizeThatFits: CGSizeMake([self contentRectForBounds: self.bounds].size.width, CGFLOAT_MAX)];
if (CGSizeEqualToSize(imageSize, labelSize)) {
labelHeight = imageSize.height;
}
return CGSizeMake(MAX(labelSize.width, imageSize.width), image.size.height + labelHeight + 5);
}
return imageSize;
}
- (id) initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self centerTitleLabel];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
[self centerTitleLabel];
}
return self;
}
- (void)centerTitleLabel {
self.titleLabel.textAlignment = NSTextAlignmentCenter;
}
@end
iOS 11 - Objective-C
-(void)layoutSubviews {
[super layoutSubviews];
CGRect titleLabelFrame = self.titleLabel.frame;
CGSize labelSize = [self.titleLabel.text sizeWithAttributes:@{NSFontAttributeName: [UIFont systemFontOfSize:12.0f]}];
CGSize adjustedLabelSize = CGSizeMake(ceilf(labelSize.width), ceilf(labelSize.height));
CGRect imageFrame = self.imageView.frame;
CGSize fitBoxSize = (CGSize){.height = adjustedLabelSize.height + kTextTopPadding + imageFrame.size.height, .width = MAX(imageFrame.size.width, adjustedLabelSize.width)};
CGRect fitBoxRect = CGRectInset(self.bounds, (self.bounds.size.width - fitBoxSize.width)/2, (self.bounds.size.height - fitBoxSize.height)/2);
imageFrame.Origin.y = fitBoxRect.Origin.y;
imageFrame.Origin.x = CGRectGetMidX(fitBoxRect) - (imageFrame.size.width/2);
self.imageView.frame = imageFrame;
// Adjust the label size to fit the text, and move it below the image
titleLabelFrame.size.width = adjustedLabelSize.width;
titleLabelFrame.size.height = adjustedLabelSize.height;
titleLabelFrame.Origin.x = (self.frame.size.width / 2) - (adjustedLabelSize.width / 2);
titleLabelFrame.Origin.y = fitBoxRect.Origin.y + imageFrame.size.height + kTextTopPadding;
self.titleLabel.frame = titleLabelFrame; }
C'est assez simple.
Au lieu de cela:
button.setImage(UIImage(named: "image"), forState: .Normal)
Utilisez ceci:
button.setBackgroundImage(UIImage(named: "image", forState: .Normal)
Ensuite, vous pouvez facilement ajouter du texte sur le bouton en utilisant:
// button.titleLabel! .font = UIFont (nom: "Nom de la police", taille: 30)
button.setTitle("TitleText", forState: UIControlState.Normal)
Au lieu d'essayer de positionner l'icône et le texte avec des incrustations Edge, vous pouvez créer un NSAttributedString avec votre image en pièce jointe et le définir sur le titre attribué à votre bouton:
let titleText = NSAttributedString(string: yourTitle, attributes: attributes)
let imageAttachment = NSTextAttachment()
imageAttachment.image = yourImage
let title = NSMutableAttributedString(string: "")
title.append(NSAttributedString(attachment: imageAttachment))
title.append(titleText)
button.setAttributedTitle(title, for: .normal)
Quelque chose comme ça à l'intérieur de la sous-classe UIButton
public override func layoutSubviews() {
super.layoutSubviews()
imageEdgeInsets = UIEdgeInsetsMake(-10, 0, 0, 0)
titleEdgeInsets = UIEdgeInsetsMake(0, -bounds.size.width/2 - 10, -30, 0)
}