I created stackView and added three labels to it. Also I put these constraints
let nameLabel = UILabel()
//set .text, .mode and other for nameLabel
let ellipsisLabel = UILabel()
//set .text, .mode and other for ellipsisLabel
let amountAndMeasureLabel = UILabel()
//set .text, .mode and other for amountAndMeasureLabel
for label in [nameLabel, ellipsisLabel, amountAndMeasureLabel] {
stackView.addArrangedSubview(label)
}
NSLayoutConstraint.activate([
nameLabel.leadingAnchor.constraint(equalTo: stackView.leadingAnchor, constant: 0.0),
nameLabel.trailingAnchor.constraint(equalTo: ellipsisLabel.leadingAnchor, constant: 0.0),
nameLabel.widthAnchor.constraint(equalToConstant: nameLabel.intrinsicContentSize.width),
amountAndMeasureLabel.leadingAnchor.constraint(equalTo: ellipsisLabel.trailingAnchor, constant: 0.0),
amountAndMeasureLabel.trailingAnchor.constraint(equalTo: stackView.trailingAnchor, constant: 0.0),
amountAndMeasureLabel.widthAnchor.constraint(equalToConstant: amountAndMeasureLabel.intrinsicContentSize.width)
])
nameLabel.frame.width // 0
ellipsisLabel.frame.width // 0
amountAndMeasureLabel.frame.width // 0
I will demonstrate the situation on .xib for better understanding (I don't use this .xib)
But when I tried to get their .frame.width I got zeros.
Just activating constraints doesn't cause a view to layout. If you need stackView to immediately layout its subviews, you need to tell it to:
stackView.layoutIfNeeded()
Otherwise the system will automatically collect and apply all layout updates together the next time it's needed. Since forcing a layout may lead to unnecessary computations (if something later in the draw cycle changes the layout), you should generally avoid calling .layoutIfNeeded() unless you really need it. But it's fully supported if you do.
There is also .setNeedsLayout(), which tells the system that a view needs layout during the next draw cycle. You should use this if something changes layout in a way that the system won't notice. You generally don't need this if your changes are through UIKit itself. UIStackView knows that it needs layout after you call .addArrangedSubview. But if you change some piece of data that your layout relies on, you may need to tell UIKit.
Related
Sorry for the extremely basic question, but I'm trying to better understand MVC and how it pertains to more advanced screens.
Say I have a exampleView, that has multiple labels in it and I want to add this view to a ScrollView using addSubview. Should the ScrollView be instantiated in the View Controller and then calling self.view.addSubview(scrollView) and self.scrollView.addSubview(exampleView), or would you turn exampleView into a scrollView and just adding self.view.addSubview(exampleScrollView).
My friend has told me that there should never be programmatic constraints in the view controller if we're following MVC, but I'm not sure if that's true or not.
Constraints have nothing to do with MVC. MVC is a programming architecture or design pattern that attempts to divide the program into three categories: data (model), the visual objects that often represent the data (view), and the objects that bind them together (controller). And constraints are simply a way of positioning visual objects. The way you position visual objects has nothing to do with the architecture you decide for your program; they are completely independent of each other.
And the idea that a view controller should never have programmatic constraints is just ridiculous. Not only do constraints (let alone programmatic constraints) have nothing to do with MVC, forbidding the use of programmatic constraints for any reason is a sign of a very poor developer. You can code an entire application programmatically but you cannot even come close to coding an entire application non-programmatically.
The interface builder, in my opinion, is mostly there for professional programmers who program for a number of clients and find themselves constantly adding view objects (views, labels, buttons, scroll views, table views, etc.) because it's drag and drop. I personally find the whole IB experience very unappealing.
To your question about a scroll view, use this very basic template as a guide. Add the scroll view to the view and add the subviews to the scroll view. To make it work, however, you must anchor the objects in the scroll view to the edges of the scroll view (especially the bottom) so that its content view stretches and it scrolls.
class Slickdaddy: UIViewController {
private let scrollView = UIScrollView()
override func viewDidLoad() {
super.viewDidLoad()
addScrollView()
addAView()
}
private func addScrollView() {
scrollView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(scrollView)
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
scrollView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
scrollView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
scrollView.heightAnchor.constraint(equalTo: view.heightAnchor).isActive = true
}
private func addAView() {
let label = UILabel()
label.text = "slick daddy"
label.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(label)
label.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor, constant: 16).isActive = true
label.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 32).isActive = true
label.sizeToFit()
label.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: -32).isActive = true
}
}
Imagine a stack view with four items, filling something. (Say, filling the screen).
Notice there are three gaps, ABC.
(Note - the yellow blocks are always some fixed height each.)
(Only the gaps change, depending on the overall height available to the stack view.)
Say UISV is able to draw everything, with say 300 left over. The three gaps will be 100 each.
In the example, 9 is left over, so A B and C are 3 each.
However.
Very often, you want the gaps themselves to enjoy a proportional relationship.
Thus - your designer may say something like
If the screen is too tall, expand the spaces at A, B and C. However. Always expand B let's say 4x as fast as the gaps at A and B."
So, if "12" is left over, that would be 2,8,2. Whereas when 18 is left over, that would be 3,12,3.
Is this concept available in stack view? Else, how would you do it?
(Note that recently added to stack view, you can indeed specify the gaps individually. So, it would be possible to do it "manually", but it would be a real mess, you'd be working against the solver a lot.)
You can achieve that by following workaround. Instead of spacing, for each space add a new UIView() that would be a stretchable space. And then just add constraints between heights of these "spaces" that would constrain their heights together based on the multipliers you want, so e.g.:
space1.heightAnchor.constraint(equalTo: space2.heightAnchor, multiplier: 2).isActive = true
And to make it work I think you'd have to add one constraint that would try to stretch those spaces in case there is free space:
let stretchingConstraint = space1.heightAnchor.constraint(equalToConstant: 1000)
// lowest priority to make sure it wont override any of the rest of constraints and compression resistances
stretchingConstraint.priority = UILayoutPriority(rawValue: 1)
stretchingConstraint.isActive = true
The "normal" content views would have to have intrinsic size or explicit constraints setting their heights to work properly.
Here is an example:
class MyViewController: UIViewController {
fileprivate let stack = UIStackView()
fileprivate let views = [UIView(), UIView(), UIView(), UIView()]
fileprivate let spaces = [UIView(), UIView(), UIView()]
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .white
self.view.addSubview(stack)
// let stack fill the whole view
stack.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
stack.topAnchor.constraint(equalTo: self.view.topAnchor),
stack.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
stack.leftAnchor.constraint(equalTo: self.view.leftAnchor),
stack.rightAnchor.constraint(equalTo: self.view.rightAnchor),
])
stack.alignment = .fill
// distribution must be .fill
stack.distribution = .fill
stack.spacing = 0
stack.axis = .vertical
for (index, view) in views.enumerated() {
stack.addArrangedSubview(view)
view.translatesAutoresizingMaskIntoConstraints = false
// give it explicit height (or use intrinsic height)
view.heightAnchor.constraint(equalToConstant: 50).isActive = true
view.backgroundColor = .orange
// intertwin it with spaces
if index < spaces.count {
stack.addArrangedSubview(spaces[index])
spaces[index].translatesAutoresizingMaskIntoConstraints = false
}
}
// constraints for 1 4 1 proportions
NSLayoutConstraint.activate([
spaces[1].heightAnchor.constraint(equalTo: spaces[0].heightAnchor, multiplier: 4),
spaces[2].heightAnchor.constraint(equalTo: spaces[0].heightAnchor, multiplier: 1),
])
let stretchConstraint = spaces[0].heightAnchor.constraint(equalToConstant: 1000)
stretchConstraint.priority = UILayoutPriority(rawValue: 1)
stretchConstraint.isActive = true
}
}
Remarkably, #MilanNosáľ 's solution works perfectly.
You do not need to set any priorities/etc - it works perfectly "naturally" in the iOS solver!
Set the four content areas simply to 50 fixed height. (Use any intrinsic content items.)
Simply don't set the height at all of "gap1".
Set gap2 and gap3 to be equal height of gap1.
Simply - set the ratios you want for gap2 and gap3 !
Versus gap1.
So, gap2 is 0.512 the height of gap1, gap3 is 0.398 the height of gap1, etc.
It does solve it in all cases.
Fantastic!!!!!!!!!!
So: in the three examples (being phones with three different screen heights). In fact the relative heights of the gaps, is always the same. Your design department will rejoice! :)
Created: a gist with a storyboard example
The key here is Equal Heights between your arranged views and your reference view:
And then change the 'Multiplier` to your desired sizes:
In this example I have 0.2 for the main view sizes (dark grey), 0.05 within the pairs (black), and 0.1 between the pairs (light grey)
Then simply changing the size of the containing view will cause the views to re-size proportionally:
This is entirely within the storyboard, but you could do the same thing in code.
Note that I'm using only proportions within the StackView to avoid having an incorrect total size, (and making sure they add up to 1.0), but it should be possible to also have some set heights within the StackView if done correctly.
I am currently trying to access the height of a view which I previously set anchors for. So for example, I set the left, right, top, and bottom anchors of searchTable using the following in my ViewController:
searchTable.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
searchTable.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
searchTable.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
searchTable.bottomAnchor.constraint(equalTo: menuBar.topAnchor).isActive = true
searchTable is an object of a class that I created that inherits from UIView and has a UIImageView in it. I constrain the UIImageView by using the following in the init function:
imageView.topAnchor.constraint(equalTo: topContainer.topAnchor, constant: 10).isActive = true
imageView.leftAnchor.constraint(equalTo: topContainer.leftAnchor, constant: 10).isActive = true
imageView.heightAnchor.constraint(equalTo: self.heightAnchor, multiplier: topContainerMultiplier * imageProfileMultiplier).isActive = true
imageView.widthAnchor.constraint(equalTo: self.heightAnchor, multiplier: topContainerMultiplier * imageProfileMultiplier).isActive = true
where:
let topContainerMultipler: Double = 1 / 7
let imageProfileMultipler: Double = 1 / 5
Inside the init function of searchTable, I try want to be able to set the corner radius to be half the image size. I tried to use self.frame.height, self.frame.size.height, self.bounds.height and also getting the .constant value of the self.heightAnchor constraint, but all returns 0.
I would think that there is a solution to get around this, but I haven't been able to find it.
You are querying the height of your view's frame after defining your constraints, but before layout has actually resolved those constraints into a frame rectangle. One approach would be to call layoutIfNeeded to force layout to happen immediately, then use view.frame.size.height. The init method is probably not an appropriate place to do this, as opposed to perhaps inside viewDidLoad on the controller. Or you might do this in viewDidLayoutSubviews to recalculate the corner radius every time your view's layout is updated (in which case you won't need layoutIfNeeded).
Don't use a height constraint if you already use top, bottom, leading, and trailing.
Try to visually debug your view by adding a background color, or using the debug view hierarchy button.
To get the height of a view you should use view.frame.size.height
EDIT (OP question edited)
Your problem is that you try to access the object height when it's just initialized but not displayed yet. You should create a function that update your UI and call it after your view is loaded.
I want to display two textfields in single tableview cell along with some separator between of them both. Those, two textfields has equal space to edges and with the separator. I want to create this in programmatically in tableview cell class in Swift language.
I want this to be fit in all dimension devices.
Note : I am not using autolayout.
Any suggestions?
I highly recommend you to use UIStackViews as it sounds like a perfect use case for your question.
Definition of a UIStackView in the Documentation:
A streamlined interface for laying out a collection of views in either
a column or a row.
It would take care of the resizing behaviour of your views and you could also easily control the margins and the spacing between your elements.
You can use UIStackViews with storyboards or programmatically depending on what you are looking for.
You may also want to read this guide in the documentation to learn more about UIStackViews.
How to use:
Say you have two text fields, textFieldA, textFieldB and your separator view named separatorView.
Here is how you could do setup your UIStackViews programmatically inside your UITableViewCell subclass:
// Create and configure your stack view
let stackView = UIStackView()
stackView.axis = UILayoutConstraintAxis.horizontal
stackView.distribution = UIStackViewDistribution.fillProportionally
stackView.alignment = UIStackViewAlignment.fill
stackView.spacing = 20.0
// Add your textfields and your separator view to the stack view
stackView.addArrangedSubview(textFieldA)
stackView.addArrangedSubview(separatorView)
stackView.addArrangedSubview(textFieldB)
stackView.translatesAutoresizingMaskIntoConstraints = false
// Add your stack view:
self.addSubview(stackView)
// Configure the constraints for your stack view
// (Of course you can set up your stack view the way you want and you don't have to absolutely use constraints, but it's just for the example)
stackView.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -20).isActive = true
stackView.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 20).isActive = true
stackView.topAnchor.constraint(equalTo: self.topAnchor, constant: 20).isActive = true
stackView.heightAnchor.constraint(equalToConstant: 200.0).isActive = true
It will save you a lot of time as you don't need to add constraints everywhere for all your views, but only for the stack view. The stack view will then take care of its arranged subviews.
One thing you may need in your case, is changing the hugging and compression resistance values for your views, to make sure it looks great on any screens.
As you can see you there are a lot of properties that you can set on your stack view, so just feel free to try different values and set it up the way you like!
Use stack view with axis horizontal and distribution is equal to fillProportionally
I have tried to make a UILabel that is a certain width using preferredMaxLayoutWidth but no matter what I do it won't work. Can you help me? I have tries so many different combinations to make it work.
#IBAction func addBottomTextButton(sender: AnyObject) {
if addBottomTextField.text.isEmpty == false {
let halfScreenWidth = screenSize.width * 0.5
let bottomScreenPosition = screenSize.width
memeBottomText = addBottomTextField.text
fontName = "Impact"
let memeBottomTextCaps = memeBottomText.uppercaseString // --> THIS IS A STRING!
labelBottom.text = memeBottomTextCaps
labelBottom.textColor = UIColor.blackColor()
labelBottom.textAlignment = .Center
labelBottom.font = UIFont(name: fontName, size: 32.0)
labelBottom.sizeToFit()
labelBottom.userInteractionEnabled = true
labelBottom.adjustsFontSizeToFitWidth = true
labelBottom.numberOfLines = 1
labelBottom.preferredMaxLayoutWidth = screenSize.width
labelBottom.setTranslatesAutoresizingMaskIntoConstraints(true)
var r = CGFloat(halfScreenWidth)
var s = CGFloat(bottomScreenPosition)
labelBottom.center = CGPoint(x: r, y: s)
self.view.addSubview(labelBottom)
self.view.addConstraint(NSLayoutConstraint(item: labelBottom, attribute:
NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: labelBottom,
attribute: NSLayoutAttribute.Bottom, multiplier: 1, constant: 0))
dismissKeyboard()
}
}
Judging by your code I'd say your problem was you haven't got your constraints setup correctly and you're mixing using NSLayoutConstraints with setting the position using center and setting the size using sizeToFit.
Firstly, in the constraint you've setup you're relating labelBottom (the item argument) to itself (the toItem argument). I'm not exactly sure what you were trying to achieve with that? I'd recommend having a look at some tutorials on AutoLayout if you're unfamiliar with its concepts. Here's a good one: http://www.raywenderlich.com/50317/beginning-auto-layout-tutorial-in-ios-7-part-1
Secondly, just a small point, on the line let memeBottomTextCaps = memeBottomText.uppercaseString you've written // --> THIS IS A STRING. An easier way to remind yourself of the variable type when looking back at your code could be to use: let memeBottomTextCaps: String = memeBottomText.uppercaseString.
Thirdly, preferredMaxLayoutWidth isn't used to set the width of a UILabel - that's what the frame is for (or NSLayoutConstraints if you're using AutoLayout).
Lets get on with it!
Here's an example of how to create a label that is pinned to the bottom edge of its container view and is not allowed to be wider than it's container: (Keep in mind that all this can be done in IB)
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let label = UILabel()
// 1.
label.setTranslatesAutoresizingMaskIntoConstraints(false)
// 2.
label.text = // Put your text here.
// 3.
self.view.addSubview(label)
// 4.
let pinToBottomConstraint = NSLayoutConstraint(item: label,
attribute: .Bottom,
relatedBy: .Equal,
toItem: self.view,
attribute: .Bottom,
multiplier: 1.0,
constant: -8.0)
// 5.
let horizontalConstraints = NSLayoutConstraint.constraintsWithVisualFormat("|-8-[label]-8-|",
options: .DirectionLeadingToTrailing,
metrics: nil,
views: ["label" : label])
// 6.
self.view.addConstraint(pinToBottomConstraint)
self.view.addConstraints(horizontalConstraints)
}
}
The following referrers to the commented numbers in the code above.
1. You need to set setTranslatesAutoresizingMaskIntoConstraints to false to stop constraints being created that would otherwise conflict with the constraints we're going to create later. Here's the what Apple have to say about it:
Because the autoresizing mask naturally gives rise to constraints that fully specify a view’s position, any view that you wish to apply more flexible constraints to must be set to ignore its autoresizing mask using this method. You should call this method yourself for programmatically created views. Views created using a tool that allows setting constraints should have this set already.
2. You need to make sure you put your own text here, otherwise the code won't run.
3. The label must be added to the view hierarchy before adding constraints between it and it's superview! Otherwise, in this case, you'll get a runtime error saying:
Unable to parse constraint format:
Unable to interpret '|' character, because the related view doesn't have a superview
|-8-[label]-8-|
This is due to our horizontalConstraints needing to know the label's superview (the superview is denoted by the "|") but the label doesn't have a superview.
4. The pinToBottomConstraint constraint does what it says. The constant of -8 just specifies that I want the label to be 8 points from the bottom of its container view.
We don't need to create a constraint to specify the label's size - that's an intrinsic property of the UILabel which is determined, for example, by the number of lines and font.
5. The horiontalConstraints are created using Visual Format Language. Here's a good tutorial: http://code.tutsplus.com/tutorials/introduction-to-the-visual-format-language--cms-22715 Basically, "|-8-[label]-8-|" creates constraints to pin the left and right edges of the label to the left and right edges of its superview.
6. Finally add the constraints!
This is what it looks like:
I hope that answers your question.
I think the property only work for multiline situation.
// Support for constraint-based layout (auto layout)
// If nonzero, this is used when determining -intrinsicContentSize for multiline labels
#available(iOS 6.0, *)
open var preferredMaxLayoutWidth: CGFloat
And it indeed true after my test. So, we need set multiline. It will work.
titleLabel.numberOfLines = 0
I don't why Apple limit it to only multiline. In fact, we often need to set max width on label easily by one property.
Finally, if we want set max width , we need set max constaint, like the following
if device.isNew == "1" {
self.title.mas_updateConstraints { (make) in
make?.width.lessThanOrEqualTo()(163.w)
}
self.newTag.isHidden = false
} else {
self.newTag.isHidden = true
self.title.mas_updateConstraints { (make) in
make?.width.lessThanOrEqualTo()(207.w)
}
}