I am refactoring my ViewControllers and one of them contains a collectionView but now the DataSource is not getting called anymore.
My ViewController:
class CoinPageVC: UIViewController, DependencyInjectionVC, Storyboarded {
lazy var mainView: CoinPageV = {
let v = CoinPageV()
v.collectionView.delegate = self
return v
}()
var coin: Coin!
var selectedBase: String!
var viewContainer: [UIView]!
var collectionViewViewDataSource: CollectionViewCoinPageDatasource?
func injectDependencys(dependency: CoinPageDependency) {
self.coin = dependency.coin
self.selectedBase = dependency.base
self.viewContainer = dependency.views
}
override func viewDidLoad() {
super.viewDidLoad()
self.collectionViewViewDataSource = CollectionViewCoinPageDatasource(data: viewContainer)
self.mainView.collectionView.dataSource = self.collectionViewViewDataSource
self.mainView.collectionView.reloadData()
}
}
extension CoinPageVC: SetMainView {}
extension CoinPageVC: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width:CGFloat = collectionView.bounds.width
let height:CGFloat = collectionView.bounds.height
// - (tabBarHeight + menuBar.frame.height + heightNavigationBarTop)
let output = Utility.shared.CGSizeMake(width, height)
return output
}
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
let index = Int(targetContentOffset.pointee.x / view.frame.width)
let indexPath = IndexPath(item: index, section: 0)
mainView.menuBar.collectionView.selectItem(at: indexPath, animated: true, scrollPosition: .centeredHorizontally)
}
}
My Datasource class:
class CollectionViewCoinPageDatasource: NSObject, UICollectionViewDataSource {
let data: [UIView]
init(data: [UIView]){
self.data = data
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 3
return data.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
//let outputCell: UICollectionViewCell
let row = indexPath.item
let outputCell = collectionView.dequeueReusableCell(withReuseIdentifier: Identifier.coinPageCollectionViewOverviewCell.rawValue, for:indexPath) as! CollectionViewCellView
outputCell.view = data[row]
return outputCell
}
}
My collectionView setup:
lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
let frame = CGRect(x: 0, y: 0, width: 0, height: 0)
let cv = UICollectionView(frame: frame, collectionViewLayout: layout)
cv.register(CollectionViewCellView.self, forCellWithReuseIdentifier: Identifier.coinPageCollectionViewOverviewCell.rawValue)
if let flowLayout = cv.collectionViewLayout as? UICollectionViewFlowLayout {
flowLayout.scrollDirection = .horizontal
flowLayout.minimumLineSpacing = 0
}
cv.backgroundColor = .green
cv.isPagingEnabled = true
//cv.backgroundColor = .blue
return cv
}()
What did I miss?
I set up the datasource and also connect it to the datasource of the collectionView, but the methods do not get called.
lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
let frame = CGRect(x: 0, y: 0, width: 0, height: 0)
let cv = UICollectionView(frame: frame, collectionViewLayout: layout)
cv.register(CollectionViewCellView.self, forCellWithReuseIdentifier: Identifier.coinPageCollectionViewOverviewCell.rawValue)
if let flowLayout = cv.collectionViewLayout as? UICollectionViewFlowLayout {
flowLayout.scrollDirection = .horizontal
flowLayout.minimumLineSpacing = 0
}
cv.backgroundColor = .green
cv.isPagingEnabled = true
//cv.backgroundColor = .blue
cv.delegate = self
return cv
}()
Related
I want to make a UICollectionView with the cells the width of the screen, but the height is based on the aspect ratio of the UIImageView inside.
In my current implementation all of the UICollectionViewCell instances are squares and this isn't what I want.
I want to use auto layout somehow to make the size of the image the width of the screen, but the relevant height depends on the aspect ratio of the image used.
Everything is done programatically in code.
My code:
class ViewController: UIViewController {
var data = ["1","2","3","4","5","6","7","8","1","2","3","4","5","6","7","8"]
var collectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
self.collectionView.dataSource = self
self.collectionView.delegate = self
self.collectionView.register(SubclassedCollectionViewCell.self, forCellWithReuseIdentifier: "cell")
self.collectionView.alwaysBounceVertical = true
self.collectionView.backgroundColor = .white
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
}
override func loadView() {
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.itemSize = CGSize(width: UIScreen.main.bounds.size.width - 20, height: UIScreen.main.bounds.size.width / 1)
layout.scrollDirection = .vertical
collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
self.view = collectionView
}
}
extension ViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
data.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as? SubclassedCollectionViewCell {
let data = self.data[indexPath.item]
cell.setupCell(image: data)
return cell
}
fatalError("Could not dequeue cell")
}
}
with the cell
class SubclassedCollectionViewCell: UICollectionViewCell {
var hotelImageView: UIImageView = {
var hotelView = UIImageView()
hotelView.contentMode = .scaleAspectFill
hotelView.clipsToBounds = true
return hotelView
}()
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(hotelImageView)
hotelImageView.translatesAutoresizingMaskIntoConstraints = false
hotelImageView.topAnchor.constraint(equalTo: topAnchor).isActive = true
hotelImageView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
hotelImageView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
hotelImageView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupCell(image: String) {
if let image : UIImage = UIImage(named: image) {
hotelImageView.image = image
}
}
}
First, calculate the ratio based on the width and multiply by collection view width you can get a new height based on width.
Final Code: Remove itemSize from the loadview function. And calculate height and return item size in sizeForItemAt method.
loadView
override func loadView() {
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
// layout.itemSize = CGSize(width: UIScreen.main.bounds.size.width - 20, height: UIScreen.main.bounds.size.width / 1) <-- Remove this
layout.scrollDirection = .vertical
collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
self.view = collectionView
}
UICollectionViewDelegateFlowLayout
extension ViewController: UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
data.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as? SubclassedCollectionViewCell {
let data = self.data[indexPath.item]
cell.setupCell(image: data)
return cell
}
fatalError("Could not dequeue cell")
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
guard let iamgeData = UIImage(named: self.data[indexPath.item]) else {
return .zero
}
let width = collectionView.bounds.width
let heightOnWidthRatio = iamgeData.size.height / iamgeData.size.width
let height = width * heightOnWidthRatio
return CGSize(width: width, height: height)
}
}
I have decided start a project with no storyboard for the first time and at the moment I am stuck trying to figuring out how to achieve a proper dynamic cell in my CollectionViewController. Reading some of the solutions here in Stackoverflow I got the point in using a layout.estimatedItemSize but it somehow stops the bouncing effect from the collection view and also in my second cell which is a horizontal scroll view will not work after this implementation.
Here is my code(UICollectionViewController):
class InfoEmpaVC: UICollectionViewController, UICollectionViewDelegateFlowLayout {
fileprivate let cell1 = "cell1"
fileprivate let cell2 = "cell2"
fileprivate let cellID = "cellID"
fileprivate let headerID = "headerID"
fileprivate let padding: CGFloat = 10
//
//
//GET THE DATA FROM:
var empanada: Empanadas!
struct Cells {
static let empanadaStepsCell = "EmpanadaStepsCell"
}
override func viewDidLoad() {
super.viewDidLoad()
setupCollectionViewLayout()
setupCollectionView()
}
//CHANGE COLOR OF STATUS BAR
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
fileprivate func setupCollectionView() {
collectionView.backgroundColor = UIColor(named: "ColorBackground")
collectionView.contentInsetAdjustmentBehavior = .never
collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(InfoHeaderVC.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: headerID)
//FirstCELL
collectionView.register(EmpaIngredientsListCell.self, forCellWithReuseIdentifier: cell1)
//SecondCELL
collectionView.register(EmpaStepsCell.self, forCellWithReuseIdentifier: cellID)
}
fileprivate func setupCollectionViewLayout() {
if let layout = collectionViewLayout as? UICollectionViewFlowLayout {
layout.sectionInset = .init(top: padding, left: padding, bottom: padding, right: padding)
layout.estimatedItemSize = CGSize(width: view.frame.width, height: 50)
}
}
var headerView: InfoHeaderVC!
//HEADER COLLECTION VIEW
override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: headerID, for: indexPath) as? InfoHeaderVC
headerView.empaImageView.image = UIImage(named: empanada.image)
headerView.empaTitleLabel.text = empanada.name
headerView.empaDescriptionLabel.text = empanada.info
headerView.buttonX.addTarget(self, action: #selector(dismissVC), for: .touchUpInside)
headerView.buttonAddFavorite.addTarget(self, action: #selector(addButtonTapped), for: .touchUpInside)
return headerView!
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
return .init(width: view.frame.width, height: 350)
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 2
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if indexPath.item == 0 {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cell1, for: indexPath)
guard let cellOne = cell as? EmpaIngredientsListCell else {
fatalError("Wrong cell type for section 0. Expected CellTypeOne")
}
//INGREDIENT LIST
cellOne.empaIngredientList.ingredientList.append(contentsOf: empanada.ingredients)
cellOne.empaIngredientList.configure()
return cellOne
} else {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellID, for: indexPath) as! EmpaStepsCell
cell.pasos.append(contentsOf: empanada.pasos)
cell.titleHeaderLabel.text = "Step by Step"
cell.configure()
print (cell.pasos.count)
return cell
}
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if indexPath.item == 0 {
return .init(width: view.frame.width - 2 * padding, height: 300)
} else {
return .init(width: view.frame.width - 2 * padding, height: 300)
}
}
//OBJC FUNC
#objc func dismissVC() {
dismiss(animated: true)
}
//SAVE DATA
#objc func addButtonTapped() {
configureSaveToFavorites(empanada: empanada!)
}
}
Cell 1:
import UIKit
import SnapKit
class EmpaIngredientsListCell: UICollectionViewCell {
let empaIngredientList = EmpaIngredientsContainerView()
override init(frame: CGRect) {
super.init(frame: frame)
setupUI()
print(intrinsicContentSize)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
setNeedsLayout()
layoutIfNeeded()
let size = contentView.systemLayoutSizeFitting(layoutAttributes.size)
var frame = layoutAttributes.frame
frame.size.height = ceil(size.height)
layoutAttributes.frame = frame
return layoutAttributes
}
func setupUI() {
//contentView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(empaIngredientList)
empaIngredientList.snp.makeConstraints { (make) in
make.top.bottom.left.right.equalTo(self.contentView)
make.edges.equalTo(self.safeAreaLayoutGuide)
}
}
}
Hi I want to do drag drop using uicollectionview. When performing drag and drop it is moving contents i want to do it like in the photo. I want the box to carry itself. For example; when I drag the photo to 4 I should leave the red area with full measurements. Swap photo 6 to photo 1 like taking photo 3 to the left. I have researched in uicollectionview so much but I can’t find anything like this. Please help me
import UIKit
final class ViewController: UIViewController {
#IBOutlet weak var collectionView: UICollectionView!
var cellIds = ["image 1","image 2","image 3","image 4","image 5","6","7"]
override func viewDidLoad() {
super.viewDidLoad()
let flowLayout = collectionView.collectionViewLayout as! UICollectionViewFlowLayout
flowLayout.estimatedItemSize = CGSize(width: 200, height: 10)
let gestureRecognizer = UILongPressGestureRecognizer(target: self,
action: #selector(self.handleLongPress(gestureRecognizer:)))
collectionView.addGestureRecognizer(gestureRecognizer)
}
#objc func handleLongPress(gestureRecognizer: UILongPressGestureRecognizer) {
guard let view = gestureRecognizer.view else { return }
let location = gestureRecognizer.location(in: view)
switch gestureRecognizer.state {
case .began:
guard let selectedIndexPath = collectionView.indexPathForItem(at: location) else { break }
collectionView.beginInteractiveMovementForItem(at: selectedIndexPath)
case .changed:
collectionView.updateInteractiveMovementTargetPosition(location)
case .ended:
collectionView.endInteractiveMovement()
default:
collectionView.cancelInteractiveMovement()
}
}
}
extension ViewController: UICollectionViewDataSource,
UICollectionViewDelegateFlowLayout
{
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return cellIds.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "SampleCell", for: indexPath) as! SampleCell
let text = cellIds[indexPath.item]
cell.label.text = text
return cell
}
func collectionView(_ collectionView: UICollectionView, canMoveItemAt indexPath: IndexPath) -> Bool {
return true
}
func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
let text = cellIds.remove(at: sourceIndexPath.item)
cellIds.insert(text, at: destinationIndexPath.item)
collectionView.reloadData()
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if (indexPath.row==0)
{
return CGSize(width: 190, height: 100)
}
if (indexPath.row==1)
{
return CGSize(width: 190, height: 100)
}
if (indexPath.row==2)
{
return CGSize(width: 190, height: 400)
}
if (indexPath.row==3)
{
return CGSize(width: 400, height: 200)
}
return CGSize(width: 0, height: 0)
}
}
final class SampleCell: UICollectionViewCell {
#IBOutlet weak var label: UILabel!
override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
setNeedsLayout()
layoutIfNeeded()
let size = self.systemLayoutSizeFitting(layoutAttributes.size)
var newFrame = layoutAttributes.frame
// note: don't change the width
newFrame.size.height = ceil(size.height)
layoutAttributes.frame = newFrame
return layoutAttributes
}
}
picture
You can use UICollectionViewDragDelegate,
For multiple sections, in order to drag to the end item, we should add an extra item when dragging.
Sample Code:
ViewController:
import UIKit
enum CellModel {
case simple(text: String)
case availableToDropAtEnd
}
class SecondController: UIViewController {
private lazy var cellIdentifier = "cellIdentifier"
private lazy var supplementaryViewIdentifier = "supplementaryViewIdentifier"
private lazy var sections = 10
private lazy var itemsInSection = 2
private lazy var numberOfElementsInRow = 3
private lazy var data: [[CellModel]] = {
var count = 0
return (0 ..< sections).map { _ in
return (0 ..< itemsInSection).map { _ -> CellModel in
count += 1
return .simple(text: "cell \(count)")
}
}
}()
override func viewDidLoad() {
super.viewDidLoad()
let collectionViewFlowLayout = UICollectionViewFlowLayout()
collectionViewFlowLayout.minimumLineSpacing = 5
collectionViewFlowLayout.minimumInteritemSpacing = 5
let _numberOfElementsInRow = CGFloat(numberOfElementsInRow)
let allWidthBetwenCells = _numberOfElementsInRow == 0 ? 0 : collectionViewFlowLayout.minimumInteritemSpacing*(_numberOfElementsInRow-1)
let width = (view.frame.width - allWidthBetwenCells)/_numberOfElementsInRow
collectionViewFlowLayout.itemSize = CGSize(width: width, height: width)
collectionViewFlowLayout.headerReferenceSize = CGSize(width: 0, height: 40)
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: collectionViewFlowLayout)
collectionView.backgroundColor = .white
view.addSubview(collectionView)
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
collectionView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
collectionView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor).isActive = true
collectionView.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor).isActive = true
collectionView.register(CollectionViewCell.self, forCellWithReuseIdentifier: cellIdentifier)
collectionView.register(SupplementaryView.self,
forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader,
withReuseIdentifier: supplementaryViewIdentifier)
collectionView.dragInteractionEnabled = true
collectionView.reorderingCadence = .fast
collectionView.dropDelegate = self
collectionView.dragDelegate = self
collectionView.delegate = self
collectionView.dataSource = self
}
}
extension SecondController: UICollectionViewDelegate { }
extension SecondController: UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return data.count
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return data[section].count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellIdentifier, for: indexPath) as! CollectionViewCell
switch data[indexPath.section][indexPath.item] {
case .simple(let text):
cell.label?.text = text
cell.backgroundColor = .gray
case .availableToDropAtEnd:
cell.backgroundColor = UIColor.green.withAlphaComponent(0.3)
}
return cell
}
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
return collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: supplementaryViewIdentifier, for: indexPath)
}
}
extension SecondController: UICollectionViewDragDelegate {
func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
let itemProvider = NSItemProvider(object: "\(indexPath)" as NSString)
let dragItem = UIDragItem(itemProvider: itemProvider)
dragItem.localObject = data[indexPath.section][indexPath.row]
return [dragItem]
}
func collectionView(_ collectionView: UICollectionView, itemsForAddingTo session: UIDragSession, at indexPath: IndexPath, point: CGPoint) -> [UIDragItem] {
let itemProvider = NSItemProvider(object: "\(indexPath)" as NSString)
let dragItem = UIDragItem(itemProvider: itemProvider)
dragItem.localObject = data[indexPath.section][indexPath.row]
return [dragItem]
}
func collectionView(_ collectionView: UICollectionView, dragSessionWillBegin session: UIDragSession) {
var itemsToInsert = [IndexPath]()
(0 ..< data.count).forEach {
itemsToInsert.append(IndexPath(item: data[$0].count, section: $0))
data[$0].append(.availableToDropAtEnd)
}
collectionView.insertItems(at: itemsToInsert)
}
func collectionView(_ collectionView: UICollectionView, dragSessionDidEnd session: UIDragSession) {
var removeItems = [IndexPath]()
for section in 0..<data.count {
for item in 0..<data[section].count {
switch data[section][item] {
case .availableToDropAtEnd:
removeItems.append(IndexPath(item: item, section: section))
case .simple:
break
}
}
}
removeItems.forEach { data[$0.section].remove(at: $0.item) }
collectionView.deleteItems(at: removeItems)
}
}
extension SecondController: UICollectionViewDropDelegate {
func collectionView(_ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator) {
let destinationIndexPath: IndexPath
if let indexPath = coordinator.destinationIndexPath {
destinationIndexPath = indexPath
} else {
// useless, just in case
let section = collectionView.numberOfSections - 1
let row = collectionView.numberOfItems(inSection: section)
destinationIndexPath = IndexPath(row: row, section: section)
}
switch coordinator.proposal.operation {
case .move:
reorderItems(coordinator: coordinator, destinationIndexPath:destinationIndexPath, collectionView: collectionView)
default:
break
}
}
func collectionView(_ collectionView: UICollectionView, canHandle session: UIDropSession) -> Bool {
return true
}
func collectionView(_ collectionView: UICollectionView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UICollectionViewDropProposal {
// made the above logic useless
if collectionView.hasActiveDrag, destinationIndexPath != nil {
return UICollectionViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath)
}
else {
return UICollectionViewDropProposal(operation: .forbidden)
}
}
private
func reorderItems(coordinator: UICollectionViewDropCoordinator, destinationIndexPath: IndexPath, collectionView: UICollectionView) {
let items = coordinator.items
if items.count == 1, let item = items.first,
let sourceIndexPath = item.sourceIndexPath,
let localObject = item.dragItem.localObject as? CellModel {
collectionView.performBatchUpdates ({
data[sourceIndexPath.section].remove(at: sourceIndexPath.item)
data[destinationIndexPath.section].insert(localObject, at: destinationIndexPath.item)
collectionView.deleteItems(at: [sourceIndexPath])
collectionView.insertItems(at: [destinationIndexPath])
})
}
}
}
View:
import UIKit
class CollectionViewCell: UICollectionViewCell {
weak var label: UILabel?
override init(frame: CGRect) {
super.init(frame: frame)
clipsToBounds = true
let label = UILabel(frame: .zero)
label.contentMode = .scaleAspectFill
addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
label.topAnchor.constraint(equalTo: topAnchor).isActive = true
label.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
label.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
label.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
label.textAlignment = .center
label.textColor = .white
self.label = label
layer.borderWidth = 1
layer.borderColor = UIColor.white.cgColor
backgroundColor = .white
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func prepareForReuse() {
super.prepareForReuse()
label?.text = nil
backgroundColor = .white
}
}
class SupplementaryView: UICollectionReusableView {
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor.blue.withAlphaComponent(0.7)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
github link
I don't understand what's happening with my UICollectionView.
I'm apologise in advance because I think the answer is very simple
I just wan't to create a UICollectionView programmatically with 4 cells but 2 of them does not appear I don't know why
Here my code:
class NewsListViewController: UIViewController {
public var newsImagesCollectionView: UICollectionView!
override func viewWillLayoutSubviews() {
if self.newsImagesCollectionView == nil {
self.createNewsImagesCollectionView()
}
}
fileprivate func createNewsImagesCollectionView() {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
self.newsImagesCollectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
self.newsImagesCollectionView.dataSource = self
self.newsImagesCollectionView.delegate = self
self.newsImagesCollectionView.register(NewsImagesCollectionViewCell.self, forCellWithReuseIdentifier: NewsImagesCollectionViewCell.reuseIdentifier)
self.newsImagesCollectionView.backgroundColor = .yellow
self.newsImagesCollectionView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(self.newsImagesCollectionView)
self.newsImagesCollectionView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
self.newsImagesCollectionView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
self.newsImagesCollectionView.leftAnchor.constraint(equalTo: self.view.leftAnchor).isActive = true
self.newsImagesCollectionView.rightAnchor.constraint(equalTo: self.view.rightAnchor).isActive = true
}
}
extension NewsListViewController: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 4
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: NewsImagesCollectionViewCell.reuseIdentifier, for: indexPath) as! NewsImagesCollectionViewCell
switch indexPath.row {
case 0:
cell.imageView.backgroundColor = .purple
case 1:
cell.imageView.backgroundColor = .green
case 2:
cell.imageView.backgroundColor = .orange
case 3:
cell.imageView.backgroundColor = .red
default:
cell.imageView.backgroundColor = .blue
}
cell.imageView.contentMode = .scaleAspectFit
cell.imageView.image = #imageLiteral(resourceName: "euro")
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
print(self.view.frame.width)
return CGSize(width: self.view.frame.width / 4, height: self.view.frame.height)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
}
}
And I get this:
enter image description here
Thanks for your help
Does your collection view has the same size than your main view? because you calculating the size with the main view, is better if you use the collection view size.
return CGSize(width: collectionView.frame.width / 4, height: collectionView frame.height)
I am having an issue with implementing a horizontal scrollToItem collectionView. I have a collectionView (menuBar) that on tap of a cell the page collectionView will horizontally scroll to the appropriate cell. On tap of the menuBar collectionViewCell the app crashes. Not sure where the error is. Thanks in advance!
// controller class
class CurrentUserPlaceDetailsVC: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
var titleText: String?
let cellId = "cellId"
// UI elements
lazy var contentCollectionView: UICollectionView = {
let cv = UICollectionView()
cv.backgroundColor = UIColor.red
cv.layer.zPosition = 0
cv.translatesAutoresizingMaskIntoConstraints = false
cv.layer.masksToBounds = true
return cv
}()
override func viewDidLoad() {
super.viewDidLoad()
setupMenuBar()
setupCollectionView()
}
func scrollToMenuIndex(_ menuIndex: Int) {
let indexPath = IndexPath(item: menuIndex, section: 0)
contentCollectionView.scrollToItem(at: indexPath, at: UICollectionViewScrollPosition(), animated: true)
}
lazy var menuBar: MenuBar = {
let mb = MenuBar()
mb.currentUserPlaceDetailsVC = self
return mb
}()
private func setupMenuBar() {
view.addSubview(menuBar)
view.addConstraintsWithFormat("H:|[v0]|", views: menuBar)
view.addConstraintsWithFormat("V:|[v0(114)]", views: menuBar)
}
func setupCollectionView() {
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.minimumLineSpacing = 0
// layout.sectionInset = UIEdgeInsets(top: 0, left: 10, bottom: 10, right: 10)
layout.itemSize = CGSize(width: self.view.frame.width, height: self.view.frame.height)
let contentCollectionView:UICollectionView = UICollectionView(frame: self.view.frame, collectionViewLayout: layout)
contentCollectionView.dataSource = self
contentCollectionView.delegate = self
// register cells
contentCollectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "cellId")
contentCollectionView.backgroundColor = .white
// contentCollectionView.scrollIndicatorInsets = UIEdgeInsets(top:114, left: 10, bottom: 10, right: 10)
self.view.addSubview(contentCollectionView)
_ = contentCollectionView.anchor(view.topAnchor, left: view.leftAnchor, bottom: view.bottomAnchor, right: view.rightAnchor, topConstant: 114, leftConstant: 0, bottomConstant: 0, rightConstant: 0, widthConstant: 0, heightConstant: 0)
view.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
contentCollectionView.isPagingEnabled = true
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
print(scrollView.contentOffset.x)
menuBar.horizontalBarLeftAnchorConstraint?.constant = scrollView.contentOffset.x / 4
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 4
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let myCell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellId", for: indexPath)
let colors: [UIColor] = [.blue, .red, .yellow, .green]
myCell.backgroundColor = colors[indexPath.item]
return myCell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.width, height: view.frame.height)
}
}
// menu bar class
class MenuBar: UIView, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.minimumLineSpacing = 0
layout.headerReferenceSize = .zero
layout.sectionInset = .zero
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.backgroundColor = .green
cv.dataSource = self
cv.delegate = self
return cv
}()
let cellId = "cellId"
var currentUserPlaceDetailsVC: CurrentUserPlaceDetailsVC?
override init(frame: CGRect) {
super.init(frame: frame)
collectionView.register(MenuCell.self, forCellWithReuseIdentifier: cellId)
addSubview(collectionView)
addConstraintsWithFormat("H:|[v0]|", views: collectionView)
addConstraintsWithFormat("V:|[v0]|", views: collectionView)
let selectedIndexPath = IndexPath(item: 0, section: 0)
collectionView.selectItem(at: selectedIndexPath, animated: false, scrollPosition: UICollectionViewScrollPosition())
setupHorizontalBar()
}
var horizontalBarLeftAnchorConstraint: NSLayoutConstraint?
func setupHorizontalBar() {
let horizontalBarView = UIView()
horizontalBarView.backgroundColor = .white
horizontalBarView.translatesAutoresizingMaskIntoConstraints = false
addSubview(horizontalBarView)
horizontalBarLeftAnchorConstraint = horizontalBarView.leftAnchor.constraint(equalTo: self.leftAnchor)
horizontalBarLeftAnchorConstraint?.isActive = true
horizontalBarView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
horizontalBarView.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: 1/4).isActive = true
horizontalBarView.heightAnchor.constraint(equalToConstant: 4).isActive = true
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print(indexPath.item)
let x = CGFloat(indexPath.item) * frame.width / 4
horizontalBarLeftAnchorConstraint?.constant = x
UIView.animate(withDuration: 0.75, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
self.layoutIfNeeded()
}, completion: nil)
currentUserPlaceDetailsVC?.scrollToMenuIndex(indexPath.item)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 4
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! MenuCell
cell.imageView.image = UIImage(named: imageNames[indexPath.item])?.withRenderingMode(.alwaysTemplate)
cell.tintColor = UIColor.rgb(91, green: 14, blue: 13)
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: frame.width / 4, height: frame.height)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
In your comment you said:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'UICollectionView must be initialized with a non-nil layout parameter'. I believe there is something wrong in my func scrollToMenuIndex()... when I remove that func the app does not crash.
That happens, because you inside your View Controller your collectionView declared as lazy, that means it will be calculated on-demand.
The problem is in initializing your collectionView (reason: 'UICollectionView must be initialized with a non-nil layout parameter')
In your view controller replace UICollectionView initialization with this code:
lazy var contentCollectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.backgroundColor = UIColor.red
cv.layer.zPosition = 0
cv.translatesAutoresizingMaskIntoConstraints = false
cv.layer.masksToBounds = true
return cv
}()
P.S. As I wrote in my comment to your question, you have reference cycle. class MenuBar should hold weak reference to your view controller to prevent this.