drag and drop collectionViewCell in swift - ios

whenever I drag and drop the cell in other section app gets crashed and when I drag and drop the cell in same section it works good any ideas how to fix it thank a lot in advance. below is the full code.
// error : Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'attempt to insert item 21 into section 1, but there are only 1 sections after the update'
// ViewController
import UIKit
class ViewController: UIViewController {
var deviceName = ""
let cellName = "IconCell". // cell name
var pressActivated = false
#IBOutlet weak var collectionView: UICollectionView!
var nameArrays = [
0: ["twitter", "facebook", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""],
1: ["twitter", "twitter", "", "twitter", "", "", "", "", "", "", "", "twitter", "", "", "twitter", "twitter", "twitter", "twitter", "twitter", "twitter", "twitter", "twitter", "twitter", "twitter"]
]
let statusBarTappedNotification = Notification(name: Notification.Name(rawValue: "statusBarTappedNotification"))
#IBOutlet weak var pageController: UIPageControl!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
setupView()
}
private func setupView(){
self.collectionView.register(UINib.init(nibName: cellName, bundle: nil), forCellWithReuseIdentifier: cellName)
pageController.numberOfPages = nameArrays.keys.count
pageController.currentPage = 0
collectionView.dragDelegate = self
collectionView.dropDelegate = self
// collectionView.dragInteractionEnabled = true
collectionView.reorderingCadence = .fast
NotificationCenter.default.addObserver(forName: statusBarTappedNotification.name, object: .none, queue: .none) { _ in
print("status bar tapped")
self.pressActivated = false
self.collectionView.reloadData()
}
}
#objc private func longPress(){
print("long Pressed")
pressActivated = true
if pressActivated{
collectionView.dragInteractionEnabled = true
}
collectionView.reloadData()
}
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.item + index, section: destinationIndexPath.section)
print(indexPath.section)
print(indexPath.item)
let key = self.nameArrays.keys.sorted()[indexPath.section]
self.nameArrays[key]!.insert(item.dragItem.localObject as! String, at: indexPath.item)
//
indexPaths.append(indexPath)
}
collectionView.insertItems(at: indexPaths)
})
}
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({
self.nameArrays.removeValue(forKey: sourceIndexPath.item)
collectionView.deleteItems(at: [sourceIndexPath])
collectionView.insertItems(at: [dIndexPath])
print(dIndexPath) // till here it works good
})
coordinator.drop(items.first!.dragItem, toItemAt: dIndexPath)
}
}
}
extension ViewController: UICollectionViewDelegate,UICollectionViewDataSource,UICollectionViewDelegateFlowLayout {
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
for cell in collectionView.visibleCells {
let indexPath = collectionView.indexPath(for: cell)
if let indexPath = indexPath {
print("\(indexPath)")
self.pageController.currentPage = indexPath.section
}
}
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return nameArrays.keys.count. // number of sections in collection view
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
let key = nameArrays.keys.sorted()[section]
return nameArrays[key]!.count // number of rows in collection View
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell: IconCell = collectionView.dequeueReusableCell(withReuseIdentifier: cellName, for: indexPath) as! IconCell
// setting data to cells
let holdGesture = UILongPressGestureRecognizer()
holdGesture.addTarget(self, action: #selector(longPress))
holdGesture.minimumPressDuration = 0.5
cell.addGestureRecognizer(holdGesture)
cell.backgroundColor = .clear
let key = nameArrays.keys.sorted()[indexPath.section]
cell.appName.text = nameArrays[key]![indexPath.item]
cell.appIcon.image = UIImage.init(named: nameArrays[key]![indexPath.item])
if pressActivated{
let transformAnim = CAKeyframeAnimation(keyPath:"transform")
transformAnim.values = [NSValue(caTransform3D: CATransform3DMakeRotation(0.04, 0.0, 0.0, 1.0)),NSValue(caTransform3D: CATransform3DMakeRotation(-0.04 , 0, 0, 1))]
transformAnim.autoreverses = true
transformAnim.duration = Double(indexPath.row).truncatingRemainder(dividingBy: 2) == 0 ? 0.115 : 0.105
transformAnim.repeatCount = Float.infinity
cell.layer.add(transformAnim, forKey: "transform")
cell.closeButton.isHidden = false
if cell.appName.text == ""{
cell.closeButton.isHidden = true
}
}
else {
cell.closeButton.isHidden = true
}
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.bounds.width/4 - 10, height: 80)
}
}
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 abs(a) < abs(b)
{
candidateAttribute = attributes
}
}
else
{
candidateAttribute = attributes
continue
}
}
if candidateAttribute != nil
{
return CGPoint(x: candidateAttribute!.center.x - halfWidthOfVC, y: proposedContentOffset.y);
}
}
}
return CGPoint.zero
}
}
extension ViewController : UICollectionViewDragDelegate
{
func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem]
{
let key = nameArrays.keys.sorted()[indexPath.section]
let item = self.nameArrays[key]![indexPath.item]
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 key = nameArrays.keys.sorted()[indexPath.section]
let item = self.nameArrays[key]![indexPath.item]
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?
{
let previewParameters = UIDragPreviewParameters()
previewParameters.visiblePath = UIBezierPath(rect: CGRect(x: 25, y: 25, width: 120, height: 120))
return previewParameters
}
}
// 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.hasActiveDrag
{
return UICollectionViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath)
}
else
{
return UICollectionViewDropProposal(operation: .forbidden)
}
}
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:
print("move")
self.reorderItems(coordinator: coordinator, destinationIndexPath:destinationIndexPath, collectionView: collectionView)
break
case .copy:
print("copy")
self.copyItems(coordinator: coordinator, destinationIndexPath: destinationIndexPath, collectionView: collectionView)
default:
return
}
}
}

