I have created a view controller that looks like this:
I want the two
Complete class in Swift 3 - based on @Jan, @Quantaliinuxite and @matt bezark:
@IBDesignable
class MultiLineButton:UIButton {
//MARK: -
//MARK: Setup
func setup () {
self.titleLabel?.numberOfLines = 0
//The next two lines are essential in making sure autolayout sizes us correctly
self.setContentHuggingPriority(UILayoutPriorityDefaultLow+1, for: .vertical)
self.setContentHuggingPriority(UILayoutPriorityDefaultLow+1, for: .horizontal)
}
//MARK:-
//MARK: Method overrides
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
override var intrinsicContentSize: CGSize {
return self.titleLabel!.intrinsicContentSize
}
override func layoutSubviews() {
super.layoutSubviews()
titleLabel?.preferredMaxLayoutWidth = self.titleLabel!.frame.size.width
}
}
There is a solution without subclassing on iOS11. Just need to set one additional constraint in code to match height of button
and button.titleLabel
.
ObjC:
// In init or overriden updateConstraints method
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:self.button
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:self.button.titleLabel
attribute:NSLayoutAttributeHeight
multiplier:1
constant:0];
[self addConstraint:constraint];
And in some cases (as said before):
- (void)layoutSubviews {
[super layoutSubviews];
self.button.titleLabel.preferredMaxLayoutWidth = CGRectGetWidth(self.button.titleLabel.frame);
}
Swift:
let constraint = NSLayoutConstraint(item: button,
attribute: .height,
relatedBy: .equal,
toItem: button.titleLabel,
attribute: .height,
multiplier: 1,
constant: 0)
self.addConstraint(constraint)
+
override func layoutSubviews() {
super.layoutSubviews()
button.titleLabel.preferredMaxLayoutWidth = button.titleLabel.frame.width
}
Instead of calling layoutSubviews twice I'd calculate preferredMaxLayoutWidth
manually
@objcMembers class MultilineButton: UIButton {
override var intrinsicContentSize: CGSize {
// override to have the right height with autolayout
get {
var titleContentSize = titleLabel!.intrinsicContentSize
titleContentSize.height += contentEdgeInsets.top + contentEdgeInsets.bottom
return titleContentSize
}
}
override func awakeFromNib() {
super.awakeFromNib()
titleLabel!.numberOfLines = 0
}
override func layoutSubviews() {
let contentWidth = width - contentEdgeInsets.left - contentEdgeInsets.right
let imageWidth = imageView?.width ?? 0 + imageEdgeInsets.left + imageEdgeInsets.right
let titleMaxWidth = contentWidth - imageWidth - titleEdgeInsets.left - titleEdgeInsets.right
titleLabel!.preferredMaxLayoutWidth = titleMaxWidth
super.layoutSubviews()
}
}
tweaks for Swift 3.1
intrisicContentSize is a property instead of a function
override var intrinsicContentSize: CGSize {
return self.titleLabel!.intrinsicContentSize
}
None of the other answers had everything working for me. Here's my answer:
class MultilineButton: UIButton {
func setup() {
titleLabel?.textAlignment = .center
titleLabel?.numberOfLines = 0
titleLabel?.lineBreakMode = .byWordWrapping
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
override var intrinsicContentSize: CGSize {
var titleContentSize = titleLabel?.intrinsicContentSize ?? CGSize.zero
titleContentSize.height += contentEdgeInsets.top + contentEdgeInsets.bottom
titleContentSize.width += contentEdgeInsets.left + contentEdgeInsets.right
return titleContentSize
}
override func layoutSubviews() {
titleLabel?.preferredMaxLayoutWidth = 300 // Or whatever your maximum is
super.layoutSubviews()
}
}
This won't cater for an image, however.
//Swift 4 - Create Dynamic Button MultiLine Dynamic
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
/// Add DemoButton 1
let demoButton1 = buildButton("Demo 1")
//demoButton1.addTarget(self, action: #selector(ViewController.onDemo1Tapped), for: .touchUpInside)
view.addSubview(demoButton1)
view.addConstraint(NSLayoutConstraint(item: demoButton1, attribute: .centerX, relatedBy: .equal, toItem: view, attribute: .centerX, multiplier: 1, constant: 0))
view.addConstraint(NSLayoutConstraint(item: demoButton1, attribute: .centerY, relatedBy: .equal, toItem: view, attribute: .centerY, multiplier: 1, constant: -180))
}
func buildButton(_ title: String) -> UIButton {
let button = UIButton(type: .system)
button.backgroundColor = UIColor(red: 80/255, green: 70/255, blue: 66/255, alpha: 1.0)
//--------------------------
//to make the button multiline
//button.titleLabel!.lineBreakMode = .byWordWrapping
button.titleLabel?.textAlignment = .center
button.titleLabel?.numberOfLines = 0
//button.titleLabel?.adjustsFontSizeToFitWidth = true
//button.sizeToFit()
button.titleLabel?.preferredMaxLayoutWidth = self.view.bounds.width//200
button.layer.borderWidth = 2
let height = NSLayoutConstraint(item: button,
attribute: .height,
relatedBy: .equal,
toItem: button.titleLabel,
attribute: .height,
multiplier: 1,
constant: 0)
button.addConstraint(height)
//--------------------------
button.setTitle(title, for: UIControlState())
button.layer.cornerRadius = 4.0
button.setTitleColor(UIColor(red: 233/255, green: 205/255, blue: 193/255, alpha: 1.0), for: UIControlState())
button.translatesAutoresizingMaskIntoConstraints = false
return button
}
}