ios - How to load more object in UICollectionView - ios

I have a collection view and data to load. My goal is when the collection view loaded it's load only first 5 items. And when I scroll to bottom of collection view it's load more 5 items and again until load all the data.
// The data to load (It already have 20 item loaded from server)
static var productInfo: [ProductInfo] = []
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: MainProductCell.reuseIdentifier, for: indexPath) as? MainProductCell else {
fatalError("Expected `\(MainProductCell.self)` type for reuseIdentifier \(MainProductCell.reuseIdentifier). Check the configuration in Main.storyboard.")
}
// Set data for collection cell
cell.modelId = String(ProductCollectionViewController.productInfo[indexPath.row].productId)
cell.modelName = String(ProductCollectionViewController.productInfo[indexPath.row].name)
cell.modelPrice = String(ProductCollectionViewController.productInfo[indexPath.row].price)
cell.modelImage = ProductCollectionViewController.productInfo[indexPath.row].image
cell.objectImageView.downloadedFrom(link: ProductCollectionViewController.productInfo[indexPath.row].image, contentMode: UIViewContentMode.scaleAspectFit)
cell.modelLink = String(ProductCollectionViewController.productInfo[indexPath.row].remoteStorage)
}
return cell
}
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
let lastItem = ProductCollectionViewController.productInfo.count - 1
if indexPath.row == lastItem {
loadMore()
}
}
func loadMore() {
print("load more...")
//How to handle load more here?
}
How to handle the loadMore() function? Thank you.

Till that it's going well plus
if indexPath.row == lastItem && !loading {
loading = true
loadMore()
}

Using itemAtIndexIndexPath to determine the last cell is not the best way, you should check when the scrollView is going to scroll to bottom.
var isScrolledToBottomWithBuffer: Bool {
let buffer = tableView.bounds.height - tableView.contentInset.top - tableView.contentInset.bottom
let maxVisibleY = tableView.contentOffset.y + self.tableView.bounds.size.height
let actualMaxY = tableView.contentSize.height + tableView.contentInset.bottom
return maxVisibleY + buffer >= actualMaxY
}
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
if( scrollView.contentSize.height == 0 ) {
return;
}
if isScrolledToBottomWithBuffer {
self.loadMore()
}
}

Improving upon #duytph answer
var isAllowedToFetchNewDataFromBottom = false
...
var products: [Products] = []
...
collectionView.delegate = self
...
extension AuthorProductsListViewController: UICollectionViewDelegate {
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
if scrollView.contentSize.height == 0 {
return
}
if isScrolledToBottomWithBuffer, isAllowedToFetchNewDataFromBottom {
fetchNewData() {
self.isAllowedToFetchNewDataFromBottom = false
}
}
}
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
lastItemCount = pendingProducts.count
if indexPath.row == lastItemCount - 1 {
isAllowedToFetchNewDataFromBottom = true
}
}
var isScrolledToBottomWithBuffer: Bool {
let buffer = collectionView.bounds.height - collectionView.contentInset.top - collectionView.contentInset.bottom
let maxVisibleY = collectionView.contentOffset.y + self.collectionView.bounds.size.height
let actualMaxY = collectionView.contentSize.height + collectionView.contentInset.bottom
return maxVisibleY + buffer >= actualMaxY
}
}

Related

How to add a custom view after every 2 rows in a collectionView swift

