UICollectionViewCell message sent to deallocated instance right after being dequeued - ios

I have a UICollectionViewCell which is being dequeued for reuse. I recently introduced a sliding drawer functionality which is basically a hidden view which gets displayed when the user swipes.
After the last change this seemed to create a case where the instance was being deallocated while being initialized. The exact location where the Zombie was being reported was on the call to "addGestureRecognizers()" which was being called from setupSlidingDrawer() which was in turn being called from the init function.
I have attached an image of the break point before and after the Zombie call. I have been pulling my hair all day looking at this and keep drawing a blank.
As a work around (hack) I have made the deleteView a instance variable and that seems to keep the class from releasing as the retain count is not decremented.
I feel like like the code for my collection view cell / controller is pretty standard and I am not doing anything crazy.
This seems like it is a Apple bug but I can not seem to find anything on the inter webs to prove it. If
class MomentsViewCell : UICollectionViewCell, UIGestureRecognizerDelegate {
static let reuseIdentifier = "MomentsViewCell"
let imageView = UIImageView()
let activityIndicator = UIActivityIndicatorView()
let shareButton = UIButton()
var shareCallback: (() -> ())?
var isSwiped = false
let overlayViewTag = 999
var delegate: MomentsViewController?
let shareButtonLength: CGFloat = 44
lazy var drawerWidth: CGFloat = {
self.frame.width * 0.15
}()
override init(frame: CGRect) {
super.init(frame: frame)
contentView.addSubview(imageView)
imageView.contentMode = UIViewContentMode.ScaleAspectFit
imageView.snp_makeConstraints { (make) -> Void in
make.centerX.equalTo(self.contentView.snp_centerX)
make.width.equalTo(self.contentView.snp_width)
make.top.equalTo(self.snp_top)
make.bottom.equalTo(self.snp_bottom)
}
contentView.addSubview(activityIndicator)
activityIndicator.hidesWhenStopped = true
activityIndicator.snp_makeConstraints { (make) -> Void in
make.center.equalTo(self.snp_center)
}
let shareImage = UIImage(named: "share-moment")
shareButton.setImage(shareImage, forState: .Normal)
shareButton.addTarget(self, action: "didTapShareButton", forControlEvents: .TouchUpInside)
shareButton.enabled = false
shareButton.alpha = 0.0
imageView.addSubview(shareButton)
imageView.userInteractionEnabled = true
shareButton.snp_makeConstraints { (make) -> Void in
let size = CGSizeMake(shareButtonLength, shareButtonLength)
var offset = shareButtonLength / 2
if let imageLength = shareImage?.size.height {
offset = (shareButtonLength - imageLength) / 2
}
make.bottom.equalTo(self.imageView).multipliedBy(0.95).offset(offset)
make.right.equalTo(self.imageView).multipliedBy(0.95).offset(offset)
make.size.equalTo(size)
}
setupSlidingDrawer()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func prepareForReuse() {
super.prepareForReuse()
imageView.image = nil
exitDeleteMode()
}
func enableSharing() {
shareButton.enabled = true
shareButton.alpha = 1.0
}
func didTapShareButton() {
shareCallback?()
}
func setupSlidingDrawer() {
let deleteView = UIView(frame: CGRectMake(self.frame.width - drawerWidth, self.frame.origin.y, drawerWidth, self.frame.height))
deleteView.backgroundColor = UIColor.whiteColor()
let deleteGradientLayer = CAGradientLayer()
deleteGradientLayer.frame = deleteView.bounds
deleteGradientLayer.colors = [UIColor(hex: 0xE8557C).CGColor, UIColor(hex: 0xCC2B49).CGColor]
deleteView.layer.insertSublayer(deleteGradientLayer, atIndex: 0)
let trashButton = UIButton()
trashButton.setImage(UIImage(named: "trash_can"), forState: UIControlState.Normal)
trashButton.addTarget(self, action: Selector("didPressDelete"), forControlEvents: .TouchUpInside)
deleteView.addSubview(trashButton)
deleteView.bringSubviewToFront(trashButton)
trashButton.snp_makeConstraints(closure: { (make) -> Void in
make.center.equalTo(deleteView.snp_center)
make.width.equalTo(deleteView.snp_width).multipliedBy(0.354)
make.height.equalTo(deleteView.snp_height).multipliedBy(0.07)
})
contentView.addSubview(deleteView)
deleteView.snp_makeConstraints(closure: { (make) -> Void in
make.left.equalTo(self.imageView.snp_right)
make.height.equalTo(self.contentView.snp_height)
make.width.equalTo(drawerWidth)
})
addGestureRecognizers()
}
func addGestureRecognizers() {
let swipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: Selector("didSwipeCell:"))
swipeGestureRecognizer.delegate = self
swipeGestureRecognizer.direction = .Left
self.addGestureRecognizer(swipeGestureRecognizer)
}
class MomentsViewController: ContainerSubController, UICollectionViewDelegate, UICollectionViewDataSource {
var moments: [Moment] = []
let layout = UICollectionViewFlowLayout()
var collectionView = UICollectionView(frame: CGRectZero, collectionViewLayout: UICollectionViewFlowLayout())
let headerIdentifier = "headerCellIdentifier"
let headerLabelText = "Moments"
let headerCellHeight: CGFloat = 103.0
let noMomentsLabelText = "It's looking pretty empty in here! Assign a Moments action (Share Moment To Twitter) to a Hot Key to get started."
let noMomentsReuseIdentifier = "NoMoments"
var currentSwipedCellIndexPath: NSIndexPath?
override func viewDidLoad() {
super.viewDidLoad()
layout.minimumLineSpacing = 0
collectionView = UICollectionView(frame: view.frame, collectionViewLayout: layout)
collectionView.showsVerticalScrollIndicator = false
collectionView.backgroundColor = UIColor.clearColor()
collectionView.registerClass(MomentsViewCell.self, forCellWithReuseIdentifier: MomentsViewCell.reuseIdentifier)
collectionView.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: noMomentsReuseIdentifier)
collectionView.registerClass(UICollectionViewCell.self, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: headerIdentifier)
collectionView.delegate = self
collectionView.dataSource = self
view.backgroundColor = UIColor.blackColor()
view.addSubview(collectionView)
collectionView.snp_makeConstraints { (make) -> Void in
make.height.equalTo(view)
make.width.equalTo(view)
make.center.equalTo(view)
}
setupTransitionToMainButton()
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
refreshMoments()
collectionView.reloadData()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "handleMomentSavedNotifcation", name: MomentNotifcation.MomentSaved.rawValue, object: nil)
}
//MARK: UICollectionViewDataSource
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
guard let momentsViewCell = collectionView.dequeueReusableCellWithReuseIdentifier(MomentsViewCell.reuseIdentifier, forIndexPath: indexPath) as? MomentsViewCell, moment = moments[safe:indexPath.row] else {
return MomentsViewCell()
}

