Adding views dynamically to UIStackview - swift

I'm trying to add views(or buttons) to UIStackView dynamically.
At first, the UIStackView has no arranged views (vertically), and
after getting from some http response, several views(buttons) are added to UIStackView.
UIStackView is also autolayout to hold a specific area.
I've tried to find dynamic adding example, but failed.
Anyone can show me the examples of adding view onto UIStackView dynamically?

It may help you. Please follow this points:
Add UIScrollView to your UIViewController in storyboard or XIB.
Initiate an NSMutableArray name it arrViews gets server response and adds view in the array.
Initialise UIStackViewpass arrView array in the init method.
After that UIStackView will be added subview of UIScrollView.
Add constraint programmatically to UIStackView. That's it.
if let response = self.serverResponse {
if let body = response.responseBody {
if let view = body.views {
arrViews = createSubViews(view)
}
}
}
let stackView = UIStackView(arrangedSubviews: arrViews)
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .vertical
stackView.spacing = 16
stackView.distribution = .fill
self.scrollView.addSubview(stackView)
//constraints
let leading = NSLayoutConstraint(item: stackView, attribute: .leading, relatedBy: .equal, toItem: self.scrollView, attribute: .leading, multiplier: 1.0, constant: 0)
self.scrollView.addConstraint(leading)
let trailing = NSLayoutConstraint(item: stackView, attribute: .trailing, relatedBy: .equal, toItem: self.scrollView, attribute: .trailing, multiplier: 1.0, constant: 0)
self.scrollView.addConstraint(trailing)
let top = NSLayoutConstraint(item: stackView, attribute: .top, relatedBy: .equal, toItem: self.scrollView, attribute: .top, multiplier: 1.0, constant: 0)
self.scrollView.addConstraint(top)
let bottom = NSLayoutConstraint(item: stackView, attribute: .bottom, relatedBy: .equal, toItem: self.scrollView, attribute: .bottom, multiplier: 1.0, constant: 0)
self.scrollView.addConstraint(bottom)
let equalWidth = NSLayoutConstraint(item: stackView, attribute: .width, relatedBy: .equal, toItem: self.scrollView, attribute: .width, multiplier: 1.0, constant: 0)
self.scrollView.addConstraint(equalWidth)
leading.isActive = true
trailing.isActive = true
top.isActive = true
bottom.isActive = true
equalWidth.isActive = true
Hope it will help you. Happy coding :)

I use this code in one of my projects:
let baseFrame = CGRect(origin: .zero, size: CGSize(width: requiredWidth, height: partitionHeight))
for instrument in instruments {
let partitionView = PartitionOnDemand(instrument: instrument, mode: playbackMode, frame: baseFrame, referenceView: partitionsAnimator)
partitionsStackView.addArrangedSubview(partitionView)
let tab = InstrumentInfoTabContainer.instantiate(with: instrument) {
self.focus(on: instrument)
}
tabsStackView.addArrangedSubview(tab)
}

While trying with answers, I happend to find how to work it.
class ViewController: UIViewController {
#IBOutlet weak var stack: UIStackView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func onBtn_Create(_ sender: Any) {
createButton("new button ...")
}
#IBAction func onBtn_Delete(_ sender: Any) {
if let v = stack.arrangedSubviews.last {
stack.removeArrangedSubview(v)
v.removeFromSuperview()
}
}
func createButton(_ title: String) {
let button = UIButton()
button.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
button.backgroundColor = UIColor.blue
button.setTitle(title, for: .normal)
button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
stack.addArrangedSubview(button)
}
#objc func buttonAction(sender: UIButton!) {
print("Button tapped")
}
}
And, I anchored to UIStackView, Trailing=0, Leading=0, Top=0, Bottom=8 to TextView.Top
The subviews inside it are intact without any constraints.
Thank you.

Related

How to put constraints for a ViewController programatically which I'm calling in a container view?

