Subviews need to be placed from the center of super view based on subview count.
If subview count is odd, the middle view will be center to the super view and remaining will be placed with respect to it with item spacing.
If subview count is even, then subviews are to be placed with offset.
Is there any generic solution to solve this without too many conditions and calculations using autolayout?
All subviews are of same size
Solution 1: UIStackView
let totalSubviews = 4
let stackView = UIStackView()
view.addSubview(stackView)
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .Horizontal
stackView.distribution = .EqualSpacing
stackView.alignment = .Center
stackView.spacing = 10
stackView.centerXAnchor.constraintEqualToAnchor(view.centerXAnchor, constant: 0).active = true
stackView.centerYAnchor.constraintEqualToAnchor(view.centerYAnchor, constant: 0).active = true
stackView.backgroundColor = UIColor.greenColor()
for _ in 0 ..< totalSubviews {
let subview = UIView()
stackView.addArrangedSubview(subview)
subview.translatesAutoresizingMaskIntoConstraints = false
subview.backgroundColor = UIColor.redColor()
subview.heightAnchor.constraintEqualToConstant(50).active = true
subview.widthAnchor.constraintEqualToConstant(50).active = true
}
Solution 2: AutoLayout
You can do this generically using AutoLayout.
totalSubviews = 4
totalSubviews = 5
Code
let totalSubviews = 4
let spacing = CGFloat(20)
var subviews = [UIView]()
var previousSubview: UIView?
for i in 0 ..< totalSubviews {
let subview = UIView()
subviews.append(subview)
view.addSubview(subview)
subview.translatesAutoresizingMaskIntoConstraints = false
subview.backgroundColor = UIColor.redColor()
view.addConstraint(NSLayoutConstraint(item: subview, attribute: .Top, relatedBy: .Equal, toItem: view, attribute: .Top, multiplier: 1, constant: spacing))
if i == 0 {
view.addConstraint(NSLayoutConstraint(item: subview, attribute: .Left, relatedBy: .Equal, toItem: view, attribute: .Left, multiplier: 1, constant: spacing))
}
else if i < totalSubviews {
view.addConstraint(NSLayoutConstraint(item: subview, attribute: .Left, relatedBy: .Equal, toItem: previousSubview, attribute: .Right, multiplier: 1, constant: spacing))
}
if i == totalSubviews - 1 {
view.addConstraint(NSLayoutConstraint(item: subview, attribute: .Right, relatedBy: .Equal, toItem: view, attribute: .Right, multiplier: 1, constant: -spacing))
}
if i != 0 {
view.addConstraint(NSLayoutConstraint(item: subview, attribute: .Width, relatedBy: .Equal, toItem: previousSubview!, attribute: .Width, multiplier: 1, constant: 0))
}
view.addConstraint(NSLayoutConstraint(item: subview, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 0, constant: 100))
previousSubview = subview
}
You will have to take 1 more view inside main view which will include your odd or even number of subviews. Then you need to set Equal width and Equal height constraints between them. So as per your number of subviews and screen size, they all will automatically be positioned.
Related
i'm not very familiar with autolay-out and constraints.
I have build a view with 7 subviews which are build with constraints to fit landscape and portait mode. Everything is fine (see picture).
Here under a sample code used for the 6th subview.
The width constraint has mediumPriority and the others (left, top, bottom) have highPriority.
6View = UIView()
mainScrollView.addSubview(6View)
6View.backgroundColor = UIColor(red: 120/255, green: 120/255, blue: 120/255, alpha: 1.0)
6View.translatesAutoresizingMaskIntoConstraints = false
// constraints
let topConstraint = NSLayoutConstraint(item: 6View, attribute: .top, relatedBy: .equal, toItem: mainScrollView, attribute: .top, multiplier: 1, constant: kMainMargin)
topConstraint.priority = highContraintPriority
mainScrollView.addConstraint(topConstraint)
let leftConstraint = NSLayoutConstraint(item: 6View, attribute: .left, relatedBy: .equal, toItem: day5View, attribute: .right, multiplier: 1, constant: kMainMargin)
leftConstraint.priority = highContraintPriority
mainScrollView.addConstraint(leftConstraint)
let bottomConstraint = NSLayoutConstraint(item: 6View, attribute: .bottom, relatedBy: .equal, toItem: mainScrollView, attribute: .bottom, multiplier: 1, constant: kMainMargin)
bottomConstraint.priority = highContraintPriority
mainScrollView.addConstraint(bottomConstraint)
let widthConstraint = NSLayoutConstraint(item: 6View, attribute: .width, relatedBy: .equal, toItem: mainScrollView, attribute: .width, multiplier: (1/7), constant: -1)
widthConstraint.priority = mediumContraintPriority
mainScrollView.addConstraint(widthConstraint)
Now i'd like when i touch any of the 7 views to see the touched view to expand (double size) and the other 6 views to collapse.
How would you achieve this ?
Thanks a lot for your help.
Best regards.
Make a reference to the width constraint for every view and when clicked do
widthConstraint.constant = // expanded value
and set this for all the other views
otherwidthConstraint.constant = 0
then call
self.view.layoutIfNeeded()
You may also put all these constraints inside an array ( var arrOfAllWidths = [NSLayoutConstraint]()) and do this
arrOfAllWidths.forEach { $0.constant = 0 }
then get the index of the clicked view say it's view 0
arrOfAllWidths[0].constant = // expanded value
Also no need for the priorities , and don't forget to set this when give constraints to a view programmatically
view6.translatesAutoresizingMaskIntoConstraints = false
I'm trying to center a logoImage horizontally & vertically by setting constraints but when tested, It was displayed on (x:0, y:0).
Any idea how to fix this?
Thanks
var movieView : UIView?
let logoImage = UIImageView(image: #imageLiteral(resourceName: "my_logo"))
// This function runs in viewWillAppear
internal func setupIntroMovie() {
movieView = UIView(frame: view.frame)
view.addSubview(movieView!)
view.addSubview(logoImage)
let horizontalConstraint = NSLayoutConstraint(item: logoImage,
attribute: .centerX,
relatedBy: .equal,
toItem: view,
attribute: .centerX,
multiplier: 1,
constant: 0)
let verticalConstraint = NSLayoutConstraint(item: logoImage,
attribute: .centerY,
relatedBy: .equal,
toItem: view,
attribute: .centerY,
multiplier: 1,
constant: 0)
view.addConstraints([horizontalConstraint,
verticalConstraint])
updateViewConstraints()
}
You need to set logoImage.translatesAutoresizingMaskIntoConstraints = false as it determines whether the view’s autoresizing mask is translated into Auto Layout constraints.
internal func setupIntroMovie() {
movieView = UIView(frame: view.frame)
view.addSubview(movieView!)
view.addSubview(logoImage)
let horizontalConstraint = NSLayoutConstraint(item: logoImage,
attribute: .centerX,
relatedBy: .equal,
toItem: view,
attribute: .centerX,
multiplier: 1,
constant: 0)
let verticalConstraint = NSLayoutConstraint(item: logoImage,
attribute: .centerY,
relatedBy: .equal,
toItem: view,
attribute: .centerY,
multiplier: 1,
constant: 0)
// Update
logoImage.translatesAutoresizingMaskIntoConstraints = false
view.addConstraints([horizontalConstraint,
verticalConstraint])
updateViewConstraints()
}
If this property’s value is true, the system creates a set of
constraints that duplicate the behavior specified by the view’s
autoresizing mask. This also lets you modify the view’s size and
location using the view’s frame , bounds , or center properties,
allowing you to create a static, frame-based layout within Auto
Layout.
How to give programmatically constraints equal width and equal height with multiple views.I check google but not perfect answer for programmatically equal width and height constraints through auto layout.
my code look like below:
var countNoOfViews:Int = 3
#IBOutlet var viewForRow1: UIView!
override func viewDidLoad() {
super.viewDidLoad()
self.specialButtonViewLeft()
}
func specialButtonViewLeft(){
for i in 0..<countNoOfViews{
var customView:UIView!
customView = UIView(frame: CGRect.zero)
customView.translatesAutoresizingMaskIntoConstraints = false
viewForRow1.addSubview(customView)
let widthConstraint = NSLayoutConstraint(item: customView, attribute: .width, relatedBy: .equal,toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 20.0)
let topConstraint = NSLayoutConstraint(item: customView, attribute: .top, relatedBy: .equal,toItem: self.viewForRow1, attribute: .top, multiplier: 1.0, constant: 0)
let bottomConstraint = NSLayoutConstraint(item: customView, attribute: .bottom, relatedBy: .equal,toItem: self.viewForRow1, attribute: .bottom, multiplier: 1.0, constant: 0)
let leadingConstraint = NSLayoutConstraint(item: customView, attribute: .leading, relatedBy: .equal, toItem: self.viewForRow1, attribute: .leading, multiplier: 1, constant: customLeadingSpaceLeft)
customLeadingSpaceLeft = customLeadingSpaceLeft + customViewWidth
arrayLeftBtnConstraints.append(widthConstraint)
if i == 0{
customView.backgroundColor = UIColor.red
}else if i == 1{
customView.backgroundColor = UIColor.green
}else if i == 2{
leftViewVal = customLeadingSpaceLeft
customView.backgroundColor = UIColor.black
}
customView.alpha = 0.50
viewForRow1.addConstraints([widthConstraint, leadingConstraint,topConstraint,bottomConstraint])
}
}
I want to add equal width constraint programmatically.
You have three possible ways to do that:
1) By using anchors:
view.widthAnchor.constraint(equalTo: otherView.widthAnchor, multiplier: 1.0).isActive = true
2) Visual format:
simple example - "H:|[otherView]-[view(==otherView)]|"
3) "Old school" constraints:
NSLayoutConstraint(item: #view, attribute: .width, relatedBy: .equal, toItem: #otherView, attribute: .width, multiplier: 1.0, constant: 0.0).isActive = true
hope it will help somebody.
Try this:
let widthConstraint = NSLayoutConstraint(item: customView, attribute: .width, relatedBy: .equal, toItem: viewForRow1, attribute: .width, multiplier: 0.25, constant: 0.0)
multiplier: 0.25, denotes that customView's width will be 1/4th of the parent view, viewForRow1.
I'm trying to implement a popup layout like following:
This works fine in a storyboard with margins and everything. In storyboard it looks like this:
But if I make the same constraint in code I get this result:
The label has a light blue background and the view the label is inside has the dark blue background. The popup background has a border around itself. So basically the popup matches the child but the label inside the child overflows parent and grand parent BUT only because it has margins... If I remove margins it goes right to the border!
I've tryed making the exact same constraint just in code. I'm very open for alternative suggestions involving automatic adjusting width.
My code for creating popup:
func showPopup(caller: UIView) {
closePopups()
// setup view
currentPopup = UIView()
self.view.addSubview(currentPopup)
currentPopup.backgroundColor = UIColorFromHex(Constants.Colors.white, alpha: 1)
// setup constraints
currentPopup.translatesAutoresizingMaskIntoConstraints = false
// top constraint
let topSideConstraint = NSLayoutConstraint(item: currentPopup, attribute: .Top, relatedBy: .Equal, toItem: intoWordsBar.view, attribute: .Bottom, multiplier: 1.0, constant: 0)
self.view.addConstraint(topSideConstraint)
// setup child elements
var children = [PopupChildButton]()
let childOne = createChild("writing_strategy_1", parent: currentPopup, aboveChild: nil, hasBorder: true, feature: FeatureManager.BarFeature.WriteReadLetterName)
children.append(childOne)
let childTwo = createChild("writing_strategy_2", parent: currentPopup, aboveChild: children[0], hasBorder: true, feature: FeatureManager.BarFeature.WriteReadLetterSound)
children.append(childTwo)
let childThree = createChild("writing_strategy_3", parent: currentPopup, aboveChild: children[1], hasBorder: true, feature: FeatureManager.BarFeature.WriteReadWord)
children.append(childThree)
let childFour = createChild("writing_strategy_4", parent: currentPopup, aboveChild: children[2], hasBorder: false, feature: FeatureManager.BarFeature.WriteReadSentence)
children.append(childFour)
let parentSize = getWidth(caller)
//TODO MARK: <-- here working, need to add toggle function and graphics to childrens, documentation on methods, move to constructor class?
// setup rest of constraints
// add bottom constraint, equal to bottom of last child
let bottomSideConstraint = NSLayoutConstraint(item: currentPopup, attribute: .Bottom, relatedBy: .Equal, toItem: children[children.count-1], attribute: .Bottom, multiplier: 1.0, constant: 0)
self.view.addConstraint(bottomSideConstraint)
// left constraint
let leftSideConstraint = NSLayoutConstraint(item: currentPopup, attribute: .Left, relatedBy: .Equal, toItem: caller, attribute: .Right, multiplier: 1.0, constant: (-parentSize)/2)
self.view.addConstraint(leftSideConstraint)
// add border
currentPopup.addBorder(edges: [.All], colour: UIColorFromHex(Constants.Colors.dark_grey, alpha: 1), thickness: 1)
//TODO <-- last piece
//childOne.addTarget(self, action: #selector(KeyboardViewController.childClick(_:)), forControlEvents: .TouchUpInside)
//childTwo.addTarget(self, action: #selector(KeyboardViewController.childClick(_:)), forControlEvents: .TouchUpInside)
//childThree.addTarget(self, action: #selector(KeyboardViewController.childClick(_:)), forControlEvents: .TouchUpInside)
//childFour.addTarget(self, action: #selector(KeyboardViewController.childClick(_:)), forControlEvents: .TouchUpInside)
self.view.setNeedsLayout()
self.view.layoutIfNeeded()
}
My code for creating child:
func createChild(text: String, parent: UIView, aboveChild: UIView?, hasBorder: Bool, feature: FeatureManager.BarFeature) -> PopupChildButton {
// setup child element
let childBtn = PopupChildButton()
childBtn.setRelatedFeature(feature)
// set the right background color
if intoWordsBar.getFeatureManager().isFeatureActive(feature) {
childBtn.backgroundColor = UIColorFromHex(Constants.Colors.light_blue, alpha: 1)
//childBtn.setImage(UIImage(named: "Checkmark"))
} else {
childBtn.backgroundColor = UIColorAndAlphaFromHex(Constants.Colors.transparent)//TODO Highlight implementation needs to be optimized, icon should be moved all the way to the left... somehow //TODO Add new checkmark icon
//childBtn.setImage(nil)
}
childBtn.translatesAutoresizingMaskIntoConstraints = false
parent.addSubview(childBtn)
// add constraints
// top constraint
if let aboveChild = aboveChild {
let topSideConstraint = NSLayoutConstraint(item: childBtn, attribute: .Top, relatedBy: .Equal, toItem: aboveChild, attribute: .Bottom, multiplier: 1.0, constant: 0)
parent.addConstraint(topSideConstraint)
} else {
let topSideConstraint = NSLayoutConstraint(item: childBtn, attribute: .Top, relatedBy: .Equal, toItem: parent, attribute: .Top, multiplier: 1.0, constant: 0)
parent.addConstraint(topSideConstraint)
}
// height constraint
let heightConstraint = NSLayoutConstraint(item: childBtn, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1, constant: CGFloat(Constants.Sizes.popupChildHeight))
parent.addConstraint(heightConstraint)
// left constraint
let leftSideConstraint = NSLayoutConstraint(item: parent, attribute: .Leading, relatedBy: .Equal, toItem: childBtn, attribute: .Leading, multiplier: 1.0, constant: 0)
parent.addConstraint(leftSideConstraint)
// right constraint
let rightSideConstraint = NSLayoutConstraint(item: parent, attribute: .Trailing, relatedBy: .Equal, toItem: childBtn, attribute: .Trailing, multiplier: 1.0, constant: 0)
parent.addConstraint(rightSideConstraint)
// add border
if hasBorder {
childBtn.addBorder(edges: .Bottom, colour: UIColorFromHex(Constants.Colors.dark_grey, alpha: 1), thickness: 1)
}
// create grandchildren
let label = UILabel()
// setup looks
label.textColor = UIColorFromHex(Constants.Colors.black, alpha: 1)
label.textAlignment = .Center
childBtn.backgroundColor = UIColorFromHex(Constants.Colors.dark_blue, alpha: 1)
label.backgroundColor = UIColorFromHex(Constants.Colors.light_blue, alpha: 1)
label.text = text.localized
label.translatesAutoresizingMaskIntoConstraints = false
childBtn.addSubview(label)
// add constraints
// left constraint label
let leftLabelConstraint = NSLayoutConstraint(item: label, attribute: .Left, relatedBy: .Equal, toItem: childBtn, attribute: .Left, multiplier: 1.0, constant: CGFloat(Constants.Sizes.popupMargin))
childBtn.addConstraint(leftLabelConstraint)
// right constraint label
let rightLabelConstraint = NSLayoutConstraint(item: label, attribute: .Right, relatedBy: .Equal, toItem: childBtn, attribute: .Right, multiplier: 1.0, constant: CGFloat(Constants.Sizes.popupMargin))
childBtn.addConstraint(rightLabelConstraint)
// top constraint
let labelTopSideConstraint = NSLayoutConstraint(item: label, attribute: .Top, relatedBy: .Equal, toItem: childBtn, attribute: .Top, multiplier: 1.0, constant: 0)
childBtn.addConstraint(labelTopSideConstraint)
// bottom constraint
//let labelBottomSideConstraint = NSLayoutConstraint(item: label, attribute: .Bottom, relatedBy: .Equal, toItem: childBtn, attribute: .Bottom, multiplier: 1.0, constant: 0)
//childBtn.addConstraint(labelBottomSideConstraint)
return childBtn
}
No, it is not broken.
When defining trailing constraints you must set the parent view as the first item and the child view as the second item. This is in reversed order compared to a leading constraint.
I pulled to constraints from a storyboard to illustrate this. These constraints make sure the header has a 10px margin from leading and trailing of parent view.
I am trying to center my subview with a button in its superview. (I want tha distance from top-to-screen and bottom-to-screen be the same and the distance from right-to-screen and left-to-screen be the same) So I want the center of the subview be the center of the superview. I am trying that with following code:
override func viewDidLoad() {
self.view.backgroundColor = UIColor.redColor()
var menuView = UIView()
var newPlayButton = UIButton()
var newPlayImage = UIImage(named: "new_game_button_5cs")
var newPlayImageView = UIImageView(image: UIImage(named: "new_game_button_5cs"))
newPlayButton.frame = CGRectMake(0, 0, newPlayImageView.frame.width, newPlayImageView.frame.height)
//newPlayButton.setImage(newPlayImage, forState: .Normal)
menuView.backgroundColor = UIColor.whiteColor()
menuView.addSubview(newPlayButton)
self.view.addSubview(menuView)
menuView.setTranslatesAutoresizingMaskIntoConstraints(false)
self.view.addConstraint(
NSLayoutConstraint(item: self.view,
attribute: .CenterX,
relatedBy: .Equal,
toItem: menuView,
attribute: .CenterX,
multiplier: 1, constant: 0)
)
self.view.addConstraint(
NSLayoutConstraint(item: self.view,
attribute: .CenterY,
relatedBy: .Equal,
toItem: menuView,
attribute: .CenterY,
multiplier: 1, constant: 0)
)
}
But when I run the code I get the following: