Autolayout: Scale to fill does not work - ios

I am attempting to programatically add the UIView courseView to a container view (called container) that I have drawn in Interface Builder in the Storyboard.
I would like courseView to scale to fit the container. With the following code nothing shows - the courseView does not appear. What am I missing?
var courseView: UIView?
#IBOutlet weak var container: UIView!
override func viewDidLoad() {
super.viewDidLoad()
courseView = UIView(frame: CGRectMake(0, 0, 1000, 1000))
courseView?.backgroundColor = UIColor.blueColor()
view.addSubview(courseView!)
courseView!.translatesAutoresizingMaskIntoConstraints = false
let courseWidthConstraint = NSLayoutConstraint(item: courseView!, attribute: .Width, relatedBy: .LessThanOrEqual, toItem: container, attribute: .Width, multiplier: 1.0, constant: 0)
let courseHeightConstraint = NSLayoutConstraint(item: courseView!, attribute: .Height, relatedBy: .LessThanOrEqual, toItem: container, attribute: .Height, multiplier: 1.0, constant: 0)
let courseViewConstraint = NSLayoutConstraint(item: courseView!, attribute: .Height , relatedBy: .Equal , toItem: courseView!, attribute: .Width, multiplier: 2.0, constant: 0)
self.view.addConstraints([courseWidthConstraint, courseHeightConstraint, courseViewConstraint])
}

It is not clear to me what you are really trying to achieve here. But as a first suggestion, try replacing the .LessThanOrEqual with .Equal for the width constraint.
One other thing you need for your constraints to work, are some top and leading constraints to the container(top is just a suggestion you might want some other alignment for the height)
let courseLeadingConstraint = NSLayoutConstraint(item: courseView!, attribute: .Leading, relatedBy: .Equal, toItem: container, attribute: .Leading, multiplier: 1.0, constant: 0)
let courseTopConstraint = NSLayoutConstraint(item: courseView!, attribute: .Top, relatedBy: .Equal, toItem: container, attribute: .Top, multiplier: 1.0, constant: 0)
The aspect ration constraint for having the height half the width would be the following
let courseViewConstraint = NSLayoutConstraint(item: courseView!, attribute: . Width , relatedBy: .Equal , toItem: courseView!, attribute: .Height, multiplier: 2.0, constant: 0)
Let me know if it worked out.

Related

NSLayoutConstraint in playground

