I've been struggling with this for almost an entire day now and can't find a solution. I'm trying to create a UICollectionView within a view controller that has cells just with a single label in each of them. However, I can't seem to add the label as a subview to the cell no matter what I do. Here is the code I have in my main view controller:
var collectionView: UICollectionView!
func configureCollectionView()
{
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.itemSize = CGSize(width: 50, height: 20)
let y = interestsUnderline.frame.origin.y + 5
let width = interestsUnderline.frame.width
let frame = CGRect(x: 40, y: y, width: width, height: 30)
collectionView = UICollectionView(frame: frame, collectionViewLayout: layout)
collectionView?.dataSource = self
collectionView?.delegate = self
collectionView?.register(InterestsCell.self, forCellWithReuseIdentifier: "InterestsCell")
collectionView?.showsHorizontalScrollIndicator = false
view.addSubview(collectionView!)
}
I call configureCollectionView() from viewDidLayoutSubviews. This is the code I have in my collection view cell.
class InterestsCell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
let label: UILabel = {
let label = UILabel()
label.text = "Hello"
label.textColor = .white
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
func setupViews() {
contentView.backgroundColor = .red
contentView.addSubview(label)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
Whenever I dequeue the cell, I get the correct size of the cell and the correct color. But when I go to set constraints on the label in my cell's setupViews function, the view controller will just not even open up altogether. If I don't set the constraints, the label is not there. I'm not sure what to do at this point and I would appreciate some help. Thank you!
I took a look at your above snippet and what I found is you haven't set the Frame you need to set the Frame of your UILabel
let label: UILabel = {
let label = UILabel()
label.frame = CGRect(x: 40, y: 100, width: self.view.frame.width, height: 30)
label.text = "Hello"
label.textColor = UIColor.red
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
Hope this help you
Since your project uses auto layout constraints you have to set them for the label:
func setupViews() {
contentView.backgroundColor = .red
contentView.addSubview(label)
label.centerXAnchor.constraint(equalTo: contentView.centerXAnchor).isActive = true
label.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
}
Inside your InterestsCell class add the following code:
override func layoutSubviews() {
super.layoutSubviews()
setupViews()
}
instead of:
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
Hope this would work
Related
I'm trying to make UIView that contains 12x7 UIViews with margins. I thought that the best way gonna be make 7 Vertical Stacks and then add all them on one big Horizontal stack. And I coded it, but problem is that this Horizontal Stacks doesn't appear on the screen at all (I've tried Xcode feature to see layers there is nothing).
This is my code:
import UIKit
class CalendarView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupView()
}
private func setupView() {
// array to add in future in columnsStackView
var columnStacks: [UIStackView] = []
for columns in 1...12 {
// array to add in future in columnStackView
var columnViews: [UIView] = []
for cell in 1...7 {
let cellView = UIView(frame: CGRect(x: 0, y: 0, width: 24, height: 24))
cellView.backgroundColor = .orange
columnViews.append(cellView)
}
// create columnStackView and add all 7 views
let columnStackView = UIStackView(arrangedSubviews: columnViews)
columnStackView.axis = .vertical
columnStackView.distribution = .fillEqually
columnStackView.alignment = .fill
columnStackView.spacing = 4
columnStacks.append(columnStackView)
}
// create columnsStackView and add those 12 stacks
let columnsStackView = UIStackView(arrangedSubviews: columnStacks)
columnsStackView.axis = .horizontal
columnsStackView.distribution = .fillEqually
columnsStackView.alignment = .fill
columnsStackView.spacing = 4
columnsStackView.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(columnsStackView)
}
}
Can you please help me with that!!!
Couple things...
A UIStackView uses auto-layout when arranging its subviews, so this line:
let cellView = UIView(frame: CGRect(x: 0, y: 0, width: 24, height: 24))
will create a UIView, but the width and height will be ignored.
You need to set those with constraints:
for cell in 1...7 {
let cellView = UIView()
cellView.backgroundColor = .orange
// we want each "cellView" to be 24x24 points
cellView.widthAnchor.constraint(equalToConstant: 24.0).isActive = true
cellView.heightAnchor.constraint(equalTo: cellView.widthAnchor).isActive = true
columnViews.append(cellView)
}
Now, because we've explicitly set the width and height of the "cellViews" we can set the stack view .distribution = .fill (instead of .fillEqually).
Next, we have to constrain the "outer" stack view (columnsStackView) to the view itself:
// constrain the "outer" stack view to self
NSLayoutConstraint.activate([
columnsStackView.topAnchor.constraint(equalTo: topAnchor),
columnsStackView.leadingAnchor.constraint(equalTo: leadingAnchor),
columnsStackView.trailingAnchor.constraint(equalTo: trailingAnchor),
columnsStackView.bottomAnchor.constraint(equalTo: bottomAnchor),
])
otherwise, the view will have 0x0 dimensions.
Here is a modified version of your class:
class CalendarView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupView()
}
private func setupView() {
// array to add in future in columnsStackView
var columnStacks: [UIStackView] = []
for columns in 1...12 {
// array to add in future in columnStackView
var columnViews: [UIView] = []
for cell in 1...7 {
let cellView = UIView()
cellView.backgroundColor = .orange
// we want each "cellView" to be 24x24 points
cellView.widthAnchor.constraint(equalToConstant: 24.0).isActive = true
cellView.heightAnchor.constraint(equalTo: cellView.widthAnchor).isActive = true
columnViews.append(cellView)
}
// create columnStackView and add all 7 views
let columnStackView = UIStackView(arrangedSubviews: columnViews)
columnStackView.axis = .vertical
columnStackView.distribution = .fill
columnStackView.alignment = .fill
columnStackView.spacing = 4
columnStacks.append(columnStackView)
}
// create columnsStackView and add those 12 stacks
let columnsStackView = UIStackView(arrangedSubviews: columnStacks)
columnsStackView.axis = .horizontal
columnsStackView.distribution = .fill
columnsStackView.alignment = .fill
columnsStackView.spacing = 4
columnsStackView.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(columnsStackView)
// constrain the "outer" stack view to self
NSLayoutConstraint.activate([
columnsStackView.topAnchor.constraint(equalTo: topAnchor),
columnsStackView.leadingAnchor.constraint(equalTo: leadingAnchor),
columnsStackView.trailingAnchor.constraint(equalTo: trailingAnchor),
columnsStackView.bottomAnchor.constraint(equalTo: bottomAnchor),
])
}
}
and a simple test controller to show how it can be used:
class CalendarTestViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let cv = CalendarView()
cv.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(cv)
// the CalendarView will size itself, so we only need to
// provide x and y position constraints
NSLayoutConstraint.activate([
cv.centerXAnchor.constraint(equalTo: view.centerXAnchor),
cv.centerYAnchor.constraint(equalTo: view.centerYAnchor),
])
// let's give it a background color so we can see its frame
cv.backgroundColor = .systemYellow
}
}
the result:
I am implementing a UIScrollView in a CollectionViewCell. I have a custom view which the scroll view should display, hence I am performing the following program in the CollectionViewCell. I have created everything programmatically and below is my code :
struct ShotsCollections {
let title: String?
}
class ShotsMainView: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
containerScrollView.contentSize.width = frame.width * CGFloat(shotsData.count)
shotsData = [ShotsCollections.init(title: "squad"), ShotsCollections.init(title: "genral")]
var i = 0
for data in shotsData {
let customview = ShotsMediaView(frame: CGRect(x: containerScrollView.frame.width * CGFloat(i), y: 0, width: containerScrollView.frame.width, height: containerScrollView.frame.height))
containerScrollView.addSubview(customview)
i += 1
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
var shotsData = [ShotsCollections]()
var containerScrollView: UIScrollView = {
let instance = UIScrollView()
instance.isScrollEnabled = true
instance.bounces = true
instance.backgroundColor = blueColor
return instance
}()
private func setupViews() { //These are constraints by using TinyConstraints
addSubview(containerScrollView)
containerScrollView.topToSuperview()
containerScrollView.bottomToSuperview()
containerScrollView.rightToSuperview()
containerScrollView.leftToSuperview()
}
}
Now the issue is, while the scrollview is displayed, the content in it is not. I on printing the contentSize and frame of the scrollview, it displays 0. But if I check the Debug View Hierarchy, scrollview containes 2 views with specific frames.
I am not sure whats going wrongs. Any help is appreciated.
When you are adding customView in your containerScrollView, you are not setting up the constraints between customView and containerScrollView.
Add those constraints and you will be able to see your customViews given that your customView has some height. Also, when you add more view, you would need to remove the bottom constraint of the last added view and create a bottom constraint to the containerScrollView with the latest added view.
I created a sample app for your use case. I am pasting the code and the resultant screen shot below. Hope this is the functionality you are looking for. I suggest you paste this in a new project and tweak the code until you are satisfied. I have added comments to make it clear.
ViewController
import UIKit
class ViewController: UIViewController {
// Initialize dummy data array with numbers 0 to 9
var data: [Int] = Array(0..<10)
override func loadView() {
super.loadView()
// Add collection view programmatically
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.register(ShotsMainView.self, forCellWithReuseIdentifier: ShotsMainView.identifier)
self.view.addSubview(collectionView)
NSLayoutConstraint.activate([
self.view.topAnchor.constraint(equalTo: collectionView.topAnchor),
self.view.bottomAnchor.constraint(equalTo: collectionView.bottomAnchor),
self.view.leadingAnchor.constraint(equalTo: collectionView.leadingAnchor),
self.view.trailingAnchor.constraint(equalTo: collectionView.trailingAnchor),
])
collectionView.delegate = self
collectionView.dataSource = self
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.backgroundColor = UIColor.white
self.view.addSubview(collectionView)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.view.backgroundColor = UIColor.white
}
}
extension ViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ShotsMainView.identifier, for: indexPath) as! ShotsMainView
return cell
}
}
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
// The cell dimensions are set from here
return CGSize(width: collectionView.frame.size.width, height: 100.0)
}
}
ShotsMainView
This is the collection view cell
import UIKit
class ShotsMainView: UICollectionViewCell {
static var identifier = "Cell"
weak var textLabel: UILabel!
override init(frame: CGRect) {
// Initialize with zero frame
super.init(frame: frame)
// Add the scrollview and the corresponding constraints
let containerScrollView = UIScrollView(frame: .zero)
containerScrollView.isScrollEnabled = true
containerScrollView.bounces = true
containerScrollView.backgroundColor = UIColor.blue
containerScrollView.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(containerScrollView)
NSLayoutConstraint.activate([
self.topAnchor.constraint(equalTo: containerScrollView.topAnchor),
self.bottomAnchor.constraint(equalTo: containerScrollView.bottomAnchor),
self.leadingAnchor.constraint(equalTo: containerScrollView.leadingAnchor),
self.trailingAnchor.constraint(equalTo: containerScrollView.trailingAnchor)
])
// Add the stack view that will hold the individual items that
// in each row that need to be scrolled horrizontally
let stackView = UIStackView(frame: .zero)
stackView.distribution = .fill
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .horizontal
containerScrollView.addSubview(stackView)
stackView.backgroundColor = UIColor.magenta
NSLayoutConstraint.activate([
containerScrollView.leadingAnchor.constraint(equalTo: stackView.leadingAnchor),
containerScrollView.trailingAnchor.constraint(equalTo: stackView.trailingAnchor),
containerScrollView.topAnchor.constraint(equalTo: stackView.topAnchor),
containerScrollView.bottomAnchor.constraint(equalTo: stackView.bottomAnchor)
])
// Add individual items (Labels in this case).
for i in 0..<10 {
let label = UILabel(frame: .zero)
label.translatesAutoresizingMaskIntoConstraints = false
stackView.addArrangedSubview(label)
label.text = "\(i)"
label.font = UIFont(name: "System", size: 20.0)
label.textColor = UIColor.white
label.backgroundColor = UIColor.purple
label.layer.masksToBounds = false
label.layer.borderColor = UIColor.white.cgColor
label.layer.borderWidth = 1.0
label.textAlignment = .center
NSLayoutConstraint.activate([
label.heightAnchor.constraint(equalTo: self.heightAnchor, multiplier: 1.0, constant: 0.0),
label.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: 0.2, constant: 0.0)
])
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Screenshot
I have a collection view that contains a cell with varying width (it has a label inside it):
public class TagView: UIView {
let textLabel: UILabel = {
let label = UILabel()
label.textColor = .black
label.textAlignment = .center
label.font = UIFont.systemFont(ofSize: 15)
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
setupLabel()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupView() {
backgroundColor = #colorLiteral(red: 0.03529411765, green: 0.6862745098, blue: 0.003921568627, alpha: 1)
backgroundColor = backgroundColor
layer.cornerRadius = 3
layer.masksToBounds = true
}
private func setupLabel() {
addSubview(textLabel)
textLabel.fillToSuperview(constant: 3)
}
}
How do I make the collection view's height dynamic? The problem is that at init time I don't know what frame I should give to the collection view, so I just give zero:
let layout = UICollectionViewFlowLayout()
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
How do I make the collection view height dynamic?
I have also looked into the sizeForItem method:
public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let view = TagView()
let data = tags[indexPath.row]
view.textLabel.text = data
view.layoutSubviews()
return view.frame.size
}
but I think this returns a size of zero width and heigth.
First here set an assumption height , but the width should be known
let collectionView = UICollectionView(frame:CGRect(x:0,y:20,width:self.view.frame.width,height:300), collectionViewLayout: layout)
Then in sizeForItemAt
let fixedWidth = (collectionView.frame.width - 40 ) / 5 // say here you need 5 items / row
let label = UILabel()
label.numberOfLines = 0
label.font = UIFont.systemFont(ofSize: 15)
let si = label.sizeThatFits(CGSize(width:fixedWidth, height: CGFloat.greatestFiniteMagnitude))
// si.height is the needed height with 6 padding from top and bottom according to your constant in tagView
return CGSize(width:fixedWidth + 6.0 ,height:si.height)
For a total height , create a function from above and call it with all your items then add the heights and set them to collectionView's height
I want to create custom UITextField with error label on bottom of it. I want the label to be multiline, I tried numberOfLines = 0. But it is not working.
Here is my snippet for the class
public class MyTextField: UITextField {
private let helperTextLabel: UILabel = {
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 12.0, weight: UIFont.Weight.regular)
label.textColor = helperTextColor
label.numberOfLines = 0
return label
}()
public override init(frame: CGRect) {
super.init(frame: frame)
self.addSubview(errorTextLabel)
}
public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.addSubview(errorTextLabel)
}
public override func layoutSubviews() {
super.layoutSubviews()
errorTextLabel.frame = CGRect(x: bounds.minX, y: bounds.maxY - 20, width: bounds.width, height: 20)
}
public override var intrinsicContentSize: CGSize {
return CGSize(width: 240.0, height: 68.0)
}
public override func sizeThatFits(_ size: CGSize) -> CGSize {
return intrinsicContentSize
}
}
I think the root cause is because I set height to 20, but how can I set the height dynamically based on the errorTextLabel.text value?
You are giving your label a fixed size.
Not using AutoLayout and giving the textfield and label room to expand it's size when needed.
Personally, if creating this particular control, I would create a UIView with a textfield and a label inside a UIStackView. That way if the label is hidden when there is no error the stackview will automatically adjust the height for you. Then when you unhide it, the view will expand to fit both controls.
A basic example:
//: Playground - noun: a place where people can play
import UIKit
import PlaygroundSupport
class LabelledTextView: UIView {
private let label = UILabel()
private let textfield = UITextField()
private let stackView = UIStackView()
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .white
addSubview(stackView)
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.topAnchor.constraint(equalTo: topAnchor).isActive = true
stackView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
stackView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
stackView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
stackView.alignment = .leading
stackView.axis = .vertical
stackView.distribution = .fillEqually
stackView.addArrangedSubview(textfield)
stackView.addArrangedSubview(label)
textfield.placeholder = "Please enter some text"
label.numberOfLines = 0
label.text = "Text did not pass validation, Text did not pass validation, Text did not pass validation, Text did not pass validation"
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
let labelledTextView = LabelledTextView(frame: CGRect(x: 50, y: 300, width: 300, height: 60))
let vc = UIViewController()
vc.view.addSubview(labelledTextView)
labelledTextView.translatesAutoresizingMaskIntoConstraints = false
labelledTextView.topAnchor.constraint(equalTo: vc.view.topAnchor).isActive = true
labelledTextView.leftAnchor.constraint(equalTo: vc.view.leftAnchor).isActive = true
labelledTextView.widthAnchor.constraint(equalToConstant: 300).isActive = true
labelledTextView.heightAnchor.constraint(greaterThanOrEqualToConstant: 60).isActive = true
PlaygroundPage.current.liveView = vc.view
You need to use sizeToFit():
errorTextLabel.sizeToFit()
I have 10 UILabels with different sizes, and I want to arrange them in 3 rows, and all rows have leading alignment, and if the last item of each row can't fit in remaining space of parent view, it have to move to the next line. How can I do that by using UIStackview?
You can try to do something like that:
Your ViewController
for item in array {
let someView = VPView(frame: CGRect(x: 0, y: 0, width: 70, height: 20))
someView.translatesAutoresizingMaskIntoConstraints = false
stackView.addArrangedSubview(someView)
}
UIView Class:
class VPView: UIView {
let myLabel = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
self.addLabel()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func addLabels() {
//Mark: - Styles
let labelH: CGFloat = 15
let labelW: CGFloat = 70
//MARK: - My Label
self.myLabel.frame = CGRect(x: 0, y: 0, width: labelW, height: labelH)
self.myLabel.backgroundColor = UIColor.blue
self.myLabel.textAlignment = NSTextAlignment.center
self.myLabel.numberOfLines = 0
self.addSubview(self.myLabel)
self.myLabel.translatesAutoresizingMaskIntoConstraints = false
self.myLabel.heightAnchor.constraint(equalToConstant: labelH).isActive = true
self.myLabel.widthAnchor.constraint(equalToConstant: labelW).isActive = true
self.myLabel.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
self.myLabel.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
}
}