I have used a PageViewController for three controllers with scroll type. Now I have called this PageViewController in a containerView, here the issue is the container height is less than that of PageViewController, Im able to call the PageViewController in the containerView but the bottom is getting out of the view. I tried to put autolayouts programatically but it didn't work. Let me know how to solve this?
#IBOutlet weak var containerView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
let storyboard = UIStoryboard.init(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "ViewController")
self.setControllerFrame(controller: controller )
addChild(controller)
self.containerView.addSubview(controller.view)
}
func setControllerFrame(controller: UIViewController){
//Validating frames according to orientation
self.view.setNeedsDisplay()
self.view.layoutIfNeeded()
//*******temp fix:******** this is solutions for a single device not for all devices(screen sizes).
if (self.view.frame.size.width < self.view.frame.size.height){
controller.view.frame = CGRect.init(x: 0, y: 0, width: self.view.frame.size.width, height: self.view.frame.size.height)
} else if (self.view.frame.size.width >= self.view.frame.size.height){
controller.view.frame = CGRect.init(x: 0, y: 0, width: self.view.frame.size.height, height: self.view.frame.size.width)
}
//tried this but crashes at last line.
controller.view.translatesAutoresizingMaskIntoConstraints = false
let topConstraint = NSLayoutConstraint(item: controller.view!, attribute: NSLayoutConstraint.Attribute.top, relatedBy: NSLayoutConstraint.Relation.equal, toItem: self.containerView, attribute: NSLayoutConstraint.Attribute.top, multiplier: 1, constant: 0)
let bottomConstraint = NSLayoutConstraint(item: controller.view!, attribute: NSLayoutConstraint.Attribute.bottom, relatedBy: NSLayoutConstraint.Relation.equal, toItem: self.containerView, attribute: NSLayoutConstraint.Attribute.bottom, multiplier: 1, constant: 0)
let leadingConstraint = NSLayoutConstraint(item: controller.view!, attribute: NSLayoutConstraint.Attribute.leading, relatedBy: NSLayoutConstraint.Relation.equal, toItem: self.containerView, attribute: NSLayoutConstraint.Attribute.leading, multiplier: 1, constant: 0)
let trailingConstraint = NSLayoutConstraint(item: controller.view!, attribute: NSLayoutConstraint.Attribute.trailing, relatedBy: NSLayoutConstraint.Relation.equal, toItem: self.containerView, attribute: NSLayoutConstraint.Attribute.trailing, multiplier: 1, constant: 0)
view.addConstraints([topConstraint, bottomConstraint, leadingConstraint, trailingConstraint])
}
the above code *******temp fix:******** solves my problem for a single device screen but not for all devices. so please let me know a proper solution for this
1- Inside viewDidLoad frame isn't yet correct so set it inside viewDidLayoutSubviews
controller.view.frame = // value
2-This crashed
view.addConstraints([topConstraint, bottomConstraint, leadingConstraint, trailingConstraint])
as you should add the subview before setting the constraints
self.containerView.addSubview(controller.view)
The Below code solved my issue.
var controller: ViewController!
override func viewDidLoad() {
super.viewDidLoad()
let storyboard = UIStoryboard.init(name: "Main", bundle: nil)
controller = storyboard.instantiateViewController(withIdentifier: "ViewController") as! ViewController
addChild(controller)
self.containerView.addSubview(controller.view)
self.setControllerFrame(controller: controller )
}
func setControllerFrame(controller: UIViewController){
self.view.setNeedsDisplay()
self.view.setNeedsLayout()
controller.view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
controller.view.topAnchor.constraint(equalTo: self.containerView.topAnchor, constant: 0),
controller.view.bottomAnchor.constraint(equalTo: self.containerView.bottomAnchor, constant: 0),
controller.view.leadingAnchor.constraint(equalTo: self.containerView.leadingAnchor, constant: 0),
controller.view.trailingAnchor.constraint(equalTo: self.containerView.trailingAnchor, constant: 0)
])
}

Adding constraints in awakeFromNib() method

