How to expand a UITableView inside a StackView? - swift

I have programatically created a Stackview along with a UITableView inside it.
func setupStack() {
view.addSubview(stackView)
stackView.axis = .vertical
stackView.distribution = .fillEqually
stackView.spacing = 8
}
func fillStackView() {
yearTable.isHidden = true
yearTable.layer.cornerRadius = 10
allViews.append(yearTable) // adding the view to an array of views
// other views also created and added to stack
}
My problem is that when I fade in the tableView, it takes up as much space as the other views in the table which all have a height of 40. The tableView has a height of 150 but it never expands to that height. How do I get it to expand?

UITableView does not have an intrinsic height/width so when placed in a UIStackView without any height/width information, it will be provided a default size. i.e. if your tallest view in the stack is 60 in height, then the tableView will also be 60.
You could alter this by giving yearTable a height constraint before adding it to the UIStackView, like so:
func fillStackView() {
//...
yearTable.translatesAutoresizingMaskIntoConstraints = false
yearTable.heightAnchor.constraint(equalToConstant: 150).isActive = true
allViews.append(yearTable)
//...
}
But do note that if you do this then because of stackView.distribution = .fillEqually, all your other views in the UIStackView will also become 150.
(EXTRA) Playground Example:
import UIKit
import PlaygroundSupport
class ViewController: UIViewController {
lazy var stackView: UIStackView = {
let stackView = UIStackView()
stackView.axis = .vertical
stackView.distribution = .fillEqually
stackView.spacing = 8
stackView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(stackView)
stackView.widthAnchor.constraint(equalToConstant: 200).isActive = true
return stackView
}()
let datasource = [1,2,3,4,5,6,7,8,9,10]
override func viewDidLoad() {
super.viewDidLoad()
appendLabel(text: "Hello, World!", color: .lightGray)
appendLabel(text: "Lorem\nipsum\ndolor\nsit", color: .gray)
appendTableView()
}
func appendLabel(text: String, color: UIColor) {
let label = UILabel()
label.backgroundColor = color
label.numberOfLines = 0
label.text = text
stackView.addArrangedSubview(label)
}
func appendTableView() {
let tableView = UITableView()
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
tableView.dataSource = self
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.heightAnchor.constraint(equalToConstant: 150).isActive = true
stackView.addArrangedSubview(tableView)
}
}
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return datasource.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let item = datasource[indexPath.row]
cell.textLabel?.text = String(item)
return cell
}
}
let vc = ViewController()
vc.view.frame = CGRect(x: 0, y: 0, width: 320, height: 480)
PlaygroundPage.current.setLiveView(vc.view)
Comment tableView.heightAnchor.constraint(equalToConstant: 150).isActive = true to see the difference

Related

Inside custom tableView cell: UILabel's background is beyond the text length (programmatically)

I create a tableView with custom cell, which contains two labels programmatically. And I cannot get left label's text aligned with its background. May need some help from your guys.
In order to narrow down the problem then I create a small project to do a few experiments:
Inside ViewController:
If with tableView.rowHeight = 40, get below result, which is not what I want. The left label's background is beyond the text length.
If comment out line tableView.rowHeight = 40, get below result, which is what I want but with a warning in console.
"[Warning] Warning once only: Detected a case where constraints ambiguously suggest a height of zero for a table view cell's content view. We're considering the collapse unintentional and using standard height instead."
Also try to use below statement, it gets the same display on screen with scenario 2. However, it has the same warning there as scenario 2.
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 40
ViewController
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var tableView = UITableView()
override func viewDidLoad() {
super.viewDidLoad()
let uiView = UIView()
uiView.backgroundColor = .systemBackground
view = uiView
tableView.delegate = self
tableView.dataSource = self
tableView.register(CustomCell.self, forCellReuseIdentifier: "countryDetail")
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.rowHeight = 40
view.addSubview(tableView)
NSLayoutConstraint.activate([
tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
tableView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
tableView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
tableView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
])
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 8
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "countryDetail", for: indexPath) as? CustomCell else {
fatalError("Unable to dequeue CustomCell")
}
cell.name.text = "Country: "
cell.value.text = "US"
return cell
}
}
CustomCell
import UIKit
class CustomCell: UITableViewCell {
var name = UILabel()
var value = UILabel()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: "countryDetail")
name.translatesAutoresizingMaskIntoConstraints = false
name.numberOfLines = 0
name.textAlignment = .left
name.layer.masksToBounds = true
name.layer.cornerRadius = 5
name.backgroundColor = .systemGreen
name.font = UIFont(name: "Helvetica Neue", size: 22)
contentView.addSubview(name)
value.translatesAutoresizingMaskIntoConstraints = false
value.numberOfLines = 0
value.textAlignment = .left
value.font = UIFont(name: "Helvetica Neue", size: 18)
contentView.addSubview(value)
NSLayoutConstraint.activate([
name.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 10),
name.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
value.leadingAnchor.constraint(equalTo: name.trailingAnchor, constant: 10),
value.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
value.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -10)
])
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Oh, it is a silly mistake. I should remove this line value.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -10) from CustomCell to remove right label's trailing anchor constraint as I want the right label left aligned to the left one.
And this time using tableView.rowHeight = 40, the app works fine.

