Setting CollectionView cell height based on number of buttons - ios

I have a collection view that will have a UILabel and between 2-5 UIButtons.
I want the cell to size to how many buttons that are visible according to each cell. I know that each button is about a 100 in height.
class myViewController: UIViewController {
var myCollectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
let layout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 100, right: 0) // add spacing to the bottom
layout.itemSize = CGSize(width: self.view.frame.width, height: 300)
layout.scrollDirection = .vertical
layout.minimumLineSpacing = 20
layout.minimumInteritemSpacing = 20
myCollectionView=UICollectionView(frame: CGRect(x: 0, y: 0, width: self.view.frame.width, height: self.view.frame.height), collectionViewLayout: layout)
myCollectionView.delegate=self
myCollectionView.dataSource=self
myCollectionView.register(MyCollectionViewCell.self, forCellWithReuseIdentifier: "Cell")
myCollectionView.alwaysBounceVertical = false
myCollectionView.showsVerticalScrollIndicator = false
myCollectionView.translatesAutoresizingMaskIntoConstraints=false
myCollectionView.backgroundColor=UIColor.white
myCollectionView.isPagingEnabled = false
loadViews()
}
func loadViews() {
self.view.addSubview(myCollectionView)
myCollectionView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive=true
myCollectionView.leftAnchor.constraint(equalTo: self.view.leftAnchor).isActive=true
myCollectionView.rightAnchor.constraint(equalTo: self.view.rightAnchor).isActive=true
myCollectionView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive=true
}
}
Note that the above code block has layout.itemSize = CGSize(width: self.view.frame.width, height: 300) which works great for 3 buttons (3*100 = 300).
Then when setting up my cell class I create my buttons and then determine their visibility based off of a variable (at the bottom of this).
class MyCollectionViewCell: UICollectionViewCell {
var btn1: UIButton!
var btn2: UIButton!
var btn3: UIButton!
var btn4: UIButton!
var btn5: UIButton!
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
func setupViews() {
addSubview(lblQue)
lblQue.topAnchor.constraint(equalTo: self.topAnchor, constant: 30).isActive=true
lblQue.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 12).isActive=true
lblQue.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -12).isActive=true
lblQue.heightAnchor.constraint(equalToConstant: 50).isActive=true
let btnWidth: CGFloat = 650
let btnHeight: CGFloat = 65
btn1 = getButton(tag: 0)
addSubview(btn1)
NSLayoutConstraint.activate([btn1.topAnchor.constraint(equalTo: lblQue.bottomAnchor, constant: 10), btn1.leftAnchor.constraint(equalTo: self.centerXAnchor, constant: -300), btn1.widthAnchor.constraint(equalToConstant: btnWidth), btn1.heightAnchor.constraint(equalToConstant: btnHeight)])
btn1.addTarget(self, action: #selector(btnOptionAction), for: .touchUpInside)
btn2 = getButton(tag: 1)
addSubview(btn2)
NSLayoutConstraint.activate([btn2.topAnchor.constraint(equalTo: btn1.bottomAnchor, constant: 10), btn2.leftAnchor.constraint(equalTo: self.centerXAnchor, constant: -300), btn2.widthAnchor.constraint(equalToConstant: btnWidth), btn2.heightAnchor.constraint(equalToConstant: btnHeight)])
btn2.addTarget(self, action: #selector(btnOptionAction), for: .touchUpInside)
btn3 = getButton(tag: 2)
addSubview(btn3)
NSLayoutConstraint.activate([btn3.topAnchor.constraint(equalTo: btn2.bottomAnchor, constant: 10), btn3.leftAnchor.constraint(equalTo: self.centerXAnchor, constant: -300), btn3.widthAnchor.constraint(equalToConstant: btnWidth), btn3.heightAnchor.constraint(equalToConstant: btnHeight)])
btn3.addTarget(self, action: #selector(btnOptionAction), for: .touchUpInside)
btn4 = getButton(tag: 3)
addSubview(btn4)
NSLayoutConstraint.activate([btn4.topAnchor.constraint(equalTo: btn3.bottomAnchor, constant: 10), btn4.leftAnchor.constraint(equalTo: self.centerXAnchor, constant: -300), btn4.widthAnchor.constraint(equalToConstant: btnWidth), btn4.heightAnchor.constraint(equalToConstant: btnHeight)])
btn4.addTarget(self, action: #selector(btnOptionAction), for: .touchUpInside)
btn5 = getButton(tag: 4)
addSubview(btn5)
NSLayoutConstraint.activate([btn5.topAnchor.constraint(equalTo: btn4.bottomAnchor, constant: 10), btn5.leftAnchor.constraint(equalTo: self.centerXAnchor, constant: -300), btn5.widthAnchor.constraint(equalToConstant: btnWidth), btn5.heightAnchor.constraint(equalToConstant: btnHeight)])
btn5.addTarget(self, action: #selector(btnOptionAction), for: .touchUpInside)
}
func getButton(tag: Int) -> UIButton {
let btn=UIButton()
btn.tag=tag
btn.setTitle("Option", for: .normal)
btn.setTitleColor(UIColor.black, for: .normal)
btn.backgroundColor=UIColor.white
btn.layer.borderWidth=1
btn.layer.borderColor=UIColor.darkGray.cgColor
btn.layer.cornerRadius=5
btn.clipsToBounds=true
btn.translatesAutoresizingMaskIntoConstraints=false
return btn
}
let lblQue: UILabel = {
let lbl=UILabel()
lbl.text="This is a question and you have to answer it?"
lbl.textColor=UIColor.black
lbl.textAlignment = .center
lbl.font = UIFont.systemFont(ofSize: 20)
lbl.numberOfLines=4
lbl.translatesAutoresizingMaskIntoConstraints=false
return lbl
}()
var myVariable: MyClassIMade? {
didSet {
// go through and determine button Text and Visibility of each button
// i.e.
// if 1>0 {
// btn3.visible = false
// } else {
// btn3.visible = true
// }
}
}
So how can I determine how many buttons are visible to determine my size of cell for each section?

How about something like this ? just get all the buttons inside an array and then loop through them to check which button are hidden and which are not. you could use
.isHidden to hide and display them and then check which is hidden and return the number..
Example function :
func getButtonsCount(buttons: [UIButton]) -> Int{
//A counter to get how many button are not hidden
var count = Int()
//A Loop to check which buttons are hidden and increment the counter
for button in buttons {
if button.isHidden == true {
count += 1
}
}
return count
}

Here is how I ended up solving it:
I used sizeForItemAt which overrides all else and allowed me to get my cell for each row/usecase.
Basically, I can access all of the controls tied to my cell and determine their visibility and alter the height according to that.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! QuizCollectionViewCell
var countOfButtonsNotHidden = 0
if !cell.btn1.isHidden {
countOfButtonsNotHidden += 1
}
if !cell.btn2.isHidden {
countOfButtonsNotHidden += 1
}
if !cell.btn3.isHidden {
countOfButtonsNotHidden += 1
}
if !cell.btn4.isHidden {
countOfButtonsNotHidden += 1
}
if !cell.btn5.isHidden {
countOfButtonsNotHidden += 1
}
return CGSize(width: self.view.frame.width, height: CGFloat(countOfButtonsNotHidden * 100))
}

Use a UIStackView to layout your buttons. When you set the buttons to .isHidden the stack view will automatically deal with the layout of the buttons.
Example:
let stackView = UIStackView(arrangedSubviews: [button1, button2, button3, button4, button5])
stackView.axis = .vertical
stackView.alignment = .fill
stackView.distribution = .equalSpacing
stackView.spacing = 10
Next, you can calculate the height of the collection view cell by multiplying the number of visible buttons with your set button height.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "YourCellIdentifier", for: indexPath) as! YourCellClass
let buttonHeight: CGFloat = 45
let numberOfVisibleButtons = 0
if !cell.button1.isHidden { numberOfVisibleButtons += 1 }
if !cell.button2.isHidden { numberOfVisibleButtons += 1 }
if !cell.button3.isHidden { numberOfVisibleButtons += 1 }
if !cell.button4.isHidden { numberOfVisibleButtons += 1 }
if !cell.button5.isHidden { numberOfVisibleButtons += 1 }
let cellHeight: CGFloat = (buttonHeight + cell.stackView.spacing) * numberOfVisibleButtons
return CGSize(width: yourCellWidth, height: cellHeight)
}

Related

How can I change the constraints so that it was like on the second screenshot?

Please can you help me to change constraints in code to take an elements like in the second screen.
enter image description here
enter image description here
class DetailsHomeViewController: UIViewController {
var images:[String] = ["label","label","label"]
let MainImageView: UIImageView = {
let theImageView = UIImageView()
theImageView.image = UIImage(named: "label.png")
theImageView.translatesAutoresizingMaskIntoConstraints = false
return theImageView
}()
let someImageView: UIImageView = {
let theImageView = UIImageView()
theImageView.backgroundColor = .white
theImageView.translatesAutoresizingMaskIntoConstraints = false
theImageView.isUserInteractionEnabled = true
return theImageView
}()
lazy var collectionView:UICollectionView = {
let layout = UICollectionViewFlowLayout()
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.dataSource = self
cv.delegate = self
cv.register(ImageCell.self, forCellWithReuseIdentifier: ImageCell.identifier)
cv.translatesAutoresizingMaskIntoConstraints = false
return cv
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(MainImageView)
view.addSubview(someImageView)
someImageView.addSubview(collectionView)
someImageViewConstraints()
MainImageViewConstraints()
view.backgroundColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
setupViews()
mysetupViews()
}
private func setupViews() {
someImageViewConstraints()
}
func someImageViewConstraints() {
someImageView.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: view.frame.width/2)
someImageView.center = view.center
let itemHeight = 120
let padding = 25
let width = (itemHeight * 3) + padding
collectionView.frame = CGRect(x: Int(view.center.x) - (width / 2),
y: Int(someImageView.frame.height) - (itemHeight + padding),
width: width, height: itemHeight)
}
private func mysetupViews() {
createCustomNavigationBar()
let RightButton = createCustomButton(
imageName: "square.and.arrow.up",
selector: #selector(RightButtonTapped)
)
let customTitleView = createCustomTitleView(
detailsName: "Label"
)
navigationItem.rightBarButtonItems = [RightButton]
navigationItem.titleView = customTitleView
}
#objc private func RightButtonTapped() {
print("RightButtonTapped")
}
func MainImageViewConstraints() {
MainImageView.translatesAutoresizingMaskIntoConstraints = false
} }
extension DetailsHomeViewController:UICollectionViewDataSource , UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return images.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ImageCell.identifier, for: indexPath) as! ImageCell
cell.someImageView.image = UIImage(named: images[indexPath.row])
return cell
}
} extension DetailsHomeViewController:UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.height, height: collectionView.frame.height)
}
}
class ImageCell:UICollectionViewCell {
static let identifier = "ImageCell"
override var isSelected: Bool {
didSet {
self.someImageView.layer.borderColor = isSelected ? UIColor.green.cgColor : UIColor.clear.cgColor
self.someImageView.layer.borderWidth = 5
}
}
let someImageView: UIImageView = {
let theImageView = UIImageView()
theImageView.clipsToBounds = true
return theImageView
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.layer.cornerRadius = 8
self.clipsToBounds = true
addSubview(someImageView)
someImageView.frame = self.bounds
}
override func layoutSubviews() {
super.layoutSubviews()
//someImageView.frame = self.frame
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
You really need to go through a bunch of good auto-layout tutorials, but here is a quick general idea...
We'll constrain the "main image view" 12-points from the top, 40-points from each side, and give it a 5:3 ratio.
Then we'll add a UIStackView (with 12-point spacing) below it, constrained 12-points from the bottom of the main image view, 75% of the width, centered horizontally.
Each image view that we add to the stack view will be constrained to a 1:2 ratio (square).
Here's sample code to do that:
class DetailsHomeViewController: UIViewController {
var images:[String] = ["label", "label", "label"]
let mainImageView: UIImageView = {
let theImageView = UIImageView()
theImageView.contentMode = .scaleAspectFill
theImageView.clipsToBounds = true
theImageView.layer.cornerRadius = 24.0
return theImageView
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
setupViews()
mysetupViews()
}
private func setupViews() {
// a stack view to hold the "thumbnail" image views
let stackView = UIStackView()
stackView.spacing = 12
mainImageView.translatesAutoresizingMaskIntoConstraints = false
stackView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(mainImageView)
view.addSubview(stackView)
// respect the safe area
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// main image view - let's go with
// Top with 12-points "padding"
mainImageView.topAnchor.constraint(equalTo: g.topAnchor, constant: 12.0),
// Leading and Trailing with 40-points "padding"
mainImageView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
mainImageView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -40.0),
// height equal to width x 3/5ths (5:3 aspect ratio)
mainImageView.heightAnchor.constraint(equalTo: mainImageView.widthAnchor, multiplier: 3.0 / 5.0),
// stack view
// Top 12-points from main image view Bottom
stackView.topAnchor.constraint(equalTo: mainImageView.bottomAnchor, constant: 12.0),
// width equal to 75% of the main image view width
stackView.widthAnchor.constraint(equalTo: mainImageView.widthAnchor, multiplier: 0.75),
// centered horizontally
stackView.centerXAnchor.constraint(equalTo: mainImageView.centerXAnchor),
])
// make sure we can load the main image
if let img = UIImage(named: images[0]) {
mainImageView.image = img
}
// now we'll add 3 image views to the stack view
images.forEach { imgName in
let thumbView = UIImageView()
thumbView.contentMode = .scaleAspectFill
thumbView.clipsToBounds = true
thumbView.layer.cornerRadius = 16.0
// we want them to be square
thumbView.heightAnchor.constraint(equalTo: thumbView.widthAnchor).isActive = true
// make sure we can load the image
if let img = UIImage(named: imgName) {
thumbView.image = img
}
stackView.addArrangedSubview(thumbView)
}
}
private func mysetupViews() {
createCustomNavigationBar()
let RightButton = createCustomButton(
imageName: "square.and.arrow.up",
selector: #selector(RightButtonTapped)
)
let customTitleView = createCustomTitleView(
detailsName: "Label"
)
navigationItem.rightBarButtonItems = [RightButton]
navigationItem.titleView = customTitleView
}
#objc private func RightButtonTapped() {
print("RightButtonTapped")
}
}