I am implementing general collectionView in a viewController to populate data and the collection view has 2 columns and the number of rows depend on the data, and now my collectionView looks like this.
Normal collectionView:
This is what I have implemented in my app as you can see it is a normal collection view with n rows and 2 columns. But, our requirement is
Business requirement Image:
There is the custom view which is added after every 2 rows and it is static with a two labels and a button...
I don't know if it is possible and how to achieve this... And after searching for some time I learned that we can do this by using DecoratorViews and I don't know what are those and how to use them.. If anyone have any idea on how to achieve this kind of layout, please guide me..
variables:
let columnsPerRow = 2
let addAfterRows = 5
var cellToShowWithAdds = 0
Function:
func getCategoryProducts() {
var id = Int()
var categoryProductsAPI = ""
if self.brandId != nil {
id = self.brandId!
if self.selectedSubCategoryId != nil {
categoryProductsAPI = "\(API.CATEGORY_BRAND_FILTER)\(self.selectedSubCategoryId!)\(API.BRAND_ID )\(id)"
} else {
categoryProductsAPI = "\(API.CATEGORY_BRAND_FILTER)\(self.categoryId!)\(API.BRAND_ID )\(id)"
}
} else {
if self.selectedSubCategoryId != nil {
id = self.selectedSubCategoryId!
} else {
id = self.categoryId!
}
categoryProductsAPI = "\(API.CATEGORY_PRODUCTS)\(id)"
}
print(categoryProductsAPI)
self.cellToShowWithAdds = 0
self.categoryProductsData = []
self.loadingView.isHidden = false
self.loadingActivityIndicator.animate()
ServiceManager.callGetAPI(url: categoryProductsAPI, view: self, closure: { response in
self.loadingView.isHidden = true
self.loadingActivityIndicator.stopAnimating()
guard let categoryData = response?.result.value else {return}
if let categories = categoryData as? [[String : Any]] {
for product in categories {
let productName = product["product_name"] as! String
let productId = product["product_id"] as! String
let productBrand = product["product_brand"] as! String
guard let productOffPercent = product["product_sale_of"] else { return }
let productImage = product["product_image"] as! String
let productPrice = product["product_price"] as! String
let productSepcialPrice = product["product_special_price"] as! String
var newProductPrice = String()
if productSepcialPrice == "Rs.0.00" {
newProductPrice = productPrice
} else {
newProductPrice = productSepcialPrice
}
self.categoryProductsData.append(ProductDetails(productID: productId, productName: productName, productPrice: productPrice, productSpecialPrice: newProductPrice, productOff: productOffPercent, productBrand: productBrand, productImageURL: productImage))
}
let quot = (self.categoryProductsData.count/(self.columnsPerRow * self.addAfterRows))
self.cellToShowWithAdds = self.categoryProductsData.count + quot + 1
DispatchQueue.main.async {
self.categoryProductsCollection.reloadData()
}
}
}, errorAction: {
self.loadingView.isHidden = true
self.loadingActivityIndicator.stopAnimating()
}, okAction: {
self.view.setNeedsLayout()
self.viewWillAppear(true)
})
}
DataSource methods:
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return cellToShowWithAdds
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if indexPath.row % 5 != 0 {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "productCell", for: indexPath) as! ProductDisplayCell
let productId = Int(categoryProductsData[indexPath.item].productID)
cell.tag = productId!
if categoryProductsData[indexPath.item].productImageURL != "" {
if let productImage = URL(string: categoryProductsData[indexPath.item].productImageURL) {
cell.productImage.getImageWith(imageUrl: productImage)
}
} else {
cell.productImage.image = nil
}
cell.productNameLabel.text = categoryProductsData[indexPath.item].productName
cell.sellerNameLabel.text = categoryProductsData[indexPath.item].productBrand
cell.offerPercentLabel.text = "\(categoryProductsData[indexPath.item].productOff)% Off"
if "\(categoryProductsData[indexPath.item].productOff)" == "" || "\(categoryProductsData[indexPath.item].productOff)" == "100" || "\(categoryProductsData[indexPath.item].productOff)" == "0" {
cell.offerPercentLabel.isHidden = true
} else {
cell.offerPercentLabel.isHidden = false
}
if categoryProductsData[indexPath.item].productSpecialPrice != "Rs.0.00" {
if categoryProductsData[indexPath.item].productPrice == categoryProductsData[indexPath.item].productSpecialPrice {
cell.originalPriceLable.isHidden = true
cell.offerPriceLabel.isHidden = false
} else {
cell.originalPriceLable.isHidden = false
cell.offerPriceLabel.isHidden = false
}
} else if categoryProductsData[indexPath.item].productSpecialPrice == "Rs.0.00" {
cell.originalPriceLable.isHidden = true
cell.offerPriceLabel.isHidden = true
} else {
cell.originalPriceLable.isHidden = false
cell.offerPriceLabel.isHidden = false
}
cell.originalPriceLable.attributedText = categoryProductsData[indexPath.item].productPrice.strikeThrough()
cell.offerPriceLabel.text = categoryProductsData[indexPath.item].productSpecialPrice
return cell
} else {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "formCollectionCell", for: indexPath) as! PostRequirementCellCollectionViewCell
return cell
}
}
My Code should be explanatory. I have set some values in viewdidload to get the kind of view you require.
import UIKit
class CollectionViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
#IBOutlet weak var collectionView : UICollectionView!
let totalProducts = 21
let columnsPerRow = 2
let addAfterRows = 2
var celltoShowWithAds = 0
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let quot = (totalProducts/(columnsPerRow * addAfterRows))
print(quot)
celltoShowWithAds = totalProducts + quot + 1
collectionView.register(UINib(nibName: "CollectionItemCell", bundle: nil), forCellWithReuseIdentifier: "CollectionItemCell")
collectionView.register(UINib(nibName: "CollectionAdvertisementCell", bundle: nil), forCellWithReuseIdentifier: "CollectionAdvertisementCell")
collectionView.delegate = self
collectionView.dataSource = self
collectionView.reloadData()
//collectionView.backgroundColor = UIColor.blue
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return celltoShowWithAds
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if indexPath.row == 0{
let myCell:CollectionAdvertisementCell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionAdvertisementCell", for: indexPath) as! CollectionAdvertisementCell
return myCell as CollectionAdvertisementCell;
}else if indexPath.row % 5 == 0{
let myCell:CollectionAdvertisementCell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionAdvertisementCell", for: indexPath) as! CollectionAdvertisementCell
return myCell as CollectionAdvertisementCell;
}else{
let myCell:CollectionItemCell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionItemCell", for: indexPath) as! CollectionItemCell
return myCell as CollectionItemCell;
}
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if indexPath.row == 0{
return CGSize(width: view.frame.width, height: 0.0)
}else if indexPath.row % 5 == 0 {
return CGSize(width: view.frame.width, height: 80.0)
}else{
return CGSize(width: view.frame.width/CGFloat(columnsPerRow), height: 200.0)
}
}
//Use for interspacing
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
func collectionView(_ collectionView: UICollectionView, layout
collectionViewLayout: UICollectionViewLayout,
minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
}
*/
}

