ViewController constraints in UIScrollView, using Storyboard - ios

I have two UIViewControllers with Storyboard identifiers "vc0" and "vc1", which I am trying to instantiate objects of within a UIScrollView with paging enabled such that the two view controllers are adjacent to each other. The goal is to have the user swipe left and right to swipe between view controllers, similar to SnapChat.
When running my code, the first page of the scrollview contains the first view controller, while the second page contains nothing at all. I am assuming that this is because the first view controller is overlapping the second. How can I alter my constraints (or anything at all) to fix this issue, so that the right edge of vc0 meets with the left edge of vc1?
The UIScrollView is contained within a UIView, within a view controller. Here is the viewDidLoad() which contains all the relevant code. Please let me know of any additional helpful information I should provide.
override func viewDidLoad()
{
super.viewDidLoad()
let screenWidth = self.view.frame.width
let screenHeight = self.view.frame.height
let vc0 = self.storyboard?.instantiateViewControllerWithIdentifier("vc0")
self.addChildViewController(vc0!)
self.scrollView.addSubview(vc0!.view)
vc0!.didMoveToParentViewController(self)
let vc1 = self.storyboard?.instantiateViewControllerWithIdentifier("vc1")
var frame1 = vc1!.view.frame
frame1.origin.x = self.view.frame.size.width
vc1!.view.frame = frame1
self.addChildViewController(vc1!)
self.scrollView.addSubview(vc1!.view)
vc1!.didMoveToParentViewController(self)
self.scrollView.contentSize = CGSizeMake(self.view.frame.size.width * 2, self.view.frame.size.height);
//ADD CONSTRAINTS TO vc0
vc0!.view.translatesAutoresizingMaskIntoConstraints = false
let horizontalConstraint = NSLayoutConstraint(item: vc0!.view, attribute: NSLayoutAttribute.Left, relatedBy: NSLayoutRelation.Equal, toItem: scrollView, attribute: NSLayoutAttribute.Left, multiplier: 1, constant: 0)
view.addConstraint(horizontalConstraint)
let widthConstraint = NSLayoutConstraint(item: vc0!.view, attribute: NSLayoutAttribute.Width, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1, constant: screenWidth)
view.addConstraint(widthConstraint)
let heightConstraint = NSLayoutConstraint(item: vc0!.view, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1, constant: screenHeight)
view.addConstraint(heightConstraint)
//ADD CONSTRAINTS TO vc1
vc1!.view.translatesAutoresizingMaskIntoConstraints = false
let horizontalConstraint2 = NSLayoutConstraint(item: vc1!.view, attribute: NSLayoutAttribute.Left, relatedBy: NSLayoutRelation.Equal, toItem: vc0!.view, attribute: NSLayoutAttribute.Right, multiplier: 1, constant: 0)
view.addConstraint(horizontalConstraint2)
let widthConstraint2 = NSLayoutConstraint(item: vc1!.view, attribute: NSLayoutAttribute.Width, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1, constant: screenWidth)
view.addConstraint(widthConstraint)
let heightConstraint2 = NSLayoutConstraint(item: vc1!.view, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1, constant: screenHeight)
view.addConstraint(heightConstraint)
}

I think there is a problem in constraints!!
You should add one view to scrollview and then add views of both VC to that view not direct to scrollview.
view's size should be same as scrollview's contentsize
Now your constrains should be like :
scrollview : top,bottom,leading,trailing
View in scrollview : top,bottom,leading,trailing, Vertical center in container(center Y) and fixed width (because you want horizontal scrolling), If want vertical scrolling then constraint should be (horizontal center in container (center X) and fixed height).
And you views from both VCs : top,leading,fixed width, fixed height (width and height should be same as screen size)
Hope this will help :)

Related

Wrong NSLayoutConstraint Causes Black Screen

