how to add delegate method into stack view. please - ios

I'm a junior developer in South Korea.
first of all, my English skill is not so good. I'm sorry.
I hava a question about UIViewController Delegate method
I just want to write featureList from UIViewController to UICollectionViewCell using FeatureDetailViewController
here my code
final class AppViewController: UIViewController {
private var featureList: [Feature] = []
private let scrollView = UIScrollView()
private let contentView = UIView()
private lazy var stackView: UIStackView = {
let stackView = UIStackView()
stackView.axis = .vertical
stackView.distribution = .equalSpacing
stackView.spacing = 0.0
let featureSectionView = FeatureSectionView(frame: .zero)
let rankingFeatureSectionView = RankingFeatureSectionView(frame: .zero)
let exchangeCodeButtonView = ExchangeCodeButtonView(frame: .zero)
let spacingView = UIView()
spacingView.snp.makeConstraints {
$0.height.equalTo(100.0)
}
[
featureSectionView,
rankingFeatureSectionView,
exchangeCodeButtonView,
spacingView
].forEach {
stackView.addArrangedSubview($0)
}
return stackView
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(contentView)
setupNavigationController()
setupLayout()
}
}
private extension AppViewController {
func setupNavigationController() {
navigationItem.title = "앱"
navigationItem.largeTitleDisplayMode = .always
navigationController?.navigationBar.prefersLargeTitles = true
}
func setupLayout() {
view.addSubview(scrollView)
scrollView.snp.makeConstraints {
$0.top.equalTo(view.safeAreaLayoutGuide.snp.top)
$0.bottom.leading.trailing.equalToSuperview()
}
scrollView.addSubview(contentView)
contentView.snp.makeConstraints {
$0.edges.equalToSuperview()
$0.width.equalToSuperview()
}
contentView.addSubview(stackView)
stackView.snp.makeConstraints {
$0.edges.equalToSuperview()
}
}
}
I just want to present this FeatureDetailViewController when imageview is tapped which is in FeatureSectionCollectionViewCell
here is FeatureSectionCollectionViewCell
final class FeatureSectionCollectionViewCell: UICollectionViewCell {
private lazy var typeLabel: UILabel = {
let label = UILabel()
label.textColor = .systemBlue
label.font = .systemFont(ofSize: 12.0, weight: .semibold)
return label
}()
private lazy var appNameLabel: UILabel = {
let label = UILabel()
label.textColor = .label
label.font = .systemFont(ofSize: 20.0, weight: .bold)
return label
}()
private lazy var descriptionLabel: UILabel = {
let label = UILabel()
label.textColor = .secondaryLabel
label.font = .systemFont(ofSize: 16.0, weight: .semibold)
return label
}()
private lazy var imageview: UIImageView = {
let imageView = UIImageView()
imageView.layer.cornerRadius = 7.0
imageView.layer.borderWidth = 0.5
imageView.clipsToBounds = true
return imageView
}()
func setup(feature: Feature) {
setupLayout()
typeLabel.text = feature.type
appNameLabel.text = feature.appName
descriptionLabel.text = feature.description
imageview.backgroundColor = .lightGray
if let imageURL = URL(string: feature.imageURL) {
imageview.kf.setImage(with: imageURL)
}
}
}
private extension FeatureSectionCollectionViewCell {
func setupLayout() {
[
typeLabel,
appNameLabel,
descriptionLabel,
imageview
].forEach {
addSubview($0)
}
}
}
and UIView named as FeatureSectionView below
final class FeatureSectionView: UIView {
//data from plist
private var featureList: [Feature] = []
private lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.delegate = self
collectionView.dataSource = self
collectionView.isPagingEnabled = true
collectionView.backgroundColor = .systemBackground
collectionView.showsHorizontalScrollIndicator = false
collectionView.register(
FeatureSectionCollectionViewCell.self,
forCellWithReuseIdentifier: "FeatureSectionCollectionViewCell"
)
return collectionView
}()
private let separatorView = SeparatorView(frame: .zero)
//FeatureSectionView이 UIView 이므로 ViewDidLoad에서 하지 않고 init 에서 실행
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
fetchData() //reloadData after read data
collectionView.reloadData()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension FeatureSectionView: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return featureList.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "FeatureSectionCollectionViewCell", for: indexPath) as? FeatureSectionCollectionViewCell
let feature = featureList[indexPath.row]
cell?.setup(feature: feature)
return cell ?? UICollectionViewCell()
}
}
extension FeatureSectionView : UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
CGSize(width: collectionView.frame.width - 32.0, height: frame.width)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
UIEdgeInsets(top: 0.0, left: 16.0, bottom: 0.0, right: 16.0)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 32.0
}
}
private extension FeatureSectionView {
func setupViews() {
[
collectionView,
separatorView
].forEach {
addSubview($0)
}
collectionView.snp.makeConstraints {
$0.leading.trailing.bottom.equalToSuperview()
$0.top.equalToSuperview().inset(16.0)
$0.height.equalTo(snp.width)
}
separatorView.snp.makeConstraints {
$0.leading.trailing.bottom.equalToSuperview()
$0.top.equalTo(collectionView.snp.bottom).offset(16.0)
}
}
func fetchData() {
guard let url = Bundle.main.url(forResource: "Feature", withExtension: "plist") else { return }
do {
let data = try Data(contentsOf: url)
let result = try PropertyListDecoder().decode([Feature].self, from: data)
featureList = result
} catch { }
}
}
HOW TO SET DELEGATE METHOD to presentation FeatureDetailViewController when imageview was tapped ?
I know it's hard to read, but I almost half-day-spent. please teach me guys.
thx

