Why is the CollectionView function called twice in Swift? - ios

I am having trouble with the function being called 2 times. Because of this, the value in PageView is not set properly because the index value is not correct. How can you control this correctly?
ViewController.swift
class ViewController: UIViewController {
#IBOutlet weak var collectionView: UICollectionView!
#IBOutlet weak var pageView: UIPageControl!
#IBOutlet var guideView: UIView!
var step : Int! = 0
var beforeStep : Int! = 0
var imageArray = [UIImage(named: "swipe1"),
UIImage(named: "swipe2"),
UIImage(named: "swipe3"),
UIImage(named: "swipe4"),
UIImage(named: "swipe5")]
override func viewDidLoad() {
super.viewDidLoad()
pageView.numberOfPages = imageArray.count
pageView.currentPage = 0
pageView.currentPageIndicatorTintColor = UIColor.color(.dacColor)
pageView.pageIndicatorTintColor = UIColor.gray
view.backgroundColor = UIColor(red: CGFloat(252.0/255.0), green: CGFloat(251.0/255.0), blue: CGFloat(245.0/255.0), alpha: CGFloat(1.0))
collectionView.backgroundColor = UIColor(red: CGFloat(252.0/255.0), green: CGFloat(251.0/255.0), blue: CGFloat(245.0/255.0), alpha: CGFloat(1.0))
}
}
extension ViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return imageArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
if let vc = cell.viewWithTag(111) as? UIImageView {
vc.image = imageArray[indexPath.row]
}
if let ab = guideView.viewWithTag(222) as? UIPageControl {
ab.currentPage = indexPath.row - 1
}
return cell
}
}
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let size = collectionView.frame.size
return CGSize(width: size.width, height: size.height)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
}
problematic function part
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
if let vc = cell.viewWithTag(111) as? UIImageView {
vc.image = imageArray[indexPath.row]
}
if let ab = guideView.viewWithTag(222) as? UIPageControl {
ab.currentPage = indexPath.row - 1
}
return cell
}
CollectionView in StoryBoard
View List in Storyboard
I want the PageController to be marked to match the location of the picture.
Has anyone solved the same problem as me?

collectionView(_:cellForItemAt:) is a collection view delegate method which tends to get called more than once, and there are a lot of factors that trigger the call like:
The number of visible cells you've got, depending on the height of the collection
When scrolling through cells to reuse them
Or triggers to change/refresh the collection like:
reloadData(), reloadItems(at:) or reloadSections(_:)
Perhaps a layoutIfNeeded() call from the view that contains this collection view.
and more...
The point is, the function getting called more than once is pretty normal. Your answer probably lies in "What triggers the call?"
EDIT:
To update your pageControl according to the index of the collection:
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let visibleRect = CGRect(origin: yourCollectionView.contentOffset, size: yourCollectionView.bounds.size)
let midPointOfVisibleRect = CGPoint(x: visibleRect.midX, y: visibleRect.midY)
if let visibleIndexPath = yourCollectionView.indexPathForItem(at: midPointOfVisibleRect) {
yourPageControl.currentPage = visibleIndexPath.row
}
}
If you're using a UIPageViewController just for the page indication, a UIPageControl should do. You might also have to set yourCollectionView.isPagingEnabled = true as that would be more appropriate for a page control.

In collection view go to layout and select custom:
class CustomCollectionViewFlowLayout: UICollectionViewFlowLayout
{
override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint
{
if let collectionViewBounds = self.collectionView?.bounds
{
let halfWidthOfVC = collectionViewBounds.size.width * 0.5
let proposedContentOffsetCenterX = proposedContentOffset.x + halfWidthOfVC
if let attributesForVisibleCells = self.layoutAttributesForElements(in: collectionViewBounds)
{
var candidateAttribute : UICollectionViewLayoutAttributes?
for attributes in attributesForVisibleCells
{
let candAttr : UICollectionViewLayoutAttributes? = candidateAttribute
if candAttr != nil
{
let a = attributes.center.x - proposedContentOffsetCenterX
let b = candAttr!.center.x - proposedContentOffsetCenterX
if fabs(a) < fabs(b)
{
candidateAttribute = attributes
}
}
else
{
candidateAttribute = attributes
continue
}
}
if candidateAttribute != nil
{
return CGPoint(x: candidateAttribute!.center.x - halfWidthOfVC, y: proposedContentOffset.y);
}
}
}
return CGPoint.zero
}
}
and then in delegate:
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
var visibleRect = CGRect()
visibleRect.origin = Collections.contentOffset
visibleRect.size = Collections.bounds.size
let visiblePoint = CGPoint(x: visibleRect.midX, y: visibleRect.midY)
guard let indexPath = Collections.indexPathForItem(at: visiblePoint) else { return }
print(indexPath.item)
}

