I have UICollectionView that manages a lot of cells. When I delete a cell, I would like the cell disappears with a refreshControl. But I don't understand why the reloadData does not act. If anyone can help me thank you in advance.
In my view didLoad :
self.collectionView!.alwaysBounceVertical = true
let refresher = UIRefreshControl()
refresher.tintColor = MyColor.Color
refresher.addTarget(self, action: #selector(PublicListController.refreshStream), forControlEvents: .ValueChanged)
refreshControl = refresher
collectionView!.addSubview(refreshControl!)
collectionView.dataSource = self
self.populateDataBest()
My simply function :
func refreshStream() {
collectionView?.reloadData()
refreshControl?.endRefreshing()
}
I complete my CollectionView with the method populateDataBest :
func populateDataBest() {
self.videosService.get(true, completionHandler: {
videosBest, error in
dispatch_async(dispatch_get_main_queue(), {
if error != nil {
if error!.code == -999 {
return
}
self.displayError(informations.LocalizedConnectionError)
return
}
self.bestClip = videosBest
for (indexBest, _) in (self.bestClip?.enumerate())! {
let videoBest:Clip = self.bestClip![indexBest]
self.pictureArrayVideo.addObject(["clip": videoBest, "group": "BEST"])
}
self.dataSource.updateData(self.pictureArrayVideo)
self.collectionView.reloadData()
})
})
}
And the first reload work at the end of my method populateDataBest..
EDIT :
I try to implement function who remove my element (I put 0 in parameters on remove method just for my test for the moment)
func refreshStream() {
dispatch_async(dispatch_get_main_queue(), {
self.remove(0)
self.collectionView.reloadData()
})
self.refreshControl.endRefreshing()
}
func remove(i: Int) {
self.listForPager.removeAtIndex(i)
let indexPath: NSIndexPath = NSIndexPath(forRow: i, inSection: 0)
self.collectionView.performBatchUpdates({
self.collectionView.deleteItemsAtIndexPaths(NSArray(object: indexPath) as! [NSIndexPath])
}, completion: {
(finished: Bool) in
self.collectionView.reloadItemsAtIndexPaths(self.collectionView.indexPathsForVisibleItems())
})
}
And I have this error after
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of items in section 0. The number of items contained in an existing section after the update (8) must be equal to the number of items contained in that section before the update (8), plus or minus the number of items inserted or deleted from that section (0 inserted, 1 deleted) and plus or minus the number of items moved into or out of that section (0 moved in, 0 moved out).'
Someone know why and can help me plz ?
Thx in advance.
If you are riding your collection from an array you should also delete the item for the line will disappear and finally to reload.
Related
A Section may contain 1 header, many content items and 1 footer.
For DiffableDataSource, most of the online examples, are using enum to represent Section. For instance
func applySnapshot(_ animatingDifferences: Bool) {
var snapshot = Snapshot()
snapshot.appendSections([.MainAsEnum])
snapshot.appendItems(filteredTabInfos, toSection: .MainAsEnum)
dataSource?.apply(snapshot, animatingDifferences: animatingDifferences)
}
However, when the Section has a dynamic content footer, we may need to use struct to represent Section. For instance
import Foundation
struct TabInfoSection {
// Do not include content items [TabInfo] as member of Section. If not, any mutable
// operation performed on content items, will misguide Diff framework to throw
// away entire current Section, and replace it with new Section. This causes
// flickering effect.
var footer: String
}
extension TabInfoSection: Hashable {
}
But, how are we suppose to update only footer?
The current approach provided by
DiffableDataSource: Snapshot Doesn't reload Headers & footers is not entirely accurate
If I try to update footer
class TabInfoSettingsController: UIViewController {
…
func applySnapshot(_ animatingDifferences: Bool) {
var snapshot = Snapshot()
let section = tabInfoSection;
snapshot.appendSections([section])
snapshot.appendItems(filteredTabInfos, toSection: section)
dataSource?.apply(snapshot, animatingDifferences: animatingDifferences)
}
var footerValue = 100
extension TabInfoSettingsController: TabInfoSettingsItemCellDelegate {
func crossButtonClick(_ sender: UIButton) {
let hitPoint = (sender as AnyObject).convert(CGPoint.zero, to: collectionView)
if let indexPath = collectionView.indexPathForItem(at: hitPoint) {
// use indexPath to get needed data
footerValue = footerValue + 1
tabInfoSection.footer = String(footerValue)
//
// Perform UI updating.
//
applySnapshot(true)
}
}
}
I will get the following flickering outcome.
The reason of flickering is that, the diff framework is throwing entire old Section, and replace it with new Section, as it discover there is change in TabInfoSection object.
Is there a good way, to update footer in Section via DiffableDataSource without causing flickering effect?
p/s The entire project source code can be found in https://github.com/yccheok/ios-tutorial/tree/broken-demo-for-footer-updating under folder TabDemo.
Have you thought about making a section only for the footer? So that way there's no reload, when it flickers, since it's technically not apart of the problematic section?
There is a fast fix for it, but you will loose the animation of the tableview. In TabInfoSettingsController.swift you can force false the animations in this function:
func applySnapshot(_ animatingDifferences: Bool) {
var snapshot = Snapshot()
let section = tabInfoSection;
snapshot.appendSections([section])
snapshot.appendItems(filteredTabInfos, toSection: section)
dataSource?.apply(snapshot, animatingDifferences: false)
}
You will not see the flickering effect but you will loose the standard animation.
if you want to update only collectionview footer text then make it variable of TabInfoSettingsFooterCell.
var tableSection: TabInfoSettingsFooterCell?
DataSource
func makeDataSource() -> DataSource {
let dataSource = DataSource(
collectionView: collectionView,
cellProvider: { (collectionView, indexPath, tabInfo) -> UICollectionViewCell? in
guard let tabInfoSettingsItemCell = collectionView.dequeueReusableCell(
withReuseIdentifier: TabInfoSettingsController.tabInfoSettingsItemCellClassName,
for: indexPath) as? TabInfoSettingsItemCell else {
return nil
}
tabInfoSettingsItemCell.delegate = self
tabInfoSettingsItemCell.reorderDelegate = self
tabInfoSettingsItemCell.textField.text = tabInfo.getPageTitle()
return tabInfoSettingsItemCell
}
)
dataSource.supplementaryViewProvider = { collectionView, kind, indexPath in
guard kind == UICollectionView.elementKindSectionFooter else {
return nil
}
let section = dataSource.snapshot().sectionIdentifiers[indexPath.section]
guard let tabInfoSettingsFooterCell = collectionView.dequeueReusableSupplementaryView(
ofKind: kind,
withReuseIdentifier: TabInfoSettingsController.tabInfoSettingsFooterCellClassName,
for: indexPath) as? TabInfoSettingsFooterCell else {
return nil
}
tabInfoSettingsFooterCell.label.text = section.footer
//set tableSection value
self.tableSection = tabInfoSettingsFooterCell
return tabInfoSettingsFooterCell
}
return dataSource
}
TabInfoSettingsItemCellDelegate
func crossButtonClick(_ sender: UIButton) {
let hitPoint = (sender as AnyObject).convert(CGPoint.zero, to: collectionView)
if let indexPath = collectionView.indexPathForItem(at: hitPoint) {
footerValue = footerValue + 1
tabInfoSection.footer = String(footerValue)
//Update section value
self.tableSection?.label.text = String(footerValue)
}
}
I have a UICollectionView which implements pages organized into sections. I want to split a section into two parts and have the following logic. But UICollectionView doesn't seem to be happy with this:
func splitSection(at index: IndexPath) {
// this performs the logical split
self.document.splitSection(at: index)
if let cv = self.collectionView {
cv.performBatchUpdates({
let N = self.document.numberOfSection
let n = N - index.section - 1
let range = Range.init(uncheckedBounds: (index.section, n))
let affectedSections = IndexSet.init(integersIn: range)
cv.reloadSections(affectedSections)
let sections = IndexSet.init(integersIn: Range.init(uncheckedBounds: (N-1, 1)))
cv.insertSections(sections)
}, completion: { (_) in
// commit section updates
})
}
}
in numberOfSections function return 2
then in cellforitemat function use switch on indexpath.section
Ok. So I think I have solved the problem after various experimentation. Here is the final solution in case any one else gets into this problem.
The basic idea is:
Move all sections after the the section being split by 1
Insert a section after the section being split
Delete all the items after the split index from the current section
Insert all the items into the newly created section.
The reverse idea can be employed for merging the sections.
Here is the solution:
func splitSection(at index: IndexPath) {
# first get a list of deleted and inserted indices as a result of splitting
let (deleted, inserted) = self.document.splitSection(at: index)
if let cv = self.collectionView {
cv.performBatchUpdates({
let N = self.document.numberOfSection
// now move over the sections to make space for one more section.
for idx in index.section+1..<N-1 {
cv.moveSection(idx, toSection: idx+1)
}
// add a new section
cv.insertSections(IndexSet.init(integer: index.section+1))
// now perform item movements to finish splitting
cv.deleteItems(at: deleted)
cv.insertItems(at: inserted)
}, completion: { (_) in
self.document.updateSections()
})
}
}
I'm doing collapsible and expandable section with Eureka in Swift 3. In a header there is a button by tapping on it section has to be collapsed and the next tap on this button will expand the section.
This is my form example
+++ Section(){ section in
var header = HeaderFooterView<customView>(HeaderFooterProvider.nibFile(name: "customView", bundle: nil))
header.onSetupView = { view, section in
view.sec = section
}
section.header = header
}
<<< TextRow()
<<< SwitchRow()
this is my customView class
var isCollapsed: Bool = false
var sec: Section!
#IBAction func hideTapped(_ sender: Any) {
if isCollapsed {
print("sec \(sec.count)")
} else {
for row in sec.reversed() {
row.hidden = true
row.evaluateHidden()
}
}
}
Hidding works perfect, but print("sec /(sec.count)") gives me sec 0.
How to get rows that were hidden?
Thanks
UPD I find the solution. The answer is here https://github.com/xmartlabs/Eureka/issues/1031
I have 3 table views within a tab controller, all of them are getting populated like this:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.dataSource = self.notesTableView.bind(to: getQuery()) { tableView, indexPath, snap in
let cell = self.notesTableView.dequeueReusableCell(withIdentifier: "cellident", for: indexPath)
let customCell = cell as! TextCell
self.notesTableView.dataSource = self.dataSource
self.notesTableView.delegate = self
if let note = snap.childSnapshot(forPath: "note").value as! String? {
customCell.notesLabel.text = note
} else {
customCell.notesLabel.text = "Undefined"
}
return cell
}
}
When I am running the app on my phone and switch between the tabs, after some point I am always running into this exception:
'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 0.
Furthermore, the console shows this, but I am not sure how to interpret this:
*** Assertion failure in -[UITableView _endCellAnimationsWithContext:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit/UIKit-3600.7.47/UITableView.m:1737
My question is if someone has an idea what the cause of this exception in relation to FirebaseUI might be. My guess is that it has to do with populating the tables.
In the viewWillDisappear set the self.dataSource = nil.
So, that you don't update the dataSource improperly.
from this question
here
from my experience, the safest way is in the viewWillDisappear set self.dataSource.unbind()
I setup a TableView and fetching paginated data. For page 1, as it's hitting route without any parameter (e.g /?page=2). Now I am trying to implement infinite scroll kind load more. I am trying to make a call everytime it hits the 5th row from bottom.
What this code does is fetches non-stop as soon as I hit the 5th cell from bottom. I think it's because the table stays on the 5th cell from bottom and keep adding cells from bottom toward up (but it may be just visual illusion)
class TableView1: UITableViewController {
var currentPage: Int = 1
var results: [JSON]? = []
var urlEndpoint: String?
var isLoading = false
override func viewDidLoad() {
loadItems()
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// connections to cell..
let rowsToLoadFromBottom = 5;
let rowsLoaded = self.results?.count // count of fetched items
if (!isLoading && (indexPath.row >= (rowsLoaded! - rowsToLoadFromBottom))) {
self.loadItems()
}
return cell
}
And loadItems() function:
func loadItems() {
isLoading = true
urlEndpoint = "http://appurl.app/feed"
if currentPage != 1 {
currentPage = currentPage! + 1
urlEndpoint = "http://appurl.app/feed?page=\(currentPage)"
// print(urlEndpoint)
}
// Alamofire call.. {
// Error handling
// Got results {
self.currentPage = json["current_page"].integer
self.currentPage = self.currentPage + 1
self.results = data
self.tableView.reloadData()
}
self.isLoading = false
}
}
Update: I made it fetch properly. Now, I am seeing the links with page parameters on console. However, now it's loading all pages in a sudden (I have 6 pages, so 6 of them) so it doesn't really stop appending and I can actually see like a loop.
Update 2:
self.currentPage = json["current_page"].int!
if self.currentPage == 1 {
self.results = data // data is json data fetched
self.tableView.reloadData()
} else {
var currentCount = self.results!.count;
let indxesPath : [NSIndexPath] = [NSIndexPath]()
for result in data {
self.results?.append(result)
currentCount++
}
self.tableView.insertRowsAtIndexPaths(indxesPath, withRowAnimation: UITableViewRowAnimation.Bottom)
}
self.currentPage = self.currentPage + 1
}
self.isLoading = false
But it's crashing on line self.tableView.insertRowsAtIndexPaths(indxesPath, withRowAnimation: UITableViewRowAnimation.Bottom)
The error: Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (40) must be equal to the number of rows contained in that section before the update (20), plus or minus the number of rows inserted or deleted from that section (0 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).
I have found my answer in another SO question. Here it is applied to my scenario
if self.currentPage == 1 {
self.results = data
self.tableView.reloadData()
} else {
var currentCount = self.results!.count;
var indxesPath : [NSIndexPath] = [NSIndexPath]()
for result in data {
indxesPath.append(NSIndexPath(forRow:currentCount,inSection:0));
self.results?.append(result)
currentCount++
}
self.tableView.insertRowsAtIndexPaths(indxesPath, withRowAnimation: UITableViewRowAnimation.Bottom)
}