How to UICollectionReusableView stretch to Safe Area

I am trying to show HeaderView in my UICollectionView class and I use UICollectionReusableView class for that.
Actually I am showing HeaderView in my CollectionView but It does not reach to safe area.
I use auto layout programmatically and I wrote extension to do that.
Here is my class and extensions that I use in my code:
import Foundation
import UIKit
private let headerIdentifer = "HeaderCell"
class ProfileController: UICollectionViewController {
//MARK: - Properties
//MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
configureProfileCollectionView()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.navigationBar.isHidden = true
}
//MARK: - Helpers
func configureProfileCollectionView() {
collectionView.backgroundColor = .white
collectionView.register(HeaderView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: headerIdentifer)
}
}
extension ProfileController {
override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: headerIdentifer, for: indexPath) as! HeaderView
return header
}
}
extension ProfileController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
return CGSize(width: view.frame.width, height: 300)
}
}
import Foundation
import UIKit
class HeaderView: UICollectionReusableView {
//MARK: - Properties
private lazy var containerView: UIView = {
let view = UIView()
view.backgroundColor = .systemBlue
view.addSubview(backButton)
backButton.anchor(top: view.topAnchor, left: view.leftAnchor,
paddingTop: 42, paddingLeft: 16)
backButton.setDimensions(width: 30, height: 30)
return view
}()
private lazy var backButton: UIButton = {
let button = UIButton(type: .system)
button.setImage(UIImage(named: "baseline_arrow_back_white_24dp")?.withRenderingMode(.alwaysOriginal), for: .normal)
button.addTarget(self, action: #selector(backButtonTapped), for: .touchUpInside)
return button
}()
//MARK: - Lifecyle
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(containerView)
containerView.anchor(top: topAnchor, left: leftAnchor, right: rightAnchor, height: 108)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
//MARK: - Selectors
#objc func backButtonTapped() {
}
}
import UIKit
extension UIView {
func anchor(top: NSLayoutYAxisAnchor? = nil,
left: NSLayoutXAxisAnchor? = nil,
bottom: NSLayoutYAxisAnchor? = nil,
right: NSLayoutXAxisAnchor? = nil,
paddingTop: CGFloat = 0,
paddingLeft: CGFloat = 0,
paddingBottom: CGFloat = 0,
paddingRight: CGFloat = 0,
width: CGFloat? = nil,
height: CGFloat? = nil) {
translatesAutoresizingMaskIntoConstraints = false
if let top = top {
topAnchor.constraint(equalTo: top, constant: paddingTop).isActive = true
}
if let left = left {
leftAnchor.constraint(equalTo: left, constant: paddingLeft).isActive = true
}
if let bottom = bottom {
bottomAnchor.constraint(equalTo: bottom, constant: -paddingBottom).isActive = true
}
if let right = right {
rightAnchor.constraint(equalTo: right, constant: -paddingRight).isActive = true
}
if let width = width {
widthAnchor.constraint(equalToConstant: width).isActive = true
}
if let height = height {
heightAnchor.constraint(equalToConstant: height).isActive = true
}
}
func center(inView view: UIView, yConstant: CGFloat? = 0) {
translatesAutoresizingMaskIntoConstraints = false
centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: yConstant!).isActive = true
}
func centerX(inView view: UIView, topAnchor: NSLayoutYAxisAnchor? = nil, paddingTop: CGFloat? = 0) {
translatesAutoresizingMaskIntoConstraints = false
centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
if let topAnchor = topAnchor {
self.topAnchor.constraint(equalTo: topAnchor, constant: paddingTop!).isActive = true
}
}
func centerY(inView view: UIView, leftAnchor: NSLayoutXAxisAnchor? = nil, paddingLeft: CGFloat? = nil, constant: CGFloat? = 0) {
translatesAutoresizingMaskIntoConstraints = false
centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: constant!).isActive = true
if let leftAnchor = leftAnchor, let padding = paddingLeft {
self.leftAnchor.constraint(equalTo: leftAnchor, constant: padding).isActive = true
}
}
func setDimensions(width: CGFloat, height: CGFloat) {
translatesAutoresizingMaskIntoConstraints = false
widthAnchor.constraint(equalToConstant: width).isActive = true
heightAnchor.constraint(equalToConstant: height).isActive = true
}
func addConstraintsToFillView(_ view: UIView) {
translatesAutoresizingMaskIntoConstraints = false
anchor(top: view.topAnchor, left: view.leftAnchor,
bottom: view.bottomAnchor, right: view.rightAnchor)
}
}
The UICollectionReusableView is set up fine I believe and I think an automatic inset is applied to the UICollectionView because of the safe area.
Try one of the two options below in your viewDidLoad and it might work
// Option 1
// Get the inset applied at the top and negate the applied inset
if let top
= UIApplication.shared.windows.first?.safeAreaInsets.top
{
collectionView.contentInset.top = -top
}
// Option 2
// Request the collection view to ignore the default behavior
// to add an inset for safe area
collectionView.contentInsetAdjustmentBehavior = .never
More simple solution. No need to write any code!!!