Related

How to implement the "tags layout" using UICollectionView iOS Swift?

I am trying to implement the Tags Layout using UICollectionView.
It works fine but only at some places the word is not displayed properly (Cropped) (mostly at the end of the UICollectioView frame). I have attached the screenshot as well.
I followed this Setup a collectionView with "tag"-like cells and tried to implement the same but it didn't work for me.
class UserProfileTagsFlowLayout: UICollectionViewFlowLayout {
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
guard let attributesForElementsInRect = super.layoutAttributesForElements(in: rect) else { return nil }
guard var newAttributesForElementsInRect = NSArray(array: attributesForElementsInRect, copyItems: true) as? [UICollectionViewLayoutAttributes] else { return nil }
var leftMargin: CGFloat = 0.0;
for attributes in attributesForElementsInRect {
if (attributes.frame.origin.x == self.sectionInset.left) {
leftMargin = self.sectionInset.left
}
else {
var newLeftAlignedFrame = attributes.frame
newLeftAlignedFrame.origin.x = leftMargin
attributes.frame = newLeftAlignedFrame
}
leftMargin += attributes.frame.size.width + 8 // Makes the space between cells
newAttributesForElementsInRect.append(attributes)
}
return newAttributesForElementsInRect
}
}
I am using the below array to populate the data :
data = ["Kishor", "iOS", "Developer", "Software Engineer", "ABCD", "Coding", "Xcode", "I am an iOS Developer","JKLMNO" , "Testing Coding", "Development", "XYZ-XYZ-XYZCD", "Enable the Tag Layout", "Layouts Arrangement"]
But if you see in the screenshot, last word of the first line "ABCD", 'D' is cropped and also few of the words are cropped at the end of the frame.
Below is my UICollectionView (Inside the UITableViewCell)
class TagListCell: UITableViewCell {
#IBOutlet weak var tagListCollection: UICollectionView!
var data: [String] = []
override func awakeFromNib() {
super.awakeFromNib()
}
fileprivate func setCollectioView() {
self.setupCollectionnViewLayout()
self.tagListCollection.register(UINib(nibName: "TagListNameCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "TagListNameCollectionViewCell")
tagListCollection.delegate = self
tagListCollection.dataSource = self
}
private func setupCollectionnViewLayout() {
let layout = UserProfileTagsFlowLayout()
//layout.estimatedItemSize = CGSize.init(width: 50, height: 50)
layout.scrollDirection = .vertical
tagListCollection.collectionViewLayout = layout
}
// MARK: UIView functions
override func systemLayoutSizeFitting(_ targetSize: CGSize, withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, verticalFittingPriority: UILayoutPriority) -> CGSize {
self.tagListCollection.layoutIfNeeded()
let size = tagListCollection.collectionViewLayout.collectionViewContentSize
let newSize = CGSize(width: size.width, height: size.height + 1)
debugPrint("New Size : \(newSize)")
return newSize
}
func setData(data: [String]) {
self.setCollectioView()
self.data = data
}
}
//MARK:- UICollectionView Delegate and DataSource
extension TagListCell: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return data.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "TagListNameCollectionViewCell", for: indexPath) as? TagListNameCollectionViewCell {
cell.titleLable.text = data[indexPath.row]
return cell
}
else {
return UICollectionViewCell()
}
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let tag = data[indexPath.row]
let font = UIFont.systemFont(ofSize: 17)//UIFont(name: "Label Test Data", size: 16)!
let size = tag.size(withAttributes: [NSAttributedString.Key.font: font])
let dynamicCellWidth = size.width
return CGSize(width: dynamicCellWidth, height: 50)
}
// Space between rows
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 5
}
// Space between cells
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 5
}
}
I can't figure out how I can adjust the cell sizes without "breaking" rows count.

My UICollection View doesn't scroll after I set a custom layout to it