you must use beginInteractiveMovementForItemAtIndexPath:
and endInteractiveMovement
see Reordering Items Interactively section in UICollectionView reference

Related

UITableView not visible until tapped on screen

I am new to swift,
I am working on an application
I Have 2 different API to get banner data and tableView Data, for the Images I am getting URL from server .
After the Banner is loaded and running I am populating the UITableView,
problem is either the UITableView visible after sometime (around 1 min) or I have to tap on the screen to make it visible
Also the banner is not scrolling properly when I switch between screens.
Can someone review the code and help me whats wrong.
Here is what I have done so far
Controller Code
class CompanyViewController: UIViewController,IndicatorInfoProvider {
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var sliderCollectionView: UICollectionView!
#IBOutlet weak var pageView: UIPageControl!
var activityIndicator: UIActivityIndicatorView=UIActivityIndicatorView()
var imgArr = [UIImage(named: "LoginHeader")]
var imgArrTemp = [String]()
var sectionHeaderName = [String]()
var imageArray = [[String]]()
var sectionImage = [String]()
var timer = Timer()
var counter = 0
override func viewDidLoad() {
super.viewDidLoad()
pageView.numberOfPages = imgArr.count
pageView.currentPage = 0
tableView.delegate = self
tableView.dataSource = self
tableView.estimatedRowHeight = 120
tableView.tableFooterView = UIView()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.getBannerData()
DispatchQueue.main.async() {
self.getCenterData()
self.tableView.reloadData()
}
}
func indicatorInfo(for pagerTabStripController: PagerTabStripViewController) -> IndicatorInfo {
return IndicatorInfo(title: "COMPANY")
}
#objc func changeImage() {
if counter < imgArr.count {
let index = IndexPath.init(item: counter, section: 0)
self.sliderCollectionView.scrollToItem(at: index, at: .centeredHorizontally, animated: true)
pageView.currentPage = counter
counter += 1
} else {
counter = 0
let index = IndexPath.init(item: counter, section: 0)
self.sliderCollectionView.scrollToItem(at: index, at: .centeredHorizontally, animated: true)
pageView.currentPage = counter
counter = 1
}
}
func getCenterData(){
let appId = LocalStorage.getStringDataFromLocalStorage(key:Constants.APPID)
let token = "Bearer "+LocalStorage.getStringDataFromLocalStorage(key: Constants.TOKEN)
if let url = URL(string: Constants.EXPERIENCE_CENTER+appId){
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue(token, forHTTPHeaderField:"Authorization")
APIManager.sharedInstance.getCall(request: request){
(data) in
if(!data.isEmpty){
do{
let _object = try JSONDecoder().decode(ExperienceCenterModel.self, from: Data(data.utf8))
let data = try JSONEncoder().encode(_object.practice)
let jsonString = String(data: data, encoding: .utf8)!
LocalStorage.saveStringDataInLocalStorage(key: Constants.PRACTICE, value: jsonString)
let technology = try JSONEncoder().encode(_object.technology)
let technologyJsonString = String(data: technology, encoding: .utf8)!
LocalStorage.saveStringDataInLocalStorage(key: Constants.TECTNOLOGY, value: technologyJsonString)
for sectionHeader in _object.practice.data{
self.sectionHeaderName.insert(sectionHeader.practiceName!, at: self.sectionHeaderName.count)
self.sectionImage.removeAll()
for images in sectionHeader.experience{
let url = images.experienceImage.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed)
self.sectionImage.insert(url!, at: self.sectionImage.count)
}
self.imageArray.insert(self.sectionImage, at: self.imageArray.count)
}
self.tableView.reloadData()
}catch{
let nsError = error as NSError
print(nsError.localizedDescription)
}
}
}
}
}
func getBannerData(){
let defaults = UserDefaults.standard
let appId = defaults.string(forKey: Constants.APPID)
let token = "Bearer "+defaults.string(forKey: Constants.TOKEN)!
if let url = URL(string: Constants.EXPERIENCE_BANNER_CENTER+appId!){
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue(token, forHTTPHeaderField:"Authorization")
APIManager.sharedInstance.getCall(request: request){
(data) in
if(!data.isEmpty){
do{
let _object = try JSONDecoder().decode(SliderModel.self, from: Data(data.utf8))
self.imgArr.removeAll()
for images in _object.data{
let url = images.experience_image.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed)
let imageUrlString = url
let imageUrl:URL = URL(string: imageUrlString!)!
// self.imgArrTemp.insert(url!, at: self.imgArrTemp.count)
// self.pageView.numberOfPages = self.imgArrTemp.count
DispatchQueue.main.async() {
let imageData:NSData = NSData(contentsOf: imageUrl)!
self.imgArr.insert(UIImage(data: imageData as Data), at: self.imgArr.count)
self.pageView.numberOfPages = self.imgArr.count
}
}
self.stopActivityIndicator()
}
catch{
self.stopActivityIndicator()
self.showAlertMessage(alertMessage: Constants.COMMON_ERROR)
}
DispatchQueue.main.async {
self.timer = Timer.scheduledTimer(timeInterval: 2.0, target: self, selector: #selector(self.changeImage), userInfo: nil, repeats: true)
}
}
}
}
}
func startActivityIndicator(){
activityIndicator.center = CGPoint(x: 180, y: -40)
activityIndicator.hidesWhenStopped = true
activityIndicator.style = UIActivityIndicatorView.Style.gray
view.addSubview(activityIndicator)
activityIndicator.startAnimating()
UIApplication.shared.beginIgnoringInteractionEvents()
}
func stopActivityIndicator(){
activityIndicator.stopAnimating()
UIApplication.shared.endIgnoringInteractionEvents()
}
func showAlertMessage(alertMessage:String){
self.stopActivityIndicator()
let alert = UIAlertController(title: "Alert", message: alertMessage, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { action in
switch action.style{
case .default:
print("default")
case .cancel:
print("cancel")
case .destructive:
print("destructive")
}}))
self.present(alert, animated: true, completion: nil)
}
}
extension CompanyViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
if(self.imgArr.count > 0){
if let vc = cell.viewWithTag(111) as? UIImageView {
if(self.imgArr.count == indexPath.row){
vc.image = self.imgArr[indexPath.row-1]
}else{
vc.image = self.imgArr[indexPath.row]
}
}
}
return cell
}
}
extension CompanyViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let size = sliderCollectionView.frame.size
return CGSize(width: size.width, height: size.height)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0.0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 0.0
}
}
extension CompanyViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if self.sectionHeaderName.count>0 {
return self.sectionHeaderName[section]
}else{
return ""
}
}
//NUMBER OF SECTION WE WANT IN A TABLE
func numberOfSections(in tableView: UITableView) -> Int {
return self.sectionHeaderName.count
}
/*NUMNBER OF ROWS IN A SECTION*/
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 130
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "tableViewCell", for: indexPath) as? TableViewCell else {fatalError("Unable to create table view cell")}
cell.cellDataURI = self.imageArray[indexPath.section]
cell.collectionView.reloadData()
return cell
}
}
Code for Table View Cell
class TableViewCell: UITableViewCell, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
var cellData = [UIImage]()
var cellDataURI = [String]()
lazy var imageCache: NSCache<NSString,UIImage> = {
return NSCache<NSString,UIImage>.init()
}()
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.cellDataURI.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionCell", for: indexPath) as? CollectionViewCell
let url = cellDataURI[indexPath.row]
guard let image = self.imageCache.object(forKey: NSString(string: url))else{
return loadImage(url: URL(string: url)!, cell: cell!, indexPath: indexPath)
}
cell?.imageView.image = image//cellDataURI[indexPath.row]
return cell!
}
func loadImage(url: URL,cell: CollectionViewCell, indexPath: IndexPath) -> CollectionViewCell{
URLSession.shared.dataTask(with: url){
(rawData, _, _) in
guard let data = rawData else { return }
guard let image = UIImage.init(data: data) else{
return
}
self.imageCache.setObject(image, forKey: NSString(string: url.absoluteString) )
DispatchQueue.main.async {
guard let visCell = self.collectionView.cellForItem(at: indexPath) as? CollectionViewCell else{ return }
visCell.imageView.image = image
}
}.resume()
return cell
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize{
let size = CGSize(width: 150, height: 300)
return size
}
#IBOutlet weak var collectionView: UICollectionView!
override func awakeFromNib() {
super.awakeFromNib()
self.collectionView.delegate = self
self.collectionView.dataSource = self
}
}
Any help would be appreciated
You can use dispatch group to run multiple apis and get notification when all tasks are completed. Then reload tableview
let dispatchGroup = DispatchGroup()
dispatchGroup.enter()
firstApiCall(completion:{
dispatchGroup.leave()
})
dispatchGroup.enter()
secondApiCall(completion:{
dispatchGroup.leave()
})
dispatchGroup.notify(queue: .main) {[weak self] in
print("All Data fetched")
self?.tableView.reloadData()
}

