Custom Drag&Drop UICollectionViewLayout snap to a grid - swift

I am working on a App where I can keep track of my substitutions of my youth soccer team.
Following a tutorial by Payal Gupta on drag & drop into collections & tables I managed to get a drag and drop between two collection views (PlayersOntoTheField and Substitutes) working Screenshot.
When I drag a substitute player into my playground it should now snap to the predefined team line-up (e.g. 3-2-1 in the screenshot). Is it possible to get such behavior with a custom UICollectionViewLayout or does anyone have another suggestion?
Thank you you very much in advance for any help.
Danny
//Based on a work by:
//Payal Gupta (https://github.com/pgpt10/DragAndDrop-CollectionView)
import UIKit
class ViewController: UIViewController
{
private var substitutes = ["player1", "player2", "player3", "player4"]
private var players = [String]()
#IBOutlet weak var substitutesCollectionView: UICollectionView!
#IBOutlet weak var playersCollectionView: UICollectionView!
override func viewDidLoad()
{
super.viewDidLoad()
//SubstitutesCollectionView drag and drop configuration
self.substitutesCollectionView.dragInteractionEnabled = true
self.substitutesCollectionView.dragDelegate = self
self.substitutesCollectionView.dropDelegate = self
//PlayersCollectionView drag and drop configuration
self.playersCollectionView.dragInteractionEnabled = true
self.playersCollectionView.dropDelegate = self
self.playersCollectionView.dragDelegate = self
self.playersCollectionView.reorderingCadence = .fast //default value - .immediate
}
//MARK: Private Methods
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
{
var dIndexPath = destinationIndexPath
if dIndexPath.row >= collectionView.numberOfItems(inSection: 0)
{
dIndexPath.row = collectionView.numberOfItems(inSection: 0) - 1
}
collectionView.performBatchUpdates({
if collectionView === self.playersCollectionView
{
self.players.remove(at: sourceIndexPath.row)
self.players.insert(item.dragItem.localObject as! String, at: dIndexPath.row)
}
else
{
self.substitutes.remove(at: sourceIndexPath.row)
self.substitutes.insert(item.dragItem.localObject as! String, at: dIndexPath.row)
}
collectionView.deleteItems(at: [sourceIndexPath])
collectionView.insertItems(at: [dIndexPath])
})
coordinator.drop(items.first!.dragItem, toItemAt: dIndexPath)
}
}
private func copyItems(coordinator: UICollectionViewDropCoordinator, destinationIndexPath: IndexPath, collectionView: UICollectionView)
{
collectionView.performBatchUpdates({
var indexPaths = [IndexPath]()
for (index, item) in coordinator.items.enumerated()
{
let indexPath = IndexPath(row: destinationIndexPath.row + index, section: destinationIndexPath.section)
if collectionView === self.playersCollectionView
{
self.players.insert(item.dragItem.localObject as! String, at: indexPath.row)
}
else
{
self.substitutes.insert(item.dragItem.localObject as! String, at: indexPath.row)
}
indexPaths.append(indexPath)
}
collectionView.insertItems(at: indexPaths)
})
}
}
// MARK: - UICollectionViewDataSource Methods
extension ViewController : UICollectionViewDataSource
{
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
{
return collectionView == self.substitutesCollectionView ? self.substitutes.count : self.players.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
if collectionView == self.substitutesCollectionView
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell1", for: indexPath) as! MyCollectionViewCell
cell.customImageView?.image = UIImage(named: self.substitutes[indexPath.row])
cell.customLabel.text = self.substitutes[indexPath.row].capitalized
return cell
}
else
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell2", for: indexPath) as! MyCollectionViewCell
cell.customImageView?.image = UIImage(named: self.players[indexPath.row])
cell.customLabel.text = self.players[indexPath.row].capitalized
return cell
}
}
}
// MARK: - UICollectionViewDragDelegate Methods
extension ViewController : UICollectionViewDragDelegate
{
func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem]
{
let item = collectionView == substitutesCollectionView ? self.substitutes[indexPath.row] : self.players[indexPath.row]
let itemProvider = NSItemProvider(object: item as NSString)
let dragItem = UIDragItem(itemProvider: itemProvider)
dragItem.localObject = item
return [dragItem]
}
func collectionView(_ collectionView: UICollectionView, itemsForAddingTo session: UIDragSession, at indexPath: IndexPath, point: CGPoint) -> [UIDragItem]
{
let item = collectionView == substitutesCollectionView ? self.substitutes[indexPath.row] : self.players[indexPath.row]
let itemProvider = NSItemProvider(object: item as NSString)
let dragItem = UIDragItem(itemProvider: itemProvider)
dragItem.localObject = item
return [dragItem]
}
func collectionView(_ collectionView: UICollectionView, dragPreviewParametersForItemAt indexPath: IndexPath) -> UIDragPreviewParameters?
{
if collectionView == substitutesCollectionView
{
let previewParameters = UIDragPreviewParameters()
previewParameters.visiblePath = UIBezierPath(rect: CGRect(x: 15, y: 5, width: 30, height: 30))
return previewParameters
}
return nil
}
}
// MARK: - UICollectionViewDropDelegate Methods
extension ViewController : UICollectionViewDropDelegate
{
func collectionView(_ collectionView: UICollectionView, canHandle session: UIDropSession) -> Bool
{
return session.canLoadObjects(ofClass: NSString.self)
}
func collectionView(_ collectionView: UICollectionView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UICollectionViewDropProposal
{
if collectionView === self.substitutesCollectionView
{
if collectionView.hasActiveDrag
{
return UICollectionViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath)
}
else
{
return UICollectionViewDropProposal(operation: .forbidden)
}
}
else
{
if collectionView.hasActiveDrag
{
return UICollectionViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath)
}
else
{
return UICollectionViewDropProposal(operation: .copy, intent: .insertAtDestinationIndexPath)
}
}
}
func collectionView(_ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator)
{
let destinationIndexPath: IndexPath
if let indexPath = coordinator.destinationIndexPath
{
destinationIndexPath = indexPath
}
else
{
// Get last index path of table view.
let section = collectionView.numberOfSections - 1
let row = collectionView.numberOfItems(inSection: section)
destinationIndexPath = IndexPath(row: row, section: section)
}
switch coordinator.proposal.operation
{
case .move:
self.reorderItems(coordinator: coordinator, destinationIndexPath:destinationIndexPath, collectionView: collectionView)
break
case .copy:
self.copyItems(coordinator: coordinator, destinationIndexPath: destinationIndexPath, collectionView: collectionView)
default:
return
}
}
}