Before I use customized UICollectionViewFlowLayout, the scroll function works Fine, but when I set my layout to UICollectionView, it can't scroll anymore, but all contents display well because my layout works.
I want to make PinterestLayout, so I created my layout displayed as the following photo.
I am try to use this lay out into my Instagram-style app with UICollection header and cell
My Code for my layout is the following:
class MyLayout : UICollectionViewFlowLayout{
fileprivate let numberOfColumns = 2
weak var delegate : MyLayoutDelegate!
fileprivate var cache = [UICollectionViewLayoutAttributes]()
fileprivate var contentHeight : CGFloat = 0
fileprivate var contentWidth : CGFloat{
guard let collectionView = collectionView else {return 0.0}
let insets = collectionView.contentInset
return collectionView.bounds.width - (insets.left + insets.right)
}
override var collectionViewContentSize: CGSize{
return CGSize(width: contentWidth, height: contentHeight)
}
override init() {
super.init()
self.minimumLineSpacing = 1
self.minimumInteritemSpacing = 1
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func prepare() {
guard let collection = collectionView else {return}
let columnWidth = contentWidth / CGFloat(numberOfColumns)
let xOffset: [CGFloat] = [0,columnWidth]
var yOffset = [CGFloat](repeating: 190, count: numberOfColumns)
var columnToPlacePhoto = 0
for item in 0 ..< collection.numberOfItems(inSection: 0){
let indexPath = IndexPath(item: item, section: 0)
let photoHeight : CGFloat = delegate.collectionView(collection, numberOfColumns: numberOfColumns, heightForPhotoAtIndexPath: indexPath)
let frame = CGRect(x: xOffset[columnToPlacePhoto], y: yOffset[columnToPlacePhoto], width: columnWidth, height: photoHeight)
let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
attributes.frame = frame
self.cache.append(attributes)
yOffset[columnToPlacePhoto] = yOffset[columnToPlacePhoto] + photoHeight
columnToPlacePhoto = columnToPlacePhoto < (numberOfColumns - 1) ? (columnToPlacePhoto + 1) : 0
}
}
//
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let layoutAttributes = super.layoutAttributesForElements(in: rect)
layoutAttributes?.forEach({ (attribute) in
// I did this to keep attribute of Collection View Header, without this step, my Header doesn't show up
if attribute.representedElementKind == UICollectionView.elementKindSectionHeader{
cache.append(attribute)
}})
return cache
}
}
I assign my layout to Collection View
class ProfileVC: UICollectionViewController, UICollectionViewDelegateFlowLayout, {
override func viewDidLoad() {
super.viewDidLoad()
let layout = MyLayout()
layout.delegate = self
self.collectionView.collectionViewLayout = layout
self.collectionView.isScrollEnabled = true
}
I also set the number of items, number of sections, size of header and so on.
override func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return posts.count
}
// Set size of header
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
return CGSize(width: view.frame.width, height: 190)
}
// Set cell
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! ProfilePostCell
cell.post = posts[indexPath.item]
return cell
}
I am pretty sure my original Collection View works well
enter image description here
But after I add the following, it can not scroll. Please help me out of here, thanks in advance.
let layout = MyLayout()
layout.delegate = self
self.collectionView.collectionViewLayout = layout
self.collectionView.isScrollEnabled = true
I have solved this problem by adding.
contentHeight = max(contentHeight, frame.maxY)

UICollectionView Drag-N-Drop at swift