I have two custom views in my project which needs to be zoomed in the exact same proportion. So I decided to use UIScrollView for that and it fits perfectly.
I decided to develop a very simple class inherited from UIScrollView and inside of it I initialized all views structure. That way I'm avoiding any construction steps in the NIB file and my class can be used just by adding one view. But I faced an issue already on the stage of adding contentView.
Here's my class:
final class PlayerContentView: UIScrollView {
fileprivate var contentView: UIView!
override func awakeFromNib() {
super.awakeFromNib()
self.backgroundColor = .clear
setupScrollProperties()
setupContentView()
}
private func setupScrollProperties()
{
self.translatesAutoresizingMaskIntoConstraints = false
self.minimumZoomScale = 1.0
self.maximumZoomScale = 2.0
self.contentSize = frame.size
self.delegate = self
}
private func setupContentView()
{
contentView = UIView(frame: self.frame)
contentView.backgroundColor = .red
self.addSubview(contentView)
CommonSwiftUtility.setSideConstraints(superview: self, view: contentView)
CommonSwiftUtility.setSizeConstraints(superview: self, view: contentView)
}
func requireToFail(_ gestureRecognizer: UIPanGestureRecognizer) {
self.panGestureRecognizer.require(toFail: gestureRecognizer)
} }
And here're methods for adding constraints:
static func setSideConstraints(superview: UIView, view: UIView) {
let topConstraint: NSLayoutConstraint = NSLayoutConstraint(item: view,
attribute: .top,
relatedBy: .equal,
toItem: superview,
attribute: .top,
multiplier: 1.0, constant: 0.0)
let bottomConstraint: NSLayoutConstraint = NSLayoutConstraint(item: view,
attribute: .bottom,
relatedBy: .equal,
toItem: superview,
attribute: .bottom,
multiplier: 1.0, constant: 0.0)
let leadingConstraint: NSLayoutConstraint = NSLayoutConstraint(item: view,
attribute: .leading,
relatedBy: .equal,
toItem: superview,
attribute: .leading,
multiplier: 1.0, constant: 0.0)
let trailingConstraint: NSLayoutConstraint = NSLayoutConstraint(item: view,
attribute: .trailing,
relatedBy: .equal,
toItem: superview,
attribute: .trailing,
multiplier: 1.0, constant: 0.0)
superview.addConstraint(topConstraint)
superview.addConstraint(bottomConstraint)
superview.addConstraint(leadingConstraint)
superview.addConstraint(trailingConstraint)
}
static func setSizeConstraints(superview: UIView, view: UIView)
{
let wConstraint: NSLayoutConstraint = NSLayoutConstraint(item: view,
attribute: .width,
relatedBy: .equal,
toItem: superview,
attribute: .width,
multiplier: 1.0, constant: 0.0)
let hConstraint: NSLayoutConstraint = NSLayoutConstraint(item: view,
attribute: .height,
relatedBy: .equal,
toItem: superview,
attribute: .height,
multiplier: 1.0, constant: 0.0)
superview.addConstraint(wConstraint)
superview.addConstraint(hConstraint)
}
As you can see, I painted my contentView in red color in order to define it on the screen. After showing my PlayerContentView I get this:
PlayerContentView is stretched on the full screen so I'm expecting contentView to be full-size, but clearly it's not. Could someone please refer me to the solution of that issue? Thanks in advance!
Can you set
contentView.translatesAutoresizingMaskIntoConstraints = false

How do I send subviews to front programmatically? (have tried bringSubview to front)