UIImageView not resizing as circle and UILabel not resizing within StackView and Custom Collection Cell

I am trying to resize my UIImageView as a circle, however; every time I try to resize the UIImageView, which is inside a StackView along with the UILabel, I keep on ending up with a more rectangular shape. Can someone show me where I am going wrong I have been stuck on this for days? Below is my code, and what it's trying to do is add the image with the label at the bottom, and the image is supposed to be round, and this is supposed to be for my collection view controller.
custom collection view cell
import UIKit
class CarerCollectionViewCell: UICollectionViewCell {
static let identifier = "CarerCollectionViewCell"
private let imageView: UIImageView = {
let imageView = UIImageView()
imageView.frame = CGRect(x: 0, y: 0, width: 20, height: 20);
//imageView.center = imageView.superview!.center;
imageView.contentMode = .scaleAspectFill
imageView.layer.borderWidth = 4
imageView.layer.masksToBounds = false
imageView.layer.borderColor = UIColor.orange.cgColor
imageView.layer.cornerRadius = imageView.frame.height / 2
return imageView
}()
private let carerNamelabel: UILabel = {
let carerNamelabel = UILabel()
carerNamelabel.layer.masksToBounds = false
carerNamelabel.font = .systemFont(ofSize: 12)
carerNamelabel.textAlignment = .center
carerNamelabel.layer.frame = CGRect(x: 0, y: 0, width: 50, height: 50);
return carerNamelabel
}()
private let stackView: UIStackView = {
let stackView = UIStackView()
stackView.layer.masksToBounds = false
stackView.axis = .vertical
stackView.alignment = .center
stackView.backgroundColor = .systemOrange
stackView.distribution = .fillProportionally
stackView.translatesAutoresizingMaskIntoConstraints = false
return stackView
}()
override init(frame: CGRect) {
super.init(frame: frame)
configureContentView()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
private func configureContentView() {
imageView.clipsToBounds = true
stackView.clipsToBounds = true
carerNamelabel.clipsToBounds = true
contentView.addSubview(stackView)
configureStackView()
}
private func configureStackView() {
allContraints()
stackView.addArrangedSubview(imageView)
stackView.addArrangedSubview(carerNamelabel)
}
private func allContraints() {
setStackViewConstraint()
}
private func setStackViewConstraint() {
stackView.widthAnchor.constraint(equalTo: contentView.widthAnchor).isActive = true
stackView.heightAnchor.constraint(equalTo: contentView.heightAnchor).isActive = true
stackView.centerXAnchor.constraint(equalTo: contentView.centerXAnchor).isActive = true
stackView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
}
public func configureImage(with imageName: String, andImageName labelName: String) {
imageView.image = UIImage(named: imageName)
carerNamelabel.text = labelName
}
override func layoutSubviews() {
super.layoutSubviews()
stackView.frame = contentView.bounds
}
override func prepareForReuse() {
super.prepareForReuse()
imageView.image = nil
carerNamelabel.text = nil
}
}
Below here is my code for the CollectionViewControler
custom collection view
import UIKit
class CarerViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
private var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .vertical
layout.itemSize = CGSize(width: 120, height: 120)
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.register(CarerCollectionViewCell.self, forCellWithReuseIdentifier: CarerCollectionViewCell.identifier)
collectionView.showsVerticalScrollIndicator = false
collectionView.backgroundColor = .clear
return collectionView
}()
override func viewDidLoad() {
super.viewDidLoad()
collectionView.delegate = self
collectionView.dataSource = self
view.addSubview(collectionView)
collectionView.translatesAutoresizingMaskIntoConstraints = false
// Layout constraints for `collectionView`
NSLayoutConstraint.activate([
collectionView.widthAnchor.constraint(equalTo: view.widthAnchor),
collectionView.heightAnchor.constraint(lessThanOrEqualTo: view.heightAnchor, constant: 600),
collectionView.topAnchor.constraint(equalTo: view.topAnchor, constant: 200),
collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor)
])
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CarerCollectionViewCell.identifier, for: indexPath) as! CarerCollectionViewCell
cell.configureImage(with: "m7opt04g_ms-dhoni-afp_625x300_06_July_20", andImageName: "IMAGE NO. 1")
return cell
}
}
Can somebody show or point to me what I am doing wrong, thank you
This is what I am trying to achieve
UIStackView could be tricky sometimes, you can embed your UIImageView in a UIView, and move your layout code to viewWillLayoutSubviews(), this also implys for the UILabel embed that also inside a UIView, so the containers UIViews will have a static frame for the UIStackViewto be layout correctly and whats inside them will only affect itself.