The delegate method would be useful if the user taps the cell in general; however, you want it to transition only when the imageView in the cell is tapped--from what I am understanding.
So, what you need to do is add a tapGestureRecognizer onto your imageView and perform the transition with a selector.
First create a method of the functionality that you want (this will be the selector)
#objc func imageWasTapped(tapGestureRecognizer: UITapGestureRecognizer) {
DispatchQueue.main.async {
//perform transition here
}
}
Add a tapGestureRecognizer to the imageView
private lazy var imageview: UIImageView = {
let imageView = UIImageView()
imageView.layer.cornerRadius = 7.0
imageView.layer.borderWidth = 0.5
imageView.clipsToBounds = true
//Make sure it can be touched
imageView.isUserInteractionEnabled = true
//Create the Gesture Recognizer and initialize it with the selector
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(imageWasTapped(tapGestureRecognizer:)))
//add the Gesture Recognizer to the view
imageView.addGestureRecognizer(tapGestureRecognizer)
return imageView
}()

Related

SubViews are not adding in some UICollectionViewCells and flickering (programmatically)

I am trying to make custom Image Slider with collections view. I want to it to be reusable. So I made separate custom class where all collectionView stuff. and then call that class from UIViewController as shown in code below. And my UICollectonViewCell only contains imageView.
Problem is that in second cell. imageView is not being added and on third cell, it also flickers. I tried to debug these issues but could not.
ImageSlider class and UICollectionViewCell class at end end, with collection view stuff:
class ImageSlider: UIView {
var imgArr = [UIImage(named: "one.jpg"), UIImage(named: "3.jpg"), UIImage(named: "two.jpg"), UIImage(named: "4.jpg")]
var sliderCollection : UICollectionView = {
let widthRatio : Float = 16.0
let heightRatio : Float = 9.0
let collecionWidth = UIScreen.main.bounds.width - 30
let layout = UICollectionViewFlowLayout()
let collectionView = UICollectionView(frame: CGRect(x: 15, y: 100, width: collecionWidth, height: CGFloat((Float(collecionWidth)*heightRatio)/widthRatio)), collectionViewLayout: layout)
layout.scrollDirection = .horizontal
layout.minimumLineSpacing = 0
layout.minimumInteritemSpacing = 0
collectionView.backgroundColor = .systemOrange
collectionView.isPagingEnabled = true
// collectionView.isScrollEnabled = true
collectionView.register(ImageSliderCell.self, forCellWithReuseIdentifier: "ImageSliderCell")
return collectionView
}()
}
extension ImageSlider: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return imgArr.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ImageSliderCell", for: indexPath) as! ImageSliderCell
cell.imgView.image = imgArr[indexPath.item]
// cell.imgView.contentMode = .scaleAspectFit
print("cell frame : ", "(\(cell.frame.width), \(cell.frame.height)")
print("imgView frame : ", "(\(cell.imgView.frame.width), \(cell.imgView.frame.height)")
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.width, height: collectionView.frame.height)
}
}
class ImageSliderCell: UICollectionViewCell {
var imgView = UIImageView()
// override func awakeFromNib() {
// self.addSubview(imgView)
// }
override init(frame: CGRect) {
super.init(frame: frame)
imgView.frame = frame
self.addSubview(imgView)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
This is ViewController, where I am calling ImageSlider() class.
class ImageSliderVC: UIViewController, UICollectionViewDelegate {
let imageSlider = ImageSlider()
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(imageSlider.sliderCollection)
imageSlider.sliderCollection.delegate = imageSlider
imageSlider.sliderCollection.dataSource = imageSlider
imageSlider.sliderCollection.reloadData()
}
}
It looks like it does not work without constrains because UICollectionViewCell could be created with zero frame and it translated to imageView inside the cell. You need to add constrains to imageView to make it visible.
extension UIView {
func centerX(inView view: UIView, constant: CGFloat = 0) {
translatesAutoresizingMaskIntoConstraints = false
centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: constant).isActive = true
}
func centerY(inView view: UIView, constant: CGFloat = 0) {
translatesAutoresizingMaskIntoConstraints = false
centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: constant).isActive = true
}
func setDimensions(height: CGFloat, width: CGFloat) {
translatesAutoresizingMaskIntoConstraints = false
heightAnchor.constraint(equalToConstant: height).isActive = true
widthAnchor.constraint(equalToConstant: width).isActive = true
}
func setHeight(_ height: CGFloat) {
translatesAutoresizingMaskIntoConstraints = false
heightAnchor.constraint(equalToConstant: height).isActive = true
}
}
class ImageSliderCell: UICollectionViewCell {
var imgView = UIImageView()
override init(frame: CGRect) {
super.init(frame: frame)
self.addSubview(imgView)
// not sure about the right size of image ...
imgView.setDimensions(height: 100.0, width: 100.0)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

Create dynamic collectionView inside tableview cell

I'm trying to create a horizontal collection view inside a tableviewcell, with a dynamic size uiimageview inside, which will be the mandatory view.
I created a UIView :
public class CardImage: UIView {
private var imageView: UIImageView?
private var titleLabel: UILabel?
private var descriptionLabel: UILabel?
private var subtitleLabel: UILabel?
private var icon: UIImage?
private var imageURL: String?
private var screenPercentage: CGFloat = 1.0
override public func layoutSubviews() {
super.layoutSubviews()
layer.cornerRadius = 4
}
public override func awakeFromNib() {
super.awakeFromNib()
setup()
layoutSubviews()
}
public init() {
super.init(frame: .zero)
setup()
layoutSubviews()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
layoutSubviews()
}
public func fill(dto: SomeDTO) {
setupImage(icon: dto.image, imageUrl: dto.imageURL)
descriptionLabel?.text = dto.description
titleLabel?.text = dto.title
subtitleLabel?.text = dto.subtitle
screenPercentage = dto.screenPercentage
}
private func setup() {
isUserInteractionEnabled = true
translatesAutoresizingMaskIntoConstraints = false
backgroundColor = .red
titleLabel = UILabel()
titleLabel?.textColor = .white
titleLabel?.numberOfLines = 1
addSubview(titleLabel!)
descriptionLabel = UILabel()
descriptionLabel?.textColor = .white
descriptionLabel?.numberOfLines = 2
addSubview(descriptionLabel!)
subtitleLabel = UILabel()
subtitleLabel?.textColor = .white
subtitleLabel?.numberOfLines = 1
addSubview(subtitleLabel!)
imageView = UIImageView(frame: .zero)
imageView?.backgroundColor = .red
addSubview(imageView!)
setupConstraints()
}
private func setupImage(icon: UIImage?, imageUrl: String?) {
if let url = imageURL {
return
}
guard let image = icon else {
return
}
imageView?.image = image
imageView?.contentMode = .scaleAspectFit
setNeedsDisplay()
setNeedsLayout()
}
private func setupConstraints() {
imageView?.translatesAutoresizingMaskIntoConstraints = false
imageView?.topAnchor.constraint(equalTo: topAnchor).isActive = true
imageView?.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
imageView?.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
let screenSize = UIScreen.main.bounds
let computedWidth = screenSize.width * screenPercentage
imageView?.widthAnchor.constraint(equalToConstant: computedWidth).isActive = true
//the image should be 16:9
imageView?.heightAnchor.constraint(equalTo: widthAnchor, multiplier: 9.0/16.0).isActive = true
titleLabel?.translatesAutoresizingMaskIntoConstraints = false
titleLabel?.topAnchor.constraint(equalTo: imageView!.bottomAnchor, constant: 16).isActive = true
titleLabel?.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16).isActive = true
titleLabel?.heightAnchor.constraint(equalToConstant: 18).isActive = true
subtitleLabel?.translatesAutoresizingMaskIntoConstraints = false
subtitleLabel?.topAnchor.constraint(equalTo: titleLabel!.topAnchor).isActive = true
subtitleLabel?.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16).isActive = true
titleLabel?.widthAnchor.constraint(equalTo: subtitleLabel!.widthAnchor).isActive = true
titleLabel?.trailingAnchor.constraint(equalTo: subtitleLabel!.leadingAnchor, constant: 6).isActive = true
descriptionLabel?.translatesAutoresizingMaskIntoConstraints = false
descriptionLabel?.topAnchor.constraint(equalTo: titleLabel!.bottomAnchor, constant: 16).isActive = true
descriptionLabel?.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16).isActive = true
descriptionLabel?.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16).isActive = true
descriptionLabel?.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -16).isActive = true
}
}
Which will be inside a CollectionViewCell:
public class CardImageCollectionViewCell: UICollectionViewCell {
private let view: CardImage = CardImage()
override public func awakeFromNib() {
super.awakeFromNib()
translatesAutoresizingMaskIntoConstraints = false
//this only pin the view to the four anchors of the uicollectionview
view.pinToBounds(of: self.contentView)
backgroundColor = .clear
AccessibilityManager.setup(self, log: true)
}
public func fill(dto: SomeDTO) {
view.fill(dto: dto)
}
}
And then the CollectionViewCell, inside a tableviewcell, which has a collectionview:
public class CardImageCollectionView: UIView, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
#IBOutlet private weak var collectionView: UICollectionView!
private var dto: [SomeDTO] = []
public override func awakeFromNib() {
super.awakeFromNib()
setup()
}
private func setup() {
backgroundColor = .blue
collectionView.backgroundColor = .clear
collectionView.delegate = self
collectionView.dataSource = self
if let flowLayout = collectionView?.collectionViewLayout as? UICollectionViewFlowLayout {
flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
}
registerCells()
}
func fill(dto: [SomaImageCardDTO]) {
self.dto = dto
DispatchQueue.main.async {
self.collectionView.reloadData()
self.collectionView.layoutIfNeeded()
}
}
private func registerCells() {
collectionView.register(UINib(nibName: String(describing: CardImageCollectionViewCell.self), bundle: nil), forCellWithReuseIdentifier: String(describing: CardImageCollectionViewCell.self))
}
public func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return dto.count
}
//this should not be setted since the sizing is automatic
// public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
// return CGSize(width: 304, height: 238)
// }
//
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: CardImageCollectionViewCell.self), for: indexPath) as! CardImageCollectionViewCell
cell.fill(dto: dto[indexPath.row])
return cell
}
}
The two problems I'm facing is, the image cannot assume any size, since the collection view doesn't have any size until it's filled with some info.
And then, even if I set the image size, I cannot pass the info to the UITableViewcell size.
If I understand your problem correctly, you have more than one rows in an UITableView which have horizontal UICollectionViews. Those collection views have cells which have dynamic size based on the images inside them.
So the width of the UITableView is fixed, but the height for the row depends on the height of the UICollectionView, correct?
I would recommend using self-sizing cells in the UITableView. See the reference here:
https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/AutolayoutPG/WorkingwithSelf-SizingTableViewCells.html
After that, you need to find a way to calculate the height of the UICollectionView correctly, so the UITableView cell can determine the correct height. There are several ways to do it, for example by overriding the intrinsicContentSize property of the UICollectionView and returning the largest height of the images.