I want to place header view on top of screen with NSLayoutConstraint (I must use NSLayoutConstraint). When I do it like in below code, view places corruptly in somewhere else and also controllers background color turns black and nothing works. Where am I doing wrong?
I searched below posts for not opening a duplicate post but nothing fixed it:
Programmatically creating constraints bound to view controller margins
Programmatically Add CenterX/CenterY Constraints
EDIT: This controller is inside navigation controller but I'm not sure If It is related.
override func viewDidLoad(){
self.view.backgroundColor = UIColor.white
boxView.backgroundColor = Color.Common.welcomeScreenBackgroundColor.withAlphaComponent(0.5)
boxView.translatesAutoresizingMaskIntoConstraints = false
self.view.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubView(boxView)
}
override func viewDidLayoutSubviews() {
//Header = 20 from left edge of screen
let cn1 = NSLayoutConstraint(item: boxView, attribute: .leading, relatedBy: .equal, toItem: self.view, attribute: .leading, multiplier: 1.0, constant: 0)
//Header view trailing end is 20 px from right edge of the screen
let cn2 = NSLayoutConstraint(item: boxView, attribute: .trailing, relatedBy: .equal, toItem: self.view, attribute: .trailing, multiplier: 1.0, constant: 0)
//Header view height = constant 240
let cn3 = NSLayoutConstraint(item: boxView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant:240)
//Header view vertical padding from the top edge of the screen = 20
let cn5 = NSLayoutConstraint(item: boxView, attribute: .top, relatedBy: .equal, toItem: self.topLayoutGuide, attribute: .bottom, multiplier: 1.0, constant: 0)
self.view.addConstraints([cn1,cn2,cn3,cn5])
}
The problem was setting translatesAutoresizingMaskIntoConstraints to false on Superview. So I deleted the;
self.view.translatesAutoresizingMaskIntoConstraints = false
and this solves the problem. I think this causes app creates constraint for superview.

Setting custom RequestButton button

I have just started exploring the Uber iOS Sdk. I was checking for RequestButton and have noticed that its not taking any frame which we are passing. Is it possible to put button on desired location of a view?
let button = RequestButton(colorStyle:.White)
view.addSubview(button)
button.frame = CGRectMake(100,300, button.frame.size.width, button.frame.size.height)
It is possible to add the RequestButton in a desired location. The RequestButton is positioned with Auto Layout (or Constraint Based Layout) rather than Frame-Based Layout. See Apple's notes on Auto Layout Versus Frame-Based Layout.
Auto Layout guarantees resizing when the frame changes orientation and for different device sizes. In Auto Layout, you need to place UIViews based on their relative position to other views.
Here is an example of how you would center a RequestButton (button) inside a parent UIView (inView)
func centerButton(forButton button: RequestButton, inView: UIView) {
button.translatesAutoresizingMaskIntoConstraints = false
// position constraints
let horizontalConstraint = NSLayoutConstraint(item: button, attribute: NSLayoutAttribute.CenterX, relatedBy: NSLayoutRelation.Equal, toItem: inView, attribute: NSLayoutAttribute.CenterX, multiplier: 1, constant: 0)
let verticalConstraint = NSLayoutConstraint(item: button, attribute: NSLayoutAttribute.CenterY, relatedBy: NSLayoutRelation.Equal, toItem: inView, attribute: NSLayoutAttribute.CenterY, multiplier: 1, constant: 0)
// add constraints to view
inView.addConstraints([horizontalConstraint, verticalConstraint])
}
There might be an easier way but I found this solution for positioning the Uber RequestButton using frame based layouts.
Place the request button inside a wrapper UIView. Set the constraints on the wrapper view via Christine Kim's answer. Then you can position the wrapper's frame and the RequestButton will move with it
// MyView.swift
let uber = RequestButton()
let uberWrapper = UIView()
uberWrapper.addSubview(uber)
self.addSubview(uberWrapper)
//Set constraints
let horizontalConstraint = NSLayoutConstraint(item: uber, attribute: NSLayoutAttribute.CenterX, relatedBy: NSLayoutRelation.Equal, toItem: uberWrapper, attribute: NSLayoutAttribute.CenterX, multiplier: 1, constant: 0)
let verticalConstraint = NSLayoutConstraint(item: uber, attribute: NSLayoutAttribute.CenterY, relatedBy: NSLayoutRelation.Equal, toItem: uberWrapper, attribute: NSLayoutAttribute.CenterY, multiplier: 1, constant: 0)
// add constraints to view
uberWrapper.addConstraints([horizontalConstraint, verticalConstraint])
//layoutSubviews()
//set your origin
uberWrapper.frame = CGRect(origin: CGPointMake(0, 0), size: uber.intrinsicContentSize())

