Display an image in a scrollview with specific constraints (with auto layout) - ios

I would like display a title and below a image in my view controller.
My constraints are :
the label can be at 50px of the top of the screen
the label can have one or many rows
the image can be at 50px of my label
the image must have the width of the screen
the scroll view must scroll depending on the size of all these elements
I have a view controller with a scroll view :
-view controller
---view
------scroll view
---------container view
------------label
------------image
I want use storyboard and auto layout.
I have succeeded to align the label correctly, but I am unable to display the image at 50px of the label and keep it ratio.
If I use "aspect fit" or "scale to fill" for the imageview, in this case, the label and the image are at 50px like I want.
With aspect fit :
With scale to fill :
But if I use "aspect fill", I don't understand how the image is displayed.
With aspect fill :
It has been about 3 days that I'm on this issue, it make me crazy.
I tried also to use invisible "spacer" views...
I don't find the solution.
I develop in swift with Xcode 6.
EDIT 1 : Positioning the image is solved with the vacawama advice
Add an #IBOutlet for your imageView and a property to your view controller to keep track of the constraint:
#IBOutlet weak var imageView: UIImageView!
var aspectRatio:NSLayoutConstraint?
Then add a new image:
let tree = UIImage(named: "WinterTree.jpg")!
imageView.image = tree
aspectRatio = NSLayoutConstraint(item: imageView, attribute: .Height, relatedBy: .Equal, toItem: imageView, attribute: .Width, multiplier: tree.size.height/tree.size.width, constant: 1)
imageView.addConstraint(aspectRatio!)
EDIT 2 : But after, my view is no longer to scroll
Constraints of my container view :
I removed the bottom constraint in order to the height of the container view adapts to the content, but I get an error :

The problem is that the height of the imageView is not getting changed, so the image is just centered over the old frame. The view mode setting (Aspect Fit, Scale To Fit, or Aspect Fill) does not change the height of your imageView. You need a constraint for the height of your imageView, but this constraint will change depending on your image.
I was able to make this work by doing the following.
I constrained the width of the image to the width of the view.
I added an AspectRatio constraint to the image. This sets the ratio of the width to the height, and since I have specified the width of the image, the height will now be fully specified. I had hoped to be able to update this constraint in code when I loaded a new image (because different images have different aspect ratios), but I could only change the constant from code and not the multiplier. So, to get around this, I made this a Placeholder constraint by checking Placeholder in the Attributes Inspector. This means that this constraint will be removed at build time.
In code, when I set the image for the imageView, I add a constraint that sets the width of the imageView to the height of the imageView with a multiplier. This takes the place of the aspect ratio constraint that was set in Interface Builder.
First, add an #IBOutlet for your imageView and a property to your view controller to keep track of the constraint:
#IBOutlet weak var imageView: UIImageView!
var aspectRatio:NSLayoutConstraint?
Then add a new image:
let tree = UIImage(named: "WinterTree.jpg")!
imageView.image = tree
aspectRatio = NSLayoutConstraint(item: imageView, attribute: .Height, relatedBy: .Equal, toItem: imageView, attribute: .Width, multiplier: tree.size.height/tree.size.width, constant: 1)
imageView.addConstraint(aspectRatio!)
When it is time to change your image, remove the previous aspectRatio constraint before adding a new one:
imageView.removeConstraint(aspectRatio!)
I implemented something similar to your layout. My project has a button in place of your label, but otherwise it is similar. When the user presses the button, my app replaces the image with one with an entirely different aspect ratio. Here is the Document Outline and all of the constraints from my project.
First Item: WinterTree1.jpg.Width
Relation: Equal
Second Item: WinterTree1.jgp.Height
Constant: 1
Priority: 1000
Multiplier: 0.68
First Item: WinterTree1.jpg.Leading
Relation: Equal
Second Item: ContentView.Leading
Constant: 8
First Item: ContentView.Bottom
Relation: Equal
Second Item: WinterTree1.jpg.Bottom
Constant: 8
First Item: ContentView.Trailing
Relation: Equal
Second Item: WinterTree1.jpg.Trailing
Constant: 8
First Item: WinterTree1.jpg.Top
Relation: Equal
Second Item: Button.Bottom
Constant: 20
First Item: Button.Top
Relation: Equal
Second Item: ContentView.Top
Constant: 20
First Item: ContentView.Center X
Relation: Equal
Second Item: Button.Center X
Constant: 0
First Item: Superview.Trailing
Relation: Equal
Second Item: ContentView.Trailing
Constant: 0
First Item: ContentView.Leading
Relation: Equal
Second Item: Superview.Leading
Constant: 0
First Item: Superview.Bottom
Relation: Equal
Second Item: ContentView.Bottom
Constant: 0
First Item: ContentView.Top
Relation: Equal
Second Item: Superview.Top
Constant: 0
First Item: ContentView.Width
Relation: Equal
Second Item: Superview.Width
Constant: 0
First Item: Scroll View.Leading
Relation: Equal
Second Item: Superview.Leading
Constant: 0
First Item: Scroll View.Top
Relation: Equal
Second Item: Superview.Top
Constant: 0
First Item: Superview.Trailing
Relation: Equal
Second Item: Scroll View.Trailing
Constant: 0
First Item: Bottom Layout Guide.Top
Relation: Equal
Second Item: Scroll View.Bottom
Constant: 0