I'm making a project with UICollectionView at swift, I need to execute drag-and-drop with each boxes and swap their position. Each boxes has different data and different size, therefore user need to manipulate their layout, for that i need to enable users can drag-n-drop these Views. I wrote a code but when i start drag-n-drop, event will transfer their data instead of boxes itself. How can i solve this?
import UIKit
class ViewController: UIViewController {
var cellIds = ["1","2","3"]
#IBOutlet weak var collectionView: UICollectionView!
fileprivate var longPressGesture = UILongPressGestureRecognizer();
let cellSizes = [
CGSize(width:190, height:200),
CGSize(width:190, height:200),
CGSize(width:190, height:200),
]
#objc func longTap(_ gesture: UIGestureRecognizer){
switch(gesture.state) {
case .began:
guard let selectedIndexPath = collectionView.indexPathForItem(at: gesture.location(in: collectionView)) else {
return
}
collectionView.beginInteractiveMovementForItem(at: selectedIndexPath)
case .changed:
collectionView.updateInteractiveMovementTargetPosition(gesture.location(in: gesture.view!))
case .ended:
collectionView.endInteractiveMovement()
//doneBtn.isHidden = false
//longPressedEnabled = true
self.collectionView.reloadData()
default:
collectionView.cancelInteractiveMovement()
}
}
override func viewDidLoad() {
super.viewDidLoad()
longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(self.longTap(_:)))
collectionView.addGestureRecognizer(longPressGesture)
collectionView!.register(UICustomCollectionViewCell.self, forCellWithReuseIdentifier: "MenuCell")
self.automaticallyAdjustsScrollViewInsets = false
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
//top, left, bottom, right
return UIEdgeInsets(top: 130, left: 1, bottom: 0, right: 10)
}
}
extension ViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
// print("User tapped on \(cellIds[indexPath.row])")
}
func collectionView(_ collectionView: UICollectionView, canMoveItemAt indexPath: IndexPath) -> Bool {
return true
}
func userDidEndDragging(_ cell: UICollectionViewCell?) {
}
func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
let item = cellIds.remove(at: sourceIndexPath.item)
cellIds.insert(item, at: destinationIndexPath.item)
print(cellIds);
}
}
extension ViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return cellIds.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MenuCell", for: indexPath) as? UICustomCollectionViewCell
cell!.layer.cornerRadius=10
cell!.layer.masksToBounds=true
var id = cellIds[indexPath.row];
if (id == "1")
{
cell?.lblTest1.text = "amsterdam";
cell?.lblTest1.tag = 100
}
if (id == "2")
{
cell?.lblTest2.text = "madrid";
cell?.lblTest2.tag = 101
}
if (id == "3")
{
cell?.lblTest3.text = "istanbul";
cell?.lblTest3.tag = 102
}
return cell ?? UICollectionViewCell()
}
}
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return cellSizes[indexPath.item]
}
}
import UIKit
class UICustomCollectionViewCell: UICollectionViewCell {
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func prepareForReuse() {
super.prepareForReuse()
}
override init(frame: CGRect) {
super.init(frame: frame)
self.addSubview(lblTest1)
self.addSubview(lblTest2)
self.addSubview(lblTest3)
}
var lblTest1:UILabel = {
let label1 = UILabel(frame: CGRect(x:10, y: 20, width: 90 , height: 40))
label1.lineBreakMode = .byWordWrapping
label1.numberOfLines = 0
label1.tag = 100;
label1.tintColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1)
return label1
}()
var lblTest2:UILabel = {
let label2 = UILabel(frame: CGRect(x:10, y: 20, width: 90 , height: 40))
label2.lineBreakMode = .byWordWrapping
label2.numberOfLines = 0
label2.tag = 101;
label2.tintColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1)
return label2
}()
var lblTest3:UILabel = {
let label3 = UILabel(frame: CGRect(x:10, y: 20, width: 90 , height: 40))
label3.lineBreakMode = .byWordWrapping
label3.numberOfLines = 0
label3.tag = 102;
label3.tintColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1)
return label3
}()
override func layoutSubviews() {
super.layoutSubviews()
}
}
Here is how I achieved an elegant drag & drop collectionView:
My class where I have the collectionView
class FavoritesVC: UIViewController {
//MARK: IBOutlets
#IBOutlet weak var collectionView: UICollectionView!
//MARK: View Model
let viewModel = FavoritesViewModel()
//MARK: Properties
lazy var dragDropFlowLayout: UICollectionViewLayout = {
let flow = DragDropFlowLayout()
flow.scrollDirection = .vertical
flow.headerReferenceSize = CGSize(width: collectionView.frame.width, height: 0.5)
return flow
}()
//MARK: Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
setupCollectionView()
viewModel.competitionsUpdateCallback = {
self.collectionView.reloadData()
}
}
//MARK: Private functions
private func setupCollectionView() {
collectionView.delegate = self
collectionView.dataSource = self
collectionView.collectionViewLayout = dragDropFlowLayout
}
}
//MARK: CollectionView DataSource
extension FavoritesVC: UICollectionViewDataSource {
///...methods for the collectionViewDatasource which aren't relevant here
func collectionView(_ collectionView: UICollectionView, canMoveItemAt indexPath: IndexPath) -> Bool {
return true
}
func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destination: IndexPath) {
//save the new order of the objects in the data
...
}
}
//MARK: CollectionView Flow Layout Delegate
extension FavoritesVC: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let size = CGSize(width: collectionView.frame.width / 2 - 15, height: 150)
return size
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 10
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 10
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: 20, left: 10, bottom: 20, right: 10)
}
}
The Drag&Drop Custom Flow Layout
class DragDropFlowLayout: UICollectionViewFlowLayout {
var longPress: UILongPressGestureRecognizer!
var originalIndexPath: IndexPath?
var draggingIndexPath: IndexPath?
var draggingView: UIView?
var dragOffset = CGPoint.zero
override func prepare() {
super.prepare()
installGestureRecognizer()
}
func applyDraggingAttributes(attributes: UICollectionViewLayoutAttributes) {
attributes.alpha = 0
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let attributes = super.layoutAttributesForElements(in: rect)
attributes?.forEach { a in
if a.indexPath == draggingIndexPath {
if a.representedElementCategory == .cell {
self.applyDraggingAttributes(attributes: a)
}
}
}
return attributes
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
let attributes = super.layoutAttributesForItem(at: indexPath)
if let attributes = attributes, indexPath == draggingIndexPath {
if attributes.representedElementCategory == .cell {
applyDraggingAttributes(attributes: attributes)
}
}
return attributes
}
func installGestureRecognizer() {
if longPress == nil {
longPress = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress(_:)))
longPress.minimumPressDuration = 0.2
collectionView?.addGestureRecognizer(longPress)
}
}
#objc func handleLongPress(_ longPress: UILongPressGestureRecognizer) {
let location = longPress.location(in: collectionView!)
switch longPress.state {
case .began: startDragAtLocation(location: location)
case .changed: updateDragAtLocation(location: location)
case .ended: endDragAtLocation(location: location)
default:
break
}
}
func startDragAtLocation(location: CGPoint) {
guard let cv = collectionView else { return }
guard let indexPath = cv.indexPathForItem(at: location) else { return }
guard cv.dataSource?.collectionView?(cv, canMoveItemAt: indexPath) == true else { return }
guard let cell = cv.cellForItem(at: indexPath) else { return }
originalIndexPath = indexPath
draggingIndexPath = indexPath
draggingView = cell.snapshotView(afterScreenUpdates: true)
draggingView!.frame = cell.frame
cv.addSubview(draggingView!)
dragOffset = CGPoint(x: draggingView!.center.x - location.x, y: draggingView!.center.y - location.y)
draggingView?.layer.shadowPath = UIBezierPath(rect: draggingView!.bounds).cgPath
draggingView?.layer.shadowColor = UIColor.black.cgColor
draggingView?.layer.shadowOpacity = 0.8
draggingView?.layer.shadowRadius = 10
invalidateLayout()
UIView.animate(withDuration: 0.4, delay: 0, usingSpringWithDamping: 0.55, initialSpringVelocity: 0, options: [], animations: {
self.draggingView?.transform = CGAffineTransform(scaleX: 1.1, y: 1.1)
}, completion: nil)
}
func updateDragAtLocation(location: CGPoint) {
guard let view = draggingView else { return }
guard let cv = collectionView else { return }
view.center = CGPoint(x: location.x + dragOffset.x, y: location.y + dragOffset.y)
if let newIndexPath = cv.indexPathForItem(at: location) {
cv.moveItem(at: draggingIndexPath!, to: newIndexPath)
draggingIndexPath = newIndexPath
}
}
func endDragAtLocation(location: CGPoint) {
guard let dragView = draggingView else { return }
guard let indexPath = draggingIndexPath else { return }
guard let cv = collectionView else { return }
guard let datasource = cv.dataSource else { return }
let targetCenter = datasource.collectionView(cv, cellForItemAt: indexPath).center
UIView.animate(withDuration: 0.4, delay: 0, usingSpringWithDamping: 0.55, initialSpringVelocity: 0, options: [], animations: {
dragView.center = targetCenter
dragView.transform = .identity
dragView.layer.shadowColor = UIColor.white.cgColor
dragView.layer.shadowOpacity = 0
dragView.layer.shadowRadius = 0
}) { (completed) in
if indexPath != self.originalIndexPath! {
datasource.collectionView?(cv, moveItemAt: self.originalIndexPath!, to: indexPath)
}
dragView.removeFromSuperview()
self.draggingIndexPath = nil
self.draggingView = nil
self.invalidateLayout()
}
}
}