That my workaround for custom UICollectionViewLayout so far:
import UIKit
class LineUp_3_2_1View: UICollectionViewLayout {
private var center: CGPoint!
private var itemSize: CGSize!
private var radiusOfCircleViews: CGFloat!
private var numberOfItems: Int!
override func prepare() {
super.prepare()
guard let collectionView = collectionView else { return }
radiusOfCircleViews = CGFloat(30.0)
itemSize = CGSize(width: radiusOfCircleViews * 2, height: radiusOfCircleViews * 2)
center = CGPoint(x: collectionView.bounds.midX, y: collectionView.bounds.midY)
numberOfItems = collectionView.numberOfItems(inSection: 0)
}
override var collectionViewContentSize: CGSize {
return collectionView!.bounds.size
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
if (indexPath.item == 0) {attributes.center = CGPoint(x: 169, y: 344)}
if (indexPath.item == 1) {attributes.center = CGPoint(x: 46, y: 250)}
if (indexPath.item == 2) {attributes.center = CGPoint(x: 169, y: 250)}
if (indexPath.item == 3) {attributes.center = CGPoint(x: 287, y: 250)}
if (indexPath.item == 4) {attributes.center = CGPoint(x: 80, y: 156)}
if (indexPath.item == 5) {attributes.center = CGPoint(x: 253, y: 156)}
if (indexPath.item == 6) {attributes.center = CGPoint(x: 169, y: 62)}
attributes.size = itemSize
return attributes
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
return (0 ..< collectionView!.numberOfItems(inSection: 0))
.flatMap { item -> UICollectionViewLayoutAttributes? in // `compactMap` in Xcode 9.3
self.layoutAttributesForItem(at: IndexPath(item: item, section: 0))
}
}
}

Related

My pictures wont show when I build and run uicollection view

