I'm trying to make growing textview and make it scrollable at some content size. What i'm doing is:
func textViewDidChange(_ textView: UITextView) {
if textView.contentSize.height > 100 && !textView.isScrollEnabled {
let frame = textView.frame
textView.isScrollEnabled = true
let heightConstraint: NSLayoutConstraint = .init(item: textView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: frame.height)
heightConstraint.identifier = "height"
textView.addConstraint(heightConstraint)
}
if textView.contentSize.height < 100 && textView.isScrollEnabled {
textView.isScrollEnabled = false
let heightConstraint = textView.constraints.first { constraint in
constraint.identifier == "height"
}
textView.removeConstraint(heightConstraint!)
}
print(textView.contentSize.height, textView.frame.height)
}
if content size is greater than 100 i enable the scroll and add constraint for height
if content size is less than 100 i disable the scroll and remove the constraint (i expect that textview to fit to its content, as it usually does when scroll is disabled)
i have no problems with enabling the scroll, but when contentsize becomes less than 100, the second if statement fires and the textview for some reason takes the whole screen space. when i call textViewDidChange again (by deleting or adding smth to textview) both ifs fire and everything works as it should be.
what i tried to do is to call textview.sizeToFit() and view.layoutIfNeeded() but had no success. what am i doing wrong here?
I think you're doing a lot more than you need to do.
A better approach would be to create the Height constraint, equal to a constant value of 100, and then activate/de-activate it as needed.
Try it like this:
class ViewController: UIViewController, UITextViewDelegate {
let myTextView = UITextView()
var tvHeightConstraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
myTextView.translatesAutoresizingMaskIntoConstraints = false
myTextView.font = .systemFont(ofSize: 18.0)
myTextView.backgroundColor = .yellow
view.addSubview(myTextView)
let g = view.safeAreaLayoutGuide
tvHeightConstraint = myTextView.heightAnchor.constraint(equalToConstant: 100.0)
NSLayoutConstraint.activate([
myTextView.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
myTextView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
myTextView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -40.0),
])
myTextView.isScrollEnabled = false
myTextView.delegate = self
}
func textViewDidChange(_ textView: UITextView) {
textView.isScrollEnabled = (textView.contentSize.height > 100)
tvHeightConstraint.isActive = textView.isScrollEnabled
}
}
Related
I'm using swift + UIKit to autolayout a view programatically...
private func autoLayout(for child: UIView, in parent: UIView, width inset: CGFloat = 0) {
parent.addSubview(child)
child.translatesAutoresizingMaskIntoConstraints = false
child.topAnchor .constraint(equalTo: parent.safeAreaLayoutGuide.topAnchor, constant: inset) .isActive = true
child.bottomAnchor .constraint(equalTo: parent.safeAreaLayoutGuide.bottomAnchor, constant: -inset).isActive = true
child.leadingAnchor .constraint(equalTo: parent.safeAreaLayoutGuide.leadingAnchor, constant: inset) .isActive = true
child.trailingAnchor.constraint(equalTo: parent.safeAreaLayoutGuide.trailingAnchor, constant: -inset).isActive = true
}
In one instance I'm calling the method on a UILabel (with a set font size: 24), but I'd like to add a height constraint that adjusts the parent view to be 2.4x the child's height. Something like this inside the method...
if let label = child as? UILabel {
let value = child.frame.height * 2.4
parent.heightAnchor.constraint(equalToConstant: value).isActive = true
}
If I use the equalToConstant: parameter, then the size will be calculated when the method is called, which happens before the views are added to a UIStackView and their sizes are adjusted (essentially if I set the parent's constant equal to say child.frame.height * 2.4 it will calculate when the height is still zero).
How can I achieve this dynamically?
All you need to do is create a height constraint on the parent that is 2.4 times the height constraint of the child (label).
Here's an updated version of your code:
private func autoLayout(for child: UIView, in parent: UIView, width inset: CGFloat = 0) {
parent.addSubview(child)
child.translatesAutoresizingMaskIntoConstraints = false
child.leadingAnchor .constraint(equalTo: parent.safeAreaLayoutGuide.leadingAnchor, constant: inset) .isActive = true
child.trailingAnchor.constraint(equalTo: parent.safeAreaLayoutGuide.trailingAnchor, constant: -inset).isActive = true
if child is UILabel {
// This alternate syntax for creating a constraint allows you to set a multiplier
NSLayoutConstraint(item: parent, attribute: .height, relatedBy: .equal, toItem: child, attribute: .height, multiplier: 2.4, constant: 0).isActive = true
child.centerYAnchor.constraint(equalTo: parent.safeAreaLayoutGuide.centerYAnchor).isActive = true
} else {
child.topAnchor .constraint(equalTo: parent.safeAreaLayoutGuide.topAnchor, constant: inset) .isActive = true
child.bottomAnchor.constraint(equalTo: parent.safeAreaLayoutGuide.bottomAnchor, constant: -inset).isActive = true
}
}
I made an assumption that for the label you just want the label to be centered in the parent in addition to the parent's height being 2.4 times the child height. Adjust as needed.
I'm setting up a constraint in viewDidLoad and then changing its constant when the keyboard shows up.
This is the initial set Up
bottomConstraint = NSLayoutConstraint(item: bottomBar, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: .bottom, multiplier: 1, constant: 0)
view.addConstraint(bottomConstraint)
Then I change the constant when I receive a keyboard notification:
#objc func handleKeyboardNotification(notification: NSNotification){
if let keyboardFrame: NSValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue {
let keyboardRectangle = keyboardFrame.cgRectValue
let keyboardHeight = keyboardRectangle.height
let isKeyboardShowing = notification.name == UIResponder.keyboardWillShowNotification
bottomConstraint?.constant = isKeyboardShowing ? -keyboardHeight : 0
UIView.animate(withDuration:0.1, delay: 0 , options: .curveEaseOut , animations: {
self.view.layoutIfNeeded()
} , completion: {(completed) in
})
}
}
It's interesting because I'm only changing the constant and not adding another constraint. Nevertheless I receive this warning in console:
Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want.
Try this:
(1) look at each constraint and try to figure out which you don't expect;
(2) find the code that added the unwanted constraint or constraints and fix it.
(
"<NSLayoutConstraint:0x282ae4ff0 UIView:0x109f11110.bottom == UIView:0x109f13240.bottom (active)>",
"<NSLayoutConstraint:0x282ae8050 UIView:0x109f11110.bottom == UIView:0x109f13240.bottom - 291 (active)>"
)
Which basically states that the constraints I have don't work well togheter.
I don't know what I'm doing wrong.
EDIT:
This is the code I use to add the bottom bar programmatically:
let bottomBar:UIView = {
let v = UIView()
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
In ViewdidLoad()
view.addSubview(bottomBar)
bottomBar.addSubview(fontView)
bottomBar.addSubview(colorPicker)
fontView.pin(to: bottomBar)
colorPicker.pin(to: bottomBar)
func setUpConstraints(){
NSLayoutConstraint.activate([
bottomBar.leadingAnchor.constraint(equalTo: view.leadingAnchor),
bottomBar.trailingAnchor.constraint(equalTo: view.trailingAnchor),
bottomBar.heightAnchor.constraint(equalToConstant: 70),
])
}
Attempting to make it a bit easier for you...
Start simple.
This code creates a text field near the top, and a red "bottomBar" view at the bottom. When the keyboard shows or hides, the bottomBar's bottom constraint constant is updated (tap anywhere on the view to dismiss the keyboard):
class ConstraintTestViewController: UIViewController {
let bottomBar = UIView()
var bottomConstraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
// add a text field
let tf = UITextField()
tf.borderStyle = .roundedRect
tf.translatesAutoresizingMaskIntoConstraints = false
bottomBar.translatesAutoresizingMaskIntoConstraints = false
bottomBar.backgroundColor = .red
view.addSubview(tf)
view.addSubview(bottomBar)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// constrain text field 80-pts from Top, Width: 240, centerX
tf.topAnchor.constraint(equalTo: g.topAnchor, constant: 80.0),
tf.widthAnchor.constraint(equalToConstant: 240.0),
tf.centerXAnchor.constraint(equalTo: g.centerXAnchor),
// constrain bottomBar Leading and Trailing, Height: 70-pts
bottomBar.leadingAnchor.constraint(equalTo: g.leadingAnchor),
bottomBar.trailingAnchor.constraint(equalTo: g.trailingAnchor),
bottomBar.heightAnchor.constraint(equalToConstant: 70.0),
])
// create and add the bottom constraint
bottomConstraint = NSLayoutConstraint(item: bottomBar, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: .bottom, multiplier: 1, constant: 0)
view.addConstraint(bottomConstraint)
// or, use more modern syntax...
//bottomConstraint = bottomBar.bottomAnchor.constraint(equalTo: g.bottomAnchor)
//bottomConstraint.isActive = true
// keyboard show/hide notifications
NotificationCenter.default.addObserver(self,
selector: #selector(self.handleKeyboardNotification(notification:)),
name: UIResponder.keyboardWillShowNotification,
object: nil)
NotificationCenter.default.addObserver(self,
selector: #selector(self.handleKeyboardNotification(notification:)),
name: UIResponder.keyboardWillHideNotification,
object: nil)
// add a "tap on the view to dismiss the keyboard" gesture
let t = UITapGestureRecognizer(target: self, action: #selector(self.didTap(_:)))
view.addGestureRecognizer(t)
}
#objc func handleKeyboardNotification(notification: NSNotification){
if let keyboardFrame: NSValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue {
let keyboardRectangle = keyboardFrame.cgRectValue
let keyboardHeight = keyboardRectangle.height
let isKeyboardShowing = notification.name == UIResponder.keyboardWillShowNotification
bottomConstraint.constant = isKeyboardShowing ? -keyboardHeight : 0
UIView.animate(withDuration:0.1, delay: 0 , options: .curveEaseOut , animations: {
self.view.layoutIfNeeded()
} , completion: {(completed) in
})
}
}
#objc func didTap(_ g: UITapGestureRecognizer) -> Void {
view.endEditing(true)
}
}
If this runs without auto-layout errors / warnings (which it will), then start adding in your other UI elements (and supporting code) one at a time. If / when you get the constraint conflict again, you'll know exactly where you went wrong.
You are not providing enough information, so it is necessary to guess. Here is one guess. When you say:
fontView.pin(to: bottomBar)
colorPicker.pin(to: bottomBar)
You create two bottomBar bottom constraints. When you say:
bottomConstraint = NSLayoutConstraint(item: bottomBar, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: .bottom, multiplier: 1, constant: 0)
view.addConstraint(bottomConstraint)
you create a third bottom constraint.
Then when you say
bottomConstraint?.constant = isKeyboardShowing ? -keyboardHeight : 0
you change one bottom constraint but not the others. Perhaps that accounts for the conflict.
Another possibility is that perhaps you call the view.addConstraint twice in some way that you have not shown us. Again, that would mean you are changing one bottom constraint but not the other.
I have a reusable view class that I call when I want to add a disappearing subView to another view. I have a UILabel extension to determine when there is to much text for the label's current size(this extension works), and within this closure I'm trying to expand the: contianerView(regView)'s height, the label's height, and the label's height anchor, since the label was created programatically. As you guessed, the label isn't expandng correctly.
I've tried setting the numberOfLines to 0; changing the label's frame; using .layoutSubviews; removing when the height anchor was originally set, so now it's only called when the resize view method is used.
Label extension:
extension UILabel {
var isTruncated: Bool {
guard let labelText = text else {
return false
}
let labelTextSize = (labelText as NSString).boundingRect(
with: CGSize(width: frame.size.width, height: .greatestFiniteMagnitude),
options: .usesLineFragmentOrigin,
attributes: [.font: font],
context: nil).size
return labelTextSize.height > bounds.size.height
}
}
function to add reusable view(most of it is within the while loop towards the bottom):
func addDisapearingView(toview: UIView, text: String, textColor: UIColor, colorView: UIColor, alpha: CGFloat, height: CGFloat){
regView.backgroundColor = colorView
regView.alpha = alpha
toview.addSubview(regView)
regView.translatesAutoresizingMaskIntoConstraints = false
if #available(iOS 11.0, *) {
let guide = toview.safeAreaLayoutGuide
regView.trailingAnchor.constraint(equalTo: guide.trailingAnchor).isActive = true
regView.leadingAnchor.constraint(equalTo: guide.leadingAnchor).isActive = true
regView.topAnchor.constraint(equalTo: guide.topAnchor).isActive = true
UIView.animate(withDuration: 5.0) {
self.regView.heightAnchor.constraint(equalToConstant: height).isActive = true
}
} else {
NSLayoutConstraint(item: regView,
attribute: .top,
relatedBy: .equal,
toItem: toview, attribute: .top,
multiplier: 1.0, constant: 0).isActive = true
NSLayoutConstraint(item: regView,
attribute: .leading,
relatedBy: .equal, toItem: toview,
attribute: .leading,
multiplier: 1.0,
constant: 0).isActive = true
NSLayoutConstraint(item: regView, attribute: .trailing,
relatedBy: .equal,
toItem: toview,
attribute: .trailing,
multiplier: 1.0,
constant: 0).isActive = true
regView.heightAnchor.constraint(equalToConstant: height).isActive = true
}
let label = UILabel(frame: CGRect(x: regView.frame.origin.x, y: regView.frame.origin.y, width: regView.bounds.width, height: regView.bounds.height))
label.numberOfLines = 0
label.adjustsFontSizeToFitWidth = true
label.lineBreakMode = NSLineBreakMode.byWordWrapping
label.translatesAutoresizingMaskIntoConstraints = false
label.center.x = newView.center.x
label.center.y = newView.center.y
label.textAlignment = .center
label.text = text
label.textColor = textColor
regView.addSubview(label)
if label.isTruncated {
print("LABEL IS TRUNCATED")
}
//test if there is more text than the label has room for
while label.isTruncated {
print("printing while truncating in the wHiLE loop")
regView.bounds.size.height += 5
label.bounds.size.height += 5
var currentLabelHeight = label.bounds.height
let amt = currentLabelHeight + 5
label.frame = CGRect(x: regView.frame.origin.x, y: regView.frame.origin.y, width: regView.bounds.width, height: CGFloat(amt))
var heighT : CGFloat = height
heighT += 5
regView.heightAnchor.constraint(equalToConstant: heighT).isActive = true
}
regView.layoutSubviews()
label.sizeToFit()
//remove
Timer.scheduledTimer(withTimeInterval: 2.8, repeats: false) { (action) in
UIView.animate(withDuration: 2.8, animations: {
self.regView.heightAnchor.constraint(equalToConstant: 0).isActive = true
label.heightAnchor.constraint(equalToConstant: 0).isActive = true
})
}
}
I've briefly done this before in storyboard where I had to expand a label within another view when the text was too long(this time it did work!), and the important part there was editing the height constraint, so I think this might have something to do with modifying the height constraint.
Any help would be greatly appreciated!
ANSWER:
I asked another question here: Programatically Created Label Within Container View Won't Expand For Text
it has the same code here and everything in the question but the answer works.
If i understand correct, you have your view and a label, and you want your view to dynamically change height depend on label content. I suggest you to break that task to chunks and resolve it step by step.
1 - You might want to add a test UIView object instead of label with fixed size. When u do this, you will see whether you parent view expand depending of test view size.
2 - If it is, you are up to create a label with height you need. All that you need to know its font, text and width. I think this link may help you. After you sure, that your label size is correct (you may want to print it out) you may add it as any other UIView object to your parent view.
I have a set of AL constraints positioning a child vc that has two positions, expanded and collapsed.
I found that when I add the collapsed constraint, a top anchor to bottom anchor constraint with a constant, when the vc is first created, there seems to be additional spacing when I activate it. Seemingly because the actual height isn't available at the time.
When I add the constraint in viewDidLayoutSubviews there additional spacing is gone and the constraint behaves properly. Except the issue that now when I switch between the constraints in an animation, I cannot deactivate the collapsed constraint as I switch to the expanded constraint and the constraint breaks. Possibly because viewDidLayoutSubviews is called throughout the transition animation.
Here's an abstract of vc setup.
var foregroundExpandedConstraint: NSLayoutConstraint!
var foregroundCollapsedConstraint: NSLayoutConstraint!
var foregroundViewController: UIViewController? {
didSet {
setupforegroundViewController(foregroundViewController: foregroundViewController!)
}
}
func setupforegroundViewController(foregroundViewController: UIViewController) {
addChildViewController(foregroundViewController)
foregroundViewController.didMove(toParentViewController: self)
guard let foregroundView = foregroundViewController.view else { return }
foregroundView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(foregroundView)
foregroundExpandedConstraint = foregroundView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 15)
let height = view.safeAreaLayoutGuide.layoutFrame.height - 50 - 15
let cellHeight = ((height) / 6)
foregroundCollapsedConstraint = NSLayoutConstraint(item: foregroundView, attribute: .top, relatedBy: .equal, toItem: view.safeAreaLayoutGuide, attribute: .bottom, multiplier: 1, constant: (-cellHeight) * 2 - 50)
let foregroundViewControllerViewConstraints = [
foregroundView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
foregroundView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
foregroundView.heightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.heightAnchor, constant: -50 - 15),
foregroundExpandedConstraint!
]
NSLayoutConstraint.activate(foregroundViewControllerViewConstraints)
}
And here the animations are preformed using UIViewPropertyAnimator.
func animateTransitionIfNeeded(state: ForegroundState, duration: TimeInterval) {
let containerFrameAnimator = UIViewPropertyAnimator(duration: duration, dampingRatio: 1) {
[unowned self] in
switch state {
case .expanded:
self.foregroundCollapsedConstraint?.isActive = false
self.foregroundExpandedConstraint?.isActive = true
self.view.layoutIfNeeded()
case .collapsed:
self.foregroundExpandedConstraint?.isActive = false
self.foregroundCollapsedConstraint?.isActive = true
self.view.layoutIfNeeded()
}
}
containerFrameAnimator.addCompletion { [weak self] (position) in
if position == .start {
switch state {
case .collapsed:
self?.foregroundCollapsedConstraint?.isActive = false
self?.foregroundExpandedConstraint?.isActive = true
self?.foregroundIsExpanded = true
self?.view.layoutIfNeeded()
case .expanded:
self?.foregroundExpandedConstraint?.isActive = false
self?.foregroundCollapsedConstraint?.isActive = true
self?.foregroundIsExpanded = false
self?.view.layoutIfNeeded()
}
} else if position == .end {
switch state {
case .collapsed:
self?.foregroundExpandedConstraint?.isActive = false
self?.foregroundCollapsedConstraint?.isActive = true
self?.foregroundIsExpanded = false
case .expanded:
self?.foregroundExpandedConstraint?.isActive = false
self?.foregroundCollapsedConstraint?.isActive = true
self?.foregroundIsExpanded = true
}
}
self?.runningAnimations.removeAll()
}
Again to reiterate, when I use the following code, setting the constraint as the vc is added to the view hierarchy, it doesn't layout properly. Checking the constraints I see they change after view did layout subviews is called. Each constraint changes appropriately except for the collapsed constraint.
When I add the collapsed constraint in view did layout subviews it behaves properly however I am unable to deactivate it going forwards and the constraint breaks.
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let height = view.safeAreaLayoutGuide.layoutFrame.height - 50 - 15
let cellHeight = ((height) / 6)
if let v = foregroundViewController?.view {
foregroundCollapsedConstraint = NSLayoutConstraint(item: v, attribute: .top, relatedBy: .equal, toItem: view.safeAreaLayoutGuide, attribute: .bottom, multiplier: 1, constant: (-cellHeight) * 2 - 50)
}
}
Edit: I've created a repo demonstrating the issue: https://github.com/louiss98/UIViewPropertyAnimator-Layout-Test
Any suggestions?
You can eliminate the "broken" constraint by changing the constant instead of creating a new constraint.
In your viewDidLayoutSubviews() func,
change:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let height = view.safeAreaLayoutGuide.layoutFrame.height - 50 - 15
let cellHeight = ((height) / 6)
foregroundCollapsedConstraint = NSLayoutConstraint(item: testViewController.view, attribute: .top, relatedBy: .equal, toItem: view.safeAreaLayoutGuide, attribute: .bottom, multiplier: 1, constant: (-cellHeight) * 2 - 50)
}
to:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let height = view.safeAreaLayoutGuide.layoutFrame.height - 50 - 15
let cellHeight = ((height) / 6)
foregroundCollapsedConstraint.constant = (-cellHeight) * 2 - 50
}
I have a UIView that I'm appending into a stack view on my main page of my app
This is the class of my view:
class MyCustomView: UIView {
public let leftLabel: UILabel = UILabel(frame: .zero)
public let rightLabel: UILabel = UILabel(frame: .zero)
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(leftLabel)
addSubview(rightLabel)
leftLabel.translatesAutoresizingMaskIntoConstraints = false
rightLabel.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
leftLabel.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 0.4),
leftLabel.leadingAnchor.constraint(equalTo: leadingAnchor),
leftLabel.topAnchor.constraint(equalTo: topAnchor),
leftLabel.bottomAnchor.constraint(equalTo: bottomAnchor),
rightLabel.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 0.6),
rightLabel.trailingAnchor.constraint(equalTo: trailingAnchor),
rightLabel.topAnchor.constraint(equalTo: topAnchor),
rightLabel.bottomAnchor.constraint(equalTo: bottomAnchor),
])
leftLabel.text = "Short string"
rightLabel.text = "Short string too"
}
}
And I append to my main stack view with:
let myCustomView = MyCustomView(frame: .zero)
stackView.addArrangedSubview(myCustomView)
This loads in my label's correctly and resizes everything as I'd want.
However, in my main class, I am updating myCustomView.rightLabel.text = <New Way Longer Text That Takes 2 Lines Instead of One>
The text is updating properly, but my myCustomView size is not resizing, so part of the text is just being cut-off
I have tried following other answers on here, but none of them seem to work for me.
Am I missing something small in order to force the resize of the customView to fit the label inside of it?
Thank you in advance
Your code does not show that you set the .numberOfLines in the label(s) to 0 to allow for multi-line labels.
Adding only that, should allow your labels to grow in height and to expand your custom view. However... that will also make both labels expand to the size of the tallest label, resulting in the text of the "shorter" label being vertically centered (I added background colors to make it easy to see the frames / bounds of the views):
If you constrain the Bottom of your custom view to the Bottom of each label at greaterThanOrEqualTo you can keep the labels "top-aligned":
You can run this code directly in a Playground page to see the results:
//: A UIKit based Playground for presenting user interface
import UIKit
import PlaygroundSupport
class MyCustomView: UIView {
public let leftLabel: UILabel = UILabel(frame: .zero)
public let rightLabel: UILabel = UILabel(frame: .zero)
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(leftLabel)
addSubview(rightLabel)
// background colors so we can see the view frames
backgroundColor = .cyan
leftLabel.backgroundColor = .yellow
rightLabel.backgroundColor = .green
// we want multi-line labels
leftLabel.numberOfLines = 0
rightLabel.numberOfLines = 0
// use auto-layout
leftLabel.translatesAutoresizingMaskIntoConstraints = false
rightLabel.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
// constrain to top
leftLabel.topAnchor.constraint(equalTo: topAnchor),
// constrain to left
leftLabel.leadingAnchor.constraint(equalTo: leadingAnchor),
// constrain width = 40%
leftLabel.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 0.4),
// constrain to top
rightLabel.topAnchor.constraint(equalTo: topAnchor),
// constrain to right
rightLabel.trailingAnchor.constraint(equalTo: trailingAnchor),
// constrain width = 60%
rightLabel.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 0.6),
// constrain bottom of view (self) to >= 0 from bottom of leftLabel
bottomAnchor.constraint(greaterThanOrEqualTo: leftLabel.bottomAnchor, constant: 0.0),
// constrain bottom of view (self) to >= 0 from bottom of rightLabel
bottomAnchor.constraint(greaterThanOrEqualTo: rightLabel.bottomAnchor, constant: 0.0),
])
leftLabel.text = "Short string"
rightLabel.text = "Short string too"
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class MyViewController : UIViewController {
var theButton: UIButton = {
let b = UIButton()
b.setTitle("Tap Me", for: .normal)
b.translatesAutoresizingMaskIntoConstraints = false
b.backgroundColor = .red
return b
}()
var theStackView: UIStackView = {
let v = UIStackView()
v.translatesAutoresizingMaskIntoConstraints = false
v.axis = .vertical
v.spacing = 8
v.distribution = .equalSpacing
return v
}()
var myView = MyCustomView()
// on button tap, change the text in the label(s)
#objc func didTap(_ sender: Any?) -> Void {
myView.leftLabel.text = "Short string with\nA\nB\nC\nD\nE"
myView.rightLabel.text = "Short string too\nA\nB"
}
override func loadView() {
let view = UIView()
self.view = view
view.backgroundColor = .white
view.addSubview(theButton)
// constrain button to Top: 32 and centerX
NSLayoutConstraint.activate([
theButton.topAnchor.constraint(equalTo: view.topAnchor, constant: 32.0),
theButton.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0.0),
])
view.addSubview(theStackView)
// constrain stack view to Top: 100 and Leading/Trailing" 0
// no Bottom or Height constraint
NSLayoutConstraint.activate([
theStackView.topAnchor.constraint(equalTo: view.topAnchor, constant: 100.0),
theStackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0.0),
theStackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0.0),
])
theStackView.addArrangedSubview(myView)
// add an action for the button tap
theButton.addTarget(self, action: #selector(didTap(_:)), for: .touchUpInside)
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()