UICollectionView Reloading cells backwards

I am using a UICollectionView as a square grid layout for a crossword. The user will 'create' the puzzle by first tapping each cell to change the colour, press the 'insert numbers button' then add the numbers for each square and finally press the 'get clues button'. When the 'insert numbers button' is pressed, the cells are reloaded to 'fix' the colour and a textfield for each square's number is unhidden (for white cells only). The problem arises when I reload the cells to make the square's number un-editable, the cells reload in reverse. I.e. if the first cell (index 0) has a number, the last cell is updated (index 63). Pressing the button twice puts the cells in the correct position.
I have printed the indexPath to see which cell has the text and it is always the reverse...
Code:
import UIKit
class gridSetup: UIViewController, UICollectionViewDataSource,
UICollectionViewDelegate {
var setColours: Bool = true
var setNumbers: Bool = false
var numbers: Array = [Int]()
var whiteSquares:Array = [IndexPath]()
var gridSizer: Int? //value passed in from previous screen
#IBOutlet weak var getCluesButton: UIButton!
#IBOutlet weak var insertNumbersButton: UIButton!
#IBOutlet weak var grid: UICollectionView!
let reuseIdentifier = "cell"
override func viewDidLoad() {
super.viewDidLoad()
setColours = true
setNumbers = false
getCluesButton.isHidden = true
self.navigationController?.isNavigationBarHidden = false
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - UICollectionViewDataSource protocol
// tell the collection view how many cells to make
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
//return (self.gridSizer * self.gridSizer)
return (self.gridSizer! * self.gridSizer!)
}
// make a cell for each cell index path
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
// get a reference to our storyboard cell
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath as IndexPath) as! MyCollectionViewCell
cell.backgroundColor = UIColor.white
if setColours == true {
whiteSquares.append(indexPath) //initalise whiteSquares by adding all the squares
cell.cellGuess.isHidden = true //hide the letter guesses
cell.squareNumber.isHidden = true //hide the sqaure numbers
}
//redraw cells to fix the colours
// if we are on a white cell and are done setting colours
if setColours == false && whiteSquares.contains(indexPath) == true {
if setNumbers == true {
cell.squareNumber.isHidden = false
}
else {
if cell.squareNumber.text != "" {
print("I have a number inside, Index: ", indexPath.row)
}
cell.squareNumber.isUserInteractionEnabled = false
cell.squareNumber.placeholder = ""
cell.cellGuess.isHidden = false
return cell
}
}
// on a black square
else if setColours == false && whiteSquares.contains(indexPath) == false {
cell.squareNumber.isHidden = true
//print("on a black square", indexPath)
cell.backgroundColor = UIColor.black
cell.squareNumber.isHidden = true
cell.cellGuess.isHidden = true
}
//initial grid setup
else {
cell.backgroundColor = UIColor.white
cell.layer.borderColor = UIColor.black.cgColor
cell.squareNumber.isHidden = true
}
cell.layer.borderWidth = 1
cell.layer.cornerRadius = 8
return cell
}
// MARK: - UICollectionViewDelegate protocol
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
// handle tap events
//print("You selected cell #\(indexPath.item)!")
}
// change background color back when user releases touch
func collectionView(_ collectionView: UICollectionView, didUnhighlightItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath)
if setColours == true {
if cell?.backgroundColor == UIColor.black {
//print("add", indexPath.row)
cell?.backgroundColor = UIColor.white
var k: Int = 0
for i in whiteSquares {
if indexPath.row < i.row {
whiteSquares.insert(indexPath, at: k)
break
}
else {
k += 1
}
}
}
else {
//print("remove", indexPath.row)
cell?.backgroundColor = UIColor.black
var k: Int = 0
for i in whiteSquares {
if indexPath.row == i.row {
whiteSquares.remove(at: k)
}
else {
k += 1
}
}
}
}
}
#IBAction func getClues(_ sender: Any) {
setNumbers = false
grid.reloadData() //problem comes from/after calling this function
}
#IBAction func insertNumbers(_ sender: Any) {
setColours = false
insertNumbersButton.isHidden = true
setNumbers = true
getCluesButton.isHidden = false
grid.reloadData()
}
}
// some layout stuff, (not important)
extension gridSetup: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = collectionView.frame.size.width / (CGFloat(gridSizer! + 1))
//let width = collectionView.frame.size.width / (CGFloat(gridSizer! + 1))
let height = width
return CGSize(width: width, height: height)
}
}
The '1' should be in the 1st row, second column

