アイコンの下にテキストがあるボタン(アプリボタンのようなもの)を作成しようとしていますが、実現するのは非常に難しいようです。画像の下にテキストを表示するにはどうすればいいUIButton
ですか?
https://github.com/albert-zhang/AZCenterLabelButton
(Link)
アイコンの下にテキストがあるボタン(アプリボタンのようなもの)を作成しようとしていますが、実現するのは非常に難しいようです。画像の下にテキストを表示するにはどうすればいいUIButton
ですか?
https://github.com/albert-zhang/AZCenterLabelButton
(Link)
回答:
または、このカテゴリを使用することもできます。
@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
)
}
}
提案:
ボタンの高さが未満の場合totalHeight
、画像は境界線の外側に描画されます。
imageEdgeInset.top
でなければなりません:
max(0, -(totalHeight - imageViewSize.height))
CGRectGetHeight()
し、CGRectGetWidth()
ImageViewのとtitleLabel高さと幅を取得するとき。
CGFloat inset = (self.frame.size.height - totalHeight)/2; self.contentEdgeInsets = UIEdgeInsetsMake(inset, 0.0f, inset, 0.0f);
Xcodeでは、単純にEdge Title Left Insetを設定して画像の幅を負にすることができます。これにより、画像の中央にラベルが表示されます。
画像の下に表示するラベル(アプリボタンのようなもの)を取得するには、Edge Title Top Insetを正の数に設定する必要がある場合があります。
これはオーバーライドすることにより、スウィフトに実装シンプル中心にタイトルボタンであるtitleRect(forContentRect:)
とimageRect(forContentRect:)
。またintrinsicContentSize
、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
}
}
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
)
}
}
left
にimageEdgeInsets
して(self.frame.size.width - imageSize.width) / 2
layoutSubviews()
は、スーパービューでこのメソッドを呼び出します。
サブクラスUIButton
。オーバーライド- layoutSubviews
ビルトインsubviews
を新しい位置に移動するには:
- (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;
}
titleRectForContentRect
とimageRectForContentRect
icecrystal23の答えをリファクタリングしました。
Swift 3は、autolayouts、xib、ストーリーボードで動作し、アニメーション化できます。
元のicecrystal23の答えのボタンは、フレームの計算が不適切でした。私はそれを修正したと思います。
編集: Swift 5に更新され、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()
}
}
ここで答えの1つを修正しました:
スウィフト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
}
}
これは、エリックWの優れた答えの修正版です。ただし、ビューの上部に中央揃えで画像を配置する代わりに、画像とラベルをグループとしてビューの中央に配置します。
違いは次のとおりです。
+-----------+
| ( ) |
| Hello | // Erik W's code
| |
| |
+-----------+
対
+-----------+
| |
| ( ) | // My modified version
| Hello |
| |
+-----------+
以下のソース:
-(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;
}
注意:UISubアニメーションと組み合わせると、layoutSubviewsが呼び出されているときに、これが壊れる可能性があります。
SwiftにおけるDaveのソリューション:
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
}
}
UIButton
およびをサブクラス化するとoverride layoutSubviews
、以下を使用して画像を中央に配置し、タイトルをその中央に配置できます。
kTextTopPadding
画像とその下のテキストの間のスペースを決定するために導入する必要がある定数です。
-(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;
}
sizeWithFontがiOS 7で廃止されたため、Kenny Winkerの回答を更新しました。
-(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;
}
iOS 11 / Swift 4では、上記の答えはどれも実際には機能しませんでした。私はいくつかの例を見つけ、それにスピンをかけました:
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)
}
}
これが誰かを助けることを願っています。
ケニー・ウィンカーとシメオンのコードを使用して、私はこの迅速なコードを私にとってうまくいきます。
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
}
}
画像とタイトルラベルのサイズに基づいて、3つのエッジインセットすべてを調整する必要があります。
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)
タイトルラベルの境界を取得するには、テキストを設定した後でsizeToFitを呼び出します。水平方向の間隔は、テキスト、フォント、画像のサイズに関係なく機能するはずですが、垂直方向の間隔とコンテンツの下端のインセットを一致させる単一の解決策は知りません。
のサブクラスとしての「Bear With Me」の答えは次のとおりSwift 2.0
です。それだけであなたのボタンクラスを変更する使用するInterface Builder
とVerticalButton
、それは魔法のプレビューを更新します。
また、自動レイアウトの正しい固有のコンテンツサイズを計算するように更新しました。
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)
}
}
layoutSubviews()
:私の場合は繰り返し呼び出されintrinsicContentSize
、アクセスimageView
可能 layoutSubviews
どのアクセスと呼ばれているimageView
など
@ティアゴ私はあなたの答えをこのように変えます。すべてのサイズで問題なく動作します
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
)
}
私はここで答えの組み合わせを取り、Swiftで私にとってはうまくいくように見えるものを思いつきました。インセットをオーバーライドする方法は好きではありませんが、機能します。私はコメントで改善の提案を受け入れるでしょう。sizeToFit()
自動レイアウトで正しく動作するようです。
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
}
}
この2つの方法を使用します。
func titleRect(forContentRect contentRect: CGRect) -> CGRect
func imageRect(forContentRect contentRect: CGRect) -> CGRect
例:
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)
}
}
そしてあなたは使うことができます:
let myButton = UIButton.vertical(padding: 6)
Swift 5-以下の方法は私のために機能します
func centerVerticallyWithPadding(padding : CGFloat) {
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: max(0, -(totalHeight - imageViewSize.height)),
left: 0.0,
bottom: 0.0,
right: -titleLabelSize.width
)
self.titleEdgeInsets = UIEdgeInsets(
top: (totalHeight - imageViewSize.height),
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
)
}
ボタンのタイトルがストーリーボード/ xibで切り捨てられていないことを確認してください。
ソリューション2に
class SVVerticalButton: UIButton {
override func layoutSubviews() {
super.layoutSubviews()
let padding : CGFloat = 2.0
if let imageView = self.imageView {
imageView.frame.origin.x = (self.bounds.size.width - imageView.frame.size.width) / 2.0
imageView.frame.origin.y = max(0,(self.bounds.size.height - (imageView.frame.size.height + (titleLabel?.frame.size.height ?? 0.0) + padding)) / 2.0)
}
if let titleLabel = self.titleLabel {
titleLabel.frame.origin.x = 0
titleLabel.frame.origin.y = self.bounds.size.height - titleLabel.frame.size.height
titleLabel.frame.size.width = self.bounds.size.width
titleLabel.textAlignment = .center
}
}
}
Simeonの回答がおそらく最良であることがわかりましたが、一部のボタンで奇妙な結果が得られ、その理由を理解できませんでした。したがって、彼の答えをベースとして使用して、以下のようにボタンを実装しました。
#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
UIButton
これがこの問題を解決する私のサブクラスです:
@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
それでおしまい。魅力のように機能します。タイトルとテキストに合わせてボタンを小さくすると問題が発生する場合があります。
そのための最良の方法の1つは、UIButtonをサブクラス化していくつかのレンダリングメソッドをオーバーライドすることです。
- (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);
}
Objective-Cでの@simeonのソリューション
#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
CGSizeMake(MAX(labelSize.width, image.size.width), image.size.height + labelSize.height + 5.0)
if-caseに戻るだけで十分です
カスタムフォントを使用している場合、titleLabelサイズの計算が正しく機能しない場合は、
let titleLabelSize = self.titleLabel?.text?.size(withAttributes: [NSAttributedStringKey.font: self.titleLabel!.font!])
アイコンとテキストをエッジインセットで配置しようとするのではなく、画像を添付ファイルとしてNSAttributedStringを作成し、代わりにボタンの属性付きタイトルに設定できます。
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)
UIButton
さんのテキストフィールドのレイアウトは、属性付き文字列に改行を使用した場合でも、それゆえ、それは動作しません、唯一の1行を表示します。それ以外の場合は良い解決策になります。
button.titleLabel?.numberOfLines
必要な行数を得るために設定することも重要です
ローカライズに適したソリューション:
非常に多くの優れたソリューションを提供していますが、ローカリゼーションを使用する人のために、ここにメモを追加したいと思います。
言語方向がLtRからRtLに変更された場合にボタンを正しくレイアウトするには、左右のEdgeInstets値を逆にする必要があります。
同様のソリューションを使用して、次のように実装します。
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
)
}
}
UIButtonをサブクラス化した上部の画像と下部のタイトルボタン
class VerticalButton: UIButton {
override func layoutSubviews() {
super.layoutSubviews()
let padding: CGFloat = 8
let iH = imageView?.frame.height ?? 0
let tH = titleLabel?.frame.height ?? 0
let v: CGFloat = (frame.height - iH - tH - padding) / 2
if let iv = imageView {
let x = (frame.width - iv.frame.width) / 2
iv.frame.origin.y = v
iv.frame.origin.x = x
}
if let tl = titleLabel {
let x = (frame.width - tl.frame.width) / 2
tl.frame.origin.y = frame.height - tl.frame.height - v
tl.frame.origin.x = x
}
}
}
それは間違いなくこの質問にはやりすぎです...私のプロジェクトの1つでは、アイコンを左端に揃えてボタンを実装する必要がありました。次に、画像の下にタイトルのある別のボタンを取得しました。私は既存の解決策を探しましたが、運がありませんでした。そこで、ここに整列可能なボタンがあります:
@IBDesignable
class AlignableButton: UIButton {
override class var requiresConstraintBasedLayout: Bool {
return true
}
@objc enum IconAlignment: Int {
case top, left, right, bottom
}
// MARK: - Designables
@IBInspectable var iconAlignmentValue: Int {
set {
iconAlignment = IconAlignment(rawValue: newValue) ?? .left
}
get {
return iconAlignment.rawValue
}
}
var iconAlignment: IconAlignment = .left
@IBInspectable var titleAlignmentValue: Int {
set {
titleAlignment = NSTextAlignment(rawValue: newValue) ?? .left
}
get {
return titleAlignment.rawValue
}
}
var titleAlignment: NSTextAlignment = .left
// MARK: - Corner Radius
@IBInspectable
var cornerRadius: CGFloat {
get {
return layer.cornerRadius
}
set {
layer.masksToBounds = (newValue != 0)
layer.cornerRadius = newValue
}
}
// MARK: - Content size
override var intrinsicContentSize: CGSize {
switch iconAlignment {
case .top, .bottom:
return verticalAlignedIntrinsicContentSize
default:
return super.intrinsicContentSize
}
}
private var verticalAlignedIntrinsicContentSize: CGSize {
if let imageSize = imageView?.intrinsicContentSize,
let labelSize = titleLabel?.intrinsicContentSize {
let width = max(imageSize.width, labelSize.width) + contentEdgeInsets.left + contentEdgeInsets.right
let height = imageSize.height + labelSize.height + contentEdgeInsets.top + contentEdgeInsets.bottom
return CGSize(
width: ceil(width),
height: ceil(height)
)
}
return super.intrinsicContentSize
}
// MARK: - Image Rect
override func imageRect(forContentRect contentRect: CGRect) -> CGRect {
switch iconAlignment {
case .top:
return topAlignedImageRect(forContentRect: contentRect)
case .bottom:
return bottomAlignedImageRect(forContentRect: contentRect)
case .left:
return leftAlignedImageRect(forContentRect: contentRect)
case .right:
return rightAlignedImageRect(forContentRect: contentRect)
}
}
func topAlignedImageRect(forContentRect contentRect: CGRect) -> CGRect {
let rect = super.imageRect(forContentRect: contentRect)
let x = (contentRect.width - rect.width) / 2.0 + contentRect.minX
let y = contentRect.minY
let w = rect.width
let h = rect.height
return CGRect(
x: x,
y: y,
width: w,
height: h
).inset(by: imageEdgeInsets)
}
func bottomAlignedImageRect(forContentRect contentRect: CGRect) -> CGRect {
let rect = super.imageRect(forContentRect: contentRect)
let x = (contentRect.width - rect.width) / 2.0 + contentRect.minX
let y = contentRect.height - rect.height + contentRect.minY
let w = rect.width
let h = rect.height
return CGRect(
x: x,
y: y,
width: w,
height: h
).inset(by: imageEdgeInsets)
}
func leftAlignedImageRect(forContentRect contentRect: CGRect) -> CGRect {
let rect = super.imageRect(forContentRect: contentRect)
let x = contentRect.minX
let y = (contentRect.height - rect.height) / 2 + contentRect.minY
let w = rect.width
let h = rect.height
return CGRect(
x: x,
y: y,
width: w,
height: h
).inset(by: imageEdgeInsets)
}
func rightAlignedImageRect(forContentRect contentRect: CGRect) -> CGRect {
let rect = super.imageRect(forContentRect: contentRect)
let x = (contentRect.width - rect.width) + contentRect.minX
let y = (contentRect.height - rect.height) / 2 + contentRect.minY
let w = rect.width
let h = rect.height
return CGRect(
x: x,
y: y,
width: w,
height: h
).inset(by: imageEdgeInsets)
}
// MARK: - Title Rect
override func titleRect(forContentRect contentRect: CGRect) -> CGRect {
switch iconAlignment {
case .top:
return topAlignedTitleRect(forContentRect: contentRect)
case .bottom:
return bottomAlignedTitleRect(forContentRect: contentRect)
case .left:
return leftAlignedTitleRect(forContentRect: contentRect)
case .right:
return rightAlignedTitleRect(forContentRect: contentRect)
}
}
func topAlignedTitleRect(forContentRect contentRect: CGRect) -> CGRect {
let rect = super.titleRect(forContentRect: contentRect)
let x = contentRect.minX
let y = contentRect.height - rect.height + contentRect.minY
let w = contentRect.width
let h = rect.height
return CGRect(
x: x,
y: y,
width: w,
height: h
).inset(by: titleEdgeInsets)
}
func bottomAlignedTitleRect(forContentRect contentRect: CGRect) -> CGRect {
let rect = super.titleRect(forContentRect: contentRect)
let x = contentRect.minX
let y = contentRect.minY
let w = contentRect.width
let h = rect.height
return CGRect(
x: x,
y: y,
width: w,
height: h
).inset(by: titleEdgeInsets)
}
func leftAlignedTitleRect(forContentRect contentRect: CGRect) -> CGRect {
let titleRect = super.titleRect(forContentRect: contentRect)
let imageRect = self.imageRect(forContentRect: contentRect)
let x = imageRect.width + imageRect.minX
let y = (contentRect.height - titleRect.height) / 2.0 + contentRect.minY
let w = contentRect.width - imageRect.width * 2.0
let h = titleRect.height
return CGRect(
x: x,
y: y,
width: w,
height: h
).inset(by: titleEdgeInsets)
}
func rightAlignedTitleRect(forContentRect contentRect: CGRect) -> CGRect {
let titleRect = super.titleRect(forContentRect: contentRect)
let imageRect = self.imageRect(forContentRect: contentRect)
let x = contentRect.minX + imageRect.width
let y = (contentRect.height - titleRect.height) / 2.0 + contentRect.minY
let w = contentRect.width - imageRect.width * 2.0
let h = titleRect.height
return CGRect(
x: x,
y: y,
width: w,
height: h
).inset(by: titleEdgeInsets)
}
// MARK: - Lifecycle
override func awakeFromNib() {
super.awakeFromNib()
titleLabel?.textAlignment = titleAlignment
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
titleLabel?.textAlignment = titleAlignment
}
}
お役に立てば幸いです。
UIButton
サブクラス内のこのようなもの
public override func layoutSubviews() {
super.layoutSubviews()
imageEdgeInsets = UIEdgeInsetsMake(-10, 0, 0, 0)
titleEdgeInsets = UIEdgeInsetsMake(0, -bounds.size.width/2 - 10, -30, 0)
}
とてもシンプルです。
これの代わりに:
button.setImage(UIImage(named: "image"), forState: .Normal)
これを使って:
button.setBackgroundImage(UIImage(named: "image", forState: .Normal)
次に、ボタンを使用して簡単にテキストを追加できます。
// button.titleLabel!.font = UIFont(name: "FontName"、size:30)
button.setTitle("TitleText", forState: UIControlState.Normal)