Previously I had an issue having an UISegmentedControl in tableview section header cell, and I wanted to listen to value changes in the main view controller.
I got this solved by using addTarget on the UISegmentedControl in the tableView:viewForHeaderInSection method, and this works.
First segment is default selected, and this works. When tapping on second segment, it reloads the tableview with other data, which is expected behaviour. However, first segment is still selected. Which means I cannot switch back to the original data in the table view.
Please see this video and you will see what I am trying to explain.
I cannot figure out what is wrong with my code?
/*
* Main View Controller
*/
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
if section == 0 {
return nil
}
let sectionCell = tableView.dequeueReusableCellWithIdentifier("SegmentCell") as! SegmentTableViewCell
// Add event for the UISegmentedControl
sectionCell.segmentControl.addTarget(self, action: #selector(self.handleSegmentControlEvent), forControlEvents: .ValueChanged)
return sectionCell
}
func handleSegmentControlEvent(sender: UISegmentedControl) {
self.segmentControlIndex = sender.selectedSegmentIndex
switch (sender.selectedSegmentIndex) {
case 0:
if latestTableData.count == 0 {
loadItems("latest")
} else {
tableView.reloadData()
}
break
case 1:
if closestTableData.count == 0 {
loadItems("closest")
} else {
tableView.reloadData()
}
break;
default:
break
}
}
/*
* Segment table view cell
*/
class SegmentTableViewCell: UITableViewCell {
#IBOutlet weak var segmentControl: UISegmentedControl!
override func awakeFromNib() {
super.awakeFromNib()
}
}
I can't see what I am doing wrong?
when tableView is reload that means viewForHeaderInSection is also reload. So, it is got initial stage after reloading.So you should choose different approach for it like you can add segmented control outside the tableview not as headerview or reload only rows or cells etc.
for example,
[self.tableView reloadRowsAtIndexPaths:[self.tableView indexPathsForVisibleRows] withRowAnimation:UITableViewRowAnimationLeft];
Update :
reload section,
NSRange range = NSMakeRange(0, 1);
NSIndexSet *section = [NSIndexSet indexSetWithIndexesInRange:range];
[self.tableView reloadSections:section withRowAnimation:UITableViewRowAnimationNone];
It will reload first section. For more detail you can refer this link
it is objective c code convert in swift!!
hope this will help :)
Related
I am doing expand/collapse tableview cells feature in my iOS app. I have multiple sections. And each section has multiple cells. By default, cell height is 100, once user taps on cell, I am increasing height to 200.
So, Based on Bool value, I am changing it. But, While scrolling tableview, It is interchanging the expanded/collapse cells in between sections.
Like if I tap on first section first cell, It is expanding, but after scrolling tableview, Second section first cell also expanding.
My Requirement is, If user tap on particular cell, that cell only should expand/collapse. User can manually expand and close. User can expand multiple cells.
So, I have tried to store Indexpath row and Section.
var expandedIndexSet : IndexSet = []
var expandedIndexSection : IndexSet = []
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier:"cellIdentifier", for:
indexPath) as! MyTableViewCell
if expandedIndexSet.contains(indexPath.row) && expandedIndexSection.contains(indexPath.section) { // expanded true
cell.height = 200
//some other data loading here
}
else { //expanded false
cell.height = 100
}
}
#IBAction moreButtonTapped(_ sender: Any) {
if(expandedIndexSet.contains(indexPath.row)) && expandedIndexSection.contains(indexPath.section){
expandedIndexSet.remove(indexPath.row)
expandedIndexSection.remove(indexPath.section)
} else {
expandedIndexSet.insert(indexPath.row)
expandedIndexSection.insert(indexPath.section)
}
entriesTableView.beginUpdates()
entriesTableView.reloadRows(at: [indexPath], with: .none)
entriesTableView.endUpdates()
}
Anyone can give better approach than this?
If you store section and row independently in separate arrays, your algorithm will fail.
The reason is that both are dependent:
Think of three expanded cells (row:1, section:1), (row:2, section:1), (row:3, section:2)
Now what happens for the cell (row:3, section:1)?
The row-array contains the value "3", and the section-array contains value "1", therefore it will be considered as expanded.
Therefore, you need to store the index path as a whole - see the sample code:
var expanded:[IndexPath] = []
expanded.append(IndexPath(row:1, section:1))
expanded.append(IndexPath(row:2, section:1))
expanded.append(IndexPath(row:3, section:2))
let checkPath = IndexPath(row:3, section:1)
if (expanded.contains(checkPath)) {
print ("is expanded")
} else {
print ("collapsed")
}
Update
So in your button handle, you'll do the following:
#IBAction moreButtonTapped(_ sender: Any) {
if(expanded.contains(indexPath)) {
expanded.removeAll { (checkPath) -> Bool in
return checkPath == indexPath
}
} else {
expanded.append(indexPath)
}
entriesTableView.beginUpdates()
entriesTableView.reloadRows(at: [indexPath], with: .none)
entriesTableView.endUpdates()
}
I have used third party expandable table view named "ExpandableTableView",when i want to expand particular single section using custom button in my view.my code snippet is :
UIView *view=Gesture.view;
isFirsTime=NO;
//NSLog(#"%ld",(long)view.tag);
for (int h=0; h<dicAll.count; h++)
{
NSString *strRegisterId=[[[dictStatndardDefectsResult valueForKey:#"defectdata:"]valueForKey:#"projectdefectid"]objectAtIndex:h];
NSString *strBtnTag=[NSString stringWithFormat:#"%ld",(long)view.tag];
if ([strRegisterId isEqualToString:strBtnTag])
{
btnIndex=h;
// NSLog(#"%ld",(long)btnIndex);
isTappedMarker=YES;
}
}
NSMutableArray *indexPaths=[[NSMutableArray alloc]init];
[indexPaths addObject:[NSIndexPath indexPathForRow:0 inSection:btnIndex]];
NSLog(#"%#",arrDefectImages);
NSLog(#"numberOfRowsInSection: %ld",(long)[self tableView:tblSupplierDefect numberOfRowsInSection:btnIndex]);
[tblSupplierDefect beginUpdates];
[self.tblSupplierDefect insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationAutomatic];
[tblSupplierDefect endUpdates];
where the btnIndex is section number which i want to expand.
I have achieved something like that by using this:
func didTapOnHeader(tapGesture: UITapGestureRecognizer) {
let view = tapGesture.view as! ViewHeader
// collapse if already expended.
if view.tag == self.dataBinder.selectedSection {
//collaps the section
self.dataBinder.selectedSection = nil
self.tableView.reloadSections(NSIndexSet(index:view.tag), withRowAnimation: UITableViewRowAnimation.Automatic)
}else {
// collapse last selected section, at a time one section should be selected.
self.dataBinder.selectedSection = nil
self.tableView.reloadData()
//expand plan details
self.dataBinder.selectedSection = view.tag
self.tableView.reloadSections(NSIndexSet(index:view.tag), withRowAnimation: UITableViewRowAnimation.Automatic)
}
}
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let view = ViewHeader.instanceFromNib()
view.tag = section
let gesture = UITapGestureRecognizer(target: self, action: #selector(ViewController.didTapOnHeader(_:)))
view.addGestureRecognizer(gesture)
//### Assignment ###
view.planDescriptionLabel.text = plan.planDescription
return view
}
This will close perviously selected sections and will expand only one section at ta time.
I have a method in Objective-C that I've used to uncheck all cells in a UITableView:
- (void)resetCheckedCells {
for (NSUInteger section = 0, sectionCount = self.tableView.numberOfSections; section < sectionCount; ++section) {
for (NSUInteger row = 0, rowCount = [self.tableView numberOfRowsInSection:section]; row < rowCount; ++row) {
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:row inSection:section]];
cell.accessoryType = UITableViewCellAccessoryNone;
cell.accessoryView = nil;
}
}
}
In Swift, I think I need to use enumeration to accomplish this. I'm stumped as to how to get the values I need. Here's a "physics for poets" sketch of what I'm trying to do:
func resetCheckedCells() {
// TODO: figure this out?
for (section, tableView) in tableView.enumerate() {
for (row, tableView) in tableView {
let cell = UITableView
cell.accessoryType = .None
}
}
}
This doesn't work, but it's illustrative of what I'm trying to accomplish. What am I missing?
UPDATE
There was a very simple, but non-apparent (to me), way to do this involving cellForRowAtIndexPath and a global array...
var myStuffToSave = [NSManagedObject]()
... that's instantiated with the UITableViewController loads. I'm posting this update in hopes that someone else might find it helpful.
My UITableViewController is initially populated with NSManagedObjects. My didSelectRowAtIndexPath does two things:
1) adds/removes NSManagedObjects from a global myStuffToSave array
2) toggles cell.accessoryType for the cell between .Checkmark and .None
That when cellForRowAtIndexPath is called, I compare items from myStuffToSave with what's in the tableView.
Here's a snippet of my cellForRowAtIndexPath:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
// I set the cells' accessory types to .None when they're drawn
// ** SO RELOADING THE tableView NUKES THE CHECKMARKS WITH THE FOLLOWING LINE... **
cell.accessoryType = .None
// boilerplate cell configuration
// Set checkmarks
// ** ...IF THE ARRAY IS EMPTY
if self.myStuffToSave.count > 0 {
// enumerate myStuffToSave...
for (indexOfMyStuffToSave, thingToSave) in stuffToSave.enumerate() {
// if the object in the array of stuff to save matches the object in the index of the tableview
if stuffInMyTableView[indexPath.row].hashValue == stuffToSave[indexOfMyStuffToSave].hashValue {
// then set its accessoryView to checkmark
cell.accessoryType = .Checkmark
}
}
}
return cell
}
So removing everything from myStuffToSave and reloading the tableView will reset all the checked cells. This is what my resetCheckedCells method looks like at the end:
func resetCheckedCells() {
// remove everything from myStuffToSave
self.myStuffToSave.removeAll()
// and reload tableView where the accessoryType is set to .None by default
self.tableView.reloadData()
}
Thanks to #TannerNelson for pointing me towards a solution.
This seems like a strange way to use UITableView.
You should look at the UITableViewDataSource protocol and implement your code using that.
The main function you will need to implement is tableView:cellForRowAtIndexPath. In this function, you dequeue and return a cell.
Then when you need to update cells to be checked or unchecked, you can just call reloadAtIndexPaths: and pass the visible index paths.
This gist has a nice UITableView extension for reloading only visible cells using self.tableView.reloadVisibleCells()
https://gist.github.com/tannernelson/6d140c5ce2a701e4b710
What I am trying to do:
I have a view controller with a table in it and that table is controlled by a custom class called protoCell. Here is my ViewController code:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("pCell", forIndexPath: indexPath) as! protoCell
return cell
}
The output of this table is just 3 rows with two buttons. The buttons are controlled by a custom class which here is the code for that:
//
// protoCell.swift
// Handling Cells
import UIKit
class protoCell: UITableViewCell {
var isPressed = [0, 0]
#IBOutlet var button: UIButton!
#IBAction func buttonPress(sender: AnyObject) {
if isPressed[sender.tag] == 0 {
isPressed[sender.tag] = 1
} else {
isPressed[sender.tag] = 0
}
println(isPressed)
}
}
I have the buttons mapped to the same pressed action and managed by tags. When a button is pressed it marks down which button was pressed. In the main ViewController I want to know which buttons are pressed as well as which row (because the buttons would correspond to different values based upon which row the user is tapping).
I searched here a lot and couldn't find how to do this while managing the cells using a custom class.
Any help would be greatly appreciated.
This worked for me. I had similar requirement as yours.
//Create a variable in your class for indexPath
var customIndexPath: NSIndexPath = NSIndexPath()
//Set the value of indexPath to customIndexPath in your tableView function
customIndexPath = indexPath
// You can use this customIndexPath as given in the examples below:
self.array.removeAtIndex(customIndexPath.row)
self.tableName.reloadSections(NSIndexSet(index: customIndexPath.section), withRowAnimation: UITableViewRowAnimation.Fade)
self.tableName.deleteRowsAtIndexPaths([customIndexPath], withRowAnimation: UITableViewRowAnimation.Top)
I have used this in similar way
func btnOKClicked(sender:UIButton) {
if sender.tag == 1
{
self.tabBarController!.tabBar.userInteractionEnabled = true
println("Successful")
}
else if sender.tag == 2
{
self.tabBarController!.tabBar.userInteractionEnabled = true
self.tableName.reloadSections(NSIndexSet(index: customIndexPath.section), withRowAnimation: UITableViewRowAnimation.Fade)
self.activityIndicator.stopAnimating()
}
else if sender.tag == 3
{
self.tabBarController!.tabBar.userInteractionEnabled = true
var labelone = self.stringArray[customIndexPath.row]
self.deleteFromLocal(labelone)
self.arrary2.removeAtIndex(customIndexPath.row)
self.tableName.deleteRowsAtIndexPaths([customIndexPath], withRowAnimation: UITableViewRowAnimation.Top)
self.tableName.reloadSections(NSIndexSet(index: customIndexPath.section), withRowAnimation: UITableViewRowAnimation.Fade)
}
println("customIndexPath : customIndexPath :::::::\(customIndexPath)")
println("btnOKClicked")
}
Hope this might be helpful.
You say "The output of this table is just 3 rows with two buttons. The buttons are controlled by a custom class which here is the code for that"
For this type of task, a static table view may be much easier.
To do this:
In the story board go to your TableView (inside the UITableViewCotroller). In the Attribute Inspector choose Content = static cells.
when you do this, you can draw each cell individually with buttons and you can connect your Buttons directly with your class in the storyboard (#IBOutlet and #IBAction)
You'll find many good tutorials with the keywords "static table view".
I have create a UITableView in IB. This view contains 5 sections and every section some cells. The first cell in some sections gives the option to the end user to show/hide the rest of the cells that belongs to the same section.
My code so far:
import UIKit
class SettingsVC: UITableViewController {
#IBOutlet var showCallForwardSwitch: UISwitch?
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func toggleValueChanged(sender: UISwitch) {
if showCallForwardSwitch!.on {
println("switch is on")
} else {
println("switch is off")
}
tableView.reloadData()
}
}
So there is only an IBOutlet and an IBAction. I can get the event via the toogleValueChanged func, however I don't know what to do from now on. Which methods to I need to use?
override func tableView(tableView:UITableView, heightForRowAtIndexPath indexPath:NSIndexPath)->CGFloat
{
let cell:DetailCell = self.tableView.cellForRowAtIndexPath(indexPath) as DetailCell // ????????
var height:CGFloat = 84.0;
if ("toggel on"){
height = 84.0;
}
else{
height = 0.0;
}
return height;
}
You don't wanna reload the whole tableView because of one section. I suggest using deleteRowsAtIndexPaths(_:withRowAnimation:) keeping only the first cell so the user will be able to display the rest of the section again. Update your data source as well and use some flag so you know which cell to not display.