Segue not performing swift

This is the code form the 'Opgeslagen Rooster' Viewcontroller
class vcSavedTimetable: UIViewController {
#IBOutlet weak var noTimetableSavedLabel: UILabel!
var timetable: Timetable!
override func viewWillAppear(_ animated: Bool) {
prepareView()
}
override func viewWillDisappear(_ animated: Bool) {
noTimetableSavedLabel.isHidden = true
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showSavedTimetable"{
let viewController = segue.destination as! vcTimetableShow
viewController.timetable = self.timetable
viewController.willSave = false
}
}
func prepareView(){
let savedTimetable = getTimetableFromUserDefaults()
guard (savedTimetable != nil) else {
noTimetableSavedLabel.isHidden = false
return
}
self.timetable = savedTimetable!
performSegue(withIdentifier: "showSavedTimetable", sender: nil)
}
func getTimetableFromUserDefaults()->Timetable?{
let timetableID = UserDefaults.standard.string(forKey: "savedTimetableID")
if isConnectedToNetwork(){
if let id = timetableID{
let table = Timetable(timetableID: id)
return table
}
}
let val = UserDefaults.standard.data(forKey: "savedTimetable")
if let data = val{
let table = NSKeyedUnarchiver.unarchiveObject(with: data) as! Timetable
return table
}
return nil
}
}
This is the code of the destination viewcontroller 'Rooster Bekijken'. The viewdidload() function should be executed, which doesn't happen. I tested it using breakpoints
//
// vcTimetableShowCollectionViewController.swift
// Ostrea Rooster App
//
// Created by Giel-Jan Looij on 14-12-16.
// Copyright © 2016 GJ-Computers. All rights reserved.
//
import UIKit
private let reuseIdentifier = "Cell"
class vcTimetableShow: UICollectionViewController, UICollectionViewDelegateFlowLayout {
weak var timetable: Timetable?
var willSave = false
//UIVIEWCONTROLLER
override func viewDidLoad() {
super.viewDidLoad()
collectionView?.delegate = self
collectionView?.register( UINib(nibName: "timetableViewCell", bundle: nil), forCellWithReuseIdentifier: "lessonEntry")
NotificationCenter.default.addObserver(self, selector: #selector(self.dataDidArrive(_:)), name: .didFetchTimetable, object: nil)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
deinit {
NotificationCenter.default.removeObserver(self)
}
//METHODES
func dataDidArrive(_ notification: NSNotification){
collectionView?.reloadData()
if let cv = self.collectionView {
cv.layoutIfNeeded()
let indexPath = IndexPath(item: 0, section: getDayOfWeek()-1)
if let attributes = cv.layoutAttributesForSupplementaryElement(ofKind: UICollectionElementKindSectionHeader, at: indexPath) {
let topOfHeader = CGPoint(x: 0, y: attributes.frame.origin.y - cv.contentInset.top)
cv.setContentOffset(topOfHeader, animated:true)
}
}
if willSave {
UserDefaults.standard.set(timetable!.timetableID, forKey: "savedTimetableID")
let encodedTimetable = NSKeyedArchiver.archivedData(withRootObject: self.timetable!)
UserDefaults.standard.set(encodedTimetable, forKey: "savedTimetable")
UserDefaults.standard.synchronize()
}
}
func getDayOfWeek()->Int {
let myCalendar = Calendar(identifier: .gregorian)
let weekDay = myCalendar.component(.weekday, from: Date())
if weekDay > 6 || weekDay < 2{
return 5
}
return weekDay-1
}
//UICOLLECTIONVIEW DATASOURCE
override func numberOfSections(in collectionView: UICollectionView) -> Int {
return timetable!.days.count
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return timetable!.days[section+1]!.hour.count * 2
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if indexPath.item % 2 == 0{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "hourIndicator", for: indexPath)
let dayLabel = cell.viewWithTag(1) as! UILabel
let dayIndex = indexPath.section + 1
let hourIndex = indexPath.item/2 + 1
let cellHour = timetable!.days[dayIndex]!.hour[hourIndex]!
dayLabel.text = String(cellHour.hourID)
cell.layer.borderColor = UIColor.black.cgColor
cell.layer.borderWidth = 2
return cell
}else{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "lessonEntry", for: indexPath) as! timetableViewCell
let dayIndex = indexPath.section + 1
let hourIndex = indexPath.item/2 + 1
let cellHour = timetable!.days[dayIndex]!.hour[hourIndex]!
cell.cellHour = cellHour
cell.prepareCell()
return cell
}
}
override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
let cell = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "dayHeaderCell", for: indexPath as IndexPath)
let dayIndex = indexPath.section + 1
let dayName = timetable!.days[dayIndex]!.dayName
let day = cell.viewWithTag(1) as! UILabel
day.text = dayName
if timetable!.days[dayIndex]!.dayID == getDayOfWeek(){
cell.backgroundColor = UIColor.lightGray
}
return cell
}
//UICOLLECTIONVIEW DELEGATE
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if indexPath.item % 2 == 0{
let dayIndex = indexPath.section + 1
let hourIndex = indexPath.item/2 + 1
let cellHour = timetable!.days[dayIndex]!.hour[hourIndex]!
var cellHeight: CGFloat = 0
for _ in cellHour.lessons{
cellHeight += 23
}
if cellHour.notice != ""{
cellHeight += 23
}
return CGSize(width: 50, height: cellHeight)
}else{
let dayIndex = indexPath.section + 1
let hourIndex = indexPath.item/2 + 1
let cellHour = timetable!.days[dayIndex]!.hour[hourIndex]!
var cellHeight: CGFloat = 0
for _ in cellHour.lessons{
cellHeight += 23
}
if cellHour.notice != ""{
cellHeight += 23
}
return CGSize(width: 226.0, height: cellHeight)
}
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 1
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
let edgeInset: CGFloat = (collectionView.frame.width - (226.0 + 50.0))/2
return UIEdgeInsets.init(top: 0, left: edgeInset, bottom: 30, right: edgeInset)
}
}
I'm trying to segue from 'Opgeslagen Rooster' to 'Rooster Bekijken', but it doesn't work. It is running in the main thread (thread 1), so that isn't the problem. It gets to prepare(for segue...), does everything. After that the view should change right? But that doesn't happen. The viewdidload method of the 'Rooster Bekijken' ViewController doesn't get called. It hangs after the last line in the pepare(for segue ....) function.
Why doesn't it change to the other viewcontroller? It does work when im accessing the 'Rooster Bekijken' viewcontroller form the other viewcontroller which segue's to it.