iOS UICollectionView background around cell

I have screen which look like:
As you see there is UICollectionView with image background. Background should be whiter than original image. But cells of collection view have to be transparent and display original part of image. Does somebody have idea how to it?
Update
Sample of background image
https://i.stack.imgur.com/h9Qp4.jpg
The basic idea will be:
add a background imageView behind the collectionView
configure your cells to have a translucent background with a transparent rectangular "hole"
configure the collectionView to exactly fit two columns with no spacing between the cells
handle variable "padding" on the cells so the outer portion matches the cell / row size
Here is an example:
class SeeThruCollectionViewCell: UICollectionViewCell {
static let identifier = "seeThruCellIdentifier"
var theLabel: UILabel = {
let v = UILabel()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .clear
v.textColor = .white
v.shadowColor = .black
v.shadowOffset = CGSize(width: 1, height: 1)
v.font = UIFont.boldSystemFont(ofSize: 17)
return v
}()
var overlayPath: UIBezierPath!
var transparentPath: UIBezierPath!
var fillLayer: CAShapeLayer!
var padding: CGRect!
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() -> Void {
padding = CGRect.zero
// init and add the sublayer we'll use as a mask
fillLayer = CAShapeLayer()
layer.addSublayer(fillLayer)
// add a label
addSubview(theLabel)
// center the label
NSLayoutConstraint.activate([
theLabel.centerXAnchor.constraint(equalTo: centerXAnchor, constant: 0.0),
theLabel.centerYAnchor.constraint(equalTo: centerYAnchor, constant: 0.0),
])
}
override func layoutSubviews() {
super.layoutSubviews()
// overlayPath and transparentPath will combine to
// create a translucent mask with a transparent rect in the middle
overlayPath = UIBezierPath(rect: bounds)
var r = bounds
r.origin.x += padding.origin.x
r.origin.y += padding.origin.y
r.size.width -= r.origin.x + padding.size.width
r.size.height -= r.origin.y + padding.size.height
transparentPath = UIBezierPath(rect: r)
overlayPath.append(transparentPath)
overlayPath.usesEvenOddFillRule = true
fillLayer.path = overlayPath.cgPath
fillLayer.fillRule = kCAFillRuleEvenOdd
fillLayer.fillColor = UIColor(white: 1.0, alpha: 0.75).cgColor
}
}
class SeeThruViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
var theCollectionView: UICollectionView = {
let v = UICollectionView(frame: CGRect.zero, collectionViewLayout: UICollectionViewFlowLayout())
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .clear
return v
}()
var theBackgroundImageView: UIImageView = {
let v = UIImageView()
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
// translucent padding for cells
let pad = CGFloat(12)
let numItems = 10
let numSections = 1
override func viewDidLoad() {
super.viewDidLoad()
// add background image view
view.addSubview(theBackgroundImageView)
// add collection view view
view.addSubview(theCollectionView)
if let img = UIImage(named: "cvBKG") {
theBackgroundImageView.image = img
}
let guide = view.safeAreaLayoutGuide
// constrain background image view and collection view to same frames
NSLayoutConstraint.activate([
theBackgroundImageView.topAnchor.constraint(equalTo: guide.topAnchor, constant: 0.0),
theBackgroundImageView.bottomAnchor.constraint(equalTo: guide.bottomAnchor, constant: 0.0),
theBackgroundImageView.leadingAnchor.constraint(equalTo: guide.leadingAnchor, constant: 0.0),
theBackgroundImageView.trailingAnchor.constraint(equalTo: guide.trailingAnchor, constant: 0.0),
theCollectionView.topAnchor.constraint(equalTo: theBackgroundImageView.topAnchor, constant: 0.0),
theCollectionView.bottomAnchor.constraint(equalTo: theBackgroundImageView.bottomAnchor, constant: 0.0),
theCollectionView.leadingAnchor.constraint(equalTo: theBackgroundImageView.leadingAnchor, constant: 0.0),
theCollectionView.trailingAnchor.constraint(equalTo: theBackgroundImageView.trailingAnchor, constant: 0.0),
])
// normal collection view tasks
theCollectionView.register(SeeThruCollectionViewCell.self, forCellWithReuseIdentifier: SeeThruCollectionViewCell.identifier)
theCollectionView.dataSource = self
theCollectionView.delegate = self
// we want ZERO spacing between the cells
if let flow = theCollectionView.collectionViewLayout as? UICollectionViewFlowLayout {
flow.minimumLineSpacing = 0
flow.minimumInteritemSpacing = 0
}
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// set cell size to fill exactly two "columns"
if let flow = theCollectionView.collectionViewLayout as? UICollectionViewFlowLayout {
flow.itemSize = CGSize(width: theCollectionView.frame.size.width / 2.0, height: theCollectionView.frame.size.width / 2.0)
}
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return numSections
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return numItems
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: SeeThruCollectionViewCell.identifier, for: indexPath) as! SeeThruCollectionViewCell
cell.theLabel.text = "Cell: \(indexPath.item)"
var padRect = CGRect(x: pad, y: pad, width: pad, height: pad)
if indexPath.item <= 1 {
// if it's the first row
// add paddingn on the top
padRect.origin.y = pad * 2
}
if indexPath.item % 2 == 0 {
// if it's the left "column"
// add padding on the left
padRect.origin.x = pad * 2
} else {
// if it's the right "column"
// add padding on the right
padRect.size.width = pad * 2
}
if indexPath.item / 2 >= (numItems / 2) - 1 {
// if it's the last row
// add paddingn on the bottom
padRect.size.height = pad * 2
}
cell.padding = padRect
return cell
}
}
Name your background image cvNKG.png and add it to your project's Assets, then create a new UIViewController and assign its class to SeeThruViewController. You should be able to run this as-is.
Result:
Note: this is a starting point. You'll (obviously) need to add labels for your needs, and you'll want to handle cases such as an odd number of items or if the number of rows don't fill the screen.