UICollectionViewDropDelegate: Drag cells on Delete Icon

I have a collectionview and will support the drag and drop functionality of iOS 11. One requirement is that cells need to be removed by dragging them on a garbage bin on the bottom of the view. Is there a another possibility then using a second collectionview that holds that delete symobol?
Unfortunately a UIView can't be a UICollectionViewDropDelegate.
Best solution so far is to put an invisible collectionview above the delete icon. Here is my code:
import UIKit
class DragDropViewController: UIViewController
{
private var items1 = [String]()
//MARK: Outlets
#IBOutlet weak var collectionView1: UICollectionView!
#IBOutlet weak var collectionView2: UICollectionView!
#IBOutlet weak var trashImage: UIImageView!
private func createData(){
for index in 1...130{
items1.append("\(index)")
}
}
private func indexForIdentifier(identifier: String)->Int?{
return items1.firstIndex(of: identifier)
}
//MARK: View Lifecycle Methods
override func viewDidLoad()
{
super.viewDidLoad()
createData()
trashImage.alpha = 0
trashImage.layer.cornerRadius = 30
self.collectionView1.dragInteractionEnabled = true
self.collectionView1.dragDelegate = self
self.collectionView1.dropDelegate = self
self.collectionView2.dropDelegate = self
}
//MARK: Private Methods
/// This method moves a cell from source indexPath to destination indexPath within the same collection view. It works for only 1 item. If multiple items selected, no reordering happens.
///
/// - Parameters:
/// - coordinator: coordinator obtained from performDropWith: UICollectionViewDropDelegate method
/// - destinationIndexPath: indexpath of the collection view where the user drops the element
/// - collectionView: collectionView in which reordering needs to be done.
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({
self.items1.remove(at: sourceIndexPath.row)
self.items1.insert(item.dragItem.localObject as! String, at: dIndexPath.row)
collectionView.deleteItems(at: [sourceIndexPath])
collectionView.insertItems(at: [dIndexPath])
})
coordinator.drop(items.first!.dragItem, toItemAt: dIndexPath)
}
}
/// This method copies a cell from source indexPath in 1st collection view to destination indexPath in 2nd collection view. It works for multiple items.
///
/// - Parameters:
/// - coordinator: coordinator obtained from performDropWith: UICollectionViewDropDelegate method
/// - destinationIndexPath: indexpath of the collection view where the user drops the element
/// - collectionView: collectionView in which reordering needs to be done.
private func removeItems(coordinator: UICollectionViewDropCoordinator, destinationIndexPath: IndexPath, collectionView: UICollectionView)
{
collectionView.performBatchUpdates({
for item in coordinator.items
{
guard let identifier = item.dragItem.localObject as? String else {
return
}
if let index = indexForIdentifier(identifier: identifier){
let indexPath = IndexPath(row: index, section: 0)
items1.remove(at: index)
collectionView1.deleteItems(at: [indexPath])
}
}
})
}
}
// MARK: - UICollectionViewDataSource Methods
extension DragDropViewController : UICollectionViewDataSource
{
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
{
return collectionView == self.collectionView1 ? self.items1.count : 0
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell1", for: indexPath) as! DragDropCollectionViewCell
cell.customLabel.text = self.items1[indexPath.row].capitalized
return cell
}
}
// MARK: - UICollectionViewDragDelegate Methods
extension DragDropViewController : UICollectionViewDragDelegate
{
func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem]
{
let item = self.items1[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 = self.items1[indexPath.row]
let itemProvider = NSItemProvider(object: item as NSString)
let dragItem = UIDragItem(itemProvider: itemProvider)
dragItem.localObject = item
return [dragItem]
}
}
// MARK: - UICollectionViewDropDelegate Methods
extension DragDropViewController : 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.collectionView1
{
return collectionView.hasActiveDrag ?
UICollectionViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath) :
UICollectionViewDropProposal(operation: .forbidden)
}
else
{
if collectionView.hasActiveDrag{
return UICollectionViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath)
}
for item in session.items
{
guard let identifier = item.localObject as? String else {
return UICollectionViewDropProposal(operation: .forbidden)
}
//not every cell is allowed to be deleted
if Int(identifier)! % 3 == 0{
return UICollectionViewDropProposal(operation: .forbidden)
}
}
trashImage.backgroundColor = UIColor.red.withAlphaComponent(0.4)
return UICollectionViewDropProposal(operation: .move, intent: .unspecified)
}
}
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)
}
if coordinator.proposal.operation == .move{
if coordinator.proposal.intent == .insertAtDestinationIndexPath{
self.reorderItems(coordinator: coordinator, destinationIndexPath:destinationIndexPath, collectionView: collectionView)
}
else{
self.removeItems(coordinator: coordinator, destinationIndexPath: destinationIndexPath, collectionView: collectionView)
}
}
}
func collectionView(_ collectionView: UICollectionView, dropSessionDidExit session: UIDropSession) {
trashImage.backgroundColor = UIColor.clear
}
func collectionView(_ collectionView: UICollectionView, dropSessionDidEnd session: UIDropSession) {
trashImage.backgroundColor = UIColor.clear
}
}

why is collection view header loading before viewdidload?

I am trying to add a activity indicator but I am not able to do so successfully because my collection view header is loading before viewdidload so therefore I have things showing before my activity indicator even starts. Can someone please help me or point me in the right direction. I have posted my code below
viewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
fetchUser()
profileCollectionView?.register(guestHeaderCollectionViewCell.self, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: "headerId")
profileCollectionView?.register(photoViewCollectionViewCell.self, forCellWithReuseIdentifier: cellId)
}
func fetchUser() {
self.activityIndicator.startAnimating()
if let uid = selectedUser.uid{
Database.fetchUserWithUID(uid: uid) { (user) in
self.selectedUser = user
self.navigationItem.title = self.selectedUser?.fullName
self.profileCollectionView?.reloadData()
self.activityIndicator.stopAnimating()
self.activityIndicator.isHidden = true
self.fetchOrderedPosts()
}
}
}
Collection view header thats being called before viewDidLoad
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "headerId", for: indexPath) as! guestHeaderCollectionViewCell
header.user = self.selectedUser
header.delegate = self
return header
}
view controller
class newGuestPhotoViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource, guestProfileHeaderDelegate, HomePostCellDelegate {
let cellId = "cellId"
let homePostCellId = "homePostCellId"
#IBOutlet weak var profileCollectionView: UICollectionView!
#IBOutlet weak var activityIndicator: UIActivityIndicatorView!
var user: userAccount?
var selectedUser: userAccount!
var posts = [Post]()
let stringID = NSUUID().uuidString
var isGridView = true
func didChangeToGridView() {
isGridView = true
profileCollectionView?.reloadData()
}
func didChangeToListView() {
isGridView = false
profileCollectionView?.reloadData()
}
var showDataFlag : Bool = false!
override func viewDidLoad() {
super.viewDidLoad()
fetchUser()
profileCollectionView?.register(guestHeaderCollectionViewCell.self, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: "headerId")
profileCollectionView?.register(photoViewCollectionViewCell.self, forCellWithReuseIdentifier: cellId)
profileCollectionView?.register(newsfeedCollectionViewCell.self, forCellWithReuseIdentifier: homePostCellId)
}
func fetchUser() {
self.activityIndicator.startAnimating()
if let uid = selectedUser.uid{
Database.fetchUserWithUID(uid: uid) { (user) in
self.selectedUser = user
self.navigationItem.title = self.selectedUser?.fullName
self.showDataFlag = true;
self.profileCollectionView?.reloadData()
self.activityIndicator.stopAnimating()
self.activityIndicator.isHidden = true
self.fetchOrderedPosts()
}
}
}
fileprivate func fetchOrderedPosts() {
guard let uid = selectedUser?.uid else { return }
let ref = Database.database().reference().child("post").child(uid)
ref.queryOrdered(byChild: "dateOfPost").observe(.childAdded, with: { (snapshot) in
guard let dictionary = snapshot.value as? [String: Any] else { return }
let uid = dictionary["postUserUID"] as? String
Database.fetchUserWithUID(uid: uid!, completion: { (userSnap) in
let post = Post(user:userSnap, dictionary: dictionary)
self.posts.insert(post, at: 0)
guard let uid = Auth.auth().currentUser?.uid else {return}
Database.database().reference().child("likes").child(post.postID!).child(uid).observeSingleEvent(of: .value, with: { (snapshot) in
print(snapshot)
if let value = snapshot.value as? Int, value == 1 {
post.hasLiked = true
} else {
post.hasLiked = false
}
DispatchQueue.main.async(execute: {
self.profileCollectionView?.reloadData()
})
}, withCancel: { (err) in
print("Failed to fetch like info for post:", err)
})
})
}) { (err) in
print("Failed to fetch ordered posts:", err)
}
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if(!showDataFlag){
return 0
}
return posts.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if isGridView {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! photoViewCollectionViewCell
cell.post = posts[indexPath.item]
return cell
} else {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: homePostCellId, for: indexPath) as! newsfeedCollectionViewCell
cell.post = posts[indexPath.item]
cell.delegate = self
return cell
}
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 1
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 1
}
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "headerId", for: indexPath) as! guestHeaderCollectionViewCell
header.user = self.selectedUser
header.delegate = self
return header
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
return CGSize(width: view.frame.width, height: 250)
}
As I said in mi comment, you can add a flag and after your fetchUser() you can set this flag true and your collectionView will show your data
something like this
var showDataFlag : Bool = false
override func viewDidLoad() {
super.viewDidLoad()
fetchUser()
profileCollectionView?.register(guestHeaderCollectionViewCell.self, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: "headerId")
profileCollectionView?.register(photoViewCollectionViewCell.self, forCellWithReuseIdentifier: cellId)
}
func fetchUser() {
self.activityIndicator.startAnimating()
if let uid = selectedUser.uid{
Database.fetchUserWithUID(uid: uid) { (user) in
self.selectedUser = user
self.navigationItem.title = self.selectedUser?.fullName
self.showDataFlag = true;
self.profileCollectionView?.reloadData()
self.activityIndicator.stopAnimating()
self.activityIndicator.isHidden = true
self.fetchOrderedPosts()
}
}
}
//In your Collection View Data Source Implementation
func numberOfSections(in collectionView: UICollectionView) -> Int {
if(!showDataFlag){
return 0
}
return yourNeededNumberOfSections
}

CollectionView fatal error: Index out of range

I've been trying to figure out what the issue is in this code for it to throw an index out of range error. However, I am unable to understand where the issue is.
Here is the code
import UIKit
import Alamofire
class MenuViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
let cellId = "cellId"
let headerId = "headerId"
var itemCategories: [MenuItemCategory]?
var menuItem: [MenuItem]?
override func viewDidLoad() {
super.viewDidLoad()
collectionView?.backgroundColor = .white
navigationItem.titleView = UIImageView(image: #imageLiteral(resourceName: "mooyahLabelLogo"))
collectionView?.register(MenuViewControllerCell.self, forCellWithReuseIdentifier: cellId)
collectionView?.register(MenuViewControllerHeader.self, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: headerId)
MenuItemCategory.fetchMenuItems { (itemCategories) in
self.itemCategories = itemCategories
self.collectionView?.reloadData()
}
}
override func numberOfSections(in collectionView: UICollectionView) -> Int {
if let count = itemCategories?.count {
print("Number of Sections: \(count)")
return count
}
return 0
}
override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: headerId, for: indexPath) as! MenuViewControllerHeader
if let categoryName = itemCategories?[indexPath.section].name {
header.categoryNameLabel.text = categoryName
}
return header
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
return CGSize(width: view.frame.width, height: 44)
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if let count = itemCategories?[section].items?.count {
print("Number of Items in Section: \(count)")
return count
}
return 0
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! MenuViewControllerCell
if let category = itemCategories?[indexPath.item] {
print("Section: \(category.name!)")
if let itemsCount = category.items?.count {
for i in 0..<itemsCount {
print("Item: \(category.items?[i].name ?? "")")
cell.itemNameLabel.text = category.items?[i].name ?? ""
cell.itemDescriptionLabel.text = category.items?[i].desc ?? ""
if let price = category.items?[i].price {
cell.itemPriceLabel.text = "AED \(price)"
}
}
}
}
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.width, height: 85)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
}
Here is the debugger print where it shows that my number of sections are correct as well as the number of items in section is correct. I am not sure where the issue arises from?
Debugger screenshot
In override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
Shouldn't this line be
if let category = itemCategories?[indexPath.section] { .... }
Not
if let category = itemCategories?[indexPath.item] { .... }
I would suggest to use a guard to make sure that your items are available like this
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! MenuViewControllerCell
guard let itemCategories = itemCategories, itemCategories.indices.contains(indexPath.item), let items = category.items else {
return cell
}
if let category = itemCategories[indexPath.item] {
print("Section: \(category.name!)")
if let itemsCount = items.count {
for i in 0..<itemsCount {
if items.indices.contains(i) {
print("Item: \(items[i].name ?? "")")
cell.itemNameLabel.text = items[i].name ?? ""
cell.itemDescriptionLabel.text = items[i].desc ?? ""
if let price = category.items?[i].price {
cell.itemPriceLabel.text = "AED \(price)"
}
}
}
}
}
return cell
}

UICollectionView reloadData() does not update cells in the collection view

Here's high level description of what I'm trying to achieve;
1. fetch data
2. save the fetched data in an array object
3. update collection view with the size of the array
Here's my code
class ItemcollectionViewController:UICollectionViewController, UICollectionViewDelegateFlowLayout {
let cellId = "CellId"
var categories = [Category]()
let viewOptionVar:ViewOptionBar = {
let vOV = ViewOptionBar()
vOV.translatesAutoresizingMaskIntoConstraints = false
return vOV
}()
private func fetchData() {
let categoryController = CategoryController()
categoryController.getAllCategory(username: "blah", password: "password") {(returnedCategories, error) -> Void in
if error != nil {
print(error)
return
}
self.categories = returnedCategories!
print("size of the array is \(self.categories.count)")
OperationQueue.main.addOperation{self.collectionView?.reloadData()}
}
}
override func viewDidLoad() {
super.viewDidLoad()
fetchData()
collectionView?.backgroundColor = UIColor.white
collectionView?.register(ItemCell.self, forCellWithReuseIdentifier: cellId)
collectionView?.contentInset = UIEdgeInsetsMake(50, 0, self.view.frame.height, self.view.frame.width)
collectionView?.scrollIndicatorInsets = UIEdgeInsetsMake(50, 0, 0, self.view.frame.width)
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
print("in the method \(self.categories.count)")
return self.categories.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! ItemCell
cell.category = categories[indexPath.item]
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 111, height: 111)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
private func setupViweOptionBar() {
view.addSubview(viewOptionVar)
view.addConstraintsWithFormat(format: "H:|[v0]|", views: viewOptionVar)
view.addConstraintsWithFormat(format: "V:|[v0(50)]", views: viewOptionVar)
}
}
In the log I could see the following statements:
in the method 0
size of the array is 3
and could not see any cell from my view.
Can someone advise me what I've done wrong?
Thanks in advance.
EDIT 1
Now I'm fetching data after registering the customised cells. However, it still doesn't work
updated code:
class ItemcollectionViewController:UICollectionViewController, UICollectionViewDelegateFlowLayout {
let cellId = "CellId"
var categories = [Category]()
let viewOptionVar:ViewOptionBar = {
let vOV = ViewOptionBar()
vOV.translatesAutoresizingMaskIntoConstraints = false
return vOV
}()
private func fetchData() {
let categoryController = CategoryController()
categoryController.getAllCategory(username: "blah", password: "password") {(returnedCategories, error) -> Void in
if error != nil {
print(error)
return
}
self.categories = returnedCategories!
print("size of the array is \(self.categories.count)")
}
}
override func viewDidLoad() {
super.viewDidLoad()
collectionView?.backgroundColor = UIColor.white
collectionView?.register(ItemCell.self, forCellWithReuseIdentifier: cellId)
collectionView?.contentInset = UIEdgeInsetsMake(50, 0, self.view.frame.height, self.view.frame.width)
collectionView?.scrollIndicatorInsets = UIEdgeInsetsMake(50, 0, 0, self.view.frame.width)
collectionView?.dataSource = self
collectionView?.delegate = self
fetchData()
DispatchQueue.main.async{self.collectionView?.reloadData()}
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
print("in the method \(self.categories.count)")
return self.categories.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! ItemCell
cell.category = categories[indexPath.item]
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 111, height: 111)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
private func setupViweOptionBar() {
view.addSubview(viewOptionVar)
view.addConstraintsWithFormat(format: "H:|[v0]|", views: viewOptionVar)
view.addConstraintsWithFormat(format: "V:|[v0(50)]", views: viewOptionVar)
}
}
EDIT 2
The following code is my querying method
func getAllCategory(username:String, password:String, callback: #escaping ([Category]?, String?) -> Void){
var categories = [Category]()
let fetchCategories = URL(string: userURL + "all")
URLSession.shared.dataTask(with: fetchCategories!, completionHandler: { (data, response, error) in
if let err = error {
print(err)
return
}
do {
let jsonCategoryObj = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as! [[String: AnyObject]]
for categoryDictionary in jsonCategoryObj {
let category = Category()
category.categoryId = categoryDictionary["categoryId"] as? String
category.categoryName = categoryDictionary["categoryName"] as? String
category.categoryDescription = categoryDictionary["categoryDescription"] as? String
let categoryRegisteredDateString = categoryDictionary["categoryRegisteredDate"] as? String
let df = DateFormatter()
df.dateFormat = self.shapeDateFormat
let categoryRegisteredDate = df.date(from: categoryRegisteredDateString!)!
category.categoryRegisteredDate = categoryRegisteredDate
categories.append(category)
}
callback(categories, nil)
}catch let jsonError {
callback(nil, String(describing: jsonError))
}
}).resume()
}
FYI: I know I'm not using passed user credential, it's just a copy and paste error from my different query method
When the DataSource changes, reloadData does not update the cell that has been displayed in the view. Reload visible items will do this job.
self.collectionView.reloadData()
self.collectionView.performBatchUpdates({ [weak self] in
let visibleItems = self?.collectionView.indexPathsForVisibleItems ?? []
self?.collectionView.reloadItems(at: visibleItems)
}, completion: { (_) in
})
I'm not sure how this resolved this issue. But I just added
print("size of the array is \(self.categories?.count)")
just next to
OperationQueue.main.addOperation{self.collectionView?.reloadData()}
and it magically works.. even thought when I go back and come back to the screen, it does not show anything.
I'll investigate it more and try to find out why this is happening
Updated
Using
DispatchQueue.main.sync
instead of
DispatchQueue.main.async
resolved the problem.
DispatchQueue.main.async{self.collectionView?.reloadData()}
collectionView?.backgroundColor = UIColor.white
collectionView?.register(ItemCell.self, forCellWithReuseIdentifier: cellId)
You are reloading data before you even have registered your cell.
Register your cell and datasource and delegate methods FIRST, and reload data LAST.
EDIT:
I see you edited your post.
But again, you are fetchData() before you even have registered your cell. So, again, move the fetchData method AFTER you have registered all cells , datasources and delegates.

Resources