Add a label to a section of tableView when it's empty - swift

Almost the same question many times has been asked here, but my question is a bit different, for example here, a users shows a really good way to handing an empty tableview with a label
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if self.numberOfRow == 0{
var emptyLabel = UILabel(frame: CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height))
emptyLabel.text = "No Data"
emptyLabel.textAlignment = NSTextAlignment.Center
self.tableView.backgroundView = emptyLabel
self.tableView.separatorStyle = UITableViewCellSeparatorStyle.None
return 0
} else {
return self.numberOfRow
}
}
It works fine when there is a section, but my problem is, I have two section that users can move cells between them and I want, when one of them become empty, a label appear in the section to say it's empty.
Could anyone modify this way to do that? also it should reload data to show this label, is it right?
Many thanks

The easiest way is to show a regular cell in the section if there are no other rows in the section.
Here's some rough pseudocode:
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let sectionData = data[section]
return sectionData.isEmpty ? 1 : sectionData.count
}
tableView(tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let sectionData = data[section]
if sectionData.isEmpty {
// create plain cell
cell.textLabel.text = "Nothing to see here"
return cell
} else {
// create and return your normal data cell from the data for the index path
}
}
Another option is to show a section header or footer for any section that has no rows.

Related

How to prevent a UITableViewCell from moving (Swift 5)

This is actually a two part question. First take a look at the code:
//canEditRowAt
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
if tableView.tag == 1000{
return indexPath.row == 0 ? false : true
}
return true
}
//canMoveRowAt
func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
if tableView.tag == 1000{
return indexPath.row == 0 ? false : true
}
return true
}
So from this I would expect that it would prevent the row at index 0 from having other cells move to it, but no... as you can see:
I obviously want to prevent it, but can't seem to find any documentation to solve the issue.
The other bit I'm struggling with; if you look at the recording you can see that once I move a cell into any location, a black bar appears behind the cell. I would like to avoid that from happening as well, and have tried several things but nothing has worked.
Any help is appreciated, thanks!
To answer the first question, if you look at the tableView(_:canMoveRowAt:) documentation :
This method allows the data source to specify that the reordering
control for the specified row not be shown. By default, the reordering
control is shown if the data source implements the
tableView(_:moveRowAt:to:) method.
This mainly talks about the reordering control being shown rather than specifically saying it can never be moved. So if you look at your UI, the reordering control is not showing for indexPath.row == 0
I can suggest 2 alternatives:
1. Reverse the move action
override func tableView(_ tableView: UITableView,
moveRowAt sourceIndexPath: IndexPath,
to destinationIndexPath: IndexPath)
{
// Reverse the action for the first row
if destinationIndexPath.row == 0
{
// You need the give a slight delay as the table view
// is still completing the first move animation
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5)
{
tableView.beginUpdates()
tableView.moveRow(at: destinationIndexPath,
to: sourceIndexPath)
tableView.endUpdates()
}
return
}
// update the data source
}
2. User a header view
This way you don't have to worry about specify any logic of which cells can move and which cannot as you can have the non movable data in a header view:
override func tableView(_ tableView: UITableView,
viewForHeaderInSection section: Int) -> UIView?
{
if section == 0
{
// Customize any view
let headerView = UIView(frame: CGRect(x: 0,
y: 0,
width: tableView.bounds.width,
height: 100))
headerView.backgroundColor = .red
let label = UILabel(frame: headerView.bounds)
label.text = "Header view"
label.textColor = .white
label.textAlignment = .center
headerView.addSubview(label)
return headerView
}
return nil
}
override func tableView(_ tableView: UITableView,
heightForHeaderInSection section: Int) -> CGFloat
{
if section == 0
{
// specify any height
return 100
}
return 0
}
I recommend the second option as it has the better user experience and seems to be the right way of approaching this problem.
To answer your second question, in your cellForRowAt indexPath or custom cell implementation, you probably set the background view of the cell or the contentView to black.
Try setting one of these or both:
cell.backgroundColor = .clear
cell.contentView.backgroundColor = .clear
This should not give you a black background
Implement tableView(_:targetIndexPathForMoveFromRowAt:toProposedIndexPath:) and ensure row 0 is never returned (return row 1 when proposedDestinationIndexPath is row 0).

