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
Related
With the below code I'm able to scroll all cells upto bottom in iPhone 8 but when I run this code in iPhone12 and iPhone11 its not scrolling to bottom.. here showing only 10 cells but the delegate method is calling
here count parameter is page.. each time it displays 10 cells.. like.. when I reach this screen initially it shows 10 cells that's it if there are more products still. it's not scrolling
override func viewDidLoad() {
super.viewDidLoad()
// using framework
let bottomRefreshController = UIRefreshControl()
bottomRefreshController.triggerVerticalOffset = 50
bottomRefreshController.addTarget(self, action: #selector(refresh), for: .valueChanged)
self.collectionView.bottomRefreshControl = bottomRefreshController
self.collectionView.bottomRefreshControl?.tintColor = .clear
}
#objc func refresh() {
serviceCall()
}
fileprivate func allProductsServiceCall(){
let param = ["params": ["category" : catId,
"sub_category" : self.subCatId,
"brands": brands,
"count" : self.currentPageNumber
]] as [String : Any]
APIReqeustManager.sharedInstance.serviceCall(param: param, vc: self, url: getUrl(of: .all_products/*.search*/), header: header) {(responseData) in
if responseData.error != nil{
self.view.makeToast(NSLocalizedString("Something went wrong!", comment: ""))
}else{
if (SearchDataModel(dictionary: responseData.dict as NSDictionary? ?? NSDictionary())?.result?.products ?? [Products]()).count == 0 {
self.view.makeToast("No product to show!".localizeWithLanguage)
return
}
if self.currentPageNumber > 0 {
if (SearchDataModel(dictionary: responseData.dict as NSDictionary? ?? NSDictionary())?.result?.products ?? [Products]()).count > 0 {
self.searchResultData?.result?.products! += SearchDataModel(dictionary: responseData.dict as NSDictionary? ?? NSDictionary())?.result?.products ?? [Products]()
}else {
self.view.makeToast("No more products to show!".localizeWithLanguage)
}
self.productCollectionView.bottomRefreshControl?.endRefreshing()
self.currentPageNumber+=1
DispatchQueue.main.async{
self.productCollectionView.reloadData()
}
}
}
}
// Scroll delegates
// Things needed for expand / collapse
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if (scrollView.contentOffset.y + scrollView.frame.size.height) == scrollView.contentSize.height {
print("at the end")
self.productCollectionView.bounces = true
}else {
print("at the middle, moving down")
}
}
The code is working fine. I mean how I want to show it's coming like that in iPhone8 but, why the scrolling with pagination is not working in iPhone12.
where am I wrong.. please do help... I got stuck here for long
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 one screen with the title "current team" , below that one table view will be there, and below that one text field with + button. So when user enter any data in UITextField and if user press + button that data will add in above UITableView
This blow image is my current screen :
Now, I my above screen I have designed and added some constraints for that. Now when no data and at least one data in table view, there is more space between the title and the text field.
But what i need is :
I need to show like below image :
No space I need to show between that title and text field. And when at least one data is there in table view, the height of the table view should increase and the text field also should need to come below .
How can i handle this :
I did like this :
if currentTeams.visibleCells.count == 0 {
tableViewHeight.constant = 5
}
else{
tableViewHeight.constant = 50
}
Current team = table view name
But it din work, Please help me out. How can I do that?
Code snip :
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if tableView.isEqual(currentTeams) {
return teamNames.count
}
return pastTeamNames.count
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 20
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if tableView.isEqual(currentTeams) {
return getCellForRow(indexPath)
}
return getCellForPastTeamsRow(indexPath)
}
// your table hight constrain.
#IBOutlet var current_team_tablehight: NSLayoutConstraint!
#IBOutlet var playpast_role_table_hight: NSLayoutConstraint!
#IBOutlet var past_team_table_hight: NSLayoutConstraint!
#IBOutlet var certificate_table_hight: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
// put it as 0 for default
current_team_tablehight.constant = 0
certificate_table_hight.constant = 0
past_team_table_hight.constant = 0
playpast_role_table_hight.constant = 0
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(true)
if profileData.FirstName.length > 0 {
// data available for that table then .
currentTeams.reloadData()
current_team_tablehight.constant = currentTeams.contentSize.height + 5;
pastTeams.reloadData()
past_team_table_hight.constant = pastTeams.contentSize.height + 5;
playedTableView.reloadData()
playpast_role_table_hight.constant = playedTableView.contentSize.height + 5;
certificationtableview.reloadData()
certificate_table_hight.constant = certificationtableview.contentSize.height + 5;
}
}
func deleteTeamFromCurrentTeams(sender: UIButton) {
let but = sender
let view = but.superview!
let cell = view.superview as! CurrentTeamsTableViewCell
if let tblView = cell.superview?.superview as? UITableView {
if tblView.isEqual(self.currentTeams) {
let indexPath = currentTeams.indexPathForCell(cell)
teamNames.removeAtIndex((indexPath?.row)!)
currentTeams.reloadData()
if teamNames.count > 0 {
current_team_tablehight.constant = currentTeams.contentSize.height;
}else{
current_team_tablehight.constant = 0;
}
}
else if tblView.isEqual(self.pastTeams) {
let indexPath = pastTeams.indexPathForCell(cell)
pastTeamNames.removeAtIndex((indexPath?.row)!)
pastTeams.reloadData()
if pastTeamNames.count > 0 {
past_team_table_hight.constant = pastTeams.contentSize.height;
}else{
past_team_table_hight.constant = 0;
}
}
else if tblView.isEqual(self.playedTableView) {
let indexPath = playedTableView.indexPathForCell(cell)
playedTeamNames.removeAtIndex((indexPath?.row)!)
playedTableView.reloadData()
if playedTeamNames.count > 0 {
playpast_role_table_hight.constant = playedTableView.contentSize.height;
}else{
playpast_role_table_hight.constant = 0;
}
}
else
{
let indexPath = certificationtableview.indexPathForCell(cell)
ExpTeamNames.removeAtIndex((indexPath?.row)!)
certificationtableview.reloadData()
if ExpTeamNames.count > 0 {
certificate_table_hight.constant = certificationtableview.contentSize.height;
}else{
certificate_table_hight.constant = 0;
}
}
}
self.view .layoutIfNeeded()
self.view .setNeedsLayout()
}
#IBAction func addPastTeamsPressed(sender: AnyObject) {
if pastTeamName.text?.trimWhiteSpace != "" && pastTeamName.text?.trimWhiteSpace != "-" {
pastTeamNames.append(pastTeamName.textVal)
pastTeamName.text = ""
pastTeams.reloadData()
past_team_table_hight.constant = pastTeams.contentSize.height + 5;
}
}
#IBAction func addTeamsPressed(sender: AnyObject) {
if teamName.text?.trimWhiteSpace != "" && teamName.text?.trimWhiteSpace != "-" {
teamNames.append(teamName.textVal)
teamName.text = ""
currentTeams.reloadData()
current_team_tablehight.constant = currentTeams.contentSize.height + 5;
}
}
// for played role
#IBAction func addPlayedTeamsPressed(sender: AnyObject) {
if playedTeam.text?.trimWhiteSpace != "" && playedTeam.text?.trimWhiteSpace != "-" {
playedTeamNames.append(playedTeam.textVal)
playedTeam.text = ""
playedTableView.reloadData()
playpast_role_table_hight.constant = playedTableView.contentSize.height + 5;
}
}
#IBAction func addcertificatecoursePressed(sender: AnyObject) {
if Experience.text?.trimWhiteSpace != "" && Experience.text?.trimWhiteSpace != "-" {
ExpTeamNames.append(Experience.textVal)
Experience.text = ""
certificationtableview.reloadData()
certificate_table_hight.constant = certificationtableview.contentSize.height + 5;
}
}
Output :
You can achieve same by taking a fix UITableviewCell at the and of your table. If You have no data initially then in numberOfRowsInSection method return 1.
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return (<Your Array>.count > 1) ? <Your Array>.count + 1 : 1;
}
This 1 is for that static cell which contains your UITextField and UIButton for Plus.
So cellForRow method will be as
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (<Your Array>.count > 1)
{
if (indexPath.row < <Your Array>.count - 1)
{
// Initialize your UITableview cell which you have design to show your list
// Your Code to show the list.
// return your cell
}
if (indexPath.row==<Your Array>.count-1 ) {
// Initialize and return the cell which you have design using UITextField and "Plus" Button
}
}
else
{
// Initialize and return the cell which you have design using UITextField and "Plus" Button
}
Your design will be like
You can also check example From Github Here.
you can have dynamic cell height by setting tableView row height to UITableViewAutomaticDimension. and also you need to provide estimated height. so in your viewdidload add this code:
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 140 // or your estimated height
then use autolayout in your tableview cell to adjust items and container height
Setup your constraints like so-
First you should have a UILabel for "Current Teams" with a leading, trailing and top constraint to the superView.
Add the UITableView below it with a leading, trailing and top to the UILabel and a fixed height with a priority of 250, lets put it at 0 initially.
Add the UITextField with a leading, trailing, fixed height, top to the UITableView and bottom spacing to the superView with a >=0
This will allow the UITextField always stick to the bottom of your UITableView.
Make an outlet for the UITableView height constraint in your UIViewController.
Now if you can use a default UITableViewCell height, then your job is easier otherwise you will have to calculate the amount of height each string will take inside the cell and get that value. In your viewDidLoad update the UITableView height constraint to be -
//do this after you do the insertion into the tableview
tableViewHeightConstraint.constant = numberOfRows * rowHeight
view.layoutIfNeeded()
You will have to update this height every time you add or delete a row in the UITableView. As soon as your cells take up the entire screen, the height constraint will break and the UITextField will be stuck at the bottom and your UITableView will become scrollable.
Edit: You have to re-add the height constraint programmatically once it breaks and the UITableView stops taking up the entire screen.
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.
This is my viewController and as you can see when it loads up only three header views are visible.
why am I not getting the headerView textlabel text for those headers located below the initial view. I got 6 sections in total which corresponds to 6 headers views in total.
This is my code:
//
// FillinTheBlanksTableViewController.swift
// GetJobbed
//
// Created by Rustam Allakov on 9/22/15.
// Copyright (c) 2015 Rustam Allakov. All rights reserved.
//
import UIKit
class FillinTheBlanksTableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
//
// print("the first section name is: ")
// println(tableView.headerViewForSection(0)!.textLabel.text!)
let headerNames = getSectionNames()
println(headerNames)
print("the number of sections in a tableview ")
println(tableView.numberOfSections())
}
//get all the sections you got
func getVisibleSectionNames () -> [String] {
var headerNames = [String]()
if let indexPaths = tableView.indexPathsForVisibleRows() as? [NSIndexPath] {
headerNames = indexPaths.map { [unowned self] indexPath -> String in
let section = indexPath.section
if let headerText = self.tableView.headerViewForSection(section) {
return headerText.textLabel.text!
} else {
return ""
}
}
}
return headerNames
}
///array of strings with names of the headerViews in a tableview
//why I am not getting the not visible part of my table view?
func getSectionNames() -> [String] {
var sectionNames = [String]()
//how many section do my table view got ?
for i in 0..<tableView.numberOfSections() {
// if let headerView = tableView.headerViewForSection(i) {
// println("the header number \(i)")
// sectionNames.append(headerView.textLabel.text!)
// } else {
// println("i am not getting these \(i)")
//
// }
let headerView = tableView.headerViewForSection(i)
sectionNames.append(headerView?.textLabel.text ?? "not retreived header")
}
return sectionNames
}
}
the print to the console:
HEADER INFORMATION
EDUCATION
WORK EXPERIENCE
not retreived header
not retrieved header
not retreived header
I can only retrieve 3 of the 6 section header titles. What am I missing?
This is expected behavior.
An off-screen header view won't exist, so headerViewForSection will return nil for that section.
From the UITableView class reference:
Return Value
The header view associated with the section, or nil if the section does not have a header view.
If you scroll your tableView, you'll see that the tableView creates header views as they appear on-screen, and deallocates them once they scroll off-screen.
You can determine this by logging the address of the view that is returned to you. The sections that have scrolled off-screen now return nil, while the ones that have now scrolled on-screen now return a view.
If you scroll a section header off-screen, then back on screen, the tableView actually returns a different view for that section, since the original one was deallocated, and a new one was created.
If you need the titles for off-screen headers, you will have to retrieve them from your model (or dataSource), as no headers will exist for those sections.
You may want to consider implementing tableView:titleForHeaderInSection: to avoid having to access a headerView's textLabel to get the title.