Making animating horizontal indicator using collectionView

I'm working on App where I have to manage my horizontal indicator on CollectionviewCell, while scrolling CollectionView and by selecting any Cell.
This is what I’m looking for(Just focus on Horizontal CollectionView)
As I have implemented this But I’m not getting the exact functionality/behavior AND unable to stick horizontal indicator on collectionviewCell while scrolling. I can only stick if i make a horizontal indicator in CollectionViewCell But in this Case I’m unable to apply sliding animation.
This is what I have implemented
Here is my Code Snippet for MENUBAR
import UIKit
class MenuBar: UITableViewHeaderFooterView, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, UIScrollViewDelegate {
//MARK:- Properties
let cellId = "cellId"
let menuNames = ["Recommeded", "Popular", "Free", "Trending", "Love Songs", " Free Songs"]
var horizontalBarLeftAnchorConstraint : NSLayoutConstraint?
lazy var collectionView : UICollectionView = {
let cv = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
cv.translatesAutoresizingMaskIntoConstraints = false
cv.dataSource = self
cv.delegate = self
cv.showsHorizontalScrollIndicator = false
cv.backgroundColor = UIColor.clear
return cv
}()
let horizontalView : UIView = {
let v = UIView()
v.backgroundColor = UIColor.red
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
//MARK:- default methods
override init(reuseIdentifier: String?) {
super.init(reuseIdentifier: reuseIdentifier)
setupCollectionView()
setupHorizontalBar()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
//MARK:- Functions
private func setupCollectionView() {
if let flowLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
flowLayout.scrollDirection = .horizontal
}
addSubview(collectionView)
collectionView.register(MenuCell.self, forCellWithReuseIdentifier: cellId)
collectionView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
collectionView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
collectionView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
collectionView.topAnchor.constraint(equalTo: topAnchor).isActive = true
}
private func setupHorizontalBar() {
addSubview(horizontalView)
let textSize = (menuNames[0] as NSString).size(withAttributes: nil)
let cellSize = textSize.width + 50
let indicatorLineWith = 25/2
let x = (cellSize/2) - CGFloat(indicatorLineWith)
//x,y,w,h
horizontalBarLeftAnchorConstraint =
horizontalView.leftAnchor.constraint(equalTo: leftAnchor, constant: x )
horizontalBarLeftAnchorConstraint?.isActive = true
horizontalView.heightAnchor.constraint(equalToConstant: 5).isActive = true
horizontalView.widthAnchor.constraint(equalToConstant: 25).isActive = true
horizontalView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
}
//MARK:- CollectionView methods
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return menuNames.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! MenuCell
cell.menuName = menuNames[indexPath.row]
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let size = (menuNames[indexPath.row] as NSString).size(withAttributes: nil)
return CGSize(width: (size.width + 50), height: frame.height)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
//getting Cell size on screen
let attributes : UICollectionViewLayoutAttributes = collectionView.layoutAttributesForItem(at: indexPath)!
let indicatorSize = 25/2
let cellRect = attributes.frame
let cellFrameInSuperView = collectionView.convert(cellRect, to: collectionView)
let textSize = (menuNames[indexPath.row] as NSString).size(withAttributes: nil)
let cellSize = textSize.width + 50
let x = (CGFloat(cellFrameInSuperView.origin.x) + (cellSize/2)) - CGFloat(indicatorSize)
horizontalBarLeftAnchorConstraint?.constant = x
UIView.animate(withDuration: 0.75, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
collectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
self.layoutIfNeeded()
}, completion: nil)
}
}
Here is my code snippet for MENUCELL:-
import UIKit
//MARK:- CollectionViewBaseCell
class CollectionViewBaseCell : UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupViews() {}
}
//MARK:- MenuCell
class MenuCell : CollectionViewBaseCell {
//MARK:- Properties
var menuName : String? {
didSet {
label.text = menuName
}
}
let label : UILabel = {
let lbl = UILabel()
lbl.translatesAutoresizingMaskIntoConstraints = false
lbl.text = "Label"
return lbl
}()
//MARK:- default methods
override func setupViews() {
addSubview(label)
//x,y,w,h Constraint
label.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
label.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
}
}
TRY THIS:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView == self.collectionView{
print( scrollView.contentOffset.x ) // use this contentOffset
}
}