First cell and last cell are changing each other in UICollectionView

I have MenuItemCollectionView and FoodItemColletionView and I am setting FoodItemCollectionView in MenuItemColletionViewCell.Error happened in first MenuItemCell and last MenuItemCell are changing each other.But sometimes they are appeared right place.I have no idea to fix this error
This is MenuItemCollectionView
func numberOfSections(in collectionView: UICollectionView) -> Int {
print("Food Array Count = \(self.foodArray.count)")
return self.foodArray.count
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
switch kind {
case UICollectionElementKindSectionHeader:
let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: RESUABLE_VIEW, for: indexPath) as! CollectionReusableView
headerView.header.text = nameArray[indexPath.section]
return headerView
default:
assert(false, "Unexpected element kind")
}
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = UIScreen.main.bounds.width
let height = UIScreen.main.bounds.height/3
return CGSize(width: width, height: height)
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ITEM_CELL, for: indexPath) as! MenuItemCollectionViewCell
cell.foodArray.removeAll()
cell.foodArray = self.foodArray[nameArray[indexPath.section]]!
print("Cell COol index = \(indexPath.section) , ARrray count = \(cell.foodArray.count)")
return cell
}
This is MenuItemCollectionViewCell and I set up FoodItemColletionView in this
import UIKit
import SDWebImage
class MenuItemCollectionViewCell: UICollectionViewCell ,UICollectionViewDelegate,UICollectionViewDataSource{
#IBOutlet weak var foodItemColletion: UICollectionView!
var foodArray :[Food] = []
var imageArray : [UIImage?] = []
let FOOD_ITEM_CELL : String = "FoodCell"
override func awakeFromNib() {
foodItemColletion.delegate = self
foodItemColletion.dataSource = self
super.awakeFromNib()
}
override func prepareForReuse() {
foodArray.removeAll()
super.prepareForReuse()
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
for food in foodArray{
print("Title Menu item \(food.title)")
}
return foodArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: FOOD_ITEM_CELL, for: indexPath) as! FoodItemCollectionViewCell
let foodObj = foodArray[indexPath.row]
cell.foodLabel.text = foodObj.title
cell.foodPrice.text = foodObj.price
cell.foodImage.sd_setImage(with: URL(string: "\(URLS.IMG_URL)\(foodObj.imageUrl)"), placeholderImage: nil, options: .refreshCached, progress: nil, completed: nil)
print("Title Menu Item Cell \(indexPath.item) = \(foodObj.title)")
return cell
}
}
This is FoodItemColletionViewCell
import UIKit
class FoodItemCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var foodImage: UIImageView!
#IBOutlet weak var foodLabel: UILabel!
#IBOutlet weak var foodPrice: UILabel!
#IBOutlet weak var cardView: CardView!
override func awakeFromNib() {
foodImage.layer.masksToBounds = true
foodImage.roundCorners(corners: [.topLeft,.topRight], radius: 5)
cardView.setElevation(points: 1.5)
layer.shouldRasterize = true
layer.rasterizationScale = UIScreen.main.scale
cardView.layer.cornerRadius = 5
}
override func prepareForReuse() {
super.prepareForReuse()
foodImage.image = nil
}
}
extension UIImageView{
func roundCorners(corners:UIRectCorner, radius: CGFloat) {
let path = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
let mask = CAShapeLayer()
mask.path = path.cgPath
self.layer.mask = mask
}
}
This is top screen that is actually what I want
This is bottom screen that is actually want I want too
But when i scroll fast again from top to bottom, first cell moved to last cell and last cell moved to first cell.This is what happened what i want to fix
In MenuItemCollectionView change this method:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ITEM_CELL, for: indexPath) as! MenuItemCollectionViewCell
cell.foodArray.removeAll()
cell.foodArray = self.foodArray[nameArray[indexPath.section]]!
// add this, or else because of reusing the old data will be presented, even though you have set the new one
cell.foodItemColletion.reloadData()
print("Cell COol index = \(indexPath.section) , ARrray count = \(cell.foodArray.count)")
return cell
}