Load more activity cell CollectionViewCell

I'm trying to create a activityCell, so when the user reach the button it will show an cell with an activity indicator. This seem to work fine however if moreDataAvailable is false it should remove this cell. However i keep getting following error?
'NSInternalInconsistencyException', reason: 'attempt to delete item 0 from section 1 which only contains 0 items before the update'
numberOfItemsInSection
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
{
if section == 0 {
return organizationArray.count
} else {
if self.moreDataAvailable == true {
return 1
} else {
return 0
}
}
}
Hide Collection Cell
func hideCollectionViewFooter() {
self.collectionView!.deleteItemsAtIndexPaths([NSIndexPath(forRow: 0, inSection: 1)])
}
numberOfSectionsInCollectionView
override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 2
}
cellForItemAtIndexPath
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
if indexPath.section == 0 {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("OrganizationCell", forIndexPath: indexPath) as! OrganizationCollectionViewCell
cell.customerLabel?.text = organizationArray[indexPath.item].name.uppercaseString
cache.fetch(key: organizationArray[indexPath.item].coverPhoto).onSuccess { data in
cell.customerImageView?.image = UIImage(data: data)
}
return cell
} else {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("ActivityCell", forIndexPath: indexPath) as UICollectionViewCell
return cell
}
}
Load More when reach bottom
override func collectionView(collectionView: UICollectionView, willDisplayCell cell: UICollectionViewCell, forItemAtIndexPath indexPath: NSIndexPath) {
if !loadingData && indexPath.item == organizationArray.count - 1 && self.moreDataAvailable {
self.loadingData = true
proposeAccess(false, success: {
self.loadingData = false
})
}
}
Update Organization and check if more data is available
func updateOrganizations(refresh: Bool) {
let realm = try! Realm()
GetOrganization.request(String(self.lastLoadedPage), limit: String(limit), location: self.lastLocation!, radius: String(100), refresh: refresh,
success: { numberOfResults in
//Sort by distance
self.organizationArray = GetOrganization.sortOrganizationsByDistanceFromLocation(realm.objects(Organization), location: self.lastLocation!)
self.lastLoadedPage = self.lastLoadedPage + 1
if numberOfResults < self.limit {
//Hide FooterView
self.moreDataAvailable = false
self.hideCollectionViewFooter()
}
}, error: {
self.organizationArray = GetOrganization.sortOrganizationsByDistanceFromLocation(realm.objects(Organization), location: self.lastLocation!)
print("error")
})
}
This error means that you're trying to delete cell that not existed in current table view state. Probably moreDataAvailable already was false before request in updateOrganizations was finished.
I would recommend you using table footer view for displaying activity indicator. Also, after data is loaded you can display a number of loaded items.