Cells won't show in UICollectionView

I'm trying to create a UICollectionView and display few cells there.
This is my code:
class MainVC: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
var mForecast = [CustomCollectionViewCell]()
let CVCellIdentifier = "forecastCell"
lazy var mCollectionView: UICollectionView = {
var collectionView = UICollectionView(frame: CGRect(x: 0, y: 0, width: 300, height: 150), collectionViewLayout: UICollectionViewFlowLayout())
collectionView.clipsToBounds = true
collectionView.backgroundColor = .red
collectionView.translatesAutoresizingMaskIntoConstraints = false
return collectionView
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor(red: 80/255, green: 135/255, blue: 179/255, alpha: 1.0)
setupNavBar()
self.navigationItem.searchController = mSearchBarController
setupMainWeatherIcon()
fillArrayWithData()
mCollectionView.register(CustomCollectionViewCell.self, forCellWithReuseIdentifier: CVCellIdentifier)
mCollectionView.dataSource = self
mCollectionView.delegate = self
}
private func fillArrayWithData(){
for _ in 1...6 {
let forecastCell: ForecastCell = ForecastCell()
forecastCell.mDayLabel = "DAY-TEST"
forecastCell.mWeatherIcon = UIImage(named: "partly-cloudy")
forecastCell.mTempLabel = "TEMP-TEST"
mForecast.append(forecastCell)
}
mCollectionView.reloadData()
}
//MARK: COLLECTION VIEW METHODS
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return mForecast.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = mCollectionView.dequeueReusableCell(withReuseIdentifier: CVCellIdentifier, for: indexPath) as! CustomCollectionViewCell
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: (view.frame.width / 6) - 16 , height: 70)
}
func collectionView(_ collectionView: UICollectionView, layout
collectionViewLayout: UICollectionViewLayout, insetForSectionAt
section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: 10, left: 8, bottom: 10, right: 8)
}
}
This is the CustomCollectionViewCell class:
import UIKit
class CustomCollectionViewCell: UICollectionViewCell {
var mDayLabel: String?
var mWeatherIcon: UIImage?
var mTempLabel: String?
let dayTV: UILabel = {
var label = UILabel()
label.textAlignment = .center
label.font = UIFont.boldSystemFont(ofSize: 12)
label.textColor = .blue
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
let weatherImg: UIImageView = {
var img = UIImageView()
img.translatesAutoresizingMaskIntoConstraints = false
return img
}()
let tempLabel: UILabel = {
var label = UILabel()
label.textAlignment = .center
label.font = UIFont.systemFont(ofSize: 8)
label.textColor = .blue
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
if let label = mDayLabel{
dayTV.text = label
}
if let image = mWeatherIcon{
weatherImg.image = image
}
if let temp = mTempLabel{
tempLabel.text = temp
}
setupDayTextView()
setupWeatherImage()
setupTempLabel()
}
private func setupDayTextView(){
addSubview(dayTV)
dayTV.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
dayTV.topAnchor.constraint(equalTo: self.topAnchor, constant: 10).isActive = true
}
private func setupWeatherImage(){
addSubview(weatherImg)
weatherImg.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
weatherImg.topAnchor.constraint(equalTo: dayTV.bottomAnchor, constant: 10).isActive = true
}
private func setupTempLabel(){
addSubview(tempLabel)
tempLabel.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
tempLabel.topAnchor.constraint(equalTo: weatherImg.bottomAnchor, constant: 10).isActive = true
}
}
Of course I have a method to fill mForecast array with data, if I add a subview I do see the cell, but I know I shouldn't do that to view the cells inside the collection view.
I tried looking but couldn't find what's wrong here and why the cells won't be displayed.
This is what I get
ORIGINAL:
After setting the delegate and the datasource, you need to call collectionView.reloadData()
REVISED:
You are calling fillArrayWithData, which calls reloadData before you finish configuring the collectionView's datasource and delegate. Thus, when reloadData is called, there is no source that sets the data and loads the cells.
Try calling your fillArrayWithData after you finalize the configuration of your collection view.
I personally recommend configuring your collection view in viewDidLoad or in the didSet property observer of collectionView:
var collectionView: UICollectionView! {
didSet {
collectionView.delegate = self
collectionView.dataSource = self
}
}
And then I initiate the load of the data in my viewWillAppear method.
EXAMPLE:
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor(red: 80/255, green: 135/255, blue: 179/255, alpha: 1.0)
setupNavBar()
self.navigationItem.searchController = mSearchBarController
setupMainWeatherIcon()
// This is where you are calling fillArrayWithData right now.
// fillArrayWithData()
mCollectionView.register(CustomCollectionViewCell.self, forCellWithReuseIdentifier: CVCellIdentifier)
mCollectionView.dataSource = self
mCollectionView.delegate = self
// This is where you should be calling fillArrayWithData
fillArrayWithData()
}

