I have to dynamically create some UILabel and UITextViews according to some Data ~ around 20 - all of them with dynamic height/lines of Text for an IOS App in Swift.
Since the screen is not large enough I am adding the Views to a ScrollView, but unfortunately the contentsize property of my ScrollView seems not to receive the proper values.
I'm debugging since a couple of hours and tried different set ups but so far none of them worked out.
The sizing is done in a custom method refreshUI() which gets firstly called in viewDidLoad(). The ViewController simply contains one centred Heading Label and the ScrollView which fills the rest of the space (pinned to Top, Left, Right, Bottom with 8.0).
Then I'm trying to populate my Data in that scrollView as follows:
func refreshUI(){
println("refreshUI()")
questionnaireTitle.text = site.questionnaireName
let questionnaire = site.questionnaire
//removes all SubViews in ScrollView
clearView(scrollView)
scrollView.setTranslatesAutoresizingMaskIntoConstraints(false)
for questionGroup in questionnaire{
//QUESTIONGROUP HEADING
let questionGroupHeading = UILabel()
questionGroupHeading.setTranslatesAutoresizingMaskIntoConstraints(false)
questionGroupHeading.text = questionGroup.questionsHeading
questionGroupHeading.sizeToFit()
scrollView.addSubview(questionGroupHeading)
viewStack.append(questionGroupHeading)
//QUESTION
for question in questionGroup.questions{
//QUESTION LABEL
let questionLabel = UILabel()
questionLabel.setTranslatesAutoresizingMaskIntoConstraints(false)
questionLabel.text = question.text
questionLabel.numberOfLines = 0
questionLabel.lineBreakMode = .ByWordWrapping
questionLabel.sizeToFit()
scrollView.addSubview(questionLabel)
viewStack.append(questionLabel)
if question.type == "selector"{
//SELECTOR QUESTION
println("selector Question")
for statement in question.statements{
//TODO add Statement + Picker
}
}
else if question.type == "standard"{
//STANDARD QUESTION
println("standard question")
let answerLabel = UITextField()
answerLabel.placeholder = "here goes your answer"
answerLabel.sizeToFit()
answerLabel.backgroundColor = UIColor.whiteColor()
answerLabel.setTranslatesAutoresizingMaskIntoConstraints(false)
scrollView.addSubview(answerLabel)
viewStack.append(answerLabel)
}
}
}
//setUpConstraints
var counter = 0
var height:CGFloat = 0.0
for view in viewStack{
let rightConst = NSLayoutConstraint(item: view, attribute: .Right, relatedBy: .Equal, toItem: scrollView, attribute: .Right, multiplier: 1.0, constant: 0.0)
let leftConst = NSLayoutConstraint(item: view, attribute: .Left, relatedBy: .Equal, toItem: scrollView, attribute: .Left, multiplier: 1.0, constant: 0.0)
let widthConst = NSLayoutConstraint(item: view, attribute: .Width, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: scrollView.frame.width)
view.addConstraint(widthConst)
scrollView.addConstraint(leftConst)
scrollView.addConstraint(rightConst)
//pin first view to top of scrollView
if counter == 0{
let topConst = NSLayoutConstraint(item: view, attribute: .Top, relatedBy: .Equal, toItem: scrollView, attribute: .Top, multiplier: 1.0, constant: 0.0)
scrollView.addConstraint(topConst)
}
//pin all other views to the top of the previousView
else{
let topConst = NSLayoutConstraint(item: view, attribute: .Top, relatedBy: .Equal, toItem: viewStack[counter - 1], attribute: .Bottom, multiplier: 1.0, constant: 8.0)
scrollView.addConstraint(topConst)
}
counter++
height += view.bounds.height
}
let contentSize = CGSize(width: scrollView.frame.width, height: height)
scrollView.contentSize = contentSize
scrollView.contentOffset = CGPoint(x: 0, y: 0)
println("refreshUI() done")
}
The ScrollView is not vertically scrollable, although some content is not displayed due to being out of the screen.
But it scrolls horizontally, although I'm setting the width of each view to the width of the SCrollView, which should mean that every SubView is just as big as the size of the ScrollView and thus not vertically scrollable.
If you are using AutoLayout then this tutorial will be useful for implementing scrollview.
When you run refreshUI() inside of viewDidLoad(), the subviews that are being created are using the Interface Builder defaults for the parent views instead of the actual sizes on the device. This is likely why your sizes are not what you expect. If you run refreshUI() inside of viewDidLayoutSubviews() instead, then the subviews will correctly read the widths and heights of the parent view.
Related
I want to hide a view completely. I am new to ios. But I have great working experience in android.
So in android We can set visibility to gone. and it completely removes the view from layout. Same I want to do in IOS. here is the layout/design example
View 1
View 2 ( it is the one I want to hide and show)
View 3
now when I want to hide the view 2 I want the space of view 2 also vanish from screen and View 1 and View 3 must stick together . and when view 2 is set to visible then it must display in sequence. i.e View 1,2,3
Right now what I am doing is setting view2.ishidden = true but its not working in the way I want.
Please tell me what is equivalent to view.gone of android in IOS. ???
There are a few ways in achieving this but what you are missing here is some key information on how your layout is being set.
I will assume that the 3 views you are having are vertically aligned, are one next to each other and have equal width.
Programmatically what we are looking at from horizontal perspective this is done:
let firstLeading = NSLayoutConstraint(item: view1, attribute: .leading, relatedBy: .equal, toItem: parent, attribute: .leading, multiplier: 1.0, constant: 0.0)
let betweenSecondAndFirst = NSLayoutConstraint(item: view2, attribute: .leading, relatedBy: .equal, toItem: view1, attribute: .trailing, multiplier: 1.0, constant: 0.0)
let secondEqualWidth = NSLayoutConstraint(item: view2, attribute: .width, relatedBy: .equal, toItem: view1, attribute: .width, multiplier: 1.0, constant: 0.0)
let betweenThirdAndSecond = NSLayoutConstraint(item: view3, attribute: .leading, relatedBy: .equal, toItem: view2, attribute: .trailing, multiplier: 1.0, constant: 0.0)
let lastEqualWidth = NSLayoutConstraint(item: view3, attribute: .width, relatedBy: .equal, toItem: view1, attribute: .width, multiplier: 1.0, constant: 0.0) // Note to view1 to make things easier
let lastTrailing = NSLayoutConstraint(item: view3, attribute: .trailing, relatedBy: .equal, toItem: parent, attribute: .trailing, multiplier: 1.0, constant: 0.0)
And when the second is being skipped we may simply disable betweenThirdAndSecond and add betweenThirdAndFirst as:
let betweenThirdAndFirst = NSLayoutConstraint(item: view3, attribute: .leading, relatedBy: .equal, toItem: view1, attribute: .trailing, multiplier: 1.0, constant: 0.0)
You can play with properties on constraints to enable or disable them. Or you can simply use priorities and toggle them for instance from 900 to 100. You can actually setup all of these constraints in storyboard and then drag the 2 as outlets into your code. Then simply have:
func setMiddleViewShown(_ shown: Bool) {
UIView.animate(withDuration: 0.3) {
self.betweenThirdAndSecond.priority = UILayoutPriority(rawValue: shown ? 900.0 : 100.0)
self.betweenThirdAndFirst.priority = UILayoutPriority(rawValue: shown ? 100.0 : 900.0)
self.view2.alpha = shown ? 1.0 : 0.0
parent.layoutIfNeeded() // parent is most likely "self.view"
}
}
This way is probably best from what you can control (mostly animations). But you may as well use UIStackView which has methods to insert or remove views. UICollectionView should work as well. Or you know.. just do it all programmatically, ignore constraints and simply set frame for each of the views.
A minimum I can think of in using UIStackView is the following and it works:
class ViewController: UIViewController {
let views: [UIView] = [
{ let view = UIView(); view.backgroundColor = .red; return view; }(),
{ let view = UIView(); view.backgroundColor = .green; return view; }(),
{ let view = UIView(); view.backgroundColor = .blue; return view; }()
]
lazy var stackView: UIStackView = {
let stackView = UIStackView(frame: CGRect(x: 50.0, y: 100.0, width: 300.0, height: 200.0))
self.view.addSubview(stackView)
stackView.backgroundColor = .black
stackView.distribution = .fillEqually
return stackView
}()
override func viewDidLoad() {
super.viewDidLoad()
stackView.addArrangedSubview(views[0])
stackView.addArrangedSubview(views[1])
stackView.addArrangedSubview(views[2])
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
UIView.animate(withDuration: 0.3) {
if self.stackView.arrangedSubviews.count == 3 {
self.views[1].alpha = 0.0
self.stackView.removeArrangedSubview(self.views[1])
} else {
self.views[1].alpha = 1.0
self.stackView.insertArrangedSubview(self.views[1], at: 1)
}
self.stackView.layoutIfNeeded()
}
}
}
I have tried to achieve this using Autoresizing in IB, using constraints such as
pinning,
aspect ratio,
vertical spacing ,
horizontal/vertical spacing,
leading/trailing space;
but none seems to work uniformly for all screen sizes.
For example
the 4 image views in iphone 7 is the required output positions for them but as soon as I check it in iphone 7 plus screen size or iphone SE screen size, these image views change position.
below is the expected output as on iphone 7.
iphone7 Image
below is the not expected output as the positon of 4 image views change on iphone 7 plus.
iphone7plus
I want one more image to share of iPhone SE in which positon of the all 4 image views change more drastically, but not able to share cause reputation less than 10
I will explain, for example, from 1st image i.e. of iphone 7, those 4 small image views can be taken as the expected position along with the image view with white background, but in other two screen sizes(iphone7 plus and iphoneSE) all views(4 small image view and the view with white back ground) should have positioned and scaled accordingly as compared to appearance on the first one.
Update: code added
I just made some changes to the one of the answer I got in this post by #diana_prodan, to get the expected output.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var imgOne: UIImageView!
#IBOutlet weak var imgTwo: UIImageView!
#IBOutlet weak var imgThree: UIImageView!
#IBOutlet weak var imgFour: UIImageView!
let icon_small : Float = 45.0
let icon_medium : Float = 50.0
override func viewDidLoad() {
super.viewDidLoad()
let viewSize = UIScreen.main.bounds.size
print(viewSize)
//image positioned
//for 4' screen
if (viewSize.height == 568.0){
addImageAtPosition(imgOne, icon_small,41, 106 )
addImageAtPosition(imgTwo, icon_small,105,91 )
addImageAtPosition(imgThree, icon_small,105, 147)
addImageAtPosition(imgFour, icon_small,139, 197 )
//for 4.7' screen
}else if(viewSize.height == 667.0){
addImageAtPosition(imgOne, icon_medium,52, 135 )
addImageAtPosition(imgTwo, icon_medium,127,106 )
addImageAtPosition(imgThree, icon_medium,127, 172 )
addImageAtPosition(imgFour, icon_medium,167, 230 )
//for 5.5' screen
}else if(viewSize.height == 736.0){
//add code woth cordinates for 5.5 screen size iPhone
}
}
//method to positon image views
func addImageAtPosition(_ img:UIImageView, _ size: Float, _ y: Float, _ x: Float) {
let imageView = img
imageView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(imageView)
let imageSize: CGFloat = CGFloat(size)
let topConstraint = NSLayoutConstraint(item: imageView, attribute: .top, relatedBy: .equal, toItem: self.topLayoutGuide, attribute: .bottom, multiplier: 1, constant: CGFloat(y))
let leftContraint = NSLayoutConstraint(item: imageView, attribute: .left, relatedBy: .equal, toItem: self.view, attribute: .left, multiplier: 1, constant: CGFloat(x))
let widthConstraint = NSLayoutConstraint(item: imageView, attribute: NSLayoutAttribute.width, relatedBy: NSLayoutRelation.equal, toItem: nil, attribute: NSLayoutAttribute.notAnAttribute, multiplier: 1, constant: imageSize)
let heightConstraint = NSLayoutConstraint(item: imageView, attribute: NSLayoutAttribute.height, relatedBy: NSLayoutRelation.equal, toItem: nil, attribute: NSLayoutAttribute.notAnAttribute, multiplier: 1, constant: imageSize)
view.addConstraints([topConstraint, leftContraint, widthConstraint, heightConstraint])
}
}
problem with this code is I have to get the coordinates for all image views view on different screen sizes and hardcode it, which is not a good practise I think
Take View A with give half height Constrain of main view's height. After that Give all images same height and width constarin. give one image's x,y constrain with view A's center. and link all images set defullts constrain.
You have to set random values for top and left constraints of your ImageViews.
func addImageAtRadomPosition() {
let imageView = UIImageView()
imageView.backgroundColor = UIColor.red
imageView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(imageView)
let imageSize: CGFloat = 50.0
let screenHeight = UIScreen.main.bounds.size.height
let screenWidth = UIScreen.main.bounds.size.width
// Get random values which will be used for top and left constraints
let randomYPosition = Int(arc4random_uniform(UInt32(screenHeight - imageSize)))
let randomXPosition = Int(arc4random_uniform(UInt32(screenWidth - imageSize)))
let topConstraint = NSLayoutConstraint(item: imageView, attribute: .top, relatedBy: .equal, toItem: self.topLayoutGuide, attribute: .bottom, multiplier: 1, constant: CGFloat(randomYPosition))
let leftContraint = NSLayoutConstraint(item: imageView, attribute: .left, relatedBy: .equal, toItem: self.view, attribute: .left, multiplier: 1, constant: CGFloat(randomXPosition))
let widthConstraint = NSLayoutConstraint(item: imageView, attribute: NSLayoutAttribute.width, relatedBy: NSLayoutRelation.equal, toItem: nil, attribute: NSLayoutAttribute.notAnAttribute, multiplier: 1, constant: imageSize)
let heightConstraint = NSLayoutConstraint(item: imageView, attribute: NSLayoutAttribute.height, relatedBy: NSLayoutRelation.equal, toItem: nil, attribute: NSLayoutAttribute.notAnAttribute, multiplier: 1, constant: imageSize)
view.addConstraints([topConstraint, leftContraint, widthConstraint, heightConstraint])
}
Call this method as many times as your ImageViews count
for _ in 0...5 {
addImageAtRadomPosition()
}
If this is your expected output then notify me so that I can describe how to achieve this and if not then describe me what else you are expecting.
Comment below.
edit
When I run the same VC in iPhone 6+ I got below output. So consider this output.
To acheive the expected output I went to this reference [reference]: https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/AnatomyofaConstraint.html ,so all I had to is
select one of the view and hold ctrl+ drag to the external view
then hold shift and select equal width & equal height, center
horizontally in container & center vertically in container.
then selecting each constraints line(yellow coloured)or from view controller scene column and modify the multiplier until the yellow coloured line became blue i.e. until
it satisfies the position where I have put my image views originally in view controller layout.
here is the screen shot link it shows where to select constraints one by one(on left) and modify multiplier(on right).
It's been a while since I'm trying to create a custom view that has at the top right corner a simple badge.
The custom view is composed by 3 part.
In red there is the custom view itself, that contains:
in blue the container view
in green the badge view a simple UILabel
Container view and the badge view are sibling.
At the moment the container view contains a UIImageView
That view must fit those requirements:
Full auto layout approach
Made programmatically
The custom view must be aligned only considering the blue frame(the container view)
Why that? imagine that you need to position that view by aligning the top edge to the top edge of another view or button, wouldn't be nice if the only the content showed int he container is taken into account?
Here you can see how I set the constraints. The label is placed at the top right corner of the container view.
func setUpConstraint() {
var horContraints = [NSLayoutConstraint]()
horContraints.append(NSLayoutConstraint(item: containerView, attribute: .Leading, relatedBy: .Equal, toItem: self, attribute: .Leading, multiplier: 1, constant: 0))
horContraints.append(NSLayoutConstraint(item: containerView, attribute: .Trailing, relatedBy: .Equal, toItem: badge, attribute: .CenterX, multiplier: 1, constant: 0))
horContraints.append(NSLayoutConstraint(item: badge, attribute: .Trailing, relatedBy: .Equal, toItem: self, attribute: .Trailing, multiplier: 1, constant: 0))
var verContraints = [NSLayoutConstraint]()
verContraints.append(NSLayoutConstraint(item: containerView, attribute: .Bottom, relatedBy: .Equal, toItem: self, attribute: .Bottom, multiplier: 1, constant: 0))
verContraints.append(NSLayoutConstraint(item: containerView, attribute: .Top, relatedBy: .Equal, toItem: badge, attribute: .CenterY, multiplier: 1, constant: 0))
verContraints.append(NSLayoutConstraint(item: badge, attribute: .Top, relatedBy: .Equal, toItem: self, attribute: .Top, multiplier: 1, constant: 0))
addConstraints(verContraints + horContraints)
containerView.addConstraint(NSLayoutConstraint(item: containerView, attribute: .Height, relatedBy: .Equal, toItem: containerView, attribute: .Width, multiplier: 1, constant: 0))
}
The container view has also an aspect ratio constraint to keep a square size.
As you can see from the picture everything seems to be fine, except for the fact that when I try to constraint the custom view to the center of its superview, it seems misaligned, because I want the view to be centered respect to the container view (the one with the image). The badge is a kind of decoration such as a shadow and I don't want it to be consider.
To align it correctly I'm trying to override the alignment rect by adding an insets that would "cut" half the label size.
override func alignmentRectInsets() -> UIEdgeInsets {
let badgeSize = badge.bounds.size
return UIEdgeInsets(top: badgeSize.height / 2, left: 0, bottom: 0, right: badgeSize.width / 2)
}
I tried also different configurations but I never was able to fit in the wanted position
If I try to use the other 2 methods alignmentRectForFrame and frameForAlignmentRect (deleting alignmentRectInsets) they are never be called.
Here is what I'd like to obtain:
I've created a little sample code
If the problem is that you want the other view (the "content view", showing the image) to be centered in the ultimate superview, then simply make centering constraints (center x to center x, center y to center y) between the superview and the content view. No law says that a constraint has to be between a view and its direct superview; you can make constraints between any pair of views (that is one of the wonderful things about constraints).
I made a quick mock-up that looks a lot like your "here's what I'd like to obtain" image:
As you can see, Moe (the middle Pep Boy, in the middle of the image) is exactly centered in the superview (shown by the green lines).
For simplicity, the entire interface is created in code, so that I can show you the whole thing. Here it is:
// create the custom view
let customView = UIView()
customView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(customView)
customView.backgroundColor = UIColor.redColor()
NSLayoutConstraint.activateConstraints([
customView.widthAnchor.constraintEqualToConstant(100),
customView.heightAnchor.constraintEqualToConstant(100),
])
// add the content view (image) to the custom view
let contentView = UIImageView(image: UIImage(named:"pep.jpg")!)
contentView.translatesAutoresizingMaskIntoConstraints = false
customView.addSubview(contentView)
NSLayoutConstraint.activateConstraints([
contentView.leadingAnchor.constraintEqualToAnchor(customView.leadingAnchor),
contentView.bottomAnchor.constraintEqualToAnchor(customView.bottomAnchor),
contentView.heightAnchor.constraintEqualToAnchor(customView.heightAnchor, constant: -10),
contentView.widthAnchor.constraintEqualToAnchor(customView.widthAnchor, constant: -10)
])
// add the badge (label) to the custom view
let badge = UILabel()
badge.translatesAutoresizingMaskIntoConstraints = false
customView.addSubview(badge)
badge.backgroundColor = UIColor.greenColor().colorWithAlphaComponent(0.5)
badge.font = UIFont(name: "GillSans", size: 14)
badge.textAlignment = .Center
badge.text = "567"
NSLayoutConstraint.activateConstraints([
badge.centerXAnchor.constraintEqualToAnchor(contentView.trailingAnchor),
badge.centerYAnchor.constraintEqualToAnchor(contentView.topAnchor),
])
// position the whole thing with respect to the content view
NSLayoutConstraint.activateConstraints([
contentView.centerXAnchor.constraintEqualToAnchor(self.view.centerXAnchor),
contentView.centerYAnchor.constraintEqualToAnchor(self.view.centerYAnchor),
])
But this is not a complete answer to your question. You should now be asking: Yes, but what went wrong when I tried to use the alignmentRectInsets? The answer is: you forgot that the alignmentRectInsets affect alignment with internal views as well as external views. In other words, you certainly can use that approach instead, but then you must adjust the position of the content view accordingly.
So, here's a rewrite in which I use the alignmentRectInsets. First, I'll define a custom view subclass for our custom view:
class AlignedView : UIView {
override func alignmentRectInsets() -> UIEdgeInsets {
return UIEdgeInsetsMake(10, 0, 0, 10)
}
}
Now here's the rewrite. I've put a star (*) next to all the lines that have changed from the previous example:
// create the custom view
let customView = AlignedView() // *
customView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(customView)
customView.backgroundColor = UIColor.redColor()
NSLayoutConstraint.activateConstraints([
customView.widthAnchor.constraintEqualToConstant(100),
customView.heightAnchor.constraintEqualToConstant(100),
])
// add the content view (image) to the custom view
let contentView = UIImageView(image: UIImage(named:"pep.jpg")!)
contentView.translatesAutoresizingMaskIntoConstraints = false
customView.addSubview(contentView)
NSLayoutConstraint.activateConstraints([
contentView.leadingAnchor.constraintEqualToAnchor(customView.leadingAnchor),
contentView.bottomAnchor.constraintEqualToAnchor(customView.bottomAnchor),
contentView.heightAnchor.constraintEqualToAnchor(customView.heightAnchor), // *
contentView.widthAnchor.constraintEqualToAnchor(customView.widthAnchor) // *
])
// add the badge (label) to the custom view
let badge = UILabel()
badge.translatesAutoresizingMaskIntoConstraints = false
customView.addSubview(badge)
badge.backgroundColor = UIColor.greenColor().colorWithAlphaComponent(0.5)
badge.font = UIFont(name: "GillSans", size: 14)
badge.textAlignment = .Center
badge.text = "567"
NSLayoutConstraint.activateConstraints([
badge.centerXAnchor.constraintEqualToAnchor(contentView.trailingAnchor),
badge.centerYAnchor.constraintEqualToAnchor(contentView.topAnchor),
])
// position the whole thing with respect to the custom view
NSLayoutConstraint.activateConstraints([
customView.centerXAnchor.constraintEqualToAnchor(self.view.centerXAnchor), // *
customView.centerYAnchor.constraintEqualToAnchor(self.view.centerYAnchor), // *
])
That gives exactly the same visual result as before.
Alright so in the interface builder (Main.storyboard), I have a containerView:UIView embedded in a UIScrollView. Within in the containerView I want to create additional UIView's to hold blocks of content such as a header, body, etc. The reason for doing it like this, is so that the content can scroll vertically but not horizontally.
My goal is to use autolayout to create these different UIView's. As of right now the containerView automatically adjusts it's width depending on the screen size of the device being used, as to prevent horizontal scrolling. It does this using an IBOutlet I created for the width constraint. It currently looks like so:
#IBOutlet var containerView: UIView!
#IBOutlet var containerViewWidthConstraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
//Prevents horizontal scrolling
containerViewWidthConstraint.constant = self.view.frame.size.width
createHeader()
}
Then I created a function called createheader{} which pins a headerView:UIView at the top of the containerView, and 8 points from either edge of the containerView:
func createHeader() {
//Create header
let headerView = UIView()
headerView.backgroundColor = UIColor.blueColor()
self.containerView.addSubview(headerView)
//Create header constraints
let leftMargin = NSLayoutConstraint(item: headerView, attribute: .Leading, relatedBy: .Equal, toItem: containerView, attribute: .Leading, multiplier: 1.0, constant: 8)
let rightMargin = NSLayoutConstraint(item: containerView, attribute: .Trailing, relatedBy: .Equal, toItem: headerView, attribute: .Trailing, multiplier: 1.0, constant: 8)
let topMargin = NSLayoutConstraint(item: headerView, attribute: .Top, relatedBy: .Equal, toItem: containerView, attribute: .Top, multiplier: 1.0, constant: 70)
let heightConstraint = NSLayoutConstraint(item: headerView, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1.0, constant: 160)
//Activate header constraints
headerView.setTranslatesAutoresizingMaskIntoConstraints(false)
NSLayoutConstraint.activateConstraints([leftMargin,rightMargin,topMargin,heightConstraint])
println(headerView.frame.size.width)
}
Now since the size of the content inside the headerView will be dependent on the screen size of the device being used, I want to be able to create functions that size the width of the content depending on the size of the width of the headerView itself. However every time I try to grab the width of the headerView using:
println(headerView.frame.size.width)
It returns a value of zero, which is obviously not the case because it is still creating a blue-background headerView according to the constraints above.
Why is SWIFT not recognizing that the headerView has a width? And how can I grab the width of the headerView?
After installing constraints you need to call layoutIfNeeded if you want to update the frames immediately.
func createHeader() {
//Create header
let headerView = UIView()
headerView.backgroundColor = UIColor.blueColor()
self.containerView.addSubview(headerView)
...
//Activate header constraints
headerView.setTranslatesAutoresizingMaskIntoConstraints(false)
NSLayoutConstraint.activateConstraints([leftMargin,rightMargin,topMargin,heightConstraint])
self.containerView.layoutIfNeeded() // Updates the frames
println(headerView.frame.size.width) // Will output the correct width
}
Note that this will happen automatically on the next iteration of the UI loop which is, however, not helpful to you when you want to see the effects immediately.
I'm trying to place my subview with a left margin based on the width of the parent view. This sounds simple but I can't figure out how to do it using autolayout.
Logically, I would only need to set the left margin value at a certain percentage value of the parent's width but at the moment, I fail to translate that logic to autolayout.
This is my code at the moment:
var view = UIView();
view.backgroundColor = UIColor.redColor();
view.frame = CGRectMake(0, 0, 320, 400);
var sview = UIView();
sview.setTranslatesAutoresizingMaskIntoConstraints(false);
sview.backgroundColor = UIColor.yellowColor();
//sview.frame = CGRectMake(0, 0, 50, 50);
view.addSubview(sview);
var dict = Dictionary<String, UIView>()
dict["box"] = sview;
var con1 = NSLayoutConstraint(item: sview, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: view, attribute: NSLayoutAttribute.Top, multiplier: 1.0, constant: 20.0);
view.addConstraint(con1);
var con2 = NSLayoutConstraint(item: sview, attribute: NSLayoutAttribute.Left, relatedBy: NSLayoutRelation.Equal, toItem: view, attribute: NSLayoutAttribute.Width, multiplier: 0.75, constant: 0.0);
view.addConstraint(con2);
var con3 = NSLayoutConstraint(item: sview, attribute: NSLayoutAttribute.Width, relatedBy: NSLayoutRelation.Equal, toItem: view, attribute: NSLayoutAttribute.Width, multiplier: 1.0, constant: 0.0);
view.addConstraint(con3);
var con4 = NSLayoutConstraint(item: sview, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: view, attribute: NSLayoutAttribute.Height, multiplier: 1.0, constant: 0.0);
view.addConstraint(con4);
This is the where the code returns an error:
var con2 = NSLayoutConstraint(item: sview, attribute: NSLayoutAttribute.Left, relatedBy: NSLayoutRelation.Equal, toItem: view, attribute: NSLayoutAttribute.Width, multiplier: 0.75, constant: 0.0);
view.addConstraint(con2);
Error:
* Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '* +[NSLayoutConstraint
constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:constant:]:
Invalid pairing of layout attributes'
Does anyone have any idea on how to achieve this? I just want the left margin to be 0.75% of the parent view's width.
Thanks.
What you want is the left of sview to be at some point of the left of view and you are writing that you want the left of sview to be at some point of the width of view which is not a correct pairing of layout attributes as your error says.
Here is what you need to do:
NSLayoutConstraint(item: sview,
attribute: NSLayoutAttribute.Left,
relatedBy: NSLayoutRelation.Equal,
toItem: view,
attribute: NSLayoutAttribute.Left,
multiplier: 1,
constant: (CGRectGetWidth(view.bounds) * 0.75));
Hope it helps!
EDIT
I found a great article about Percented based margins: https://web.archive.org/web/20170624134422/http://simblestudios.com/blog/development/percentage-width-in-autolayout.html
Or even simpler:
https://web.archive.org/web/20170704113819/http://simblestudios.com/blog/development/easier-percentage-width-in-autolayout.html
It is possible to create a percentage-based margins with auto layout constraints between a subview and its superview. The margin will change dynamically as the size of the superview changes. For example, this is how to create a trailing 10% margin.
Create a trailing constraint between your view and its superview.
Make sure the first constraint item is the subview and the second item is the superview. This can be done by clicking on the first item drop down and selecting Reverse First and Second Item.
Change the constant value of the constraint to zero in the attributes inspector.
Change the multiplier value to 0.9.
The is one problem with this manual approach though - it does not work in right-to-left language, Arabic, for example. Right-to-left layouts require a bit different settings for the constraint but we can not keep both in one storyboard.
Here is a library that I wrote that lets you create percentage-based constraints. It does handle the right-to-left language case.
https://github.com/exchangegroup/PercentageMargin
You can subclass NSLayoutConstraint to accept a margin percentage via #IBInspectable. Then subscribe to UIDeviceOrientationDidChangeNotification to run the calculation and stuff into the constant value so it is updated whenever the layout changes.
/// Layout constraint to calculate size based on multiplier.
class PercentLayoutConstraint: NSLayoutConstraint {
#IBInspectable var marginPercent: CGFloat = 0
var screenSize: (width: CGFloat, height: CGFloat) {
return (UIScreen.mainScreen().bounds.width, UIScreen.mainScreen().bounds.height)
}
override func awakeFromNib() {
super.awakeFromNib()
guard marginPercent > 0 else { return }
NSNotificationCenter.defaultCenter().addObserver(self,
selector: #selector(layoutDidChange),
name: UIDeviceOrientationDidChangeNotification,
object: nil)
}
/**
Re-calculate constant based on orientation and percentage.
*/
func layoutDidChange() {
guard marginPercent > 0 else { return }
switch firstAttribute {
case .Top, .TopMargin, .Bottom, .BottomMargin:
constant = screenSize.height * marginPercent
case .Leading, .LeadingMargin, .Trailing, .TrailingMargin:
constant = screenSize.width * marginPercent
default: break
}
}
deinit {
guard marginPercent > 0 else { return }
NSNotificationCenter.defaultCenter().removeObserver(self)
}
}
First you specify the new subclass in Identity Inspector:
Then you can use it like this:
The only caveat I can think of is the constants in the Storyboard are not used at runtime, but instead are overwritten with the percentage based calculation. So it does require some duplicate effort, once to actually layout the views on Storyboard based on points just so you get a sense of what the screen layout looks like, then percentages kick in at runtime.
For more details, check out this article: http://basememara.com/percentage-based-margin-using-autolayout-storyboard/
Along with the Kevin answer you also need to add constraint for the box superview.
var view = UIView();
view.backgroundColor = UIColor.redColor();
view.setTranslatesAutoresizingMaskIntoConstraints(false);
self.view.addSubview(view);
var viewObject = ["mainview" : self.view , "view" : view];
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:[view(==320)]", options: NSLayoutFormatOptions(0), metrics: nil, views: viewObject));
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:[view(==400)]", options: NSLayoutFormatOptions(0), metrics: nil, views: viewObject));