Related

How to change aspectRatio of IBOutlet for a view programmatically?

Okay here is my constraints.
The view is aligned centreX and centreY and has leading, trailing, top & bottom as >= 0.
It also has another constraint of aspectRatio.
Now I have created the #IBOutlet of aspectRation constraint.
#IBOutlet weak var contentViewAspectRatio: NSLayoutConstraint!
The problem is I don't know how to change the aspectRatio programmatically.
if value {
//Change aspect ratio to 16/9
} else {
//Change aspect ratio to 19.5/9
}
Any kind of help would be appreciated.
Changing the aspect ratio means changing the multiplier of the constraint which is a read-only so you need to deactivate that constraint and create a new 1 with a new multiplier
let newCon = contentViewAspectRatio.addConstraintWithMul(0.3)
parentViewOfConstraint.removeConstraint(contentViewAspectRatio)
parentViewOfConstraint.addConstraint(newCon)
view.layoutIfNeeded()
contentViewAspectRatio = newCon
extension NSLayoutConstraint {
func addConstraintWithMul(_ multiplier: CGFloat) -> NSLayoutConstraint {
return NSLayoutConstraint(item: self.firstItem!, attribute: self.firstAttribute, relatedBy: self.relation, toItem: self.secondItem, attribute: self.secondAttribute, multiplier: multiplier, constant: self.constant)
}
}
Another option is to create 2 constraints with 2 different aspects and play with their active / priority state in case you have a limited and know number of aspects

Swift - Xcode 8 - iOS 10 - Can't create simple constraint

EDIT - I fixed it. See answer.
I'm trying to learn how to create constraints programmatically. I tried to constrain a UILabel to the top of the screen, using this code, in viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
//the variable label already exists. I make a UILabel.
label = UILabel()
label.text = "Hello"
label.backgroundColor = UIColor(red: 1, green: 1, blue: 0, alpha: 1)
//I add the label to the ViewController
view.addSubview(label)
//I use an array of constraints because I will make more later.
let constraints : [NSLayoutConstraint] = [
NSLayoutConstraint(item: label, attribute: .top, relatedBy: .equal, toItem: topLayoutGuide, attribute: .bottom, multiplier: 1.0, constant: 0.0)
]
view.addConstraints(constraints)
}
When I run my app, I get an error beginning with:
[LayoutConstraints] Unable to simultaneously satisfy constraints.
My intended equation for the constraint was:
label.top = 1.0 * topLayoutGuide.bottom + 0.0
When I created what seems to be an identical constraint with Interface Builder, it worked. However, it doesn't when I try to do it programatically. What's wrong with what I'm doing, and how can I programmatically make a constraint using NSLayoutConstraint that will pin my Label to the top of the screen right below the status bar?
My sources:
Apple - Anatomy of a Constraint
Apple - Programmatically creating Constraints
StackOverflow - SWIFT | Adding constraints programmatically
This can be fixed by setting translatesAutoresizingMaskIntoConstraints to be false, to avoid clashes with existing Mask Constraints:
label.translatesAutoresizingMaskIntoConstraints = false

Swift - How changing textView height dynamically in constraint?

I want to change a text views's height in the middle of the table view cell by context. After textView I have another views, constant height provided by auto layout constraints.
Actually I will get first 150 characters from context to show, but I think using auto resize needed to prevent another screen sizes problem.
How Can I use auto dimension, Is there any way to assign table view row height like this?
let height = 4 + 17 + contextHeight + 4
in the storyboard, add an aspect ratio for the textview, Then check "Remove at build time" option.
In viewDidLayoutSubviews() :
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let contentSize = self.TextViewTitle.sizeThatFits(self.TextViewTitle.bounds.size)
var frame = self.TextViewTitle.frame
frame.size.height = contentSize.height
self.TextViewTitle.frame = frame
aspectRatioTextViewConstraint = NSLayoutConstraint(item: self.TextViewTitle, attribute: .Height, relatedBy: .Equal, toItem: self.TextViewTitle, attribute: .Width, multiplier: TextViewTitle.bounds.height/TextViewTitle.bounds.width, constant: 1)
self.TextViewTitle.addConstraint(aspectRatioTextViewConstraint!)
}

textView height equal to content size without viewDidLayoutSubviews