How do I access content of a uicollectionviewcell in a view controller?

I'm trying add a bottom border to a textfield inside a UICollectionViewCell, I registered the cell inside a view controller where my collection view is. But to set the size of the bottom border I need to the it own size, and I don't know how to do it inside the collection view cell, so Im trying to pass It to the view controller where It Is registered, but no success yet.
*Obs: I cut out some parts of the code because is not relevant.
UICollectionViewCell
class NameStepCell: UICollectionViewCell {
let safeAreaHolder: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
let title: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
label.font = UIFont.boldSystemFont(ofSize: 40)
label.text = "What is\nyour\nname?"
return label
}()
let txtFieldStack: UIStackView = {
let stack = UIStackView()
stack.translatesAutoresizingMaskIntoConstraints = false
stack.alignment = .center
stack.axis = .horizontal
stack.distribution = .fillEqually
stack.spacing = 20
return stack
}()
let nameField: UITextField = {
let txtFld = UITextField()
txtFld.keyboardType = UIKeyboardType.default
txtFld.textContentType = UITextContentType.name
txtFld.autocapitalizationType = UITextAutocapitalizationType.words
txtFld.autocorrectionType = .no
txtFld.textColor = UIColor.black
return txtFld
}()
let lastNameField: UITextField = {
let txtFld = UITextField()
txtFld.keyboardType = UIKeyboardType.default
txtFld.textContentType = UITextContentType.familyName
txtFld.autocapitalizationType = UITextAutocapitalizationType.words
txtFld.autocorrectionType = .no
txtFld.textColor = UIColor.black
return txtFld
}()
override init(frame: CGRect) {
super.init(frame: frame)
configuringView()
configuringTitle()
configuringTxtField()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configuringView(){
addSubview(safeAreaHolder)
safeAreaHolder.topAnchor.constraint(equalTo: topAnchor).isActive = true
safeAreaHolder.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16).isActive = true
safeAreaHolder.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
safeAreaHolder.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16).isActive = true
}
func configuringTitle(){
safeAreaHolder.addSubview(title)
title.topAnchor.constraint(equalTo: safeAreaHolder.topAnchor, constant: 50).isActive = true
title.trailingAnchor.constraint(equalTo: safeAreaHolder.trailingAnchor).isActive = true
title.leadingAnchor.constraint(equalTo: safeAreaHolder.leadingAnchor).isActive = true
}
func configuringTxtField(){
safeAreaHolder.addSubview(txtFieldStack)
txtFieldStack.topAnchor.constraint(equalTo: title.bottomAnchor, constant: 50).isActive = true
txtFieldStack.trailingAnchor.constraint(equalTo: safeAreaHolder.trailingAnchor).isActive = true
txtFieldStack.leadingAnchor.constraint(equalTo: safeAreaHolder.leadingAnchor).isActive = true
txtFieldStack.addArrangedSubview(nameField)
txtFieldStack.addArrangedSubview(lastNameField)
nameField.heightAnchor.constraint(equalToConstant: 45).isActive = true
lastNameField.heightAnchor.constraint(equalToConstant: 45).isActive = true
}
}
UIViewController
class SignupViewController: UIViewController, UICollectionViewDelegateFlowLayout, UICollectionViewDelegate, UICollectionViewDataSource{
let stepsCollectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.backgroundColor = .white
collectionView.contentInsetAdjustmentBehavior = UIScrollView.ContentInsetAdjustmentBehavior.never
collectionView.isPagingEnabled = true
collectionView.showsHorizontalScrollIndicator = false
collectionView.isScrollEnabled = false
return collectionView
}()
override func viewDidLoad() {
super.viewDidLoad()
stepsCollectionView.dataSource = self
stepsCollectionView.delegate = self
stepsCollectionView.register(NameStepCell.self, forCellWithReuseIdentifier: "nameStepId")
stepsCollectionView.register(GenderStepCell.self, forCellWithReuseIdentifier: "genderStepId")
stepsCollectionView.register(BirthdayStepCell.self, forCellWithReuseIdentifier: "birthdayStepId")
stepsCollectionView.register(EmailStepCell.self, forCellWithReuseIdentifier: "emailStepId")
stepsCollectionView.register(PasswordStepCell.self, forCellWithReuseIdentifier: "passwordStepId")
view.backgroundColor = .white
configuringBottomButton()
configuringStepCollectionView()
}
override func viewDidAppear(_ animated: Bool) {
}
Here is where I try to get the nameFied to add the border
override func viewDidLayoutSubviews() {
NameStepCell().self.nameField.addBottomBorder()
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 5
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if indexPath.item == 1 {
let genderCell = collectionView.dequeueReusableCell(withReuseIdentifier: "genderStepId", for: indexPath)
return genderCell
}else if indexPath.item == 2{
let birthdayCell = collectionView.dequeueReusableCell(withReuseIdentifier: "birthdayStepId", for: indexPath)
return birthdayCell
}else if indexPath.item == 3{
let emailCell = collectionView.dequeueReusableCell(withReuseIdentifier: "emailStepId", for: indexPath)
return emailCell
}else if indexPath.item == 4{
let passwordCell = collectionView.dequeueReusableCell(withReuseIdentifier: "passwordStepId", for: indexPath)
return passwordCell
}
let nameCell = collectionView.dequeueReusableCell(withReuseIdentifier: "nameStepId", for: indexPath)
return nameCell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: stepsCollectionView.frame.size.width, height: stepsCollectionView.frame.size.height)
}
}
Extension to textfield to add the bottom border
extension UITextField {
func addBottomBorder() {
let border = CALayer()
border.frame = CGRect(x: 0, y: 32, width: self.frame.size.width, height: 1)
border.cornerRadius = 2
border.masksToBounds = true
border.backgroundColor = UIColor.init(red: 112/255, green: 112/255, blue: 112/255, alpha: 1).cgColor
self.layer.masksToBounds = true
self.layer.addSublayer(border)
}
}
Instead of calling addBottomBar() inside viewDidLayoutSubviews, you can try something like this.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if indexPath.item == 1 {
let genderCell = collectionView.dequeueReusableCell(withReuseIdentifier: "genderStepId", for: indexPath)
return genderCell
}else if indexPath.item == 2{
let birthdayCell = collectionView.dequeueReusableCell(withReuseIdentifier: "birthdayStepId", for: indexPath)
return birthdayCell
}else if indexPath.item == 3{
let emailCell = collectionView.dequeueReusableCell(withReuseIdentifier: "emailStepId", for: indexPath)
return emailCell
}else if indexPath.item == 4{
let passwordCell = collectionView.dequeueReusableCell(withReuseIdentifier: "passwordStepId", for: indexPath)
return passwordCell
}
// Dequeue your NameStepCell from collection view
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "nameStepId", for: indexPath)
if let nameCell = cell as? NameStepCell {
// Add bottom border to it right here and return it
nameCell.nameField.addBottomBorder()
return nameCell
}
return cell
}
Edit:
Now you don't need to change anything in your SignUpViewController. Please replace your NameStepCell class with the below code.
class NameStepCell: UICollectionViewCell {
var safeAreaHolder: UIView!
var title: UILabel!
var txtFieldStack: UIStackView!
var nameField: UITextField!
var lastNameField: UITextField!
override init(frame: CGRect) {
super.init(frame: frame)
configuringView()
configuringTitle()
configuringTxtField()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
private extension NameStepCell {
func configuringView(){
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
self.safeAreaHolder = view
addSubview(safeAreaHolder)
safeAreaHolder.topAnchor.constraint(equalTo: topAnchor).isActive = true
safeAreaHolder.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16).isActive = true
safeAreaHolder.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
safeAreaHolder.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16).isActive = true
self.safeAreaHolder.layoutIfNeeded()
}
func configuringTitle(){
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
label.font = UIFont.boldSystemFont(ofSize: 40)
label.text = "What is\nyour\nname?"
self.title = label
safeAreaHolder.addSubview(title)
title.topAnchor.constraint(equalTo: safeAreaHolder.topAnchor, constant: 50).isActive = true
title.trailingAnchor.constraint(equalTo: safeAreaHolder.trailingAnchor).isActive = true
title.leadingAnchor.constraint(equalTo: safeAreaHolder.leadingAnchor).isActive = true
self.title.layoutIfNeeded()
}
func configuringTxtField(){
let stack = UIStackView()
stack.backgroundColor = .lightGray
stack.translatesAutoresizingMaskIntoConstraints = false
stack.alignment = .center
stack.axis = .horizontal
stack.distribution = .fillEqually
stack.spacing = 20
self.txtFieldStack = stack
safeAreaHolder.addSubview(txtFieldStack)
txtFieldStack.topAnchor.constraint(equalTo: title.bottomAnchor, constant: 50).isActive = true
txtFieldStack.trailingAnchor.constraint(equalTo: safeAreaHolder.trailingAnchor).isActive = true
txtFieldStack.leadingAnchor.constraint(equalTo: safeAreaHolder.leadingAnchor).isActive = true
self.txtFieldStack.layoutIfNeeded()
self.nameField = getTextField(.name)
self.lastNameField = getTextField(.familyName)
txtFieldStack.addArrangedSubview(nameField)
txtFieldStack.addArrangedSubview(lastNameField)
nameField.heightAnchor.constraint(equalToConstant: 45).isActive = true
lastNameField.heightAnchor.constraint(equalToConstant: 45).isActive = true
// After adding constraints, you should call 'layoutIfNeeded()' which recomputes the size and position based on the constraints you've set
self.nameField.layoutIfNeeded()
self.lastNameField.layoutIfNeeded()
self.nameField.addBottomBorder()
self.lastNameField.addBottomBorder()
}
func getTextField(_ textContentType: UITextContentType) -> UITextField {
let textField = UITextField()
textField.keyboardType = .default
textField.textContentType = textContentType
textField.autocapitalizationType = .words
textField.autocorrectionType = .no
textField.textColor = .black
textField.placeholder = textContentType.rawValue // P.S. Remove placeholder if you don't need.
return textField
}
}