tableview with custom table view cells disappear on scroll

I have a table view with custom tableview cells.
Each may have different heights.
Totally there are 14 rows but other rows are not visible and when i scroll up and down the rows are disappearing.
Please suggest where i am doing wrong. This is bugging me from 2 days. I am not able to find any solution.
Please find my code below for the tableview.
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat
{
return UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if(fetchedElements[indexPath.row].elementType=="textarea")
{
return 100 ;
}
else if (fetchedElements[indexPath.row].elementType=="picklist")
{
return 60;
}
else if (fetchedElements[indexPath.row].elementType=="divider")
{
return 30;
}
else if (fetchedElements[indexPath.row].elementType=="scanner")
{
return 50;
}
else if (fetchedElements[indexPath.row].elementType=="multiselect")
{
return 60;
}
else if(fetchedElements[indexPath.row].elementType=="text")
{
var length = fetchedElements[indexPath.row].elementText?.characters.count
if length! < 30
{
return 80;
}else if length! < 60 && length! > 30 {
return 110;
}else if(length! < 100 && length! > 60)
{
return 130;
}else if(length! < 150 && length! > 100 )
{
return 170;
}
else{
return 140;
}
}else
{
return 200;
}
}
func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat
{
return UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("No of elements Fetched===\(fetchedElements.count)")
return fetchedElements.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var variableType = fetchedElements[indexPath.row].elementType
if(variableType==nil)
{
variableType = "";
}
var elementType = variableType!
print("=============Element Type=\(elementType)==============")
let frame = UIScreen.main.bounds
switch elementType {
case "picklist":
let dpcell = Bundle.main.loadNibNamed("Textbox", owner: self, options: nil)?.first as! Textbox
dpcell.backgroundColor = UIColor.lightGray
dpcell.txtValue.layer.cornerRadius = 3
dpcell.LabelName.text = "Country"//fetchedElements[indexPath.row].elementText
dpcell.txtValue.tag = Int(fetchedElements[indexPath.row].elementId)
dpcell.txtValue.addTarget(self, action: #selector(GoToDropdown), for: UIControlEvents.touchDown)
dpcell.txtValue.backgroundColor = UIColor.white
dpcell.txtValue.titleLabel?.numberOfLines = 0
dpcell.txtValue.titleLabel?.adjustsFontSizeToFitWidth = true;
dpcell.LabelName.lineBreakMode = .byWordWrapping
dpcell.LabelName.numberOfLines = 0
dpcell.selectionStyle = .none
print("============picklist==========\(indexPath.row)")
return dpcell
case "multiselect":
let dpcell = Bundle.main.loadNibNamed("Textbox", owner: self, options: nil)?.first as! Textbox
dpcell.backgroundColor = UIColor.lightGray
dpcell.txtValue.layer.cornerRadius = 3
dpcell.LabelName.text = fetchedElements[indexPath.row].elementText
dpcell.txtValue.tag = Int(fetchedElements[indexPath.row].elementId)
dpcell.txtValue.addTarget(self, action: #selector(GoToMultiSelect), for: UIControlEvents.touchDown)
dpcell.txtValue.backgroundColor = UIColor.white
dpcell.txtValue.contentHorizontalAlignment = UIControlContentHorizontalAlignment.left
dpcell.txtValue.titleLabel?.numberOfLines = 0
dpcell.txtValue.titleLabel?.adjustsFontSizeToFitWidth = true;
dpcell.selectionStyle = .none
print("===========multiselect===========\(indexPath.row)")
return dpcell
default:
let cell = self.tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! UITableViewCell
cell.textLabel?.text = "default"
print("==========dummy cell============\(indexPath.row)")
return cell
}
This happens after scroll down and up
Note : I can't comment because i don't have enough reputation, so i could not ask you regarding certain queries i have.. But by reading your codes, this is what i can suggest.
I can see that your data has quite a number of element types,for example, Picklist, divider, scanner and ect, in your heightForRow Delegate method..
However, in your cellForRow function, you have only two cases, PickList and MultiSelect... Thus, all the data types that have different element type, will return you a cell with a big "default" on it.
The only peculiar thing that i do not understand is that the tableview seems to have loaded perfectly in the first try. Thus, i was wondering on how you set up the tableview.
If you set it up in storyboard manually.. Then the first time, when the application loads, it will present whatever you have designed in your UITableView, but the moment you scroll up and down, the CellForItem method will kick in, and re-write the cell, returning you the big "default" cell you see on your list.
So if i am guessing it right..
Then what you have to do is simply adding all the type cases in your cellForRow methods's switch statement.
You might want to look into dispatch async & tableView.reloadData
Since I cannot see all of your code, I would suggest that you create a function that will be called inside viewDidLoad. Inside this function, of course include the lines below, as well as whatever you want inside of your tableView cells. I'm assuming you're using an array for your tableViews data
DispatchQueue.main.async {
tableView.reloadData
}

UITableView reloadData() overwrites previous cell data

I'm dynamically creating a UITableView and populating it with an Array, which is populated my a Dictionary. Issue is that when I make some changes in Dictionary, re-populate the Array accordingly and reload tableview data, previous cell datas remain and new data is written over the previous ones. I tried lots of workarounds, like changing the order of method calls, using viewWithTag on subviews, removing subviews from superviews before adding again etc. None worked, since most of the answers were old and had to do with "(cell == nil)" issue whereas I'm not taking that path. Here is the code:
var sepetDict: [String: Int] = [:]
var sepetDictCopy = [String]()
override fun viewDidLoad() {
sepetTableView.frame = CGRectMake(0, 0, sepetDialogView.bounds.size.width, sepetDialogView.bounds.size.height)
sepetTableView.delegate = self
sepetTableView.dataSource = self
sepetTableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "sepetCell")
}
func sepetClick() {
sepetDictCopy.removeAll()
sepetDictCopy = Array(sepetDict.keys)
sepetTableView.reloadData()
sepetTableView.removeFromSuperview()
self.view.addSubview(sepetDialogView)
sepetDialogView.addSubview(sepetTableView)
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let sepetCell = sepetTableView.dequeueReusableCellWithIdentifier("sepetCell", forIndexPath: indexPath)
let sepetNameLabel = UILabel()
sepetNameLabel.frame = CGRectMake(0, 0, sepetDialogView.bounds.size.width / 2, 60)
sepetNameLabel.center = CGPointMake(25 + sepetDialogView.bounds.size.width / 4 , 30)
sepetNameLabel.font = sepetNameLabel.font.fontWithSize(13)
sepetNameLabel.numberOfLines = 0
sepetNameLabel.textAlignment = NSTextAlignment.Left
sepetNameLabel.text = sepetDictCopy[indexPath.row]
sepetCell.contentView.addSubview(sepetNameLabel)
return sepetCell
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sepetDictCopy.count
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 60
}
In this case, I click on a UIButton, it pops up a UIView and UITableView is added to this UIView as a subView. At first click, everything works great. When I close that UIView and re-click on the button, this time tableview loads with new data (correct data as well) but it overwrites the previous data. In the provided code, I know I don't add any data to the array, but issue is the same since for example I had "Pizza" written in UILabel and when I reloaded the table, there still was "Pizza", but this time darker (which tells me that label is overwritten on previous label). Say I add "Burger" to the array, tableview shows "Burger" and "Pizza", but this time "Burger" is written over the previous "Pizza" at index 0 and "Pizza" is at index 1 alone. I can also provide additional information or some pictures of the issue if not understood correctly.
Looks like I didn't quite understand the concept of viewWithTag method. I rearranged my code and it worked. For those who are looking for an up to date answer and code, here it is:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var sepetCell: UITableViewCell! = sepetTableView.dequeueReusableCellWithIdentifier("")
if (sepetCell == nil) {
sepetCell = sepetTableView.dequeueReusableCellWithIdentifier("sepetCell")!
let sepetNameLabel = UILabel()
sepetNameLabel.tag = 1
sepetNameLabel.frame = CGRectMake(0, 0, sepetDialogView.bounds.size.width / 2, 60)
sepetNameLabel.center = CGPointMake(25 + sepetDialogView.bounds.size.width / 4 , 30)
sepetNameLabel.font = sepetNameLabel.font.fontWithSize(13)
sepetNameLabel.numberOfLines = 0
sepetNameLabel.textAlignment = NSTextAlignment.Left
sepetCell.contentView.addSubview(sepetNameLabel)
}
let sepetNameLabel = sepetCell.contentView.viewWithTag(1) as! UILabel
sepetNameLabel.text = sepetDictCopy[indexPath.row]
return sepetCell
}

How to set uitableviewcellselectionstyle.none to static cells?

Sorry I really cant find this anywhere.
I need to set my selection style to none so that the rows dont highlight when i click on it. Also, i need rows to be selectable as I have some rows which needs expanding and collapsing. I know the piece of code is UITableViewCellSelectionStyle.None but I have no idea where I can implement it. Thanks!
EDIT ADDED IN CODES
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return 7
}
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
// Set height for date picker
if indexPath.section == 0 && indexPath.row == 2 {
let height:CGFloat = datePicker.hidden ? 0.0 : 216.0
return height
}
return super.tableView(tableView, heightForRowAtIndexPath: indexPath)
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
// Expanding and collapsing date picker
UITableViewCellSelectionStyle.None
let datePickerIndexPath = NSIndexPath(forRow: 1, inSection: 0)
if datePickerIndexPath == indexPath {
datePicker.hidden = !datePicker.hidden
UIView.animateWithDuration(0.3, animations: { () -> Void in
self.tableView.beginUpdates()
// apple bug fix - some TV lines hide after animation
self.tableView.deselectRowAtIndexPath(indexPath, animated: true)
self.tableView.endUpdates()
})
}
}
The codes are mainly for the datepicker that i have implemented. everything works fine but clicking on the cell highlights the whole row in the default selection color.
Hard to know where to best implement it without seeing your code, but you can definitely put it in your cellForRowAtIndexPath when you dequeue/initialize your cell. Just call yourCell.selectionStyle = .None before return yourCell
cell.selectionStyle = .None
When you write the code after equal(=) , just press dot(.) so that many type of functionality will be pop up like this-
And for your second issue just put some value in array to check that is working correctly or not.
I have had the same issue, the solution for me was:
self.tableView.allowsSelection = true
in storyboard, manually select all the static cells and change selection from "default" to "none"
for those cells are allowed to be selected, change the selection to "Default".
It would appear as if only some cells can be selected.
You can still handle selection for those exclusive cells in didSelectRowAt by checking indexPath.
Hope that helps.
Just found a way to programmatically insert the selection style setting for static cells, instead of using storyboard:
In viewWillAppear, use:
tableView.cellForRow(at: yourIndexPath1)?.selectionStyle = .none
tableView.cellForRow(at: yourIndexPath1)?.selectionStyle = .none
...