import UIKit
class HomePageCell: UICollectionViewCell {
#IBOutlet private weak var containerView: UIView!
#IBOutlet weak var photos: UIImageView!
#IBOutlet weak var songname: UILabel!
#IBOutlet weak var songmaker: UILabel!
//#IBOutlet weak var pfppic: UIImageView!
#IBOutlet weak var viewcount: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
containerView.layer.cornerRadius = 6
containerView.layer.masksToBounds = true
}
var photo: Photo? {
didSet {
if let photo = photo {
photos.image = photo.image
songname.text = photo.name
songmaker.text = photo.makername
viewcount.text = photo.likecount
}
}
}
override func prepareForReuse() {
super.prepareForReuse()
self.photos.image = nil
}
}
import UIKit
protocol PinterestLayoutDelegate: AnyObject {
func collectionView(
_ collectionView: UICollectionView,
heightForPhotoAtIndexPath indexPath: IndexPath) -> CGFloat
}
class PinterestLayout: UICollectionViewLayout {
// 1
weak var delegate: PinterestLayoutDelegate?
// 2
private let numberOfColumns = 2
private let cellPadding: CGFloat = 6
// 3
private var cache: [UICollectionViewLayoutAttributes] = []
// 4
private var contentHeight: CGFloat = 0
private var contentWidth: CGFloat {
guard let collectionView = collectionView else {
return 0
}
let insets = collectionView.contentInset
return collectionView.bounds.width - (insets.left + insets.right)
}
// 5
override var collectionViewContentSize: CGSize {
return CGSize(width: contentWidth, height: contentHeight)
}
override func prepare() {
// 1
guard
cache.isEmpty,
let collectionView = collectionView
else {
return
}
// 2
let columnWidth = contentWidth / CGFloat(numberOfColumns)
var xOffset: [CGFloat] = []
for column in 0..<numberOfColumns {
xOffset.append(CGFloat(column) * columnWidth)
}
var column = 0
var yOffset: [CGFloat] = .init(repeating: 0, count: numberOfColumns)
// 3
for item in 0..<collectionView.numberOfItems(inSection: 0) {
let indexPath = IndexPath(item: item, section: 0)
// 4
let photoHeight = delegate?.collectionView(
collectionView,
heightForPhotoAtIndexPath: indexPath) ?? 180
let height = cellPadding * 2 + photoHeight
let frame = CGRect(x: xOffset[column],
y: yOffset[column],
width: columnWidth,
height: height)
let insetFrame = frame.insetBy(dx: cellPadding, dy: cellPadding)
// 5
let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
attributes.frame = insetFrame
cache.append(attributes)
// 6
contentHeight = max(contentHeight, frame.maxY)
yOffset[column] = yOffset[column] + height
column = column < (numberOfColumns - 1) ? (column + 1) : 0
}
}
override func layoutAttributesForElements(in rect: CGRect)
-> [UICollectionViewLayoutAttributes]? {
var visibleLayoutAttributes: [UICollectionViewLayoutAttributes] = []
// Loop through the cache and look for items in the rect
for attributes in cache {
if attributes.frame.intersects(rect) {
visibleLayoutAttributes.append(attributes)
}
}
return visibleLayoutAttributes
}
override func layoutAttributesForItem(at indexPath: IndexPath)
-> UICollectionViewLayoutAttributes? {
return cache[indexPath.item]
}
}
extension HomePageViewController: PinterestLayoutDelegate {
func collectionView(
_ collectionView: UICollectionView,
heightForPhotoAtIndexPath indexPath:IndexPath) -> CGFloat {
return photos[indexPath.item].image.size.height
}
}
import UIKit
struct Photo {
var image: UIImage
var name: String
var makername: String
var likecount: String
init(image: UIImage, name: String, makername: String, likecount: String) {
self.image = image
self.name = name
self.makername = makername
self.likecount = likecount
}
init?(dictionary: [String: String]) {
guard
let name = dictionary["Name"],
let makername = dictionary["Makername"],
let photo = dictionary["Photo"],
let likecount = dictionary["Likecount"],
let image = UIImage(named: photo)
else {
return nil
}
self.init(image: image, name: name, makername: makername, likecount: likecount)
}
static func allPhotos() -> [Photo] {
var photos: [Photo] = []
guard
let URL = Bundle.main.url(forResource: "Photos", withExtension: "plist"),
let photosFromPlist = NSArray(contentsOf: URL) as? [[String:String]]
else {
return photos
}
for dictionary in photosFromPlist {
if let photo = Photo(dictionary: dictionary) {
photos.append(photo)
}
}
return photos
}
}
import UIKit
final class HomePageViewController: UICollectionViewController {
// MARK: - Properties
var photos = Photo.allPhotos()
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
override func viewDidLoad() {
super.viewDidLoad()
if let layout = collectionView?.collectionViewLayout as? PinterestLayout {
layout.delegate = self
}
self.collectionView.backgroundColor = .white
self.collectionView.dataSource = self
self.collectionView.delegate = self
let iconImage = UIImage(named: "name")
let imageView = UIImageView(image: iconImage)
self.navigationItem.titleView = imageView
}
private let sectionInsets2 = UIEdgeInsets(
top: 50.0,
left: 20.0,
bottom: 50.0,
right: 20.0)
private let itemsPerRow: CGFloat = 1
private let itemsPerRow1: CGFloat = 1
private let itemsPerRow2: CGFloat = 2
// MARK: - Private
override func numberOfSections(in collectionView: UICollectionView) -> Int {
return 3
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if section == 0 {
return 1
} else if section == 1 {
return 1
} else {
return photos.count
}
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if indexPath.section == 0 {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "HomePageHeaderCell", for: indexPath) as! HomePageHeaderCell
return cell
} else if indexPath.section == 1 {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "HomePageProfileBar", for: indexPath) as! HomePageProfileBar
return cell
} else if indexPath.section == 2 {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "HomePageCell", for: indexPath) as! HomePageCell
cell.photo = photos[indexPath.item]
//cell.pfppic.image = UIImage(named: "pfppic")
//cell.pfppic.layer.cornerRadius = 15.0
return cell
}
return UICollectionViewCell()
}
}
extension HomePageViewController: UICollectionViewDelegateFlowLayout {
// 1
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
// 2H
if indexPath.section == 0 {
let paddingSpace = 0 * (itemsPerRow + 1)
let availableWidth = view.frame.width - paddingSpace
let widthPerItem = availableWidth / itemsPerRow
return CGSize(width: widthPerItem, height: 50)
} else if indexPath.section == 1 {
let paddingSpace1 = 0 * (itemsPerRow1 + 1)
let availableWidth1 = view.frame.width - paddingSpace1
let widthPerItem1 = availableWidth1 / itemsPerRow1
return CGSize(width: widthPerItem1, height: 100)
} else {
let paddingSpace2 = sectionInsets2.left * (itemsPerRow2 + 1)
let availableWidth2 = view.frame.width - paddingSpace2
let widthPerItem2 = availableWidth2 / itemsPerRow2
return CGSize(width: widthPerItem2, height: widthPerItem2)
}
}
// 3
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return sectionInsets2
}
// 4
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return sectionInsets2.left
}
}
I have the array built and I don't know why any pictures won't show