In your guard, make sure to return momentsViewCell, the one you dequeued before.
for example:
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
if let momentsViewCell = collectionView.dequeueReusableCellWithReuseIdentifier(MomentsViewCell.reuseIdentifier, forIndexPath: indexPath) as? MomentsViewCell {
return momentsViewCell
else {
return MomentsViewCell()
}
Edit
As I see the screenshots, the log is warning about optimizations. The whole assignment might not work because the variable is not used later on.

Related

how to add delegate method into stack view. please

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
}()

UITableView CustomCell Reuse (ImageView in CustomCell)

I'm pretty new to iOS dev and I have an issue with UITableViewCell.
I guess it is related to dequeuing reusable cell.
I added an UIImageView to my custom table view cell and also added a tap gesture to make like/unlike function (image changes from an empty heart(unlike) to a filled heart(like) as tapped and reverse). The problem is when I scroll down, some of the cells are automatically tapped. I found out why this is happening, but still don't know how to fix it appropriately.
Below are my codes,
ViewController
import UIKit
struct CellData {
var title: String
var done: Bool
}
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var models = [CellData]()
private let tableView: UITableView = {
let table = UITableView()
table.register(TableViewCell.self, forCellReuseIdentifier: TableViewCell.identifier)
return table
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(tableView)
tableView.frame = view.bounds
tableView.delegate = self
tableView.dataSource = self
configure()
}
private func configure() {
self.models = Array(0...50).compactMap({
CellData(title: "\($0)", done: false)
})
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return models.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let model = models[indexPath.row]
guard let cell = tableView.dequeueReusableCell(withIdentifier: TableViewCell.identifier, for: indexPath) as? TableViewCell else {
return UITableViewCell()
}
cell.textLabel?.text = model.title
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
tableView.reloadData()
}
}
TableViewCell
import UIKit
class TableViewCell: UITableViewCell {
let mainVC = ViewController()
static let identifier = "TableViewCell"
let likeImage: UIImageView = {
let imageView = UIImageView()
imageView.image = UIImage(systemName: "heart")
imageView.tintColor = .darkGray
imageView.isUserInteractionEnabled = true
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.addSubview(likeImage)
layout()
//Tap Gesture Recognizer 실행하기
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(didTapImageView(_:)))
likeImage.addGestureRecognizer(tapGestureRecognizer)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
}
override func prepareForReuse() {
super.prepareForReuse()
}
private func layout() {
likeImage.widthAnchor.constraint(equalToConstant: 30).isActive = true
likeImage.heightAnchor.constraint(equalToConstant: 30).isActive = true
likeImage.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
likeImage.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20).isActive = true
}
#objc func didTapImageView(_ sender: UITapGestureRecognizer) {
if likeImage.image == UIImage(systemName: "heart.fill"){
likeImage.image = UIImage(systemName: "heart")
likeImage.tintColor = .darkGray
} else {
likeImage.image = UIImage(systemName: "heart.fill")
likeImage.tintColor = .systemRed
}
}
}
This gif shows how it works now.
enter image description here
I've tried to use "done" property in CellData structure to capture the status of the uiimageview but failed (didn't know how to use that in the correct way).
I would be so happy if anyone can help this!
You've already figured out that the problem is cell reuse.
When you dequeue a cell to be shown, you are already setting the cell label's text based on your data:
cell.textLabel?.text = model.title
you also need to tell the cell whether to show the empty or filled heart image.
And, when the user taps that image, your cell needs to tell the controller to update the .done property of your data model.
That can be done with either a protocol/delegate pattern or, more commonly (particularly with Swift), using a closure.
Here's a quick modification of the code you posted... the comments should give you a good idea of what's going on:
struct CellData {
var title: String
var done: Bool
}
class ShinViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var models = [CellData]()
private let tableView: UITableView = {
let table = UITableView()
table.register(ShinTableViewCell.self, forCellReuseIdentifier: ShinTableViewCell.identifier)
return table
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(tableView)
tableView.frame = view.bounds
tableView.delegate = self
tableView.dataSource = self
configure()
}
private func configure() {
self.models = Array(0...50).compactMap({
CellData(title: "\($0)", done: false)
})
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return models.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: ShinTableViewCell.identifier, for: indexPath) as! ShinTableViewCell
let model = models[indexPath.row]
cell.myLabel.text = model.title
// set the "heart" to true/false
cell.isLiked = model.done
// closure
cell.callback = { [weak self] theCell, isLiked in
guard let self = self,
let pth = self.tableView.indexPath(for: theCell)
else { return }
// update our data
self.models[pth.row].done = isLiked
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
}
}
class ShinTableViewCell: UITableViewCell {
// we'll use this closure to communicate with the controller
var callback: ((UITableViewCell, Bool) -> ())?
static let identifier = "TableViewCell"
let likeImageView: UIImageView = {
let imageView = UIImageView()
imageView.image = UIImage(systemName: "heart")
imageView.tintColor = .darkGray
imageView.isUserInteractionEnabled = true
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
}()
let myLabel: UILabel = {
let v = UILabel()
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
// we'll load the heart images once in init
// instead of loading them every time they change
var likeIMG: UIImage!
var unlikeIMG: UIImage!
var isLiked: Bool = false {
didSet {
// update the image in the image view
likeImageView.image = isLiked ? likeIMG : unlikeIMG
// update the tint
likeImageView.tintColor = isLiked ? .systemRed : .darkGray
}
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
// make sure we load the heart images
guard let img1 = UIImage(systemName: "heart"),
let img2 = UIImage(systemName: "heart.fill")
else {
fatalError("Could not load the heart images!!!")
}
unlikeIMG = img1
likeIMG = img2
// add label and image view
contentView.addSubview(myLabel)
contentView.addSubview(likeImageView)
layout()
//Tap Gesture Recognizer 실행하기
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(didTapImageView(_:)))
likeImageView.addGestureRecognizer(tapGestureRecognizer)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
}
override func prepareForReuse() {
super.prepareForReuse()
}
private func layout() {
// let's use the "built-in" margins guide
let g = contentView.layoutMarginsGuide
// image view bottom constraint
let bottomConstraint = likeImageView.bottomAnchor.constraint(equalTo: g.bottomAnchor)
// this will avoid auto-layout complaints
bottomConstraint.priority = .required - 1
NSLayoutConstraint.activate([
// constrain label leading
myLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor),
// center the label vertically
myLabel.centerYAnchor.constraint(equalTo: g.centerYAnchor),
// constrain image view trailing
likeImageView.trailingAnchor.constraint(equalTo: g.trailingAnchor),
// constrain image view to 30 x 30
likeImageView.widthAnchor.constraint(equalToConstant: 30),
likeImageView.heightAnchor.constraint(equalTo: likeImageView.widthAnchor),
// constrain image view top
likeImageView.topAnchor.constraint(equalTo: g.topAnchor),
// activate image view bottom constraint
bottomConstraint,
])
}
#objc func didTapImageView(_ sender: UITapGestureRecognizer) {
// toggle isLiked (true/false)
isLiked.toggle()
// inform the controller, so it can update the data
callback?(self, isLiked)
}
}