How do I clear images from my UIImageView

I have the following code for a compositionalLayout in Swift, but my images are not going away with the reuse of cells.
import UIKit
class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
#IBOutlet var collectionView: UICollectionView!
let myInset: CGFloat = 4.0
let dataColors = [UIColor.red, UIColor.blue, UIColor.green, UIColor.magenta, UIColor.purple, UIColor.orange, UIColor.red, UIColor.blue, UIColor.green, UIColor.magenta, UIColor.purple, UIColor.systemYellow, UIColor.red, UIColor.blue, UIColor.green, UIColor.magenta, UIColor.purple, UIColor.orange, UIColor.red, UIColor.blue, UIColor.green, UIColor.magenta, UIColor.purple, UIColor.systemYellow]
let theImages = [
"MEN_8882","002","003","004","005","006","001","002","003","004","005","006",
"MEN_8882","002","003","004","005","006","001","002","003","004","005","006"
]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
collectionView.setCollectionViewLayout(createCustomLayout(), animated: false)
collectionView.backgroundColor = .white
self.collectionView.delegate = self
self.collectionView.dataSource = self
collectionView.register(QuickCell.self, forCellWithReuseIdentifier: "cellID")
//configureCollectionView()
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1//dataColors.count
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return dataColors.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellID", for: indexPath) as? QuickCell {
cell.backgroundColor = dataColors[indexPath.row]
let mySubView = UIImageView()
mySubView.image = UIImage(named: theImages[indexPath.row])
cell.addSubview(mySubView)
mySubView.translatesAutoresizingMaskIntoConstraints = false
mySubView.topAnchor.constraint(equalTo: cell.topAnchor, constant: myInset).isActive = true
mySubView.leadingAnchor.constraint(equalTo: cell.leadingAnchor, constant: myInset).isActive = true
mySubView.trailingAnchor.constraint(equalTo: cell.trailingAnchor, constant: myInset * (-1)).isActive = true
mySubView.bottomAnchor.constraint(equalTo: cell.bottomAnchor, constant: myInset * (-1)).isActive = true
mySubView.clipsToBounds = true
// mySubView.layer.cornerRadius = 8
mySubView.contentMode = .scaleAspectFit
cell.clipsToBounds = true
cell.layoutIfNeeded()
//cell.layer.cornerRadius = 12
return cell
} else {
return UICollectionViewCell()
}
}
func createCustomLayout() -> UICollectionViewLayout {
let layout = UICollectionViewCompositionalLayout { (section: Int, environment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in
let myItemInset: CGFloat = 2.0
let leadingItem = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(1.0)))
leadingItem.contentInsets = NSDirectionalEdgeInsets(top: myItemInset, leading: myItemInset, bottom: myItemInset, trailing: myItemInset)
let leadingGroupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.7), heightDimension: .fractionalHeight(1.0))
let leadingGroup = NSCollectionLayoutGroup.vertical(layoutSize: leadingGroupSize, subitem: leadingItem, count: 1)
let trailingGroupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.3), heightDimension: .fractionalHeight(1.0))
let trailingGroup = NSCollectionLayoutGroup.vertical(layoutSize: trailingGroupSize, subitem: leadingItem, count: 5)
let fullGroupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(1.0))
let fullGroup = NSCollectionLayoutGroup.horizontal(layoutSize: fullGroupSize, subitems: [leadingGroup, trailingGroup])
let section = NSCollectionLayoutSection(group: fullGroup)
section.orthogonalScrollingBehavior = .groupPagingCentered
section.contentInsets = NSDirectionalEdgeInsets(top: 20, leading: 0, bottom: 20, trailing: 0)
return section
}
return layout
}
}
The image "MEN..." is portrait while the rest are landscape, and as i scroll back and forth, i see overlapping images in the items.
the code for QuickCell is empty - I'm not sure what to put ther, some kind of initalization? But it should work anyways, right?
import UIKit
class QuickCell: UICollectionViewCell {
}
A subview of type UIImageView is added to your custom Cell (QuickCell) each time your collection view cell is resued. It happens in cellForRowAt delegate method.
So, you have to remove previously added image views from your cell first before adding a new one.
I suggest you move your cell configuration code to QuickCell.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellID", for: indexPath)
if let quickCell = quickCell as? QuickCell {
quickCell.backgroundColor = self.dataColors[indexPath.row]
quickCell.setImage(self.theImages[indexPath.row], insetBy: self.myInset)
return quickCell
}
return cell
}
Do your custom cell configurations here!
class QuickCell: UICollectionViewCell {
func setImage(_ image: UIImage, insetBy inset: CGFloat) {
// Remove previously added image views first if any
for subview in self.subviews where subview.isKind(of: UIImageView.self) {
subview.removeFromSuperview()
}
let imageView = UIImageView()
imageView.image = image
imageView.clipsToBounds = true
imageView.contentMode = .scaleAspectFit
self.addSubview(imageView)
imageView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
imageView.topAnchor.constraint(equalTo: self.topAnchor, constant: inset),
imageView.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: inset),
imageView.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: inset * (-1)),
imageView.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: inset * (-1))
])
self.layoutIfNeeded()
}
}
To remove image from UIImageView do the following.
yourImageView.image = nil
The problem here is that you are adding a new UIImageView every time the cell is reused. The ones added in previous interations of the cellForItemAtIndexPath: method do not go away, so others are added on top.
These are the problem lines:
let mySubView = UIImageView()
mySubView.image = UIImage(named: theImages[indexPath.row])
cell.addSubview(mySubView)
It would be better to add the image view once when a cell is initialised (or in storyboard with an outlet) then just set the image in the cellForItemAtIndexPath: method.
You keep adding subviews inside cellForItemAt and this causes the overlapping as cells are dequeued , you need to create an outlet for the imageview or create it programmatically inside the cell like
class QuickCell: UICollectionViewCell {
let mySubView = UIImageView()
override init(frame: CGRect) {
super.init(frame: frame)
let myInset: CGFloat = 4.0
self.contentView.addSubview(mySubView)
mySubView.translatesAutoresizingMaskIntoConstraints = false
mySubView.topAnchor.constraint(equalTo: self.contentView.topAnchor, constant: myInset).isActive = true
mySubView.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor, constant: myInset).isActive = true
mySubView.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor, constant: myInset * (-1)).isActive = true
mySubView.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor, constant: myInset * (-1)).isActive = true
mySubView.clipsToBounds = true
// mySubView.layer.cornerRadius = 8
mySubView.contentMode = .scaleAspectFit
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
With
cell.mySubView.image = UIImage(named: theImages[indexPath.row])
or
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellID", for: indexPath) as? QuickCell {
cell.subviews.forEach {
if $0.tag == 333 {
$0.removeFromSuperview()
}
}
cell.backgroundColor = dataColors[indexPath.row]
let mySubView = UIImageView()
mySubView.tag = 333
This is checked by default for UICollectionView, try to uncheck it. It should work for you.