I'm trying to reproduce a scenario like this where the red and blue rectangles can occupy same width and height (and same gap between them) for different screen sizes.
I'm using NSLayoutConstraint (I know that anchors are preferred now, just trying to explore the basics). I tried the following code in swift playground:
import Foundation
import UIKit
import PlaygroundSupport
class MyViewController : UIViewController {
var firstColorView: UIView!
var secondColorView: UIView!
override func viewWillAppear(_ animated: Bool) {
var myView: UIView!
myView = view
myView.backgroundColor = .white
firstColorView = UIView()
secondColorView = UIView()
firstColorView.backgroundColor = .red
secondColorView.backgroundColor = .blue
myView.addSubview(firstColorView)
myView.addSubview(secondColorView)
//myView.translatesAutoresizingMaskIntoConstraints = false
//view.addSubview(myView)
// horizontal constraints
let left_constraint = NSLayoutConstraint(item: firstColorView, attribute: .leftMargin, relatedBy: .equal, toItem: myView, attribute: .left, multiplier: 1.0, constant: 20)
let middle_constraint = NSLayoutConstraint(item: secondColorView, attribute: .leftMargin, relatedBy: .equal, toItem: firstColorView, attribute: .right, multiplier: 1.0, constant: 10)
let right_constraint = NSLayoutConstraint(item: myView, attribute: .rightMargin, relatedBy: .equal, toItem: secondColorView, attribute: .right, multiplier: 1.0, constant: 20)
let width_constraint = NSLayoutConstraint(item: firstColorView, attribute: .width, relatedBy: .equal, toItem: secondColorView, attribute: .width, multiplier: 1.0, constant: 0)
// vertical constraints
let top_constraint1 = NSLayoutConstraint(item: firstColorView, attribute: .top, relatedBy: .equal, toItem: myView, attribute: .top, multiplier: 1.0, constant: 10)
let top_constraint2 = NSLayoutConstraint(item: secondColorView, attribute: .top, relatedBy: .equal, toItem: myView, attribute: .top, multiplier: 1.0, constant: 10)
let bottom_constraint1 = NSLayoutConstraint(item: myView, attribute: .bottom, relatedBy: .equal, toItem: firstColorView, attribute: .bottom, multiplier: 1.0, constant: 10)
let bottom_constraint2 = NSLayoutConstraint(item: myView, attribute: .bottom, relatedBy: .equal, toItem: secondColorView, attribute: .bottom, multiplier: 1.0, constant: 0)
NSLayoutConstraint.activate([left_constraint, middle_constraint, right_constraint, width_constraint, top_constraint1, top_constraint2, bottom_constraint1, bottom_constraint2])
self.view.layoutIfNeeded()
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.needsIndefiniteExecution = true
PlaygroundPage.current.liveView = MyViewController()
But all it shows is a white screen, whose width doesn't match an iphone's. What am I doing wrong here? Why can't I see the red and blue screen?
You miss
firstColorView.translatesAutoresizingMaskIntoConstraints = false
secondColorView.translatesAutoresizingMaskIntoConstraints = false

I need top constraint of unknow element

I have an app which is downloading image from server by clicking the button. After image have downloaded i create a new imageView and add it to the my contentView(UIView). I need to create the constraints - every new imageview need top constraint from previous one
func addNewImageToTheScrollView(img: UIImage?) {
if let imageResponse = img {
let imageView = UIImageView(image: imageResponse.crop(rect: CGRect(x: 0, y: imageResponse.size.height/2, width: self.contentView.frame.width, height: 200)))
self.contentView.addSubview(imageView)
imageView.translatesAutoresizingMaskIntoConstraints = false
let x = NSLayoutConstraint(item: imageView, attribute: .centerX, relatedBy: .equal, toItem: self.contentView, attribute: .centerX, multiplier: 1.0, constant: 0)
let y = NSLayoutConstraint(item: imageView, attribute: .top, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 30)
let width = NSLayoutConstraint(item: imageView, attribute: .width, relatedBy: .equal, toItem: self.contentView, attribute: .width, multiplier: 1.0, constant: 0)
let height = NSLayoutConstraint(item: imageView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 150)
self.contentView.addConstraints([x, y])
imageView.addConstraints([width, height])
}
}
If i comment the constraint code, it will be work fine unless every new imageView will be on the same place, on the top of the View. Now whit this constraint code i have such code issue after downloading
2017-07-02 14:50:01.018 ImageFromServerTest[11516:1080948] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'NSLayoutConstraint for >: A multiplier of 0 or a nil second item together with a location for the first attribute creates an illegal constraint of a location equal to a constant. Location attributes must be specified in pairs.'
Whenever you are working with scrollViews, there are 2 thumb rules for it:-
Give the scrollView leading, trailing, bottom, and top constraint with respect to the superview, that is self.view
#IbOutlet weak var scrollView: UIScrollView!
let leading = NSLayoutConstraint(item: scrollView, attribute: .leading, relatedBy: .equal, toItem: self.view, attribute: .leadingMargin, multiplier: 1.0, constant: 0)
let trailing = NSLayoutConstraint(item: scrollView, attribute: .trailing, relatedBy: .equal, toItem: self.view, attribute: .trailingMargin, multiplier: 1.0, constant: 0)
let top = NSLayoutConstraint(item: scrollView, attribute: .top, relatedBy: .equal, toItem: self.view, attribute: .top, multiplier: 1.0, constant: 0)
let bottom = NSLayoutConstraint(item: scrollView, attribute: .bottom, relatedBy: .equal, toItem: self.view, attribute: .bottom, multiplier: 1.0, constant: 0)
Add constraints to the contentView with respect to the scrollView
#IbOutlet weak var contentView: UIView!
let leading = NSLayoutConstraint(item: contentView, attribute: .leading, relatedBy: .equal, toItem: scrollView, attribute: .leading, multiplier: 1.0, constant: 0)
let trailing = NSLayoutConstraint(item: contentView, attribute: .trailing, relatedBy: .equal, toItem: scrollView, attribute: .trailing, multiplier: 1.0, constant: 0)
let top = NSLayoutConstraint(item: contentView, attribute: .top, relatedBy: .equal, toItem: scrollView, attribute: .top, multiplier: 1.0, constant: 0)
//increase the constant according to how much long you need the scrollview to be
let bottom = NSLayoutConstraint(item: contentView, attribute: .bottom, relatedBy: .equal, toItem: scrollView, attribute: .bottom, multiplier: 1.0, constant: 0)
Now add your subviews constraints (labels, images) with respect to the contentView
For example- You received your first image, so we will maintain an array of UIImageViews outside your function.
var imageViews = [UIImageViews]() //declared outside the function
//received an image here
var imageView = UIImageView() // define the frame according to yourself using frame init method
imageView.image = image
if imageViews.isEmpty { // first image view
//add constraints to first image view
let x = NSLayoutConstraint(item: imageView, attribute: .centerX, relatedBy: .equal, toItem: contentView, attribute: .centerX, multiplier: 1.0, constant: 0)
let y = NSLayoutConstraint(item: imageView, attribute: .top, relatedBy: .equal, toItem: contentView, attribute: .top, multiplier: 1.0, constant: 30)
let width = NSLayoutConstraint(item: imageView, attribute: .width, relatedBy: .equal, toItem: self.contentView, attribute: .width, multiplier: 1.0, constant: 0)
let height = NSLayoutConstraint(item: imageView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 150)
}
else { //second, third, fourth image view... so on
let x = NSLayoutConstraint(item: imageView, attribute: .centerX, relatedBy: .equal, toItem: contentView, attribute: .centerX, multiplier: 1.0, constant: 0)
let y = NSLayoutConstraint(item: imageView, attribute: .top, relatedBy: .equal, toItem: imageViews[imageViews.count - 1], attribute: .bottom, multiplier: 1.0, constant: 30)
let width = NSLayoutConstraint(item: imageView, attribute: .width, relatedBy: .equal, toItem: self.contentView, attribute: .width, multiplier: 1.0, constant: 0)
let height = NSLayoutConstraint(item: imageView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 150)
}
imageViews.append(imageView)
}
Hope you got an idea now, how to proceed with this problem. If having more than 4 or 5 imageviews, you'll probably want to check the count of the array and increase the contentView of the scrollView accordingly. you can do so by using
self.scrollView.contentSize = CGSize(width, height)
I believe you have a problem here:
let y = NSLayoutConstraint(item: imageView, attribute: .top, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 30)
"toItem" parameter should have some value. Also first parameter should be imageView.topAnchor. Probably it should look something like this:
let y = NSLayoutConstraint(item: imageView.topAnchor, attribute: .top, relatedBy: .equal, toItem: self.contentView.topAnchor, attribute: .notAnAttribute, multiplier: 1.0, constant: 30)

Autolayout - UIView does not resize correctly to subview's size

I'm trying to programmatically create a UIView with a UILabel as a subview using autolayout.
Constraints
I used the following constraints on view:
CenterX of view to fastAttacksContainerView (superview).
Top constraint of constant 8 to superview.
Then created a label which is a subview of view and added constraints of constant 8 for Top, Bottom, Left, and Right to view.
Problem
The view only resizes to the frame of the label and does not account for the 4 constraints of constant 8 on all 4 sides. Which causes the label to be displayed partially outside the view.
let view = UIView()
view.backgroundColor = pokemon.secondaryColor
let label = UILabel()
fastAttacksContainerView.addSubview(view)
view.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
label.text = fa
let gest = UITapGestureRecognizer(target: self, action: #selector(self.selectFastAttack))
view.addGestureRecognizer(gest)
fastAttackButtons.append(view)
fastAttackLables.append(label)
let top = NSLayoutConstraint(item: view, attribute: .Top, relatedBy: .Equal, toItem: fastAttacksContainerView, attribute: .Top, multiplier: 1, constant: 8)
let centerX = NSLayoutConstraint(item: view, attribute: .CenterX, relatedBy: .Equal, toItem: fastAttacksContainerView, attribute: .CenterX, multiplier: 1, constant: 0)
let labLeft = NSLayoutConstraint(item: label, attribute: .Left, relatedBy: .Equal, toItem: view, attribute: .Left, multiplier: 1, constant: 8)
let labTop = NSLayoutConstraint(item: label, attribute: .Top, relatedBy: .Equal, toItem: view, attribute: .Top, multiplier: 1, constant: 8)
let labRigth = NSLayoutConstraint(item: label, attribute: .Right, relatedBy: .Equal, toItem: view, attribute: .Right, multiplier: 1, constant: 8)
let labBottom = NSLayoutConstraint(item: label, attribute: .Bottom, relatedBy: .Equal, toItem: view, attribute: .Bottom, multiplier: 1, constant: 8)
view.addConstraints([labLeft, labTop, labRigth, labBottom])
fastAttacksContainerView.addConstraints([top, centerX])
Output
view has ambiguous height.
You should add constraint for height of view or distance from it to bottom of fastAttacksContainerView.
I managed to fix this problem by making a similar setup on storyboard and copying the constraints in.
Things that I changed:
Used Leading and Trailing instead of Left and Right layout attributes.
For the labLeft and labRight constraints, I interchanged item and toItem. (This seems like a bug to me. Can anyone verify, please?)
Code change:
let labLeft = NSLayoutConstraint(item: label, attribute: .Leading, relatedBy: .Equal, toItem: view, attribute: .Leading, multiplier: 1, constant: 8)
let labTop = NSLayoutConstraint(item: label, attribute: .Top, relatedBy: .Equal, toItem: view, attribute: .Top, multiplier: 1, constant: 8)
let labRigth = NSLayoutConstraint(item: view, attribute: .Trailing, relatedBy: .Equal, toItem: label, attribute: .Trailing, multiplier: 1, constant: 8)
let labBottom = NSLayoutConstraint(item: view, attribute: .Bottom, relatedBy: .Equal, toItem: label, attribute: .Bottom, multiplier: 1, constant: 8)

Use autolayout on view added to UIWindow

I'm trying to add a subview to the keyWindow of the app, and position it using autolayout. However, autolayout doesn't seem to work at all, whereas setting a frame does. I want to align my view infoSc to the bottom of the keyWindow using the following code:
let infoSc = InfoScreenView()
infoSc.translatesAutoresizingMaskIntoConstraints = false
let keyWindow = UIApplication.sharedApplication().keyWindow!
keyWindow.addSubview(infoSc)
keyWindow.addConstraint(NSLayoutConstraint(item: infoSc, attribute: .Left, relatedBy: .Equal, toItem: keyWindow, attribute: .Left, multiplier: 1, constant: 0))
keyWindow.addConstraint(NSLayoutConstraint(item: infoSc, attribute: .Right, relatedBy: .Equal, toItem: keyWindow, attribute: .Right, multiplier: 1, constant: 0))
keyWindow.addConstraint(NSLayoutConstraint(item: infoSc, attribute: .Bottom, relatedBy: .Equal, toItem: keyWindow, attribute: .Bottom, multiplier: 1, constant: 0))
infoSc.addConstraint(NSLayoutConstraint(item: infoSc, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 100))
However, it appears to have a frame of CGRectZero using this method. Any ideas how to make this work? Ideally I'd also want to align it to something that's inside self.view too, but that throws an error that self.view is not in the view hierarchy of keyWindow.
If you need to draw on the entire window, here is code to do it (in this example I am in the AppDelegate, so window is the AppDelegate.window property).
func tryToDrawOnTheWindow()
{
if let window = window, view = window.rootViewController?.view
{
print("I have a root view")
let infoSc = InfoScreenView(frame: view.frame)
let count = view.subviews.count
view.insertSubview(infoSc, atIndex: count)
infoSc.translatesAutoresizingMaskIntoConstraints = false
let height = NSLayoutConstraint(item: infoSc, attribute: .Height, relatedBy: .Equal, toItem: view, attribute: .Height, multiplier: 1, constant: 0)
let width = NSLayoutConstraint(item: infoSc, attribute: .Width, relatedBy: .Equal, toItem: view, attribute: .Width, multiplier: 1, constant: 0)
let offset = NSLayoutConstraint(item: infoSc, attribute: .Top, relatedBy: .Equal, toItem: view, attribute: .Top, multiplier: 1, constant: 0)
print([height, width, offset])
view.addConstraints([height, width, offset])
}
else
{
print("No root view on which to draw")
}
}
This will let you draw on top of whatever is in the view hierarchy. In my test app, I added a textfield and a blue rect, and the overlay was orange with 40% opacity. Bear in mind that by default the overlay view will consume all taps in this case.
Place and call a method below, based on David S's answer, where you want, passing your view as variable.
func addToWindow(view : UIView) {
guard let delegate = UIApplication.shared.delegate, let window = delegate.window!, let topView = window.rootViewController?.view else {
print("No root view on which to draw")
return
}
print("I have a root view")
let count = topView.subviews.count
topView.insertSubview(view, at: count)
view.translatesAutoresizingMaskIntoConstraints = false
let height = NSLayoutConstraint(item: view, attribute: .height, relatedBy: .equal, toItem: topView, attribute: .height, multiplier: 1, constant: 0)
let width = NSLayoutConstraint(item: view, attribute: .width, relatedBy: .equal, toItem: topView, attribute: .width, multiplier: 1, constant: 0)
let pinToTop = NSLayoutConstraint(item: view, attribute: .top, relatedBy: .equal, toItem: topView, attribute: .top, multiplier: 1, constant: 0)
print([height, width, pinToTop])
topView.addConstraints([height, width, pinToTop])
}

ios swift - auto layout programatically to subview

I'm adding a prompt to purchase the full version of the app by displaying a uiview to the user; however, I'm having issues settings constraints programatically as to not have the issue shown in the image below on the iPhone 6 plus.
I'm doing:
let purchasePopUp = PromptPurchase.instanceFromNib() as! PromptPurchase
//purchasePopUp.frame = UIScreen.mainScreen().bounds
purchasePopUp.translatesAutoresizingMaskIntoConstraints = false
// let leadingConstraint = NSLayoutConstraint(item: purchasePopUp, attribute: .Leading, relatedBy: .Equal, toItem: view, attribute: .Leading, multiplier: 1, constant: 0)
let trailingConstraint = NSLayoutConstraint(item: purchasePopUp, attribute: .Trailing, relatedBy: .Equal, toItem: view, attribute: .Trailing, multiplier: 1, constant: 0)
// let topConstraint = NSLayoutConstraint(item: purchasePopUp, attribute: .Top, relatedBy: .Equal, toItem: view, attribute: .Top, multiplier: 1, constant: 0)
let bottomConstraint = NSLayoutConstraint(item: purchasePopUp, attribute: .Bottom, relatedBy: .Equal, toItem: view, attribute: .Bottom, multiplier: 1, constant: 0)
//view.addConstraints([leadingConstraint, trailingConstraint, topConstraint, bottomConstraint])
view.addConstraints([trailingConstraint, bottomConstraint])
view.addSubview(purchasePopUp)
And it's still only taking up the same space.
Thank you
With EasyPeasy your code would look like this, give it a try :)
import EasyPeasy
if let purchasePopUp = PromptPurchase.instanceFromNib() as? PromptPurchase {
view.addSubview(purchasePopUp)
purchasePopUp <- Edges()
}
https://github.com/nakiostudio/EasyPeasy

Resources