CollectionView in TableView Cell Swift mix card type reuse issue

A collection view is contained in a table view cell.
Collection view cells are drawn for each card type.
In indexPath 0 and 1, the card is of a single type.
indexPath 2 is a mix type.
There are three card types, live reserved vod, and playLayer is added when it is a vod type.
When drawing collection view cells in mix type, playLayer is added to reserved type, and playLayer is added to all cells when scrolling up and down.
class TableViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
lazy private var homeManager = HomeManager()
var sections: [Section]?
var liveData: [Item]?
var vodData: [Item]?
var mixData: [Item]?
var table: UITableView = {
let tableView = UITableView()
tableView.register(CardCollectionTableViewCell.self, forCellReuseIdentifier: CardCollectionTableViewCell.identifier)
return tableView
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .black
view.addSubview(table)
homeManager.getdata{ [weak self] response in
self?.sections = response.sections ?? []
self?.liveData = self?.sections?[1].items ?? []
self?.vodData = self?.sections?[2].items ?? []
self?.mixData = self?.sections?[3].items ?? []
self?.table.reloadData()
}
table.delegate = self
table.dataSource = self
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
table.frame = view.bounds
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 && liveData != nil {
let cell = table.dequeueReusableCell(withIdentifier: CardCollectionTableViewCell.identifier, for: indexPath) as! CardCollectionTableViewCell
cell.configure(with: liveData)
cell.titleLabel.text = sections![1].title!
return cell
} else if indexPath.row == 1 && vodData != nil {
let cell = table.dequeueReusableCell(withIdentifier: CardCollectionTableViewCell.identifier, for: indexPath) as! CardCollectionTableViewCell
cell.configure(with: vodData)
cell.titleLabel.text = sections![2].title!
return cell
} else if indexPath.row == 2 && mixData != nil {
let cell = table.dequeueReusableCell(withIdentifier: CardCollectionTableViewCell.identifier, for: indexPath) as! CardCollectionTableViewCell
cell.configure(with: mixData)
cell.titleLabel.text = sections![3].title!
return cell
}
else {
return UITableViewCell()
}
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 400
}
CardCollection TableViewCell
static let identifier = "CardCollectionTableViewCell"
var titleLabel: UILabel = {
let label = UILabel()
label.font = Fonts.text16()
label.textColor = .white
return label
}()
var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
layout.scrollDirection = .horizontal
collectionView.register(MyCollectionViewCell.self, forCellWithReuseIdentifier: MyCollectionViewCell.identifier)
collectionView.backgroundColor = .black
return collectionView
}()
var models:[Item]?
func configure(with models: [Item]?) {
self.models = models
titleLabel.snp.makeConstraints { make in
make.top.equalToSuperview()
make.leading.equalToSuperview().offset(20)
make.width.equalTo(300)
make.height.equalTo(24)
}
collectionView.snp.makeConstraints { make in
make.top.equalToSuperview().offset(44)
make.leading.equalToSuperview().offset(20)
make.trailing.bottom.equalToSuperview()
}
collectionView.reloadData()
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.addSubview(collectionView)
contentView.addSubview(titleLabel)
collectionView.delegate = self
collectionView.dataSource = self
contentView.backgroundColor = .black
//guard models != nil else { return }
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// Collectionview
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return models?.count ?? 5
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: MyCollectionViewCell.identifier, for: indexPath) as! MyCollectionViewCell
cell.setupViews(with: models![indexPath.item])
cell.setupConstraints(with: models![indexPath.item])
cell.configure(with: models![indexPath.item])
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 140, height: 350)
}
CollectionViewCell
class MyCollectionViewCell: UICollectionViewCell {
static let identifier = "MyCollectionViewCell"
var player: AVPlayer?
private lazy var imageView: UIImageView = {
let image = UIImageView()
image.contentMode = .scaleAspectFill
image.clipsToBounds = true
image.backgroundColor = .blue
image.layer.cornerRadius = 8
return image
}()
private lazy var typeLabelBackgroud: UIImageView = {
let image = UIImageView()
image.clipsToBounds = true
image.layer.cornerRadius = 8
return image
}()
private lazy var playerView: AVPlayerLayer? = {
let url = URL(string: "https://1303309272.vod2.myqcloud.com/7e895809vodkr1303309272/8155555e3701925920462082823/f0.mp4")
player = AVPlayer(url: url!)
let playerLayer = AVPlayerLayer(player: player)
playerLayer.frame = CGRect(x: 0, y: 0, width: 140, height: 210)
playerLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.player?.play()
}
return playerLayer
}()
private lazy var typeLabel: UILabel = {
let label = UILabel()
label.font = Fonts.text10()
label.textColor = .white
return label
}()
private lazy var timeLabel: UILabel? = {
let label = UILabel()
label.font = Fonts.text11()
label.textColor = .white
label.frame.size.width = 42
label.frame.size.height = 16
return label
}()
private lazy var titleLabel: UILabel = {
let label = UILabel()
label.numberOfLines = 0
label.font = Fonts.text14()
label.textColor = .white
return label
}()
private lazy var storeName: UILabel = {
let label = UILabel()
label.font = Fonts.text11()
label.textColor = .gray
return label
}()
private lazy var heartImage: UIImageView = {
let image = UIImageView()
image.image = UIImage(named: "Vector")
image.contentMode = .scaleAspectFit
return image
}()
private lazy var heartCountLabel: UILabel = {
let label = UILabel()
label.font = Fonts.text11()
label.textColor = .gray
label.frame.size.width = 27
label.frame.size.height = 16
label.textAlignment = .left
return label
}()
private lazy var eyeImage: UIImageView = {
let image = UIImageView()
image.image = UIImage(named: "Eye")
image.contentMode = .scaleAspectFit
return image
}()
private lazy var eyeCountLabel: UILabel = {
let label = UILabel()
label.font = Fonts.text11()
label.textColor = .gray
label.frame.size.width = 27
label.frame.size.height = 16
label.textAlignment = .left
return label
}()
private override init(frame: CGRect) {
super.init(frame: frame)
}
public convenience init() {
self.init(frame:. zero)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupViews(with model: Item?) {
guard model != nil else {return}
print("CollectionViewCell의 model! data: \(model)")
if model!.type == "LIVE" {
addSubview(imageView)
addSubview(typeLabelBackgroud)
addSubview(typeLabel)
addSubview(titleLabel)
addSubview(storeName)
addSubview(heartImage)
addSubview(heartCountLabel)
addSubview(eyeImage)
addSubview(eyeCountLabel)
} else if model!.type == "RESERVED"{
addSubview(imageView)
addSubview(typeLabelBackgroud)
addSubview(typeLabel)
addSubview(titleLabel)
addSubview(storeName)
addSubview(heartImage)
addSubview(heartCountLabel)
addSubview(eyeImage)
addSubview(eyeCountLabel)
} else if model!.type == "VOD" {
addSubview(imageView)
addSubview(typeLabelBackgroud)
addSubview(typeLabel)
addSubview(titleLabel)
addSubview(storeName)
addSubview(heartImage)
addSubview(heartCountLabel)
addSubview(eyeImage)
addSubview(eyeCountLabel)
addSubview(timeLabel!)
imageView.layer.addSublayer(playerView!)
}
}
func setupConstraints(with model: Item?) {
guard model != nil else {return}
if model!.type == "LIVE" {
imageView.snp.makeConstraints { make in
make.width.equalTo(140)
make.height.equalTo(210)
}
typeLabelBackgroud.snp.makeConstraints { make in
make.leading.equalTo(imageView).inset(8)
make.top.equalTo(imageView).inset(10)
make.width.equalTo(33)
make.height.equalTo(20)
}
typeLabel.snp.makeConstraints { make in
make.leading.equalTo(typeLabelBackgroud).inset(6)
make.top.equalTo(typeLabelBackgroud).inset(2)
make.width.equalTo(21)
make.height.equalTo(16)
}
titleLabel.snp.makeConstraints { make in
make.top.equalTo(imageView.snp.bottom).offset(8)
make.width.equalTo(140)
make.height.equalTo(42)
}
storeName.snp.makeConstraints { make in
make.top.equalTo(titleLabel.snp.bottom).offset(4)
make.width.equalTo(140)
make.height.equalTo(16)
}
heartImage.snp.makeConstraints { make in
make.top.equalTo(storeName.snp.bottom).offset(3)
make.width.height.equalTo(16)
}
heartCountLabel.snp.makeConstraints { make in
make.top.equalTo(storeName.snp.bottom).offset(3)
make.leading.equalTo(heartImage.snp.trailing).offset(5)
}
eyeImage.snp.makeConstraints { make in
make.leading.equalTo(heartCountLabel.snp.trailing).offset(13)
make.top.equalTo(storeName.snp.bottom).offset(3)
make.width.height.equalTo(16)
}
eyeCountLabel.snp.makeConstraints { make in
make.top.equalTo(storeName.snp.bottom).offset(3)
make.leading.equalTo(eyeImage.snp.trailing).offset(5)
}
} else if model!.type == "RESERVED" {
imageView.snp.makeConstraints { make in
make.width.equalTo(140)
make.height.equalTo(210)
}
typeLabelBackgroud.snp.makeConstraints { make in
make.leading.equalTo(imageView).inset(8)
make.top.equalTo(imageView).inset(10)
make.width.equalTo(33)
make.height.equalTo(20)
}
typeLabel.snp.makeConstraints { make in
make.leading.equalTo(typeLabelBackgroud).inset(6)
make.top.equalTo(typeLabelBackgroud).inset(2)
make.width.equalTo(21)
make.height.equalTo(16)
}
titleLabel.snp.makeConstraints { make in
make.top.equalTo(imageView.snp.bottom).offset(8)
make.width.equalTo(140)
make.height.equalTo(42)
}
storeName.snp.makeConstraints { make in
make.top.equalTo(titleLabel.snp.bottom).offset(4)
make.width.equalTo(140)
make.height.equalTo(16)
}
heartImage.snp.makeConstraints { make in
make.top.equalTo(storeName.snp.bottom).offset(3)
make.width.height.equalTo(16)
}
heartCountLabel.snp.makeConstraints { make in
make.top.equalTo(storeName.snp.bottom).offset(3)
make.leading.equalTo(heartImage.snp.trailing).offset(5)
}
eyeImage.snp.makeConstraints { make in
make.leading.equalTo(heartCountLabel.snp.trailing).offset(13)
make.top.equalTo(storeName.snp.bottom).offset(3)
make.width.height.equalTo(16)
}
eyeCountLabel.snp.makeConstraints { make in
make.top.equalTo(storeName.snp.bottom).offset(3)
make.leading.equalTo(eyeImage.snp.trailing).offset(5)
}
} else if model!.type == "VOD" {
imageView.snp.makeConstraints { make in
make.width.equalTo(140)
make.height.equalTo(210)
}
typeLabelBackgroud.snp.makeConstraints { make in
make.leading.equalTo(imageView).inset(8)
make.top.equalTo(imageView).inset(10)
make.width.equalTo(33)
make.height.equalTo(20)
}
typeLabel.snp.makeConstraints { make in
make.leading.equalTo(typeLabelBackgroud).inset(6)
make.top.equalTo(typeLabelBackgroud).inset(2)
make.width.equalTo(21)
make.height.equalTo(16)
}
timeLabel!.snp.makeConstraints { make in
make.top.equalTo(imageView).inset(8)
make.trailing.equalTo(imageView).inset(10)
}
titleLabel.snp.makeConstraints { make in
make.top.equalTo(imageView.snp.bottom).offset(8)
make.width.equalTo(140)
make.height.equalTo(42)
}
storeName.snp.makeConstraints { make in
make.top.equalTo(titleLabel.snp.bottom).offset(4)
make.width.equalTo(140)
make.height.equalTo(16)
}
heartImage.snp.makeConstraints { make in
make.top.equalTo(storeName.snp.bottom).offset(3)
make.width.height.equalTo(16)
}
heartCountLabel.snp.makeConstraints { make in
make.top.equalTo(storeName.snp.bottom).offset(3)
make.leading.equalTo(heartImage.snp.trailing).offset(5)
}
eyeImage.snp.makeConstraints { make in
make.leading.equalTo(heartCountLabel.snp.trailing).offset(13)
make.top.equalTo(storeName.snp.bottom).offset(3)
make.width.height.equalTo(16)
}
eyeCountLabel.snp.makeConstraints { make in
make.top.equalTo(storeName.snp.bottom).offset(3)
make.leading.equalTo(eyeImage.snp.trailing).offset(5)
}
}
}
public func configure(with model: Item?) {
//self.model! = model!
guard model != nil else {return}
if model!.type == "LIVE" {
imageView.kf.setImage(with: URL(string: model!.image!))
typeLabel.text = "LIVE"
typeLabelBackgroud.backgroundColor = .orange
titleLabel.text = String(model!.title!)
storeName.text = String(model!.store!)
heartCountLabel.text = String(model!.likeCount!)
eyeCountLabel.text = String(model!.playedCount!)
} else if model!.type == "RESERVED" {
imageView.kf.setImage(with: URL(string: model!.image!))
typeLabel.text = "예정"
typeLabelBackgroud.backgroundColor = Colors.primary()
titleLabel.text = String(model!.title!)
storeName.text = String(model!.store!)
heartCountLabel.text = String(model!.likeCount!)
eyeCountLabel.text = String(model!.playedCount!)
} else if model!.type == "VOD" {
imageView.kf.setImage(with: URL(string: model!.image!))
typeLabel.text = "VOD"
titleLabel.text = String(model!.title!)
typeLabelBackgroud.backgroundColor = Colors.vodBackgroud()
storeName.text = String(model!.store!)
heartCountLabel.text = String(model!.likeCount!)
eyeCountLabel.text = String(model!.playedCount!)
timeLabel?.text = "01:35:40"
}
}
}
The way I tried is let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) ->
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: collectionViewFlowLayout.init()) .
The reuse problem did not occur, but it cannot be used because the scroll direction is changed.
You could solve your problem in two ways:
1 - reset your cell in cellForItemAt
Currently you call cell.setupViews(with: models![indexPath.item]) for each cell, which adds subviews to the cells. Since collectionView reuses cells, when scrolling you are getting cells which where used as all types (live, reserved, vod), that is why you see that "playLayer is added to all cells when scrolling up and down".
With this code you also add subviews each time the cell is reused, which is also not the best practice.
You could add an additional function to reset the cell and remove all subviews before adding new ones:
cell.subviews.forEach { $0.removeFromSuperview() }
(it is also better to add views to the cell.contentView)
2 - Create separate classes for different cell types
In cellFroItemAtyou could check for the type of the cell and return the required on. Each type will have a different ReuseIdentifier and playLayer will be added only to the type you need