What are the necessary steps to create a UIScrollView programmatically as a subview of another UIView?

TL;DR -> what are the necessary steps to create a class that extends UIScrollView, instantiate that class, and then add it as a subview of an existing UIView using NSLayoutConstraints for my UI?
I'm in a situation where I need to add a UIScrollView to an existing UIViewController's subview (not self.view - a child of self.view). I'm also not using IB because I want to understand my code. I've managed to lay out each UI component, and I can see that my view hierarchy has been assembled correctly in the debugger.
Unfortunately, my UIScrollView will not scroll and for some reason none of my subviews are being displayed visually (note that the immediate children of my scroll view are also scroll views so my problem is likely my understanding of the UIScrollView class).
The above image shows the general structure of my UI, and the light gray area is my UIScrollView. I have a class that extends UIScrollView and UIScrollDelegate (not 100% if the latter parent class is necessary). The class looks like this
import UIKit
class PlayWorkout: UIScrollView, UIScrollViewDelegate{
var workoutInstructionTiles : [WorkoutInstruction] = [WorkoutInstruction]();
var heightsTotal : Int = 0; //This variable will keep track of the running total for each of the workout tile subviews to help with positioning
init(workoutSubviews : [WorkoutInstruction]){
super.init(frame : CGRect.zero);
workoutInstructionTiles = workoutSubviews;
appendTiles();
self.translatesAutoresizingMaskIntoConstraints = false;
}
func appendTiles(){
print("appending tiles \(workoutInstructionTiles.count)");
for(var i = 0; i < workoutInstructionTiles.count; i++){
let setCount = workoutInstructionTiles[i].getNumSets();
workoutInstructionTiles[i].layer.cornerRadius = 5;
workoutInstructionTiles[i].backgroundColor = UIColor.whiteColor();
workoutInstructionTiles[i].translatesAutoresizingMaskIntoConstraints = false;
changeContentSize(workoutInstructionTiles[i]);
self.addSubview(workoutInstructionTiles[i]);
//Formula for position relative to top of the ScrollView will be 10 (margin) + 20 (label space) + setCount * 20 (content) + heightsTotal
let verticalConstraint = NSLayoutConstraint(item: workoutInstructionTiles[i], attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: self, attribute: NSLayoutAttribute.Top, multiplier: 1.0, constant: CGFloat(30 + heightsTotal));
let leftConstraint = NSLayoutConstraint(item: workoutInstructionTiles[i], attribute: NSLayoutAttribute.Trailing, relatedBy: NSLayoutRelation.Equal, toItem: self, attribute: NSLayoutAttribute.Trailing, multiplier: 1.0, constant: 10);
let rightConstraint = NSLayoutConstraint(item: workoutInstructionTiles[i], attribute: NSLayoutAttribute.Leading, relatedBy: NSLayoutRelation.Equal, toItem: self, attribute: NSLayoutAttribute.Leading, multiplier: 1.0, constant: 10);
let heightConstraint = NSLayoutConstraint(item: workoutInstructionTiles[i], attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1.0, constant: CGFloat(setCount * 20 + 20));
self.addConstraints([verticalConstraint, leftConstraint, rightConstraint, heightConstraint]);
heightsTotal += setCount * 20;
}
}
func setViewConstraints(superView : UIView) -> Void{
let verticalConstraint = NSLayoutConstraint(item: self, attribute: NSLayoutAttribute.CenterY, relatedBy: NSLayoutRelation.Equal, toItem: superView, attribute: NSLayoutAttribute.CenterY, multiplier: 1.0, constant:30);
let horizontalConstraint = NSLayoutConstraint(item: self, attribute: NSLayoutAttribute.CenterX, relatedBy: NSLayoutRelation.Equal, toItem: superView, attribute: NSLayoutAttribute.CenterXWithinMargins, multiplier: 1.0, constant: 0);
let heightConstraint = NSLayoutConstraint(item: self, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: superView, attribute: NSLayoutAttribute.Height, multiplier: 1.0, constant: -60);
let widthConstraint = NSLayoutConstraint(item: self, attribute: NSLayoutAttribute.Width, relatedBy: NSLayoutRelation.Equal, toItem: superView, attribute: NSLayoutAttribute.Width, multiplier: 1.0, constant: 0);
superView.addConstraints([verticalConstraint, horizontalConstraint, heightConstraint, widthConstraint]);
}
func changeContentSize(aView : UIView){
var contentRect : CGRect = CGRect.zero;
for view in aView.subviews {
contentRect = CGRectUnion(contentRect, view.frame);
}
self.contentSize = contentRect.size;
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
I then implement the class when a button is clicked to start a workout. From what I've been able to ascertain thus far it seems like the only methods I need to call/set for UIScrollView are
scrollView.contentSize = CGSize(width: aWidth, height: aHeight)
scrollView.translateAutoResizingMaskIntoConstraints = false
So, here's my class instantiation - this is inside of a UIGestureRecognizer event handler inside of a UIViewController that also extends UIScrollViewDelegate:
workoutConfig = PlayWorkout(workoutSubviews : workoutInstructions);
workoutConfig.delegate = self;
workoutConfig.changeContentSize(workoutConfig);
workoutConfig.translatesAutoresizingMaskIntoConstraints = false;
self.contentView.addSubview(workoutConfig);
workoutConfig.setViewConstraints(self.contentView);
I'm also using a method called changeContentSize() that was adapted from an objective-C method posted to SO that looks like this:
func changeContentSize(aView : UIView){
var contentRect : CGRect = CGRect.zero;
for view in aView.subviews {
contentRect = CGRectUnion(contentRect, view.frame);
}
self.contentSize = contentRect.size;
}
I know that's a lot of reading, but I wanted to be thorough. I appreciate any help. Thanks!
Ok, so after 3 days of maddening research and going down all kinds of nonsense paths to accomplish this I FINALLY get it, and it's actually not that hard (just extremely poorly documented by apple). I'm going to try to share what I found - this link is what finally helped me understand how to do this so I feel obliged to give credit where it's due:
http://spin.atomicobject.com/2014/03/05/uiscrollview-autolayout-ios/#comment-541731
Ok, here's the step by step to create a scrollable view using auto layout.
1 - a UIScrollView can only have 1 child view, which should generally be a UIView
to hold all of the scrollable content. Make sure to create a UIView, add any content you want to scroll to that UIView, and then add that UIView as a subview of your UIScrollView
2 - Pin the scroll view to its superview with whatever constraints you want, just make sure you set the top, leading, trailing and bottom. Here's what my code looked like (self is a class extending UIScrollView in my case):
func setViewConstraints(superView : UIView) -> Void{
let verticalConstraint = NSLayoutConstraint(item: self, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: superView, attribute: NSLayoutAttribute.Top, multiplier: 1.0, constant:60);
let leftConstraint = NSLayoutConstraint(item: self, attribute: NSLayoutAttribute.Trailing, relatedBy: NSLayoutRelation.Equal, toItem: superView, attribute: NSLayoutAttribute.Trailing, multiplier: 1.0, constant: 0);
let rightConstraint = NSLayoutConstraint(item: self, attribute: NSLayoutAttribute.Leading, relatedBy: NSLayoutRelation.Equal, toItem: superView, attribute: NSLayoutAttribute.Leading, multiplier: 1.0, constant: 0);
let bottomConstraint = NSLayoutConstraint(item: self, attribute: NSLayoutAttribute.Bottom, relatedBy: .Equal, toItem: superView, attribute: NSLayoutAttribute.Bottom, multiplier: 1.0, constant: 0);
superView.addConstraints([verticalConstraint, leftConstraint, bottomConstraint,rightConstraint]);
}
3 - Pin that child UIView mentioned in step 1 to the scroll view ensuring to set -> top, leading, trailing, and bottom. This should generally be pinned exactly unless the UIView is designed to be smaller than the dimension of the scroll view. This step is accomplished to ensure that the device knows where to place the UIView. My code looked like this (again, self is referring to a class that extends UIScrollView)
let pinTop = NSLayoutConstraint(item: subScrollView, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: self, attribute: NSLayoutAttribute.Top, multiplier: 1.0, constant:0);
let pinBottom = NSLayoutConstraint(item: subScrollView, attribute: NSLayoutAttribute.Bottom, relatedBy: NSLayoutRelation.Equal, toItem: self, attribute: NSLayoutAttribute.Bottom, multiplier: 1.0, constant: 0);
let pinLeft = NSLayoutConstraint(item: subScrollView, attribute: NSLayoutAttribute.Leading, relatedBy: NSLayoutRelation.Equal, toItem: self, attribute: NSLayoutAttribute.Leading, multiplier: 1.0, constant: 0);
let pinRight = NSLayoutConstraint(item: subScrollView, attribute: NSLayoutAttribute.Trailing, relatedBy: NSLayoutRelation.Equal, toItem: self, attribute: NSLayoutAttribute.Trailing, multiplier: 1.0, constant: 0);
self.addConstraints([pinTop, pinBottom, pinLeft, pinRight]);
4 - in order for the scroll view to understand the content size of the view, you MUST SET THE HEIGHT AND THE WIDTH OF THE UIVIEW BASED ON THE SCROLL VIEWS SUPERVIEW OR A CALCULATION THAT DOESN'T INVOLVE THE SCROLL VIEW. Don't worry about setting other dimensions of the UIView relative to the scroll views superview, just height and width are necessary for the cocoa api to infer the content size. My code looked like this (self.viewController.contentView in this case refers to the scroll view's superview):
let widthConstraint = NSLayoutConstraint(item: subScrollView, attribute: NSLayoutAttribute.Width, relatedBy: NSLayoutRelation.Equal, toItem: self.viewController.contentView, attribute: NSLayoutAttribute.Width, multiplier: 1.0, constant: 0);
let heightConstraint = NSLayoutConstraint(item: subScrollView, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1.0, constant: CGFloat(heightsTotal + (self.workoutInstructionTiles.count * 10)));
self.viewController.contentView.addConstraints([heightConstraint, widthConstraint]);
If each of these steps are followed correctly, your result should be a scrollable view. Now that I get this if I can help anyone with their code just leave a comment.

Adding a UISearchController and position it with constraints programmatically

I want to add a leading, trailing, bottom and width constraint programmatically to a UISearchController. This is my code:
#IBOutlet weak var navigationBar: UIView!
// create search bar
searchBar = UISearchController(searchResultsController: nil)
navigationBar.addSubview(searchBar.searchBar)
searchBar.searchBar.translatesAutoresizingMaskIntoConstraints = false
let leftConstraint = NSLayoutConstraint(item: searchBar.searchBar, attribute: .Leading, relatedBy: .Equal, toItem: navigationBar, attribute: .Leading, multiplier: 1, constant: 0)
let rightConstraint = NSLayoutConstraint(item: searchBar.searchBar, attribute: .Trailing, relatedBy: .Equal, toItem: navigationBar, attribute: .Trailing, multiplier: 1, constant: 0)
let bottomConstraint = NSLayoutConstraint(item: searchBar.searchBar, attribute: .Bottom, relatedBy: .Equal, toItem: navigationBar, attribute: .Bottom, multiplier: 1, constant: 0)
let heightConstraint = NSLayoutConstraint(item: searchBar.searchBar, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 44)
navigationBar.addConstraints([leftConstraint, rightConstraint, bottomConstraint, widthConstraint])
When running the app, the search bar appears correctly, but when I press on the search bar, it shrinks, and if I press another time the app crashes. Here is the output:
2015-08-12 20:20:37.696 Contacts++[96997:8547485] The view hierarchy is not prepared for the constraint: <NSLayoutConstraint:0x7fb22580c7a0 UIView:0x7fb225817b20.leading == UIView:0x7fb223449860.leading>
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. Break on -[UIView(UIConstraintBasedLayout) _viewHierarchyUnpreparedForConstraint:] to debug.
2015-08-12 20:20:37.697 Contacts++[96997:8547485] *** Assertion failure in -[UIView _layoutEngine_didAddLayoutConstraint:roundingAdjustment:mutuallyExclusiveConstraints:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit_Sim/UIKit-3491.2.5/NSLayoutConstraint_UIKitAdditions.m:590
Why do you need to create an instance of 'UISearchController' and get its searchBar?
why not just make the searchBar from UISearchBar?
// create a searchBar from UISearchBar
let searchBar = UISearchBar(frame: CGRectZero)
// add searchBar to navigationBar
navigationController?.navigationBar.addSubview(searchBar)
// call sizeToFit.. this will set the frame of the searchBar to exactly the same as the size of the allowable frame of the navigationBar
searchBar.sizeToFit()
// now reframe the searchBar to add some margins
var frame = searchBar.frame
frame.origin.x = 20
frame.size.width -= 40
searchBar.frame = frame // set new frame with margins
note: you won't need any of those constraint to achieve this.
But if you really prefer the constraint, here's the constraint code without a crash.
let searchBar = UISearchBar(frame: CGRectZero)
navigationController?.navigationBar.addSubview(searchBar)
searchBar.setTranslatesAutoresizingMaskIntoConstraints(false)
let leftConstraint = NSLayoutConstraint(item: searchBar, attribute: .Leading, relatedBy: .Equal, toItem: navigationController?.navigationBar, attribute: .Leading, multiplier: 1, constant: 20) // add margin
let bottomConstraint = NSLayoutConstraint(item: searchBar, attribute: .Bottom, relatedBy: .Equal, toItem: navigationController?.navigationBar, attribute: .Bottom, multiplier: 1, constant: 1)
let topConstraint = NSLayoutConstraint(item: searchBar, attribute: .Top, relatedBy: .Equal, toItem: navigationController?.navigationBar, attribute: .Top, multiplier: 1, constant: 1)
let widthConstraint = NSLayoutConstraint(item: searchBar, attribute: .Width, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: self.view.frame.size.width - 40) // - margins from both sides
navigationController?.navigationBar.addConstraints([leftConstraint, bottomConstraint, topConstraint, widthConstraint])
Do you really want to set the width of the navigatinBar to 44 points? Width is horizontal. You already have a width constraint by adding trailing and leading constraints.

iOS Scaling Constraints for Different Screen Sizes

This is my first iOS app and I'm using Swift. I'm following tutorials on CodeWithChris.com.
The user will be tapping on a series of icons to determine what device model they have. So far I've used a handful of constraints to get the icons placed properly on an iPhone 6 display. When using iPhone 5S/5c/5 or 4S/4 display sizes the icons and text still render at the full size for iPhone 6 and they go off the screen.
Here's my process for building the layout:
Grab and parse JSON to NSArray. Returns ["iPhone", "Android", "Windows", "iPad"]
Create UIView object for each item in NSArray. Set Name variable to the associated name.
Place each UIView using addSubview()
Apply height, width, and bottom-margin constraints to the UIView
Set UIView position constraints based on its position in the row
Add the icons (UIImageViews) to each UIView
Set height and width constraints based on the type of device
Add text labels
I'm using a bunch of constraints to place the UIViews and the UIImageViews. How can I make these constraints dynamic based on the device's screen size?
iPhone 6 with contentView in blue and UIViews in red
iPhone 6
iPhone 5/5S/5c
iPhone 4S/4
Here's where I add the device UIViews and apply constraints:
// Loop through the array of DeviceTypes to add each to the UIView
for index in 0...deviceTypes.count-1 {
// Grab the current device
var thisDevice:DeviceType = deviceTypes[index]
// Add to the view!
contentView.addSubview(thisDevice)
thisDevice.setTranslatesAutoresizingMaskIntoConstraints(false)
// How tall is the Device UIView
var heightConstraint:NSLayoutConstraint = NSLayoutConstraint(item: thisDevice, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1, constant: 200)
// How wide is the device UIView
var widthConstraint:NSLayoutConstraint = NSLayoutConstraint(item: thisDevice, attribute: NSLayoutAttribute.Width, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1, constant: 130)
// How far is the bottom of the UIView from the top of the contentView
var bottomMarginConstraint:NSLayoutConstraint = NSLayoutConstraint(item: thisDevice, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: contentView, attribute: NSLayoutAttribute.Top, multiplier: 1, constant: 100)
// Add thisDevice's UIView constraints
thisDevice.addConstraints([heightConstraint, widthConstraint])
contentView.addConstraint(bottomMarginConstraint)
thisDevice.backgroundColor = UIColor.redColor()
// set UIView position constraints based on place in row
if (index > 0) {
// device is second or further in row
var deviceOnTheLeft = deviceTypes[index-1]
var leftMarginConstraint:NSLayoutConstraint = NSLayoutConstraint(item: thisDevice, attribute: NSLayoutAttribute.Left, relatedBy: NSLayoutRelation.Equal, toItem: deviceOnTheLeft, attribute: NSLayoutAttribute.Right, multiplier: 1, constant: 10)
contentView.addConstraint(leftMarginConstraint)
}
else {
// device is first in row
var leftMarginConstraint:NSLayoutConstraint = NSLayoutConstraint(item: thisDevice, attribute: NSLayoutAttribute.Left, relatedBy: NSLayoutRelation.Equal, toItem: contentView, attribute: NSLayoutAttribute.Left, multiplier: 1, constant: 0)
// Add constraint
contentView.addConstraint(leftMarginConstraint)
}
Here are the constraints that I apply to the icon images:
func addDeviceImages() {
// Set right image based on deviceType name
self.frontImageView.image = UIImage(named: self.Name!)
// Set translates autoresizing mask to fault
self.frontImageView.setTranslatesAutoresizingMaskIntoConstraints(false)
// Add the imageview to the view
self.addSubview(self.frontImageView)
if (self.Name == "Windows") {
self.addImageSizeConstraints(90, height: 160)
}
else if (self.Name == "iPad"){
self.addImageSizeConstraints(120, height: 180)
}
else if (self.Name == "iPhone"){
self.addImageSizeConstraints(85, height: 175)
}
else if (self.Name == "Android"){
self.addImageSizeConstraints(95, height: 185)
}
}
func addImageSizeConstraints(width: CGFloat, height: CGFloat) {
// Set the size constraints for the imageview based on the values passed in
var heightConstraint:NSLayoutConstraint = NSLayoutConstraint(item: self.frontImageView, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1, constant: height)
var widthConstraint:NSLayoutConstraint = NSLayoutConstraint(item: self.frontImageView, attribute: NSLayoutAttribute.Width, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1, constant: width)
self.frontImageView.addConstraints([heightConstraint, widthConstraint])
// Set the position of the imageview in the UIView
var verticalConstraint:NSLayoutConstraint = NSLayoutConstraint( item: self.frontImageView, attribute: NSLayoutAttribute.Bottom, relatedBy: NSLayoutRelation.Equal, toItem: self, attribute: NSLayoutAttribute.Bottom, multiplier: 1, constant: -25)
var horizontalConstraint:NSLayoutConstraint = NSLayoutConstraint( item: self.frontImageView, attribute: NSLayoutAttribute.Left, relatedBy: NSLayoutRelation.Equal, toItem: self, attribute: NSLayoutAttribute.Left, multiplier: 1, constant: 5)
self.addConstraints([horizontalConstraint,verticalConstraint])
}
You are using the wrong kinds of constraints. Your width constraints are absolute - you are setting a fixed constant. Instead, make the constant 0, and the widths depend on the height, using a value for the multiplier that fixes the aspect ratio correctly. In this way, as the height changes, the width will change to match. Do the same sort of thing with the separation between the images - make the separation depend on the width of the superview, as a multiplier.

Resources