How can I match Custom View's frame with TableViewCell frame properly?

I have a TableView which contains image stretched into cell and I added a view for darkening the image. Since I want to draw this view only in the beginning and not every time I scroll in table I added my codes into awakeFromNib in TableViewCell.
TableViewCell.swift
#IBOutlet weak var equipmentImageView: UIImageView!
let darkFilter = UIView()
override func awakeFromNib() {
super.awakeFromNib()
darkFilter.backgroundColor = .black
darkFilter.layer.opacity = 0.6
darkFilter.frame = self.equipmentImageView.frame
equipmentImageView.addSubview(darkFilter)
}
My problem is that this view will not have the same width with the ImageView, so the effect I'm trying to implement takes half of the screen.
I followed this solution but the problem is that this solution will only apply after the cells recreate themselves by scrolling down and up again.
TableViewCell.swift
override func layoutSubviews() {
super.layoutSubviews()
darkFilter.backgroundColor = .black
darkFilter.layer.opacity = 0.6
darkFilter.frame = self.equipmentImageView.frame
equipmentImageView.addSubview(darkFilter)
}
How can I apply this solution before the cells are created and will not redraw when scrolling through it?
You could set the cell's .background color to .black, and then lower the alpha of the equipmentImageView and you'd achieve the same darkened effect without having to add a new UIView.
EDIT
To illustrate my point: here's a really sloppy and quick example I drew up in Playground with an image titled "Untitled.jpg" to prove fading the image with a black cell background works the same as adding a faded black layer on top of a cell with an image:
Picture of result:
code:
//: A UIKit based Playground for presenting user interface
import UIKit
import PlaygroundSupport
class CustomTableViewCell: UITableViewCell {
let myImageView: UIImageView = {
let imageView = UIImageView()
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFill
if let sample = Bundle.main.path(forResource: "Untitled", ofType: "jpg") {
let image = UIImage(contentsOfFile: sample)
imageView.image = image
}
imageView.clipsToBounds = true
return imageView
}()
let blackView: UIView = {
let myView = UIView()
myView.translatesAutoresizingMaskIntoConstraints = false
myView.backgroundColor = .black
return myView
}()
let title: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.textColor = .white
label.font = UIFont.systemFont(ofSize: 12)
return label
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
backgroundColor = .black
let views = [myImageView,
blackView,
title]
views.forEach {
contentView.addSubview($0)
NSLayoutConstraint.activate([
$0.heightAnchor.constraint(equalTo: contentView.heightAnchor),
$0.widthAnchor.constraint(equalTo: contentView.widthAnchor),
$0.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
$0.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
])
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setCellViews(indexPath: IndexPath) {
blackView.alpha = 0
if indexPath.row % 2 == 0 {
if indexPath.row % 4 == 0 {
blackView.alpha = 0.4
title.text = "blackLayer added and faded"
} else {
backgroundColor = .black
myImageView.alpha = 0.6
title.text = "backView faded"
}
} else {
title.text = "not faded at all"
}
}
}
private let reuseId = "cellId"
class MyViewController : UIViewController {
let tableView: UITableView = {
let tableView = UITableView()
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.separatorStyle = .none
return tableView
}()
let data = ["one", "two", "three", "four", "five"]
override func viewDidLoad() {
super.viewDidLoad()
setTableView()
}
func setTableView() {
tableView.dataSource = self
tableView.delegate = self
tableView.register(CustomTableViewCell.self, forCellReuseIdentifier: reuseId)
view.addSubview(tableView)
NSLayoutConstraint.activate([
tableView.heightAnchor.constraint(equalTo: view.heightAnchor),
tableView.widthAnchor.constraint(equalTo: view.widthAnchor),
tableView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
tableView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
])
}
}
extension MyViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: reuseId, for: indexPath) as! CustomTableViewCell
cell.setCellViews(indexPath: indexPath)
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 50
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()
2 suggestions:
1) Since you are dealing with IBOutlets for the cell, you could also add the view on the IB on top of the image view, and then adding constrains to it to match up the image view size.
2) If you are aiming to adding it programmatically, you might need to setup its constraints based on the cell content view. For example:
darkFilter.backgroundColor = .black
darkFilter.layer.opacity = 0.6
contentView.addSubview(darkFilter)
darkFilter.translatesAutoresizingMaskIntoConstraints = false
darkFilter.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
darkFilter.leftAnchor.constraint(equalTo: contentView.leftAnchor).isActive = true
darkFilter.rightAnchor.constraint(equalTo: contentView.rightAnchor).isActive = true
darkFilter.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true