In my UICollectionViewCell I have an image and a label. The picture takes up the whole cell (which I want) - however, the label is placed behind the image, so it's not visible. I have tried bringSubview(toFront: titleLabel), but nothing happens... I got no clue what to do really, have done a lot of searching.
This is the code for the cell, I don't use Storyboard as you can see (sorry for messy constraints, was testing different solutions to find out if this was the problem)
import UIKit
class BaseCell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
setupBasket()
}
func setupViews() {
}
func setupBasket(){
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class VideoCell: BaseCell {
var selectedItemID : String!
static let sharedInstance = VideoCell()
var video: Video? {
didSet {
titleLabel.text = video?.title
setupThumbnailImage()
}
}
func setupThumbnailImage() {
if let thumbnailImageUrl = video?.thumbnail_image_name {
thumbnailImageView.loadImageUsingUrlString(thumbnailImageUrl)
}
}
let thumbnailImageView: CustomImageView = {
let imageView = CustomImageView()
imageView.image = UIImage(named: "taylor_swift_blank_space")
imageView.contentMode = .scaleAspectFill
imageView.clipsToBounds = true
return imageView
}()
let titleLabel: UILabel = {
let textView = UILabel()
textView.translatesAutoresizingMaskIntoConstraints = false
textView.text = "Clothes"
textView.textColor = UIColor.lightGray
return textView
}()
let separatorView: UIView = {
let view = UIView()
view.backgroundColor = UIColor(red: 230/255, green: 230/255, blue: 230/255, alpha: 1)
return view
}()
var titleLabelHeightConstraint: NSLayoutConstraint?
let addtoBasket = UIButton(type: .contactAdd)
override func setupViews() {
addtoBasket.frame = CGRect(x: 50, y: 0, width: 20, height: 60)
addSubview(addtoBasket)
addSubview(titleLabel)
addSubview(thumbnailImageView)
addSubview(separatorView)
addSubview(addtoBasket)
titleLabel.superview!.bringSubview(toFront: titleLabel)
//horizontal constraints
addConstraintsWithFormat("H:|-0-[v0]-0-|", views: thumbnailImageView)
//vertical constraints
addConstraintsWithFormat("V:|-1-[v0]-1-|", views: thumbnailImageView)
addConstraintsWithFormat("H:|-0-[v0]-1-|", views: separatorView)
addtoBasket.translatesAutoresizingMaskIntoConstraints = false
addtoBasket.heightAnchor.constraint(equalToConstant: 20).isActive = true
addtoBasket.widthAnchor.constraint(equalToConstant: 20).isActive = true
addtoBasket.centerXAnchor.constraint(equalTo: addtoBasket.superview!.centerXAnchor, constant: 90).isActive = true
addtoBasket.centerYAnchor.constraint(equalTo: addtoBasket.superview!.centerYAnchor, constant: -50).isActive = true
//top constraint
addConstraint(NSLayoutConstraint(item: titleLabel, attribute: .top, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1, constant: 8))
//right constraint
addConstraint(NSLayoutConstraint(item: titleLabel, attribute: .right, relatedBy: .equal, toItem: self, attribute: .right, multiplier: 1, constant: 0))
//right constraint
addConstraint(NSLayoutConstraint(item: titleLabel, attribute: .left, relatedBy: .equal, toItem: self, attribute: .left, multiplier: 1, constant: 20))
//height constraint
titleLabelHeightConstraint = NSLayoutConstraint(item: titleLabel, attribute: .height, relatedBy: .equal, toItem: self, attribute: .height, multiplier: 1, constant: -10)
addConstraint(titleLabelHeightConstraint!)
}
}
Try accessing the labels layer and set its zPosition.
Try titleLabel.layer.zPosition = 1
There was clearly something wrong with the constraints, now working! Thanks

Adding constraints programatically to custom components

I'm developing custom components in Swift by inheriting the default components. Below there is a piece of my code:
class DirectionButton: UIButton {
var backgroundImage: UIImageView!
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
styleComponent()
}
override init(frame: CGRect) {
super.init(frame: frame)
styleComponent()
}
func styleComponent() {
backgroundImage = UIImageView(image: UIImage(named: "seta-proximo"))
backgroundImage.contentMode = .ScaleAspectFit
self.setNeedsLayout()
self.layoutIfNeeded()
self.addSubview(backgroundImage)
let imageConstraints = NSLayoutConstraint(item: self, attribute: .TrailingMargin, relatedBy: .Equal, toItem: self, attribute: .Trailing, multiplier: 1, constant: 10)
backgroundImage.addConstraint(imageConstraints)
}
}
The backgroundImage variable needs to be 10 points from the right part of the button, that's why I need the constraints.
After running I got an exception like that: The view hierarchy is not prepared for the constraint.
How can I add constraints correctly?
Reading the documentation, I followed the tip to use layout anchors as #AjayBeniwal suggested. The code I needed was the following (after I add the subview to the button):
backgroundImage.trailingAnchor.constraintEqualToAnchor(self.layoutMarginsGuide.trailingAnchor, constant: -10).active = true
backgroundImage.bottomAnchor.constraintEqualToAnchor(self.layoutMarginsGuide.bottomAnchor).active = true
backgroundImage.centerYAnchor.constraintEqualToAnchor(self.layoutMarginsGuide.centerYAnchor).active = true
This way, the image is vertically centralized to the button an 20 points far from the right edge.
Edited
first do this :
backgroundImage.translatesAutoresizingMaskIntoConstraints = false
Add constraint to self not backgroundImage
You are adding constraint from self to self (incorrect) add from backroundImage to self
Constraints are incomplete. You are adding only one trailing constraint.
Overall solution is this:
func styleComponent() {
// Change clear.png with your own image name.
backgroundImage = UIImageView(image: UIImage(named: "clear.png"))
backgroundImage.contentMode = .ScaleAspectFit
self.addSubview(backgroundImage)
backgroundImage.translatesAutoresizingMaskIntoConstraints = false
let imageConstraints = NSLayoutConstraint(item: backgroundImage, attribute: .Trailing, relatedBy: .Equal, toItem: self, attribute: .Trailing, multiplier: 1, constant: -10)
self.addConstraint(imageConstraints)
let topConstraints = NSLayoutConstraint(item: backgroundImage, attribute: .Top, relatedBy: .Equal, toItem: self, attribute: .Top, multiplier: 1, constant: 10)
self.addConstraint(topConstraints)
let bottomConstraints = NSLayoutConstraint(item: backgroundImage, attribute: .Bottom, relatedBy: .Equal, toItem: self, attribute: .Bottom, multiplier: 1, constant: -10)
self.addConstraint(bottomConstraints)
let leadingConstraints = NSLayoutConstraint(item: backgroundImage, attribute: .Left, relatedBy: .Equal, toItem: self, attribute: .Left, multiplier: 1, constant: 10)
self.addConstraint(leadingConstraints)
}
The resultant output is this. You can modifiy the constants values to do according to your own will
In this case, The green is a Button and Red one is background image.
Try this and let me know if you are still getting any warning or complications.
//Give Constrain Outlet to ImageView
func setConstrain()
{
self.imageConstrain.constant = -10
self.topConstraints.constant = 10
self.bottomConstraints.constant = -10
self.leadingConstraints.constant = 10
self.layoutIfNeeded()
}