Some UICollectionViewDropDelegateMethods get not called

I have an iPad application where you can drag images from Safari into a collection view. I also want to to drag and drop within the collection view to rearrange the collection View cells.
The drag and drop from safari is working. Dragging a cell within the collection view is also possible, but dropping is not possible. I took a look at the UICollectionViewDropDelegate methods "canLoadObjects", "sessionDidUpdate" and "performDrop" which get called in this order. I made another simple collection view app with drag and drop, so I know what to do.
I recognized that in the simple app, when I drag the cell onto another place "canLoadObjects" get called. I confirmed that with a breakpoint. In the other app this does not happen. Instead only "performDrop" gets called, but not "canLoadObjects" and "sessionDidUpdate". I set the delegate correctly (otherwise "performDrop would not get called either).
Does anybody know why some delegate methods doesn't get called? Thanks!
import UIKit
class ImageGalleryViewController: UIViewController {
var cellWidthScale: CGFloat = 1.0
var currentCellSize = CGSize()
var imageFetcher : ImageFetcher!
var loadAllImages = false
var tappedCellIndex = 0
#IBOutlet weak var collectionView: UICollectionView!{
didSet{
collectionView.delegate = self
collectionView.dataSource = self
collectionView.dragInteractionEnabled = true
collectionView.dragDelegate = self
collectionView.dropDelegate = self
collectionView.addGestureRecognizer(UIPinchGestureRecognizer(target: self, action: #selector(adjustCellWidth(with:))))
}
}
var userIsPinching = false
var imageDataTuple : [(NSURL?, CGSize)]? {
didSet{
if loadAllImages == true{
collectionView?.reloadData()
loadAllImages = false
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
collectionView.register(UINib(nibName: "ImageCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "ImageCell")
}
#objc func adjustCellWidth(with recognizer: UIPinchGestureRecognizer){
switch recognizer.state {
case .changed:
userIsPinching = true
cellWidthScale *= recognizer.scale
recognizer.scale = 1.0
collectionView.collectionViewLayout.invalidateLayout()
default: break
}
}
}
//MARK: UICollectionViewDelegate
extension ImageGalleryViewController: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout{
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return imageDataTuple?.count ?? 0
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ImageCell", for: indexPath)
if let imageCell = cell as? ImageCollectionViewCell{
imageCell.imageView.image = nil
imageCell.spinner.startAnimating()
if let url = imageDataTuple?[indexPath.row].0{
imageCell.imageUrl = url
//never fetch data on the main queue
DispatchQueue.global(qos: .userInitiated).async {
let urlContent = try? Data(contentsOf: (url as URL).imageURL)
//update the UI always on the main queue
DispatchQueue.main.async {
//check if the url is really the one we want to show
if let imageData = urlContent, url == imageCell.imageUrl, let image = UIImage(data: imageData){
imageCell.spinner.stopAnimating()
imageCell.imageView.image = image
}
else{
imageCell.spinner.stopAnimating()
imageCell.imageView.image = UIImage(systemName: "xmark.octagon.fill")
imageCell.imageView.tintColor = UIColor.red
imageCell.imageView.sizeToFit()
}
}
}
}
}
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if let checkedImageData = imageDataTuple{
let imageHeightToWidth = checkedImageData[indexPath.row].1.height/checkedImageData[indexPath.row].1.width
let calculatedWidth = (self.view.frame.width/4) * cellWidthScale
currentCellSize = CGSize(width: calculatedWidth, height: calculatedWidth * imageHeightToWidth)
return currentCellSize
}
return CGSize.zero
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
tappedCellIndex = indexPath.item
performSegue(withIdentifier: "ShowImage", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "ShowImage"{
if let destVC = segue.destination as? DetailImageViewController{
destVC.imageUrl = imageDataTuple![tappedCellIndex].0
}
}
}
}
//MARK: UICollectionViewDropDelegate
extension ImageGalleryViewController: UICollectionViewDropDelegate{
//1. Can the dropDelegate handle the object?
func dropInteraction(_ interaction: UIDropInteraction, canHandle session: UIDropSession) -> Bool {
//requirement: only drop with image and url should be handled
return session.canLoadObjects(ofClass: NSURL.self)
}
//2. update the session. Return a propsal in which manner the object schould be dropped (.copy; .move...)
func dropInteraction(_ interaction: UIDropInteraction, sessionDidUpdate session: UIDropSession) -> UIDropProposal {
//Es wird überprüft, ob der collectionView auf dem gedroppt werden soll der ist, von dem das DragItem stammt
let isSelf = (session.localDragSession?.localContext as? UICollectionView) == collectionView
let dropOperation: UIDropOperation = isSelf ? .move : .copy
//intent: Soll eine neue Cell erstellt werden oder in die eingefügt werden, über der man sich gerde befindet?
return UICollectionViewDropProposal(operation: dropOperation, intent: .insertAtDestinationIndexPath)
}
//3. perform the actual drop. update UI and the model
func collectionView(_ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator) {
let destinationIntexPath = coordinator.destinationIndexPath ?? IndexPath(row: 0, section: 0)
for item in coordinator.items{
//Is the drop from the same collectionView or external? internal --> != nil
if let sourceIndexPath = item.sourceIndexPath{
if let url = item.dragItem.localObject as? NSURL{
collectionView.performBatchUpdates {
imageDataTuple?.remove(at: sourceIndexPath.item)
imageDataTuple?.insert((url, currentCellSize), at: destinationIntexPath.item)
collectionView.deleteItems(at: [sourceIndexPath])
collectionView.insertItems(at: [destinationIntexPath])
}
coordinator.drop(item.dragItem, toItemAt: destinationIntexPath)
}
}
//external --> nil
else{
var dataTupel: (NSURL?, CGSize?) {
didSet{
DispatchQueue.main.async {
if let controllerTitel = self.title, let presentImageSize = dataTupel.1, let presentURL = dataTupel.0{
if ImageGalleryModel.imageURLs[controllerTitel] != nil {
ImageGalleryModel.imageURLs[controllerTitel]!.append((presentURL, presentImageSize))
print(ImageGalleryModel.imageURLs)
self.imageDataTuple = ImageGalleryModel.imageURLs[controllerTitel]!
self.collectionView.insertItems(at: [IndexPath(item: ImageGalleryModel.imageURLs[controllerTitel]!.count - 1, section: 0)])
}
}
}
}
}
item.dragItem.itemProvider.loadObject(ofClass: NSURL.self) { provider, error in
dataTupel.0 = provider as? NSURL
}
item.dragItem.itemProvider.loadObject(ofClass: UIImage.self) { provider, error in
if let receivedImage = provider as? UIImage{
dataTupel.1 = receivedImage.size
}
}
}
}
}
}
//MARK: UICollectionViewDragDelegate
extension ImageGalleryViewController: UICollectionViewDragDelegate{
//Proveide inital drag items
func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
return dragItem(at: indexPath)
}
//Provide drag items which were added while dragging
func collectionView(_ collectionView: UICollectionView, itemsForAddingTo session: UIDragSession, at indexPath: IndexPath, point: CGPoint) -> [UIDragItem] {
session.localContext = collectionView
return dragItem(at: indexPath)
}
private func dragItem(at indexPath: IndexPath) -> [UIDragItem]{
if let image = (collectionView.cellForItem(at: indexPath) as? ImageCollectionViewCell)?.imageView.image{
let dragItem = UIDragItem(itemProvider: NSItemProvider(object: image))
dragItem.localObject = image
return [dragItem]
}
return []
}
}
//MARK: New selected Gallery
extension ImageGalleryViewController: GalleryListViewControllerDelegate{
func gallerySelected(name: String) {
loadAllImages = true
imageDataTuple = ImageGalleryModel.imageURLs[name]
self.title = name
}
}
The problem was that the auto correction chose delegate methods of the UIDropInteraction-delegate and not from the UICollectionViewDropDelegate. Just picked the correct methods and everything works as intended.

Collection view with dynamic cell sizes glitches on drag and drop

I have a CollectionView where all of my cells are aligned to the center of the view, utilising a custom flow layout. Here is the code for the collectionViewController:
let columnLayout = FlowLayout(
minimumInteritemSpacing: 10,
minimumLineSpacing: 10,
sectionInset: UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
)
var dataSource: [String] = ["USA", "Brazil", "China","Really long long long", "Unite Kingdom", "Japan", "Mexico", "India", "Really long long", "Short", "USA", "Brazil", "China","Really long long long", "Unite Kingdom", "Japan", "Mexico", "India", "Really long long"]
override func viewDidLoad() {
super.viewDidLoad()
collectionView.collectionViewLayout = columnLayout
collectionView.contentInsetAdjustmentBehavior = .always
collectionView.register(TestCell.self, forCellWithReuseIdentifier: "Cell")
collectionView.dragInteractionEnabled = true
collectionView.dragDelegate = self
collectionView.dropDelegate = self
collectionView.delegate = self
collectionView.dataSource = self
}
override func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return dataSource.count
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
// dataArary is the managing array for your UICollectionView.
let item: String = dataSource[indexPath.row]
return CGSize(width: item.widthOfString(usingFont: .systemFont(ofSize: 16)) + 25, height: 40)
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as? TestCell
cell?.textLabel.text = dataSource[indexPath.row]
return cell!
}
fileprivate func reorderItems(coordinator: UICollectionViewDropCoordinator, destinationIndexPath: IndexPath, collectionView: UICollectionView) {
if let item = coordinator.items.first,
let sourceIndexPath = item.sourceIndexPath {
collectionView.performBatchUpdates ({
self.dataSource.remove(at: sourceIndexPath.item)
self.dataSource.insert(item.dragItem.localObject as! String, at: destinationIndexPath.item)
collectionView.deleteItems(at: [sourceIndexPath])
collectionView.insertItems(at: [destinationIndexPath])
}, completion: nil)
collectionView.reloadData()
coordinator.drop(item.dragItem, toItemAt: destinationIndexPath)
}
Code for the FlowLayout Class:
class FlowLayout: UICollectionViewFlowLayout {
required init(minimumInteritemSpacing: CGFloat = 0, minimumLineSpacing: CGFloat = 0, sectionInset: UIEdgeInsets = .zero) {
super.init()
estimatedItemSize = UICollectionViewFlowLayout.automaticSize
self.minimumInteritemSpacing = minimumInteritemSpacing
self.minimumLineSpacing = minimumLineSpacing
self.sectionInset = sectionInset
sectionInsetReference = .fromSafeArea
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let layoutAttributes = super.layoutAttributesForElements(in: rect)!.map { $0.copy() as! UICollectionViewLayoutAttributes }
guard scrollDirection == .vertical else { return layoutAttributes }
// Filter attributes to compute only cell attributes
let cellAttributes = layoutAttributes.filter({ $0.representedElementCategory == .cell })
// Group cell attributes by row (cells with same vertical center) and loop on those groups
for (_, attributes) in Dictionary(grouping: cellAttributes, by: { ($0.center.y / 10).rounded(.up) * 10 }) {
// Get the total width of the cells on the same row
let cellsTotalWidth = attributes.reduce(CGFloat(0)) { (partialWidth, attribute) -> CGFloat in
partialWidth + attribute.size.width
}
// Calculate the initial left inset
let totalInset = collectionView!.safeAreaLayoutGuide.layoutFrame.width - cellsTotalWidth - sectionInset.left - sectionInset.right - minimumInteritemSpacing * CGFloat(attributes.count - 1)
var leftInset = (totalInset / 2 * 10).rounded(.down) / 10 + sectionInset.left
// Loop on cells to adjust each cell's origin and prepare leftInset for the next cell
for attribute in attributes {
attribute.frame.origin.x = leftInset
leftInset = attribute.frame.maxX + minimumInteritemSpacing
}
}
return layoutAttributes
}
}
I then added a UICollectionViewDragDelegate and a UICollectionViewDropDelegate extensions in order to enable the user to reorder the cells. The two extensions:
extension CollectionViewController: UICollectionViewDragDelegate {
func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
let item = self.dataSource[indexPath.row]
let itemProvider = NSItemProvider(object: item as NSString)
let dragItem = UIDragItem(itemProvider: itemProvider)
dragItem.localObject = item
return [dragItem]
}
}
extension CollectionViewController: UICollectionViewDropDelegate {
func collectionView(_ collectionView: UICollectionView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UICollectionViewDropProposal {
if collectionView.hasActiveDrag {
return UICollectionViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath)
}
return UICollectionViewDropProposal(operation: .forbidden)
}
func collectionView(_ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator) {
var destinationindexPath: IndexPath
if let indexPath = coordinator.destinationIndexPath {
destinationindexPath = indexPath
} else {
let row = collectionView.numberOfItems(inSection: 0)
destinationindexPath = IndexPath(item: row - 1, section: 0)
}
if coordinator.proposal.operation == .move {
self.reorderItems(coordinator: coordinator, destinationIndexPath: destinationindexPath, collectionView: collectionView)
}
}
}
For some strange reason the cells glitch out in the app when reordering them:
https://imgur.com/dmEQLqh
Thanks for the help

UICollectionView Drag and Drop cell

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

Using IGListKit for horizontal list with paging

I am building an app that relies heavily on collection view so I decided to use IGListKit. Here is what I have to do:
and here is what I have:
... and it is not moving in any direction!
Here is my code:
import UIKit
import IGListKit
class MatchCollectionViewCell: UICollectionViewCell {
var helloWorld = "Hello World"
}
class LabelSectionController: ListSectionController {
override func sizeForItem(at index: Int) -> CGSize {
return CGSize(width: collectionContext!.containerSize.width, height: 55)
}
override func cellForItem(at index: Int) -> UICollectionViewCell {
return collectionContext!.dequeueReusableCell(of: MatchCollectionViewCell.self, for: self, at: index)
}
}
class MatchViewController: UIViewController {
// MARK: - Outlets
#IBOutlet weak var matchCollectionView: UICollectionView!
// MARK: - Variables
let layout = UICollectionViewFlowLayout()
lazy var adapter: ListAdapter = {
return ListAdapter(updater: ListAdapterUpdater(), viewController: self, workingRangeSize: 0)
}()
var users: [User] = []
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
matchCollectionView.dataSource = self
matchCollectionView.delegate = self
matchCollectionView.collectionViewLayout = CenterCellCollectionViewFlowLayout()
matchCollectionView.isPagingEnabled = true
layout.minimumLineSpacing = 8
layout.scrollDirection = .horizontal
for i in 1...10 {
let newMatchingPreferences = MatchingPreferences(preferedAge: (23, 33))
let newUser = User(id: i, name: "Some Name \(i)", email: "some_name#gmail.com", age: 27, location: "New York", isOnboarded: true, isPremium: true, matchingPreferences: newMatchingPreferences)
users.append(newUser)
}
}
// MARK: - Actions
// MARK: - Methods
}
extension MatchViewController: ListAdapterDataSource {
func objects(for listAdapter: ListAdapter) -> [ListDiffable] {
return self.users
}
func listAdapter(_ listAdapter: ListAdapter, sectionControllerFor object: Any) -> ListSectionController {
return LabelSectionController()
}
func emptyView(for listAdapter: ListAdapter) -> UIView? {
let view = UIView()
view.backgroundColor = .lightGray
return view
}
}
extension MatchViewController: UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = matchCollectionView.dequeueReusableCell(withReuseIdentifier: "MatchCell", for: indexPath) as! MatchCollectionViewCell
print(cell.helloWorld)
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.bounds.width - 32, height: view.bounds.height - 40)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return .zero
}
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
}
}
class CenterCellCollectionViewFlowLayout: UICollectionViewFlowLayout {
var mostRecentOffset: CGPoint = CGPoint.zero
override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
if velocity.x == 0 {
return self.mostRecentOffset
}
guard let cv = self.collectionView,
let attributesForVisibleCells = self.layoutAttributesForElements(in: cv.bounds) else {
// Fallback
self.mostRecentOffset = super.targetContentOffset(forProposedContentOffset: proposedContentOffset)
return self.mostRecentOffset
}
let halfWidth = cv.bounds.size.width * 0.5
var candidateAttributes: UICollectionViewLayoutAttributes?
for attributes in attributesForVisibleCells {
// Skip comparison with non-cell items (headers and footers)
if attributes.representedElementCategory != UICollectionElementCategory.cell {
continue
}
if (attributes.center.x == 0) || (attributes.center.x > (cv.contentOffset.x + halfWidth) && velocity.x < 0) {
continue
}
candidateAttributes = attributes
}
// Beautification step , I don't know why it works!
if proposedContentOffset.x == -(cv.contentInset.left) {
return proposedContentOffset
}
guard let attributes = candidateAttributes else {
return mostRecentOffset
}
self.mostRecentOffset = CGPoint(x: floor(attributes.center.x - halfWidth), y: proposedContentOffset.y)
return self.mostRecentOffset
}
}
Basically, I know how to make crude collection view but paging and centering is a bit hard for me. I feel like I am close but obviously something is missing. I hope somebody can point me to a right direction! Thanks.
extension ViewController: UIScrollViewDelegate {
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
if scrollView == self.collectionView {
var currentCellOffset = self.collectionView.contentOffset
currentCellOffset.x += self.collectionView.frame.width / 2
if let indexPath = self.collectionView.indexPathForItem(at: currentCellOffset) {
self.collectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
}
}
}
}
make sure to set the
adapter.scrollDelegate = self
and
collectionView.decelerationRate = UIScrollViewDecelerationRateFast
(for more of a native iOS feel)
this worked for me I hope it helps someone else!