Swift How to get the centre cell in a collectionView and resize it when scrolling?

I have a horizontal collectionView and i would like to manage that when scrolling it, the centre cell becomes larger. So, each time a cell is the center cell it will be larger than the others. Check the image beneath.
Can anyone help me with the best way to do this? Thanks in advance!
My code so far:
import UIKit
class ViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {
#IBOutlet var horizontalCollectionView: UICollectionView!
#IBOutlet var verticalCollectionView: UICollectionView!
var horizontal = ["1","2","3","4","5","6","7","8","9","10","1","2","3","4","5","6","7","8","9","10"]
var horizontalCellSize = CGSize(width: 50, height: 50)
var horizontalLargeCellSize = CGSize(width: 80, height: 80)
var centerCell = UICollectionViewCell()
var cellIsInCenter = false
var myIndexPath = IndexPath()
override func viewDidLoad() {
super.viewDidLoad()
}
// When the main view's dimensions change this will re-layout the collection view
override func viewWillLayoutSubviews() {
verticalCollectionView.collectionViewLayout.invalidateLayout()
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return horizontal.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if collectionView == horizontalCollectionView {
let horizontalCell = collectionView.dequeueReusableCell(withReuseIdentifier: "HorizontalCell", for: indexPath) as! HorizontalCell
horizontalCell.backgroundColor = UIColor.blue
horizontalCell.textLabel.text = horizontal[indexPath.row]
return horizontalCell
}
else {
let verticalCell = collectionView.dequeueReusableCell(withReuseIdentifier: "VerticalCell", for: indexPath) as! VerticalCell
verticalCell.backgroundColor = UIColor.red
verticalCell.textLabel.text = horizontal[indexPath.row]
return verticalCell
}
}
// MARK: UIScrollViewDelegate
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView == self.horizontalCollectionView {
let horizontalScrollPosition = scrollView.contentOffset.x * 35
self.verticalCollectionView.delegate = nil
self.verticalCollectionView.contentOffset = CGPoint(x: 0, y: horizontalScrollPosition)
self.verticalCollectionView.delegate = self
}
if scrollView == self.verticalCollectionView {
let verticalScrollPosition = scrollView.contentOffset.y
self.horizontalCollectionView.delegate = nil
self.horizontalCollectionView.contentOffset = CGPoint(x: verticalScrollPosition, y: 0)
self.horizontalCollectionView.delegate = self
}
}
// MARK: CollectionViewLayout
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
if collectionView == verticalCollectionView {
let size = CGSize(width: collectionView.bounds.width, height: collectionView.bounds.height)
return size
}
if indexPath.item == 3 {
return CGSize(width: 70, height: 70)
}
return CGSize(width: 50, height: 50)
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
self.verticalCollectionView.scrollToItem(at: indexPath, at: .centeredVertically, animated: true)
print("\(indexPath)")
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
var closestCell: UICollectionViewCell = self.horizontalCollectionView.visibleCells[0]
for cell in self.horizontalCollectionView.visibleCells as [UICollectionViewCell] {
let closestCellDelta = abs(closestCell.center.x - self.horizontalCollectionView.bounds.size.width / 2.0 - self.horizontalCollectionView.contentOffset.x)
let cellDelta = abs(cell.center.x - self.horizontalCollectionView.bounds.size.width/2.0 - self.horizontalCollectionView.contentOffset.x)
if (cellDelta < closestCellDelta){
closestCell = cell
self.centerCell = cell
self.cellIsInCenter = true
horizontalCellSize = CGSize(width: 80, height: 50)
}
}
let indexPath = self.horizontalCollectionView.indexPath(for: closestCell)
self.horizontalCollectionView.scrollToItem(at: indexPath!, at: .centeredHorizontally , animated: true)
if closestCell.backgroundColor == UIColor.black {
closestCell.backgroundColor = UIColor.blue
}
horizontalCollectionView.reloadItems(at: [self.horizontalCollectionView.indexPath(for: closestCell)!])
closestCell.backgroundColor = UIColor.black
}
}
Try this out:
func collectionView(_ collectionView: UICollectionView,layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
if collectionView == verticalCollectionView {
let size = CGSize(width: collectionView.bounds.width, height: collectionView.bounds.height)
return size
}
let point = CGPoint(x: self.view.frame.size.width/2.0, y:35 )
//or you can try this code also
//let point = CGPoint(x: UIScreen.main.bounds.width/2, y:35 )
print(point)
let centerIndex = self.collectionView.indexPathForItemAtPoint(point)
if indexPath.item == centerIndex {
return CGSize(width: 70, height: 70)
}
return CGSize(width: 50, height: 50)
}
This code gives you the index of the item at the center of the collection View.
Hope this helps

Resources