How to Track UICollectionView index

I want a variable in my code that keeps track of the index of my UICollectionView, but I can't get it to work. After some troubleshooting, I've boiled down the code to the following, which if pasted into an empty viewController should work since no storyboard is involved. The animated gif illustrates the problem. Initially my variable "selectedItem" is equal to the UICollectionView Cell text which reflects the data = [0,1,2,3], but then when I swipe right, it immediately becomes off by 1. Then it stays off by 1 until at the last cell where it matches again. The pattern repeats when going in reverse. Thanks for any help --
import UIKit
class CodeCollView2: UIViewController, UICollectionViewDataSource,UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
var data = [0,1,2,3] //["0", "1", "2", "3" ]
let cellId = "cellId2"
var selectedItem = 0
lazy var cView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.minimumLineSpacing = 0
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.isPagingEnabled = true
cv.dataSource = self
cv.delegate = self
return cv
}()
var indexLabel: UILabel = {
let label = UILabel()
label.text = ""
label.font = UIFont.systemFont(ofSize: 30)
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
setupViews()
}
func setupViews() {
cView.register(CCell2.self, forCellWithReuseIdentifier: cellId)
view.addSubview(cView)
cView.translatesAutoresizingMaskIntoConstraints = false
cView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
cView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
cView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
cView.heightAnchor.constraint(equalToConstant: 200).isActive = true
view.addSubview(indexLabel)
indexLabel.translatesAutoresizingMaskIntoConstraints = false
indexLabel.bottomAnchor.constraint(equalTo: cView.topAnchor).isActive = true
indexLabel.centerXAnchor.constraint(equalTo: cView.centerXAnchor).isActive = true
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return data.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! CCell2
selectedItem = indexPath.item
indexLabel.text = "seletedItem = \(selectedItem)"
cell.itemValue = data[selectedItem]
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return collectionView.frame.size
}
}
//============== CVCell ==================
class CCell2: UICollectionViewCell {
var itemValue: Int? {
didSet {
if let val = itemValue {
itemLabel.text = "\(val)"
}
}
}
var itemLabel: UILabel = {
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 100)
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .lightGray
addSubview(itemLabel)
itemLabel.translatesAutoresizingMaskIntoConstraints = false
itemLabel.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
itemLabel.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
As Nikita's answer mentions, cellForItemAt is called when a cell is going to be shown, even if you only see a bit of it and go back to the previous one, so you shouldn't use to decided what cell is at the centre.
scrollViewDidScroll is the right way of tracking which cell you have at the centre, and you can print what index you are on with something like this:
func scrollViewDidScroll(_ scrollView:UIScrollView)
{
let midX:CGFloat = scrollView.bounds.midX
let midY:CGFloat = scrollView.bounds.midY
let point:CGPoint = CGPoint(x:midX, y:midY)
guard
let indexPath:IndexPath = collectionView.indexPathForItem(at:point)
else
{
return
}
let currentPage:Int = indexPath.item
indexLabel.text = "seletedItem = \(currentPage)"
}
Tracking the selected item in the 'cellForItemAt' is not a good idea. I would suggest you to track it in the scrollViewDidScroll delegate method of the UIScrollViewDelegate.
Something like this should work:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let currentPage = cView.contentOffset.x / self.view.bounds.width
}

Resources