Problems with complex UITableViewCell

I'm trying to implement a custom complex UITableViewCell. My data source is relatively simple, but I could have some multiple elements.
class Element: NSObject {
var id: String
var titles: [String]
var value: String
init(id: String, titles: [String], value: String) {
self.id = id
self.titles = titles
self.value = value
}
}
I have an array of elements [Element] and, as you can see, for each element titles could have multiple string values. I must use the following layouts:
My first approach was to implement a dynamic UITableViewCell, trying to add content inside self.contentView at runtime. Everything is working, but it's not so fine and as you can see, reusability is not handled in the right way. Lag is terrible.
import UIKit
class ElementTableCell: UITableViewCell {
var titles: [String]!
var value: String!
var width: CGFloat!
var titleViewWidth: CGFloat!
var cellHeight: Int!
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:)")
}
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.selectionStyle = .none
}
func drawLayout() {
titleViewWidth = (width * 2)/3
cellHeight = 46 * titles.count
for i in 0 ..< titles.count {
let view = initTitleView(title: titles[i], width: titleViewWidth, yPosition: CGFloat(cellHeight * i))
self.contentView.addSubview(view)
}
self.contentView.addSubview(initButton())
}
func initTitleView(title: String, width: CGFloat, yPosition: CGFloat) -> UIView {
let titleView: UILabel = UILabel(frame:CGRect(x:0, y:Int(yPosition), width: Int(width), height: 45))
titleView.text = title
return titleView
}
func initButton(value: String) -> UIButton {
let button = UIButton(frame:CGRect(x: 0, y: 0, width: 70, height:34))
button.setTitle(value, for: .normal)
button.center.x = titleViewWidth + ((width * 1)/3)/2
button.center.y = CGFloat(cellHeight/2)
return priceButton
}
}
And the UITableView delegate method:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = ElementTableCell(style: .default, reuseIdentifier: "ElementTableCell")
cell.width = self.view.frame.size.width
cell.titles = elements[indexPath.row].titles
cel.value = elements[indexPath.row].value
cell.drawLayout()
return cell
}
Now I'm thinking about a total different approach, such as using a UITableView Section for each element in elements array and a UITableViewCell for each title in titles. It could work, but I'm concerned about the right button.
Do you have any suggestion or other approach to share?
I solved changing application UI logic in order to overcome the problem. Thank you all.
Here's some code you can play with. It should work just be creating a new UITableView in a Storyboard and assigning it to BoxedTableViewController in this file...
//
// BoxedTableViewController.swift
//
import UIKit
class BoxedCell: UITableViewCell {
var theStackView: UIStackView!
var containingView: UIView!
var theButton: UIButton!
var brdColor = UIColor(white: 0.7, alpha: 1.0)
// "spacer" view is just a 1-pt tall UIView used as a horizontal-line between labels
// when there is more than one title label
func getSpacer() -> UIView {
let newView = UIView(frame: CGRect(x: 0, y: 0, width: 40, height: 1))
newView.backgroundColor = brdColor
newView.translatesAutoresizingMaskIntoConstraints = false
newView.heightAnchor.constraint(equalToConstant: 1.0).isActive = true
return newView
}
// "label view" is a UIView containing on UILabel
// embedding the label in a view allows for convenient borders and insets
func getLabelView(text: String, position: Int) -> UIView {
let v = UIView()
v.translatesAutoresizingMaskIntoConstraints = false
let newLabel = UILabel()
newLabel.font = UIFont.systemFont(ofSize: 15.0)
newLabel.backgroundColor = UIColor(white: 0.8, alpha: 1.0)
newLabel.textColor = .black
newLabel.layer.borderWidth = 1
newLabel.layer.borderColor = brdColor.cgColor
newLabel.numberOfLines = 0
newLabel.text = text
newLabel.translatesAutoresizingMaskIntoConstraints = false
v.addSubview(newLabel)
newLabel.leadingAnchor.constraint(equalTo: v.leadingAnchor, constant: 8.0).isActive = true
newLabel.trailingAnchor.constraint(equalTo: v.trailingAnchor, constant: -8.0).isActive = true
var iTop: CGFloat = 0.0
var iBot: CGFloat = 0.0
// the passed "position" tells me whether this label is:
// a Single Title only
// the first Title of more than one
// the last Title of more than one
// or a Title with a Title above and below
// so we can set up proper top/bottom padding
switch position {
case 0:
iTop = 16.0
iBot = 16.0
break
case 1:
iTop = 12.0
iBot = 8.0
break
case -1:
iTop = 8.0
iBot = 12.0
break
default:
iTop = 8.0
iBot = 8.0
break
}
newLabel.topAnchor.constraint(equalTo: v.topAnchor, constant: iTop).isActive = true
newLabel.bottomAnchor.constraint(equalTo: v.bottomAnchor, constant: -iBot).isActive = true
return v
}
func setupThisCell(rowNumber: Int) -> Void {
// if containingView is nil, it hasn't been created yet
// so, create it + Stack view + Button
// else
// don't create new ones
// This way, we don't keep adding more and more views to the cell on reuse
if containingView == nil {
containingView = UIView()
containingView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(containingView)
containingView.layer.borderWidth = 1
containingView.layer.borderColor = brdColor.cgColor
containingView.backgroundColor = .white
containingView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 8.0).isActive = true
containingView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -8.0).isActive = true
containingView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 6.0).isActive = true
containingView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -6.0).isActive = true
theStackView = UIStackView()
theStackView.translatesAutoresizingMaskIntoConstraints = false
containingView.addSubview(theStackView)
theStackView.axis = .vertical
theStackView.spacing = 4.0
theStackView.alignment = .fill
theStackView.distribution = .fill
theButton = UIButton(type: .custom)
theButton.translatesAutoresizingMaskIntoConstraints = false
containingView.addSubview(theButton)
theButton.backgroundColor = .blue
theButton.setTitleColor(.white, for: .normal)
theButton.setTitle("The Button", for: .normal)
theButton.setContentHuggingPriority(1000, for: .horizontal)
theButton.centerYAnchor.constraint(equalTo: containingView.centerYAnchor, constant: 0.0).isActive = true
theButton.trailingAnchor.constraint(equalTo: containingView.trailingAnchor, constant: -8.0).isActive = true
theStackView.topAnchor.constraint(equalTo: containingView.topAnchor, constant: 0.0).isActive = true
theStackView.bottomAnchor.constraint(equalTo: containingView.bottomAnchor, constant: 0.0).isActive = true
theStackView.leadingAnchor.constraint(equalTo: containingView.leadingAnchor, constant: 0.0).isActive = true
theStackView.trailingAnchor.constraint(equalTo: theButton.leadingAnchor, constant: -8.0).isActive = true
}
// remove all previously added Title labels and spacer views
for v in theStackView.arrangedSubviews {
v.removeFromSuperview()
}
// setup 1 to 5 Titles
let n = rowNumber % 5 + 1
// create new Title Label views and, if needed, spacer views
// and add them to the Stack view
if n == 1 {
let aLabel = getLabelView(text: "Only one title for row: \(rowNumber)", position: 0)
theStackView.addArrangedSubview(aLabel)
} else {
for i in 1..<n {
let aLabel = getLabelView(text: "Title number \(i)\n for row: \(rowNumber)", position: i)
theStackView.addArrangedSubview(aLabel)
let aSpacer = getSpacer()
theStackView.addArrangedSubview(aSpacer)
}
let aLabel = getLabelView(text: "Title number \(n)\n for row: \(rowNumber)", position: -1)
theStackView.addArrangedSubview(aLabel)
}
}
}
class BoxedTableViewController: UITableViewController {
let cellID = "boxedCell"
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(BoxedCell.self, forCellReuseIdentifier: cellID)
tableView.estimatedRowHeight = 100
tableView.rowHeight = UITableViewAutomaticDimension
self.tableView.contentInset = UIEdgeInsets(top: 20, left: 0, bottom: 0, right: 0)
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1250
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellID, for: indexPath) as! BoxedCell
// Configure the cell...
cell.setupThisCell(rowNumber: indexPath.row)
return cell
}
}
I'll check back if you run into any problems with it (gotta run, and haven't fully tested it yet -- and ran out of time to comment it - ugh).
You can also use tableview as tableviecell and adjust cell accordingly.
u need to layout cell in func layoutsubviews after set data to label and imageview;
Yes, split ElementTableCell to section with header and cells is much better approach. In this case you have no need to create constraints or dealing with complex manual layout. This would make your code simple and make scrolling smooth.
The button you use can be easily moved to the reusable header view
Is you still want to keep it in one complete cell, where is a way to draw manually the dynamic elements, such as titles and separators lines. Manually drawing is faster as usual. Or remove all views from cell.contentView each time you adding new. But this way is much more complicated.
Greate article about how to make UITableView appearence swmoth:
Perfect smooth scrolling in UITableViews

Resources