Autolayout constraints set in code not appearing in interface builder

I have a custom view that is set in interface builder with top, leading, trailing, height constraints.
In my Custom view i have a title and a button.
Im trying to add to the title a bottom and centerY constraints.
and to the button width, height, bottom, leading constraints.
When i add any constraint i get an warning in interface builder:
Expected: width=600, height=68.
Actual: width=0, height=0
When i run the code everything works, but i cant see anything in interface builder.
code:
#IBDesignable
class UIHeader: UIView {
var delegate: HeaderDelegate?
private lazy var titleLable: UILabel = {
let lbl = UILabel()
lbl.translatesAutoresizingMaskIntoConstraints = false
lbl.font = UIFont(name: "Lato-Light", size: 16)
lbl.text = "Title"
return lbl
}()
private lazy var backButton: UIButton = {
let btn = UIButton()
btn.tintColor = UIColor.lightGrayColor()
btn.translatesAutoresizingMaskIntoConstraints = false
let image = UIImage(named: "prev")
if let image = image {
btn.setImage(image.imageWithRenderingMode(.AlwaysTemplate), forState: .Normal)
}
btn.addTarget(self, action: #selector(UIHeader.OnBackButtonClickLister(_:)), forControlEvents: .TouchUpInside)
return btn
}()
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupView()
}
}
extension UIHeader {
#IBInspectable
var backButtonImage: UIImage? {
get {
return backButton.imageForState(.Normal)
}
set (newImage) {
backButton.setImage(newImage?.imageWithRenderingMode(.AlwaysTemplate), forState: .Normal)
}
}
#IBInspectable
var title: String? {
get {
return titleLable.text
}
set (newTitle) {
titleLable.text = newTitle
}
}
}
extension UIHeader {
private func setupView() {
backgroundColor = UIColor.whiteColor()
translatesAutoresizingMaskIntoConstraints = false
addSubview(titleLable)
addSubview(backButton)
//add shadow
layer.shadowColor = UIColor(white: 115/255, alpha: 1.0).CGColor
layer.shadowOpacity = 0.5
layer.shadowRadius = 8
layer.shadowOffset = CGSizeMake(0, -1)
NSLayoutConstraint.activateConstraints([
//Title//
//center x
NSLayoutConstraint(item: titleLable, attribute: .CenterX, relatedBy: .Equal, toItem: self, attribute: .CenterX, multiplier: 1.0, constant: 0),
//bottom
NSLayoutConstraint(item: self, attribute: .Bottom, relatedBy: .Equal, toItem: titleLable, attribute: .Bottom, multiplier: 1, constant: 12),
//button//
//bottom
NSLayoutConstraint(item: self, attribute: .Bottom, relatedBy: .Equal, toItem: backButton, attribute: .Bottom, multiplier: 1, constant: 4),
//leading
NSLayoutConstraint(item: backButton, attribute: .Leading, relatedBy: .Equal, toItem: self, attribute: .Leading, multiplier: 1, constant: 0),
//width
NSLayoutConstraint(item: backButton, attribute: .Width, relatedBy: .Equal, toItem: nil, attribute: .Width, multiplier: 1, constant: 40),
//height
NSLayoutConstraint(item: backButton, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .Height, multiplier: 1, constant: 40)
])
}
}
I also tried to add the constraints with:
addConstraint(NSLayoutConstraint)
cant figure out what is the problem.
Thanks
I removed
translatesAutoresizingMaskIntoConstraints = false
and everything works great.