UICollectionViewCell's UiTextField, overlapping in UICollectionViewController

I am trying to design a multistep sign-up menu. For this purpose I am using UICollectionViewController with screen size cells. In these cells, I have a UITextView to ask questions and a UITextField to collect the answers. I also have a Page object for passing in information from uicollectionviewcontroller upon setting.
The problem I'm having now is that after every 3rd page my textField input from 3 pages ago repeats, instead of showing the placeholder. I have noticed yet another problem, the cells seem to be instantiating just 3 times, and not 6 times for how many pages I have. The instantiation order is very odd too. At first it does it once, then upon button click, twice more, then never again.
How can fix this, I am really struggling with this and I have no idea what's going wrong.
This is my code:
import UIKit
class OnboardingPageViewCell: UICollectionViewCell{
override init(frame: CGRect) {
super.init(frame: frame)
print("made a page")
setupView()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
var oboardingPage = NewOnboardingPage() {
didSet{
reload()
}
}
private var questionTextField: UITextView = {
var q = UITextView()
q.textColor = UIColor.white
q.textAlignment = .left
q.font = UIFont(name: "Avenir-Black", size: 25)
q.isEditable = true
q.isScrollEnabled = false
q.backgroundColor = UIColor.black
q.translatesAutoresizingMaskIntoConstraints = false
print("made aquestion field")
return q
}()
private var answerField : CustomTextField = {
let tf = CustomTextField.nameField
print("made an answer field")
return tf
}()
private func setupView(){
backgroundColor = UIColor.white
addSubview(questionTextField)
questionTextField.topAnchor.constraint(equalTo: topAnchor, constant: 120).isActive = true
questionTextField.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
questionTextField.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 0.90).isActive = true
questionTextField.heightAnchor.constraint(equalToConstant: 90).isActive = true
addSubview(answerField)
answerField.topAnchor.constraint(equalTo: questionTextField.bottomAnchor, constant: 20).isActive = true
answerField.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
answerField.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 0.90).isActive = true
answerField.heightAnchor.constraint(equalToConstant: 90).isActive = true
}
private func reload(){
questionTextField.text = oboardingPage.question
answerField.placeholder = oboardingPage.answerField
}
}
class NewOnboardingPage {
var question : String?
var answerField : String?
}
import UIKit
class SignUpController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
private let cellId = "cellId"
private var pages = [NewOnboardingPage]()
override func viewDidLoad() {
super .viewDidLoad()
setupSignUpControllerView()
addPages()
}
private func addPages(){
let namePage = NewOnboardingPage()
namePage.question = "What's your name?"
namePage.answerField = "What's your name?"
pages.append(namePage)
let birthDayPage = NewOnboardingPage()
birthDayPage.question = "When's your birthdate?"
birthDayPage.answerField = "When's your birthdate?"
pages.append(birthDayPage)
let userNamePage = NewOnboardingPage()
userNamePage.question = "Choose a user name."
userNamePage.answerField = "Choose a user name."
pages.append(userNamePage)
let passWordPage = NewOnboardingPage()
passWordPage.question = "Set a password"
passWordPage.answerField = "Set a password"
pages.append(passWordPage)
let emailAuthPage = NewOnboardingPage()
emailAuthPage.question = "What's your email?"
emailAuthPage.answerField = "What's your email?"
pages.append(emailAuthPage)
let phoneNumberPage = NewOnboardingPage()
phoneNumberPage.question = "What's your phone number?"
phoneNumberPage.answerField = "What's your phone number?"
pages.append(phoneNumberPage)
}
private func setupSignUpControllerView(){
collectionView?.backgroundColor = .white
collectionView?.register(OnboardingPageViewCell.self, forCellWithReuseIdentifier: cellId)
collectionView?.isPagingEnabled = true
collectionView?.isScrollEnabled = true
if let layout = collectionView?.collectionViewLayout as? UICollectionViewFlowLayout {
layout.scrollDirection = .horizontal
layout.minimumLineSpacing = 0
}
view.addSubview(nextButton)
nextButton.topAnchor.constraint(equalTo: view.topAnchor, constant: 400).isActive = true
nextButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
nextButton.widthAnchor.constraint(equalToConstant: 250).isActive = true
nextButton.heightAnchor.constraint(equalToConstant: 60).isActive = true
}
private let nextButton: UIButton = {
let button = UIButton(type: .system)
button.backgroundColor = UIColor.RED
button.setTitle("next", for: .normal)
button.setTitleColor(UIColor.white, for: .normal)
button.titleLabel?.font = UIFont(name: "Avenir-Black", size: 25)
button.layer.cornerRadius = 30
button.translatesAutoresizingMaskIntoConstraints = false
button.addTarget(self, action: #selector(turnNextPage), for: .touchUpInside)
return button
}()
#objc private func turnNextPage() {
let visibleItems: NSArray = collectionView?.indexPathsForVisibleItems as! NSArray
let currentItem: IndexPath = visibleItems.object(at: 0) as! IndexPath
let nextItem: IndexPath = IndexPath(item: currentItem.item + 1, section: 0)
if nextItem.row < pages.count {
collectionView?.scrollToItem(at: nextItem, at: .left, animated: true)
}
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return pages.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! OnboardingPageViewCell
cell.oboardingPage = pages[indexPath.item]
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.width, height: view.frame.height)
}
}
import UIKit
class CustomTextField: UITextField, UITextFieldDelegate {
convenience init() {
self.init(frame: CGRect(x: 0, y: 0, width: 0, height: 0))
self.allowsEditingTextAttributes = false
self.autocorrectionType = .no
self.tintColor = UIColor.RED
self.translatesAutoresizingMaskIntoConstraints = false
}
override func willMove(toSuperview newSuperview: UIView?) {
addTarget(self, action: #selector(editingChanged), for: .editingChanged)
editingChanged(self)
}
#objc func editingChanged(_ textField: UITextField) {
guard let text = textField.text else { return }
textField.text = String(text.prefix(30))
}
override func selectionRects(for range: UITextRange) -> [Any] {
return []
}
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
if action == #selector(paste(_:)) ||
action == #selector(cut(_:)) ||
action == #selector(copy(_:)) ||
action == #selector(select(_:)) ||
action == #selector(selectAll(_:)){
return false
}
return super.canPerformAction(action, withSender: sender)
}
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! OnboardingPageViewCell
cell.answerField.text = nil
cell.oboardingPage = pages[indexPath.item]
return cell
}
1- textFeild showing same data instead of placeholder bacause of cell dequeuing so you must hook these properties and clear their content in cellForRowAt
2- Instantiation is 3 not 6 aslo cell dequeuing
Solve:
Add two properties to your model NewOnboardingPage name them currentQuestion and currentAnswer and as the user inputs and scroll to next page save them in the modelarray that you should make global to be accessed indside cell and outside set these values to the textfeild and textView as you scroll in cellForRowAt

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