I'm creating a viewController which contain 2 textViews a title and a fullText. At the moment i've created 2 textViews in the interface builder which is placed below each other and then created following code to change the height to equal to the content. However the issue is that it seem to be delayed, which gives a bad user experience. By delay i mean that it takes 0.5 or 1 sec before it resize? here is my code:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
setHeightToContent(self.titleText!)
setHeightToContent(self.fullText!)
scrollView?.contentSize = CGSizeMake(self.view.frame.width, self.fullText!.frame.origin.y + self.fullText!.frame.height)
println(self.fullText!.frame.origin.y + self.fullText!.frame.height)
}
func setHeightToContent(theTextView: UITextView) {
let contentSize = theTextView.sizeThatFits(theTextView.bounds.size)
var frame = theTextView.frame
frame.size.height = contentSize.height
theTextView.frame = frame
var aspectRatioTextViewConstraint = NSLayoutConstraint(item: theTextView, attribute: .Height, relatedBy: .Equal, toItem: theTextView, attribute: .Width, multiplier: theTextView.bounds.height/theTextView.bounds.width, constant: 1)
theTextView.addConstraint(aspectRatioTextViewConstraint)
}
Make each of the text views self-sizing in accordance with its own content, and use constraints so that the scroll view's contentSize is configured automatically based on its content.

VFL constraints in swift: Crashes due to no superview

I'm trying to do a simple layout programmatically and I'm missing something simple, or have something out of place, I think. The following ViewController should center the label in the super view. However, it crashes with this trimmed message: The view hierarchy is not prepared for the constraint ... When added to a view, the constraint's items must be descendants of that view (or the view itself). This will crash if the constraint needs to be resolved before the view hierarchy is assembled... View not found in container hierarchy: ... That view's superview: NO SUPERVIEW The other SO questions with this error message are using nibs for the most part, and I'm tring to avoid that, or use Obj-C instead of swift. This question deals with the topic a bit but is a bit old.
Can anyone point me in the right direction?
class ViewController: UIViewController {
let label1 = UILabel() as UILabel
func layoutView(){
label1.text = "Click to see device configuration"
label1.setTranslatesAutoresizingMaskIntoConstraints(false)
view.addSubview(label1)
let viewsDictionary = ["label1":label1]
let label1_H:NSArray = NSLayoutConstraint.constraintsWithVisualFormat("H:|-[label1]-|",
options: NSLayoutFormatOptions(0),
metrics: nil,
views: viewsDictionary)
let label1_V:NSArray = NSLayoutConstraint.constraintsWithVisualFormat("V:|-[label1]-|",
options: NSLayoutFormatOptions(0),
metrics: nil, views:
viewsDictionary)
label1.addConstraints(label1_H) // Comment these 2 lines and it runs, but
label1.addConstraints(label1_V) // of course the label is upper left
}
override func viewDidLoad() {
super.viewDidLoad()
layoutView()
}
}
Those constraints are made between the label and its superview. The constraints should be added to that superview, not to the label.
You're almost there. Just replace the following lines...
label1.addConstraints(label1_H) // Comment these 2 lines and it runs, but
label1.addConstraints(label1_V) // of course the label is upper left
... with the following code:
view.addConstraints(label1_H) // Comment these 2 lines and it runs, but
view.addConstraints(label1_V) // of course the label is upper left
However, the constraints H:|-[label1]-|" and V:|-[label1]-|" are equivalent to H:|-8-[label1]-8-|" and V:|-8-[label1]-8-|" (see the iOS Developer Library for more details on default margins). Thus, those constraints are not meant to center your label. In fact, you will just have an enormous label that has 8 unit top, leading, trailing and bottom margins to the viewController's view.
Add the following line of code in your layoutView() method to see what I mean:
label1.backgroundColor = UIColor.orangeColor()
It can be OK but if you really want to center your label, you will have to use the following code:
func layoutView() {
label1.text = "Click to see device configuration"
label1.backgroundColor = UIColor.orangeColor()
//Set number of lines of label1 to more than one if necessary (prevent long text from being truncated)
label1.numberOfLines = 0
label1.setTranslatesAutoresizingMaskIntoConstraints(false)
view.addSubview(label1)
let xConstraint = NSLayoutConstraint(item: label1,
attribute: NSLayoutAttribute.CenterX,
relatedBy: NSLayoutRelation.Equal,
toItem: view,
attribute: NSLayoutAttribute.CenterX,
multiplier: 1.0,
constant: 0.0)
self.view.addConstraint(xConstraint)
let yConstraint = NSLayoutConstraint(item: label1,
attribute: NSLayoutAttribute.CenterY,
relatedBy: NSLayoutRelation.Equal,
toItem: view,
attribute: NSLayoutAttribute.CenterY,
multiplier: 1.0,
constant: 0)
self.view.addConstraint(yConstraint)
//Add leading and trailing margins if necessary (prevent long text content in label1 to be larger than screen)
let viewsDictionary = ["label1" : label1]
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-(>=10#750)-[label1]-(>=10#750)-|", options: NSLayoutFormatOptions(0), metrics: nil, views: viewsDictionary))
}

Resources