Making Simple Accordion TableView in swift?

Is there any way that I can make simple Accordion View in swift like the one at Calendar Event Create? I don't want to use other third party library as well as other code.
I found many answer at github and over google. But,still don't meet my requirement.
Actually I want to add two table view.
The first one is section which show City such as (New York,Los Angles,Las Vegas,etc)
When I tapped one of the city,it will show store address in tableview which mean there are many stores.
All the store and data will got from json.
The accordion view that i want to do is as simple as the one at Calendar App on iOS. But,the data that I gonna insert into two tableView (Section Header & Inner Records inside each section) which is dynamic.
Any Help? Please Guide me,Help me out.
UPDATE : Please take a look
The answer provided by #TechBee works fine using sections for those interested in not using sections and use cells.
The implementation of an Accordion Menu in Swift can be achieved using UITableView in a very simple way, just having two cells one for the parent cells and another for the childs cells and in every moment keep the track for the cells expanded or collapsed because it can change the indexPath.row every time a new cell is expanded or collapsed.
Using the functions insertRowsAtIndexPaths(_:withRowAnimation:) and deleteRowsAtIndexPaths(_:withRowAnimation:) always inside a block of call to tableView.beginUpdates() and tableView.endUpdates() and updating the total of items in the data source or simulating it changes we can achieve the insertion of deletion of new cells in the UITableView in a very easy way with animation included.
I've implemented myself a repository in Github with all the explained above AccordionMenu using Swift and UITableView in a easy and understandable way. It allows several cells expanded or only one at time.
Try this:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
arrayForBool = ["0","0","0"]
sectionTitleArray = ["Pool A","Pool B","Pool C"]
var tmp1 : NSArray = ["New Zealand","Australia","Bangladesh","Sri Lanka"]
var string1 = sectionTitleArray .objectAtIndex(0) as? String
[sectionContentDict .setValue(tmp1, forKey:string1! )]
var tmp2 : NSArray = ["India","South Africa","UAE","Pakistan"]
string1 = sectionTitleArray .objectAtIndex(1) as? String
[sectionContentDict .setValue(tmp2, forKey:string1! )]
self.tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "Cell")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return sectionTitleArray.count
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
if(arrayForBool .objectAtIndex(section).boolValue == true)
{
var tps = sectionTitleArray.objectAtIndex(section) as! String
var count1 = (sectionContentDict.valueForKey(tps)) as! NSArray
return count1.count
}
return 0;
}
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return "ABC"
}
func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 50
}
func tableView(tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return 1
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if(arrayForBool .objectAtIndex(indexPath.section).boolValue == true){
return 100
}
return 2;
}
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = UIView(frame: CGRectMake(0, 0, tableView.frame.size.width, 40))
headerView.backgroundColor = UIColor.grayColor()
headerView.tag = section
let headerString = UILabel(frame: CGRect(x: 10, y: 10, width: tableView.frame.size.width-10, height: 30)) as UILabel
headerString.text = sectionTitleArray.objectAtIndex(section) as? String
headerView .addSubview(headerString)
let headerTapped = UITapGestureRecognizer (target: self, action:"sectionHeaderTapped:")
headerView .addGestureRecognizer(headerTapped)
return headerView
}
func sectionHeaderTapped(recognizer: UITapGestureRecognizer) {
println("Tapping working")
println(recognizer.view?.tag)
var indexPath : NSIndexPath = NSIndexPath(forRow: 0, inSection:(recognizer.view?.tag as Int!)!)
if (indexPath.row == 0) {
var collapsed = arrayForBool .objectAtIndex(indexPath.section).boolValue
collapsed = !collapsed;
arrayForBool .replaceObjectAtIndex(indexPath.section, withObject: collapsed)
//reload specific section animated
var range = NSMakeRange(indexPath.section, 1)
var sectionToReload = NSIndexSet(indexesInRange: range)
self.tableView .reloadSections(sectionToReload, withRowAnimation:UITableViewRowAnimation.Fade)
}
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
let CellIdentifier = "Cell"
var cell :UITableViewCell
cell = self.tableView.dequeueReusableCellWithIdentifier(CellIdentifier) as! UITableViewCell
var manyCells : Bool = arrayForBool .objectAtIndex(indexPath.section).boolValue
if (!manyCells) {
// cell.textLabel.text = #"click to enlarge";
}
else{
var content = sectionContentDict .valueForKey(sectionTitleArray.objectAtIndex(indexPath.section) as! String) as! NSArray
cell.textLabel?.text = content .objectAtIndex(indexPath.row) as? String
cell.backgroundColor = UIColor .greenColor()
}
return cell
}