Collection cell that fills entire width of screen and scrolls only one at a time in swift?

I have a UICollectionView that has 20 cells and 1 section. I made each cell 320 in width and 304 in height.
I scroll the collection view programmatically using two buttons at the bottom of the collection view using scrollToItemAtIndexPath(currentIndex + 1). I only scroll them 1 by 1.
This works fine in iPhone 4s and iPhone 5/5s. The problem appears when using an iPhone 6/6 Plus.
When I scrollToItemAtIndexPath it scrolls 2 cells at a time.
How can I prevent this from happening? I tried to make the cell fit the width of the screen but just one cell appeared, and the rest of the UICollectionView was black.
EDIT:
Here is the datasource code:
extension ViewController: UICollectionViewDataSource {
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 32
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("collectionCell", forIndexPath: indexPath) as! CollectionCell
cell.backgroundColor = UIColor.whiteColor()
self.current = indexPath
self.configureCell(cell, atIndexPath: indexPath) as! CollectionCell
return cell
}
func configureCell(cell: CollectionCell, atIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let string = textArray[indexPath.item % 20] as String
cell.textView.text = string
cell.textView.backgroundColor = UIColor.cloudsColor()
return cell
}
}
And here is how I scroll them:
func buttonSelected(sender: UIButton) {
switch sender.tag {
case 0:
let previousItem: NSIndexPath = NSIndexPath(forItem: self.current.item - 1, inSection: self.current.section)
self.collectionView?.scrollToItemAtIndexPath(previousItem, atScrollPosition:UICollectionViewScrollPosition.CenteredHorizontally, animated:true)
case 1:
let nextItem: NSIndexPath = NSIndexPath(forItem: self.current.item + 1, inSection: self.current.section)
self.collectionView?.scrollToItemAtIndexPath(nextItem, atScrollPosition:UICollectionViewScrollPosition.CenteredHorizontally, animated:true)
default: break
}
}
I've solved this using a custom FlowLayout in the collection view.
Here it is:
class CenterFlowLayout: UICollectionViewFlowLayout {
override func targetContentOffsetForProposedContentOffset(proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
if let cv = self.collectionView {
let cvBounds = cv.bounds
let halfWidth = cvBounds.size.width * 0.5
let proposedContentOffsetCenterX = proposedContentOffset.x + halfWidth
if let attributesForVisibleCells = self.layoutAttributesForElementsInRect(cvBounds) as [UICollectionViewLayoutAttributes]! {
var candidateAttributes: UICollectionViewLayoutAttributes?
for attributes in attributesForVisibleCells {
// == Skip comparison with non-cell items (headers and footers) == //
if attributes.representedElementCategory != UICollectionElementCategory.Cell {
continue
}
if let candAttrs = candidateAttributes {
let a = attributes.center.x - proposedContentOffsetCenterX
let b = candAttrs.center.x - proposedContentOffsetCenterX
if fabsf(Float(a)) < fabsf(Float(b)) {
candidateAttributes = attributes
}
} else { // == First time in the loop == //
candidateAttributes = attributes
continue
}
}
return CGPoint(x : candidateAttributes!.center.x - halfWidth, y : proposedContentOffset.y)
}
}
// Fallback
return super.targetContentOffsetForProposedContentOffset(proposedContentOffset)
}
}

Resources