Updating auto layout after changing an object's frame size

I am creating a view using auto layout and the result is this.
After the view loads, then I grab some text data and fill the UITextView's. The "About me" item can be multiple lines, so I resize that specific frame. Then, I get the following.
You see how the about me textview is covering the next field? How can I resize the auto layout with the new about me textview size? I searched and found some suggestions to use setNeedsLayout and layoutIfNeeded, but none worked.
I am setting up the auto layout like the following:
inputsContainerView.addSubview(ageInput)
ageInput.leftAnchor.constraint(equalTo: inputsContainerView.leftAnchor).isActive = true
ageInput.topAnchor.constraint(equalTo: inputsContainerView.topAnchor, constant: 10).isActive = true
ageInput.widthAnchor.constraint(equalTo: inputsContainerView.widthAnchor).isActive = true
ageInput.heightAnchor.constraint(equalToConstant: 60).isActive = true
inputsContainerView.addSubview(genderInput)
genderInput.leftAnchor.constraint(equalTo: inputsContainerView.leftAnchor).isActive = true
genderInput.topAnchor.constraint(equalTo: ageInput.bottomAnchor).isActive = true
genderInput.widthAnchor.constraint(equalTo: inputsContainerView.widthAnchor).isActive = true
genderInput.heightAnchor.constraint(equalToConstant: 60).isActive = true
inputsContainerView.addSubview(aboutInput)
aboutInput.leftAnchor.constraint(equalTo: inputsContainerView.leftAnchor).isActive = true
aboutInput.topAnchor.constraint(equalTo: genderInput.bottomAnchor).isActive = true
aboutInput.widthAnchor.constraint(equalTo: inputsContainerView.widthAnchor).isActive = true
aboutInput.heightAnchor.constraint(equalToConstant: 60).isActive = true
inputsContainerView.addSubview(memberSinceInput)
memberSinceInput.leftAnchor.constraint(equalTo: inputsContainerView.leftAnchor).isActive = true
memberSinceInput.topAnchor.constraint(equalTo: aboutInput.bottomAnchor).isActive = true
memberSinceInput.widthAnchor.constraint(equalTo: inputsContainerView.widthAnchor).isActive = true
memberSinceInput.heightAnchor.constraint(equalToConstant: 60).isActive = true
After the view loads, I fetch data and resize the about me textview's frame using the following function:
func resizeTextView(_ textView: UITextView) {
let fixedWidth = textView.frame.size.width
textView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.greatestFiniteMagnitude))
let newSize = textView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.greatestFiniteMagnitude))
var newFrame = textView.frame
newFrame.size = CGSize(width: max(newSize.width, fixedWidth), height: newSize.height)
textView.frame = newFrame
}
If I were you, I'd use UITableView to create this form and add the label and UITextView inside the cells.
You could do a UITableViewCell like below where I set the height of label to 60 and auto layout it. UITextView also using the auto layout and fitting the cell's bottom.
import UIKit
class UserDetailCell: UITableViewCell {
var userDetailLabel : UILabel = {
var label = UILabel()
label.numberOfLines = 0
label.translatesAutoresizingMaskIntoConstraints = false
label.textColor = UIColor.lightGray
return label
}()
var userDetailTextView : UITextView = {
var tv = UITextView()
tv.translatesAutoresizingMaskIntoConstraints = false
tv.isScrollEnabled = false
return tv
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupUI()
}
func setupUI(){
addSubview(userDetailLabel)
NSLayoutConstraint.activate([
userDetailLabel.topAnchor.constraint(equalTo: topAnchor),
userDetailLabel.leftAnchor.constraint(equalTo: leftAnchor, constant: 8),
userDetailLabel.rightAnchor.constraint(equalTo: rightAnchor),
userDetailLabel.heightAnchor.constraint(equalToConstant: 60)
])
addSubview(userDetailTextView)
NSLayoutConstraint.activate([
userDetailTextView.topAnchor.constraint(equalTo: userDetailLabel.bottomAnchor),
userDetailTextView.leftAnchor.constraint(equalTo: leftAnchor, constant: 8),
userDetailTextView.rightAnchor.constraint(equalTo: rightAnchor),
userDetailTextView.bottomAnchor.constraint(equalTo: bottomAnchor)
])
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Then your UIViewController should be like below. I'm setting a delegate to UITextView inside the cellForRowAt indexPath method. Since I set the delegate, the textViewDidChange delegate method will be called. It is written inside an extension.
import UIKit
class UserDetailsController: UITableViewController {
let cellId = "cell"
var person = Person(myAge: 20, myGender: "Male", aboutMe: "Hello my name is jake waisee. What is your name? goayngeHello my name is jake waisee. What is your name? goayngeHello my name is jake waisee. What is your name? goayngeHello my name is jake waisee. What is your name? goaynge")
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UserDetailCell.self, forCellReuseIdentifier: cellId)
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 100
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return 3
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! UserDetailCell
cell.userDetailTextView.delegate = self
cell.userDetailTextView.tag = indexPath.row
if indexPath.row == 0{
cell.userDetailLabel.text = "Age"
cell.userDetailTextView.text = "\(person.age)"
}else if indexPath.row == 1{
cell.userDetailLabel.text = "Gender"
cell.userDetailTextView.text = person.gender
}else if indexPath.row == 2{
cell.userDetailLabel.text = "About me"
cell.userDetailTextView.text = person.aboutMe
}
return cell
}
}
extension UserDetailsController: UITextViewDelegate {
func textViewDidChange(_ textView: UITextView) {
print(textView.text)
if textView.tag == 2 {
person.aboutMe = textView.text
}else if textView.tag == 0 {
person.age = Int(textView.text) ?? 0
}else if textView.tag == 1 {
person.gender = textView.text
}
//this will keep the textview growing as we type
tableView.beginUpdates()
tableView.endUpdates()
}
}
Hope this helps you out. Your UI should look something like below.