in my view controller I Am loading a custom CollectionViewCell with subclass. Based on the position of a cell's indexpath I want to format the text labels differently. I.e. first row has only one cell with bigger text, whereas the second has two cell with smaller text.
How can I access the indexpath from my UICollectionView in my UICollectionViewCell subclass? I tried a delegate protocol but this always returns nil.
Code below and Thanks so much!
Markus
UICollectionViewController:
import UIKit
protocol WorkoutDataViewControllerCVDataSource: AnyObject {
func workoutType(for workoutDataViewControllerCV: WorkoutDataViewControllerCV) -> WorkoutType
func workoutDistance(for workoutDataViewControllerCV: WorkoutDataViewControllerCV) -> Double
func workoutDuration(for workoutDataViewControllerCV: WorkoutDataViewControllerCV) -> Double
func workoutInstantVelocity(for workoutDataViewControllerCV: WorkoutDataViewControllerCV) -> Double
}
final class WorkoutDataViewControllerCV: UIViewController {
#IBOutlet weak var collectionView: UICollectionView!
weak var dataSource: WorkoutDataViewControllerCVDataSource!
private lazy var velocityFormatter = VelocityFormatter(dataSource: self, delegate: self)
private lazy var averageVelocityFormatter = VelocityFormatter(dataSource: self, delegate: self)
override func viewDidLoad() {
super.viewDidLoad()
self.collectionView.register(MeasurementCollectionViewCell.preferredNib, forCellWithReuseIdentifier: MeasurementCollectionViewCell.preferredReuseIdentifier)
}
}
// MARK: - Managing UICollectionView
extension WorkoutDataViewControllerCV: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 4
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Measurement Cell", for: indexPath)
return cell
}
}
extension WorkoutDataViewControllerCV: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
let availableWidth = self.view.frame.width
switch indexPath.row {
case 0: return CGSize(width: availableWidth, height: 150)
case 1: return CGSize(width: availableWidth/2.1, height: 150)
case 2: return CGSize(width: availableWidth/2.1, height: 150)
case 3: return CGSize(width: availableWidth, height: 150)
default:
return CGSize(width: availableWidth/2.1, height: 150)
}
}
}
// MARK: - Managing VelocityFormatter
extension WorkoutDataViewControllerCV: VelocityFormatterDataSource {
func duration(for velocityFormatter: VelocityFormatter) -> Double {
return dataSource.workoutDuration(for: self)
}
func distance(for velocityFormatter: VelocityFormatter) -> Double {
return dataSource.workoutDistance(for: self)
}
func instantVelocity(for velocityFormatter: VelocityFormatter) -> Double {
return dataSource.workoutInstantVelocity(for: self)
}
}
UICollectionViewCell.swift
import UIKit
final class MeasurementCollectionViewCell: UICollectionViewCell {
#IBOutlet private var measurementPropertyLabel: UILabel!
#IBOutlet private var measurementValueLabel: UILabel!
#IBOutlet private var measurementUnitLabel: UILabel!
static let preferredReuseIdentifier = "Measurement Cell"
static let preferredNib = UINib(nibName: "MeasurementCollectionViewCell", bundle: nil)
override func awakeFromNib() {
super.awakeFromNib()
updateMeasurement(property: "Speed", value: "100", unit: "km/h")
//measurementValueLabel.font = measurementValueLabel.font.monospacedDigitFont
}
func updateMeasurement(property: String, value: String, unit: String?) {
measurementPropertyLabel.text = property
measurementValueLabel.text = value
measurementUnitLabel.text = unit
}
}
Get the instance of cell in UICollectionView delegate method collectionView(_, didSelectItemAt _).
extension WorkoutDataViewControllerCV: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if let cell = collectionView.cellForItem(at: indexPath) as? MeasurementCollectionViewCell {
cell.selectedIndexPath(indexPath)
}
}
}
The indexPath will be passed as an argument in method selectedIndexPath to MeasurementCollectionViewCell from above method.
class MeasurementCollectionViewCell: UICollectionViewCell {
......
func selectedIndexPath(_ indexPath: IndexPath) {
//Do your business here.
}
}
You can use the responder chain to get the collection view of a cell with which you can get the index path. Just add these extensions in a new file called UICollectionViewCell+IndexPath.swift.
extension UIResponder {
func next<T: UIResponder>(_ type: T.Type) -> T? {
return next as? T ?? next?.next(type)
}
}
extension UICollectionViewCell {
var indexPath: IndexPath? {
return next(UICollectionView.self)?.indexPath(for: self)
}
}
Now inside your cell, you can use self.indexPath
Pretty straight forward way would be storing the indexPath into the subclass of UICollectionViewCell class. Assign it while returning from cellForRow at: index path. So now the subclassed collectionviewcell has access to the indexpath of it's own
Related
I am using storyboards, and my UICollectionView with a basic custom cell is not showing at all? My simulator is constantly running a version of my storyboard when my "Next" button was at the middle of the screen, obviously now it is at the bottom as shown here along with all the identities and links created in the storyboard: https://imgur.com/a/R8iTm9n
import UIKit
class CategoryViewController: UIViewController{
#IBOutlet weak var categoryCollectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
categoryCollectionView.delegate = self
categoryCollectionView.dataSource = self
NetworkingClient.fetchRecipeCategories{ (recipeCategories) in
//print(recipeCategories)
}
categoryCollectionView.reloadData()
}
}
extension CategoryViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
collectionView.deselectItem(at: indexPath, animated: true)
print("Taptaptap")
}
}
extension CategoryViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 12
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
var cell = UICollectionViewCell()
if let testCell = collectionView.dequeueReusableCell(withReuseIdentifier: "categoryCell", for: indexPath) as? CategoryCollectionViewCell{
testCell.configure(with: indexPath.row)
cell = testCell
print("test1")
}
print("test2")
return cell
}
}
/*
extension CategoryViewController: UICollectionViewDelegateFlowLayout {
}
*/
And my my custom view cell :
class CategoryCollectionViewCell: UICollectionViewCell {
static let identifier: String = "CategoryCollectionViewCell"
#IBOutlet weak var categoryImageView: UIImageView!
#IBOutlet private weak var testLabel: UILabel!
func configure(with id: Int){
testLabel.text = String(id)
categoryImageView.clipsToBounds = true
categoryImageView.contentMode = .scaleAspectFit
}
}
Sorry for the formatting...
Register your custom collectionview cell named "categoryCell" in viewDidLoad()
categoryCollectionView.register(UINib(nibName: "categoryCell", bundle:
nil),forCellWithReuseIdentifier: "categoryCell")
categoryCollectionView.delegate = self
categoryCollectionView.dataSource = self
categoryCollectionView.reloadData()
I'm working on a requirement where I need to add the items in a UICollectionView dynamically.
Here is my code of ViewController
import UIKit
class ViewController: UIViewController {
enum Direction {
case Horizonatal
case Verticle
}
var enumDirection: Direction = .Verticle
var direction = "Verticle"
var SectionsAndRows = [Int]()
override func viewDidLoad() {
super.viewDidLoad()
SectionsAndRows.append(4)
SectionsAndRows.append(3)
SectionsAndRows.append(2)
SectionsAndRows.append(1)
//SectionsAndRows.append(5)
}
#IBOutlet var gridCollectionView: UICollectionView! {
didSet {
gridCollectionView.bounces = false
}
}
#IBOutlet var gridLayout: UICollectionViewFlowLayout! {
didSet {
//gridLayout.stickyRowsCount = 0
gridLayout.scrollDirection = .horizontal
//gridLayout.stickyColumnsCount = 0
gridLayout.minimumLineSpacing = 5
gridLayout.minimumInteritemSpacing = 5
}
}
}
// MARK: - Collection view data source and delegate methods
extension ViewController: UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return SectionsAndRows.count
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
print(SectionsAndRows[section])
return SectionsAndRows[section]
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CollectionViewCell.reuseID, for: indexPath) as? CollectionViewCell else {
return UICollectionViewCell()
}
print("Current Section ==\(indexPath.section) CurrentRow ===\(indexPath.row) and its rows count ==\(SectionsAndRows[indexPath.section])")
cell.titleLabel.text = ""
cell.btn.addTarget(self, action: #selector(handleAdd(sender:)), for: .touchUpInside)
cell.btn.tag = (indexPath.section * 1000) + indexPath.row
if enumDirection == .Verticle {
if indexPath.section == SectionsAndRows.count - 1 {
cell.btn.setTitle("+", for: .normal)
} else {
cell.btn.setTitle("\(indexPath)", for: .normal)
}
}
return cell
}
#objc func handleAdd(sender: UIButton) {
// Perform some opration
}
}
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 5
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 100, height: 100)
}
}
CollectionViewCell.swift
import UIKit
class CollectionViewCell: UICollectionViewCell {
static let reuseID = "CollectionViewCell"
#IBOutlet weak var btn: UIButton!
#IBOutlet weak var titleLabel: UILabel!
}
If you run the code, it will show a Collection of 1 row and 1 column in each row. If you uncomment the last line of viewDidLoad() (SectionsAndRows.append(5)) function, then it works fine.
My observation is that the last section of the CollectionView will have the highest number of a column. Is that correct or is this a bug of a CollectionView?
For my app, I have a collection view that sets the background color of the cells based on a variable in the ViewController.
When MoPub places ads into the CollectionView how can I set the ad's background to match the other cells?
class RestController: UIViewController {
#IBOutlet weak var collectionView: UICollectionView!
var category: Category!
// Ads
var placer = MPCollectionViewAdPlacer()
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
collectionView.mp_reloadData()
}
override func viewDidLoad() {
super.viewDidLoad()
// AD Cell
collectionView.register(AdCell.self, forCellWithReuseIdentifier: "cell")
let settings = MPStaticNativeAdRendererSettings()
settings.renderingViewClass = AdCell.self
settings.viewSizeHandler = { (maxWidth: CGFloat) -> CGSize in
return CGSize(width: 160, height: 100)
}
let config = MPStaticNativeAdRenderer.rendererConfiguration(with: settings)
placer = MPCollectionViewAdPlacer(collectionView: collectionView, viewController: self, rendererConfigurations: [config as Any])
let targeting = MPNativeAdRequestTargeting()
targeting.desiredAssets = [ kAdStarRatingKey, kAdIconImageKey, kAdTitleKey, kAdPrivacyIconUIImageKey ]
placer.loadAds(forAdUnitID: unitId, targeting: targeting)
collectionView.mp_setDelegate(self)
collectionView.mp_setDataSource(self)
}
}
// MARK: UICollectionViewDataSource
extension RestController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return results.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.mp_dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! RestCell
cell.backgroundColor = category.colors[1]
return cell
}
}
Is there any way to pass this information to the Ad Cell or any method that MoPub offers to set the design of the cell?
I have a UIViewController with a UICollectionViewController nested inside. The collection view controller also has the moveItemAt method implemented, since I want the cells to be reorderable. So the cells have a UILongPressGestureRecognizer attached. However, long press on the cells aren't happening. I can't seem to figure out if the nested controller is causing the gesture to be ignored. Maybe the parent controller is capturing the long press but that wouldn't make sense since AFAIK, gestures go up the view hierarchy, not the other way around.
For some context, I've used this method to nest my controllers
func add(_ child: UIViewController) {
addChildViewController(child)
child.view.frame = view.bounds
view.addSubview(child.view)
child.didMove(toParentViewController: self)
}
I think this is a better approach...
Assuming you have a UICollectionViewController in your storyboard, and you've assigned the cell prototype to DragMeCell and set its Identifier to "DragMeCell" (and added a label connected to the IBOutlet), this should run and allow you to long-press drag-drop to reorder.
//
// DragReorderCollectionViewController.swift
// SW4Temp
//
// Created by Don Mag on 7/25/18.
//
import UIKit
private let reuseIdentifier = "DragMeCell"
class DragMeCell: UICollectionViewCell {
#IBOutlet var theLabel: UILabel!
}
class DragReorderCollectionViewController: UICollectionViewController {
fileprivate var longPressGesture: UILongPressGestureRecognizer!
fileprivate var dataArray = Array(0 ..< 25)
override func viewDidLoad() {
super.viewDidLoad()
longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(self.handleLongGesture(gesture:)))
if let cv = self.collectionView {
cv.addGestureRecognizer(longPressGesture)
}
}
#objc func handleLongGesture(gesture: UILongPressGestureRecognizer) {
if let cv = self.collectionView,
let gestureView = gesture.view {
switch(gesture.state) {
case .began:
guard let selectedIndexPath = cv.indexPathForItem(at: gesture.location(in: cv)) else {
break
}
cv.beginInteractiveMovementForItem(at: selectedIndexPath)
case .changed:
cv.updateInteractiveMovementTargetPosition(gesture.location(in: gestureView))
case .ended:
cv.endInteractiveMovement()
default:
cv.cancelInteractiveMovement()
}
}
}
// MARK: UICollectionViewDelegate
override func collectionView(_ collectionView: UICollectionView, canMoveItemAt indexPath: IndexPath) -> Bool {
return true
}
override func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
let i = dataArray[sourceIndexPath.item]
dataArray.remove(at: sourceIndexPath.item)
dataArray.insert(i, at: destinationIndexPath.item)
}
// MARK: UICollectionViewDataSource
override func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return dataArray.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! DragMeCell
// Configure the cell
cell.theLabel.text = "\(dataArray[indexPath.item])"
return cell
}
}
Then you can add it as a child view controller, using the code snippet you posted in your question.
I'm really new in iOS/Swift and i'm in a small project. In this project i have a UITableView inside ViewController. And i have another file custom CollectionViewCell in side UITableViewCell.
I want when user click a cell in collectionview it will open another ViewController and it get data from this collectionviewcell.
This is my uitableview swift file:
class IndexRow: UITableViewCell, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
var names:[String] = ["Movie 1","Movie 2","Movie 3","Movie 4","Movie 5","Movie 6"]
#IBOutlet weak var collectionView: UICollectionView!
override func awakeFromNib() {
super.awakeFromNib()
collectionView.registerClass(indexOneMovie.self, forCellWithReuseIdentifier: "onemovie")
let nib = UINib(nibName: "indexOneMovie",bundle: nil)
collectionView.registerNib(nib, forCellWithReuseIdentifier: "onemovie")
self.collectionView.backgroundColor = UIColor.clearColor()
self.collectionView.delegate = self
self.collectionView.dataSource = self
print("Hello")
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return names.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = self.collectionView.dequeueReusableCellWithReuseIdentifier("onemovie", forIndexPath: indexPath) as! indexOneMovie
cell.poster.image = UIImage(named: "poster.jpg")
cell.name.text = names[indexPath.row]
return cell
}
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
print(indexPath.item)
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
let itemsPerRow:CGFloat = 2
let hardCodedPadding:CGFloat = 0
let itemWidth = (collectionView.bounds.width / itemsPerRow) - hardCodedPadding
let itemHeight = collectionView.bounds.height - (hardCodedPadding)
return CGSize(width: itemWidth, height: itemHeight)
}
How i can do it?
ok i have recently implemented the same in my app these are the links where i refered initially -
https://ashfurrow.com/blog/putting-a-uicollectionview-in-a-uitableviewcell-in-swift/
http://www.thorntech.com/2015/08/want-your-swift-app-to-scroll-in-two-directions-like-netflix-heres-how/
you are making uicollectionview delegate confirms to uitableview cell so you cannot present or push to other view controller.
here is my code hope it will help you
homeController.swift which contains uitableview
extension HomeController : UITableViewDelegate {
func tableView(tableView: UITableView,willDisplayCell cell: UITableViewCell,forRowAtIndexPath indexPath: NSIndexPath) {
guard let tableViewCell = cell as? TableViewCell else { return }
//here setting the uitableview cell contains collectionview delgate conform to viewcontroller
tableViewCell.setCollectionViewDataSourceDelegate(self, forRow: indexPath.row, andForSection: indexPath.section)
tableViewCell.collectionViewOffset = storedOffsets[indexPath.row] ?? 0
}
func tableView(tableView: UITableView,didEndDisplayingCell cell: UITableViewCell,forRowAtIndexPath indexPath: NSIndexPath) {
guard let tableViewCell = cell as? TableViewCell else { return }
storedOffsets[indexPath.row] = tableViewCell.collectionViewOffset
}
}
extension HomeController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(collectionView: UICollectionView,numberOfItemsInSection section: Int) -> Int {
let element : [CollectionViewElement] = self.returnCollectionViewElementAccordingToIndex(collectionView.tag)
return element.count
}
func collectionView(collectionView: UICollectionView,cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell",forIndexPath: indexPath) as! horizontalCollectionViewCell
let element : [CollectionViewElement] = self.returnCollectionViewElementAccordingToIndex(collectionView.tag)
cell.cellTitleLabel.text = element[indexPath.row].videos.title
cell.cellGenerLabel.text = element[indexPath.row].videos.gener
return cell
}
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath){
print("collectionviewtag:\(collectionView.tag) + indexpathrow:\(indexPath.row)")
//from here you can do push or present to anyview controller
// collectionviewtag is tableView cell row value and indexpathrow return collectionView cell row value.
}
}
TableViewCell.swift :custom UITableViewCell which contains collectionView
class TableViewCell: UITableViewCell {
#IBOutlet private weak var collectionView: UICollectionView!
#IBOutlet weak var cellLabel: UILabel!
#IBOutlet weak var cellButton: UIButton!
#IBAction func CellButtonActionTry(sender: UIButton) {
print("Dude \(cellButton.tag)")
}
var collectionViewOffset: CGFloat {
get {
return collectionView.contentOffset.x
}
set {
collectionView.contentOffset.x = newValue
}
}
func setCollectionViewDataSourceDelegate<D: protocol<UICollectionViewDataSource, UICollectionViewDelegate>>
(dataSourceDelegate: D, forRow row: Int , andForSection section : Int) {
collectionView.delegate = dataSourceDelegate
collectionView.dataSource = dataSourceDelegate
collectionView.tag = row // tableView indexpathrow equals cell tag
collectionView.reloadData()
}
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
}
You can do it like this :
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
print(indexPath.item)
let name = names[indexPath.item]
let distinationViewController = DistinationViewController()
distinationViewController.name = name
if let navVC: UINavigationController = UIApplication.sharedApplication().keyWindow?.rootViewController as? UINavigationController {
navVC.pushViewController(distinationViewController, animated: true)
}
}
This is just a way to do it i dont know which view you want to push or what your names array contains so kindly change those things accordingly.
get root navigationcontroller from uiapplication and perform push on it.