Determine if a tableview cell is visible - iphone

Is there any way to know if a tableview cell is currently visible?
I have a tableview whose first cell(0) is a uisearchbar.
If a search is not active, then hide cell 0 via an offset.
When the table only has a few rows, the row 0 is visible.
How to determine if row 0 is visible or is the top row?

UITableView has an instance method called indexPathsForVisibleRows that will return an NSArray of NSIndexPath objects for each row in the table which are currently visible. You could check this method with whatever frequency you need to and check for the proper row. For instance, if tableView is a reference to your table, the following method would tell you whether or not row 0 is on screen:
-(BOOL)isRowZeroVisible {
NSArray *indexes = [tableView indexPathsForVisibleRows];
for (NSIndexPath *index in indexes) {
if (index.row == 0) {
return YES;
}
}
return NO;
}
Because the UITableView method returns the NSIndexPath, you can just as easily extend this to look for sections, or row/section combinations.
This is more useful to you than the visibleCells method, which returns an array of table cell objects. Table cell objects get recycled, so in large tables they will ultimately have no simple correlation back to your data source.

To checking tableview cell is visible or not use this code of line
if(![tableView.indexPathsForVisibleRows containsObject:newIndexPath])
{
// your code
}
here newIndexPath is IndexPath of checking cell.....
Swift 3.0
if !(tableView.indexPathsForVisibleRows?.contains(newIndexPath)) {
// Your code here
}

I use this in Swift 3.0
extension UITableView {
/// Check if cell at the specific section and row is visible
/// - Parameters:
/// - section: an Int reprenseting a UITableView section
/// - row: and Int representing a UITableView row
/// - Returns: True if cell at section and row is visible, False otherwise
func isCellVisible(section:Int, row: Int) -> Bool {
guard let indexes = self.indexPathsForVisibleRows else {
return false
}
return indexes.contains {$0.section == section && $0.row == row }
} }

Swift Version:
if let indices = tableView.indexPathsForVisibleRows {
for index in indices {
if index.row == 0 {
return true
}
}
}
return false

Another solution (which can also be used to check if a row is fully visible) would be to check if the frame for the row is inside the visible rect of the tableview.
In the following code, ip represents the NSIndexPath:
CGRect cellFrame = [tableView rectForRowAtIndexPath:ip];
if (cellFrame.origin.y<tableView.contentOffset.y) { // the row is above visible rect
[tableView scrollToRowAtIndexPath:ip atScrollPosition:UITableViewScrollPositionTop animated:NO];
}
else if(cellFrame.origin.y+cellFrame.size.height>tableView.contentOffset.y+tableView.frame.size.height-tableView.contentInset.top-tableView.contentInset.bottom){ // the row is below visible rect
[tableView scrollToRowAtIndexPath:ip atScrollPosition:UITableViewScrollPositionBottom animated:NO];
}
Also using cellForRowAtIndexPath: should work, since it returns a nil object if the row is not visible:
if([tableView cellForRowAtIndexPath:ip]==nil){
// row is not visible
}

Note that "Somewhat visible" is "visible". Also, in viewWillAppear, you might get false positives from indexPathsForVisibleRows as layout is in progress, and if you're looking at the last row, even calling layoutIfNeeded() won't help you for tables. You'll want to check things in/after viewDidAppear.
This swift 3.1 code will disable scroll if the last row is fully visible. Call it in/after viewDidAppear.
let numRows = tableView.numberOfRows(inSection: 0) // this can't be in viewWillAppear because the table's frame isn't set to proper height even after layoutIfNeeded()
let lastRowRect = tableView.rectForRow(at: IndexPath.init(row: numRows-1, section: 0))
if lastRowRect.origin.y + lastRowRect.size.height > tableView.frame.size.height {
tableView.isScrollEnabled = true
} else {
tableView.isScrollEnabled = false
}

Swift:
Improved #Emil answer
extension UITableView {
func isCellVisible(indexPath: IndexPath) -> Bool {
guard let indexes = self.indexPathsForVisibleRows else {
return false
}
return indexes.contains(indexPath)
}
}

IOS 4:
NSArray *cellsVisible = [tableView indexPathsForVisibleRows];
NSUInteger idx = NSNotFound;
idx = [cellsVisible indexOfObjectsPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop)
{
return ([(NSIndexPath *)obj compare:indexPath] == NSOrderedSame);
}];
if (idx == NSNotFound)
{

Best to check by Frame
let rectOfCellInSuperview = yourTblView.convert(yourCell.frame, to: yourTblView.superview!) //make sure your tableview has Superview
if !yourTblView.frame.contains(rectOfCellInSuperview) {
//Your cell is not visible
}

Swift 2023 simple solution, for a given index path:
tableView.indexPathsForVisibleItems.contains(cellIndexPath)

For me the problem was that indexPathsForVisibleRows doesn't take tableView insets into account. So for example if you change insets on keyboard appearance, rows covered by keyboard still are considered visible. So I had to do this:
func isRowVisible(at indexPath: IndexPath, isVisibleFully: Bool = false) -> Bool {
let cellRect = tableView.rectForRow(at: indexPath)
let cellY = isVisibleFully ? cellRect.maxY : cellRect.minY
return cellY > tableView.minVisibleY && cellY < tableView.maxVisibleY
}
And this is UITableView extension:
extension UITableView {
var minVisibleY: CGFloat {
contentOffset.y + contentInset.top
}
var maxVisibleY: CGFloat {
contentOffset.y + bounds.height - contentInset.bottom
}
}

Related

TableView reloadData resets scrollView contentOffset in header cell

I have a tableView with two custom cells. One for the header and one for the cells inside the tableView.
The Header cell looks like this:
Label Label ScrollView(inside scrollView is an imageView)
The other cell looks like this:
Label TextField ScrollView(inside scrollView is an imageView)
When I scroll one cell horizontally all other cells (including header cell) will get the same contentOffset. This works like a charm.
I added another function which adds another imageView inside the scrollView on top of the existing imageView for all cells except the header cel. Inside the new imageView I add a line which is draggable. This is realized by a longPressureGesture. Inside this gesture I need to do a tableView.reloadData() so that each time the line moves it will be updated for all cells. LongPressureGesture allows me to do this without loosing the control of the line while doing a reloadData().
But here is my problem:
The contentOffset of the scrollView inside the headerCell is reseted after calling reloadData() inside the longPressureGesture. But the contentOffset for all other scrollViews in the cells are still the same.
I tried to add the contentOffset in the headerCell so that each time the reloadData is called the contentOffset will be set. But this is not working because the contentOffset will be called to early and has no effect.
If I add a delay and then set the contentOffset again it is working. But this ends up in flickering which is not good.
Edit: Tried to use only one cell (used cell for header instead of specific header cell). Ended up with the same result. So this is an general issue for headers in tableView?
My code for the cells is:
/*
* This method fills all the given information for each signal to a custom cell by type SignalCell.
* Each cell has a signalName, a value at specific time and a wave image.
*/
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! SignalCell
cell.delegate = self
// Get the signal for this row
let signal = VCDHelper.sharedInstance.signals[indexPath.row] as! VCDSignal
// Set the name of the signal to the label
cell.signalLabel.text = signal.name
cell.name = signal.name
// cellContainer is set inside renderWave every time a new Cell appears
renderTimePicker(cell, signal: signal, indexPath: indexPath)
renderWave(cell, signal: signal, indexPath: indexPath)
cellContainer.setObject(cell, forKey: signal.name)
return cell
}
Code for header:
// Display header cell
override func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let cell = tableView.dequeueReusableCellWithIdentifier(cellHeaderIdentifier) as! HeaderCell
// Add object to renderer dictionary
var headerRenderer = headerToRender.objectForKey("header") as? TimeRenderer
// Render waves
if let headerRenderer = headerRenderer {
cell.timeImageView.image = headerRenderer.renderedTime()
} else {
// There is no renderer yet
headerRenderer = TimeRenderer()
headerToRender.setObject(headerRenderer!, forKey: "header")
// Creates and returns an NSBlockOperation object with block
let operation = NSBlockOperation(block: { () -> Void in
let renderedHeaderImage = headerRenderer!.renderedTime()
NSOperationQueue.mainQueue().addOperationWithBlock({ () -> Void in
cell.timeImageView.image = renderedHeaderImage
cell.cellContainer = self.cellContainer
})
})
queue.addOperation(operation)
headerToRenderOperations.setObject(operation, forKey: "header")
}
cellContainer.setObject(cell, forKey: "header")
return cell
}
And here the code for saving/setting the contentOffset for each Cell realized in SignalCell class:
/*
* This method allows only horizontal scrolling.
* It also handles that scrolling out of bounds is not allowed
*/
func scrollViewDidScroll(scrollView: UIScrollView) {
// Set contentOffsetX to minimum bound value because we are out of range
if scrollView.contentOffset.x < 0 {
scrollCells(scrollView, contentOffsetX: 0.0)
VCDHelper.sharedInstance.waveScrollContentOffsetX = 0.0
}
if scrollView.contentOffset.x > 0 {
// ContentOffsetX is the point at the beginning of the scrollView
// We know the total size of the image and need to subtract the size of the scrollView
// This will result in the max contentOffset.x for scrolling
if scrollView.contentOffset.x <= CGFloat(VCDHelper.sharedInstance.imageWidth) - scrollView.bounds.width {
scrollCells(scrollView, contentOffsetX: scrollView.contentOffset.x)
VCDHelper.sharedInstance.waveScrollContentOffsetX = Float(scrollView.contentOffset.x)
} else {
// Set contentOffsetX to maximal bound value because we are out of range
scrollCells(scrollView, contentOffsetX: CGFloat(VCDHelper.sharedInstance.imageWidth) - scrollView.bounds.width)
}
}
}
/*
* This method scrolls all visible images inside the scrollView at once
*/
func scrollCells(scrollView: UIScrollView, contentOffsetX: CGFloat) {
for (key, cell) in self.cellContainer {
if key as! String == "header" {
let headerCell = cell as! HeaderCell
let scrollContentOffsetY = headerCell.timeScrollView.contentOffset.y
// Dont use setContentOffset because this will call scrollViewDidScroll each time
headerCell.timeScrollView.contentOffset = CGPoint(x: contentOffsetX, y: scrollContentOffsetY)
} else {
let signalCell = cell as! SignalCell
let scrollContentOffsetY = signalCell.signalScrollView.contentOffset.y
// Dont use setContentOffset because this will call scrollViewDidScroll each time
signalCell.signalScrollView.contentOffset = CGPoint(x: contentOffsetX, y: scrollContentOffsetY)
}
}
}

UITableView - How to keep table rows fixed as user scrolls

I'd like to be able to fix the position of certain rows in a UITableView as the user scrolls.
Specifically, I have a table whereby certain rows are "headers" for the rows that follow, and I'd like the header to stay at the top of the screen as the user scrolls up. It would then move out of the way when the user scrolls far enough that the next header row would take its place.
A similar example would be the Any.DO app. The "Today", "Tommorrow" and "Later" table rows are always visible on the screen.
Does anyone have any suggestions about how this could be implemented?
I'm currently thinking of follow the TableDidScroll delegate and positioning my own cell in the appropriate place in front of the table view. The problem is that at other times I'd really like these cells to be real table cells so that they can be, for example, reordered by the user.
Thanks,
Tim
I've been playing about with this and I've come up with a simple solution.
First, we add a single UITableViewCell property to the controller. This should be initialize such that looks exactly like the row cells that we'll use to create the false section headers.
Next, we intercept scrolling of the table view
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
// Add some logic here to determine the section header. For example, use
// indexPathsForVisibleRows to get the visible index paths, from which you
// should be able to get the table view row that corresponds to the current
// section header. How this works will be implementation dependent.
//
// If the current section header has changed since the pervious scroll request
// (because a new one should now be at the top of the screen) then you should
// update the contents.
IndexPath *indexPathOfCurrentHeaderCell = ... // Depends on implementation
UITableViewCell *headerCell = [self.tableView cellForRowAtIndexPath:indexPathOfCurrentHeaderCell];
// If it exists then it's on screen. Hide our false header
if (headerCell)
self.cellHeader.hidden = true;
// If it doesn't exist (not on screen) or if it's partially scrolled off the top,
// position our false header at the top of the screen
if (!headerCell || headerCell.frame.origin.y < self.tableView.contentOffset.y )
{
self.cellHeader.hidden = NO;
self.cellHeader.frame = CGRectMake(0, self.tableView.contentOffset.y, self.cellHeader.frame.size.width, self.cellHeader.frame.size.height);
}
// Make sure it's on top of all other cells
[self.tableView bringSubviewToFront:self.cellHeader];
}
Finally, we need to intercept actions on that cell and do the right thing...
That's the default behavior for section headers in plain UITableView instances.
If you want to create a custom header, implement the tableView:viewForHeaderInSection: method in your table view delegate and return the view for your header.
Although you will have to manage sections and rows instead of just rows.
Swift 5 solution
var header: UIView?
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(indexPath: indexPath) as UITableViewCell
header = cell.contentView
return cell
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let headerCell = tableView.cellForRow(at: IndexPath(row: 0, section: 0))
guard headerCell == nil || (headerCell!.frame.origin.y < self.tableView.contentOffset.y + headerCell!.frame.height/2) else {
header?.isHidden = true
return
}
guard let hdr = header else { return }
hdr.isHidden = false
hdr.frame = CGRect(x: 0, y: tableView.contentOffset.y, width: hdr.frame.size.width, height: hdr.frame.size.height)
if !tableView.subviews.contains(hdr) {
tableView.addSubview(hdr)
}
tableView.bringSubviewToFront(hdr)
}

How to give space between two cells in tableview?

I want a space between two cell in table view,
I want cell like this,
How can i do that?
you can't set distance between cells directly, but you can set the height for header in section to achieve the same result.
1.set the numbers of cell you need as sections:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 3; // in your case, there are 3 cells
}
2.return only 1 cell for each section
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 1;
}
3.set the height for header in section to set space between cells
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
return 10.; // you can have your own choice, of course
}
4.set the header's background color to clear color, so it won't look weird
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
UIView *headerView = [[UIView alloc] init];
headerView.backgroundColor = [UIColor clearColor];
return headerView;
}
The Best way to get space between two cells in TableView, declare the numbers of sections you want in delegate method of numberofsections this way
For example you have array of 10 objects
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return [array count]; //array count returns 10
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 1;// this should be one because it will create space between two cells if you want space between 4 cells you can modify it.
}
Then the important point is in cellForRowAtIndexPath delegate method you need to use indexpath.section but not indexpath.row
cell.textLabel.text=[NSString stringWithFormat:#"%#",[array objectAtIndex:indexPath.section]];
That is is check your tableview for the space between two cells. Enjoy!
You can create a Sections of TableView also in the UITableView... This methods are compulsory so create sections and in each section you can create single cell as in your picture..
The multiple sections answer would work, but it's extremely brittle, and doesn't allow for actual sections. Instead, you should create a custom cell, or custom cell prototype that simply has a gap at the bottom and/or top.
Use your struts and springs in IB to maintain that uniform gap, and use heightForRowAtIndexPath to return a height that includes the gap.
Objective - C
UIView* separatorLineView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 3)];/// change size as you need.
separatorLineView.backgroundColor = [UIColor whiteColor];// you can also put image here
[cell.contentView addSubview:separatorLineView];
Swift 3 (This same will work for Swift 4 also)
var separatorLineView = UIView(frame: CGRect(x: 0, y: 0, width: 320, height: 3))
/// change size as you need.
separatorLineView.backgroundColor = UIColor.white
// you can also put image here
cell.contentView.addSubview(separatorLineView)
It worked for me.
If someone looking for Swift version. Here you go.
func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 10; // space b/w cells
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return items.count // count of items
}
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let header = UIView()
header.userInteractionEnabled = false
header.backgroundColor = UIColor.clearColor()
return header
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
For people that looking for alternative way of showing gaps between cells without using sections, you may want to show alternate colours and height like below. Use clearColor for the spacing.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
if (indexPath.row % 2 == 1)
{
static NSString *CellIdentifier = #"cellID1";
UITableViewCell *cell = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:CellIdentifier];
}
cell.backgroundColor = [UIColor colorWhite];
return cell;
} else {
static NSString *CellIdentifier2 = #"cellID2";
UITableViewCell *cell2 = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier2];
if (cell2 == nil) {
cell2 = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:CellIdentifier2];
}
cell2.backgroundColor = [UIColor clearColor];
return cell2;
}
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.row % 2 == 1) {
return 40.0;
} else {
return 2.0;
}
}
Add these lines in the cellForRowAtIndexPath UITableViewDelegate method before returning the cell.
let separator = UIView(frame: CGRectMake(0, 0, cell!.bounds.size.width, 1))
separator.backgroundColor = UIColor.whiteColor()
cell.contentView.addSubview(separator)
Sometimes, you might really want to keep the tableview divided in rows, and have 1 section. For example, this could happen if you need to display a custom header for that table view that stays in place when you scroll through the section.
What I would recommend doing in this case, is returning a bigger float than the normal height of a cell in:
- (float)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
and then making sure that the table style is Plain, and that the cell separator is none. You can do that in the XIB file itself, or in code:
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
self.tableView.style = UITableViewStylePlain;
Maybe also add the cell's selection style to none (otherwise it will look like you are selecting more than just the visible part of the cell).
cell.selectionStyle = UITableViewCellSelectionStyleNone;
This will give the impression of space between the cells, but at the same time keeping them as rows in one section (which sometimes is what you want).
I have used a simple and quick approach for this (using storyboard)
Add UITableView in your controller
Set the separator style to none (from attributes inspector)
Set the height of the row 5 pts more from top and bottom from the desired height
Now add an image in the cell, pin it from left and right but leave 5 pts space (for padding like feel) and set the background of the image same as the background you want for cell
When the table view will be loaded, it will feel like there are spaces between cells.
For spacing between cells like the ones in your screenshot, there is no need for custom cells (for their look anyway, like the gradient bkg and so on, this could anyway be a good idea, but this won't help for your spacing between cells)
To achieve this kind of spacing, simply use different sections in your UITableView.
[EDIT] Everything is explained In Apple's TableView Programming Guide (and that's worth reading it, as it contains a lot of stuff you should know about tableviews)
The best way to do that using xib file. give top and bottom constraint to it. for example, here I give bottom constraint 10 and make sure give perfect height to cell as shown as given below.here in code 'joinTableViewCell' is fileName of xib file.
extension JoinTableViewController: UITableViewDataSource,UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 4
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = Bundle.main.loadNibNamed("joinTableViewCell", owner: self, options: nil)?.first as! joinTableViewCell
cell.ImgView.layer.masksToBounds = true
cell.ImgView.cornerRadius(cell.ImgView.bounds.width / 2)
cell.lblName.text = "Christian Bell"
cell.lblJoin.text = "Joined"+"\t"+"Sep 3"
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 90
}
}
What you're trying to achieve, visually, would be the same as adding the content of each cell inside a container View with a gray background, and having that view inside the cell. I don't think there's a need to add spaces between cells.
I don't know why all the answers that complicated. KIS, only using storyboard I've put a UIView inside the tableCell Content View; now the UIView height is less than Content View height, and that's it!
Play with the Content View color and the UIView color to get the desired result.
My easy solution using Swift :
// Inside UITableViewCell subclass
override func layoutSubviews() {
let f = contentView.frame
let fr = UIEdgeInsetsInsetRect(f, UIEdgeInsetsMake(10, 10, 10, 10))
contentView.frame = fr
}
or one line code
override func layoutSubviews() {
contentView.frame = UIEdgeInsetsInsetRect(contentView.frame, UIEdgeInsetsMake(10, 10, 10, 10))
}
Result
* WORKING WITH IOS 9 XCODE 7.3 *
The most straight forward way to achieve this is to simply add this code to your cellForRowAtIndexPath method.
cell.separatorInset.left = 20.0
cell.separatorInset.right = 20.0
cell.separatorInset.top = 20.0
cell.separatorInset.bottom = 20.0
cell.layer.borderWidth = 3
cell.layer.cornerRadius = 20.0
cell.layer.borderColor = UIColor.flatSkyBlueColorDark().CGColor
Then go to your story board and click on the tableview. Go to identity inspector and change the View's background color to whatever border color was set in the method. Voila! Play with the values to get the desired output. Hope this helps!
Note: If using Chameleon library you must set the background color for the view in code, not via the story board plugin. For some reason the color seems to be off by a shade.
Well what I did is simply simulate the space by creating a simple custom cell that is a little larger than what I want (cell + gap/2), and then put all my cell's content in an inner-view.
Then put your cell's topmost view as the color of your background, and the inner-view as your actual cell. And you'll have the illusion of having space between cells, when it's actually just a larger cell with borders.
In Table View DataSource there are two methods named number of sections and number of rows
In sections
return 3;
In Rows
return 1;
You don't have to assign each section for each cell. Just create a UIView (container) inside your cell, margin it with cell's view. And we layout components like label, text, image on that container.
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return __titlesArray.count;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 1;
}
-(CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
return 10;
}
-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
UIView *header = [[UIView alloc]init];
header.backgroundColor = [UIColor clearColor];
return header;
}
I use like:
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 194.0;
}
override func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
cell.contentView.backgroundColor = UIColor.clearColor()
let whiteRoundedView : UIView = UIView(frame: CGRectMake(0, 0, self.view.frame.size.width, 185))
whiteRoundedView.backgroundColor = UIColor( red: CGFloat(61.0/255.0), green: CGFloat(117.0/255.0), blue: CGFloat(147.0/255.0), alpha: CGFloat(1.0))
whiteRoundedView.layer.masksToBounds = false
whiteRoundedView.layer.cornerRadius = 3.0
whiteRoundedView.layer.shadowOffset = CGSizeMake(-1, 1)
whiteRoundedView.layer.shadowOpacity = 0.5
cell.contentView.addSubview(whiteRoundedView)
cell.contentView.sendSubviewToBack(whiteRoundedView)
}
Get Color Code RGB values from:
1) first create 2 sections in table view.
2) create an empty cell.
3) create the cell with data you want to display.
4) use the method
(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.section==0) {
if (indexPath.row % 2 != 1) {
return 15.0;
} else {
return 100;
}
}
else
if (indexPath.row % 2 != 1) {
return 100.0;
} else {
return 15.0;
}
}
It will add space between the cells. It worked for me.
Here is my method with Swift 3:
In ViewDidLoad(), add:
self.tableView.rowHeight = 500.0
In tableview "CellForRowAt", add following:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
// Configure the cell...
var actualContentView = UIView()
actualContentView.frame = CGRect(x: 10, y: 10, width: cell.contentView.frame.width - 20, height: cell.contentView.frame.height - 20)
actualContentView.backgroundColor = UIColor.blue
cell.contentView.addSubview(actualContentView)
return cell
}
in your cellForRowAt
cell.layer.borderWidth = 2
cell.layer.cornerRadius = 10
cell.layer.borderColor = UIColor.systemBackground.cgColor
I suggest to create a custom UITableViewCell base class and use this class like below,
Create custom UITableViewCell class
Create a UIView in new class, this will act as the 'baseContentView' and it will be immediate child of 'UITableViewCell.contentView'
Adjust the top 'padding' on 'baseContentView' (this will be the separator/gap) from parent view (UITableViewCell.contentView)
Inherit all your custom classes from this class rather than UITableViewCell
Add all the content/subviews to 'baseContentView' rather than 'self.contentView'
You can play with the 'padding' as per your need.
I have checked all the answers but I think this is the easiest way:
UIView * theContentView = [UIView alloc]initWithFrame:CGRectMake(0,gap,width,height)];
theContentView.backgroundcolor = [UIColor lightGrayColor];//contentColor
cell.backgroundcolor = [UIColor blackColor];//gapColor
[cell addSubview: theContentView]
The prototype code says You can create a subview to show the content of cell and the rest is the gap as you wish.

Custom UITableView reorder

I'm wondering if anyone has a link to a good tutorial or can point me in the right direction to recreate the 'drag to reorder' cells in a UITableView like Epic Win App. The basic idea is you tap and hold on a list item and then you drag to where you want the item to be. Any help would be greatly appreciated.
The simplest way, using the built-in methods, is as follows:
First, set your table cell to have shows reorder control on. Simplest example (using ARC):
This is assuming NSMutableArray *things has been created somewhere else in this class
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:#"do not leak me"];
if (!cell) {
cell = [[UITableViewCell alloc] init];
cell.showsReorderControl = YES;
}
cell.textLabel.text = [things objectAtIndex:indexPath.row];
return cell;
}
Implement your two UITableViewDelegate methods, like this:
This method tells the tableView it is allowed to be reordered
-(BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath {
return YES;
}
This is where the tableView reordering actually happens
-(void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath {
id thing = [things objectAtIndex:sourceIndexPath.row];
[things removeObjectAtIndex:sourceIndexPath.row];
[things insertObject:thing atIndex:destinationIndexPath.row];
}
and then somehow, somewhere, set editing to true. This is one example of how you can do this. The tableView must be in editing mode in order for it to be reordered.
- (IBAction)doSomething:(id)sender {
self.table.editing = YES;
}
Hope that serves as a concrete example.
It is pretty straight forward - which is probibly why there is no explicit tutorial on the matter.
Just create the UITableView normally, but set the showsReorderControl of each cell to TRUE. When you go into editing mode (normally by pressing the "edit" button and setting the "Editing" value of the UITableView to TRUE) - the reorder bars will appear in the cells.
Note:
If your data source implements tableView:canMoveRowAtIndexPath: - and it returns "NO" - the reorder bars will not appear.
You will also need to implement –tableView:moveRowAtIndexPath:toIndexPath: in the data source delegate.
This solution here will function similar to the Reminders app on OSX which allows you to reorder cells by holding and dragging anywhere.
I've created a subclass of UITableView that allows you to drag and reorder the cells without having to have editing on or use the standard reorder handles.
The basic idea is I use the touches began and touches ended to get which cell was tapped, I then create a fake view that is really just a screenshot of the view you tapped and move it back and forth, swapping the cells in the background. When the user lets go I then unhide the original view.
class ReorderTableView: UITableView {
var customView:UIImageView?
var oldIndexPath:NSIndexPath?
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
guard let touch1 = touches.first else{
return
}
oldIndexPath = self.indexPathForRowAtPoint(touch1.locationInView(self))
guard (oldIndexPath != nil) else{
return
}
let oldCell = self.cellForRowAtIndexPath(self.oldIndexPath!)
customView = UIImageView(frame: CGRectMake(0, touch1.locationInView(self).y - 20, self.frame.width, 40))
customView?.image = screenShotView(oldCell!)
customView?.layer.shadowColor = UIColor.blackColor().CGColor
customView?.layer.shadowOpacity = 0.5
customView?.layer.shadowOffset = CGSizeMake(1, 1)
self.addSubview(customView!)
oldCell?.alpha = 0
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
guard let touch1 = touches.first else{
return
}
let newIndexPath = self.indexPathForRowAtPoint(touch1.locationInView(self))
guard newIndexPath != nil else{
return
}
guard oldIndexPath != nil else{
return
}
if newIndexPath != oldIndexPath{
self.moveRowAtIndexPath(oldIndexPath!, toIndexPath: newIndexPath!)
oldIndexPath = newIndexPath
self.cellForRowAtIndexPath(self.oldIndexPath!)!.alpha = 0
}
self.customView!.frame.origin = CGPointMake(0, touch1.locationInView(self).y - 20)
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
self.customView?.removeFromSuperview()
self.customView = nil
guard (oldIndexPath != nil) else{
return
}
self.cellForRowAtIndexPath(self.oldIndexPath!)!.alpha = 1
}
func screenShotView(view: UIView) -> UIImage? {
let rect = view.bounds
UIGraphicsBeginImageContextWithOptions(rect.size,true,0.0)
let context = UIGraphicsGetCurrentContext()
CGContextTranslateCTM(context, 0, -view.frame.origin.y);
self.layer.renderInContext(context!)
let capturedImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return capturedImage
}
}
In the end you can reorder cells without having to use the handles. This can be extended for different gestures, or modes depending on the use case, but I left this as a vanilla version. Let me know if you have any questions.
Update:
Some caveats I found. It is very important you figure out how you want to use scrolling and modify this accordingly. For instance, when I mocked it out I set scrollingEnabled = false. In the long run I did want scrolling, so I changed the code to only run this functionality on a press longer than 0.25 (I used some timers to do this). I would then temporarily disable scrolling during this period, and re-enable upon completion.

How to hide a section in UITableView?

There are some section in the table that does not contain any data and would like to hide that section.
How to do this?
Actually, you can "hide" a section. If you want to use a similar behaviour to the built-in contacts app, where sections are hidden but still listed in the index on the right you can do the following:
Implement the UITableViewDataSource protocol:
Return all section names (even hidden ones) in - the sectionIndexTitlesForTableView method.
For each empty section, return nil from the titleForHeaderInSection method.
For each empty section return 0 for the numberOfRowsInSection method.
I find this works better than deleting sections, because the user has consistent index navigation.
You can't "hide" a section as such, but you can "delete" it from the table view using the deleteSections:withRowAnimation: method. This will remove it from the view, with an optional animation, without affecting your backing data. (You should, however, update the data anyway so that the section doesn't reappear.)
More info: UITableView class reference
It is true that 0 is not a valid height for headers and footers. However, the heights are CGFloat values. You can specify a very small number (I've used 0.1) for the height of the section headers and footers.
Kind of a hack, but it works.
I disagree with Tim. We have a way to access any section/row of a table from anywhere in our code and change its .hidden property (and all other properties).
This is the way I usually use:
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:2];
[self.SeymourCakesTableView cellForRowAtIndexPath:indexPath].hidden = YES;
You can set the number of rows in that section to 0. However, it will leave a noticeable blank area where it used to be.
You can also return the number of records that do contain data from the
numberofSectionsInTableView:
method and use a
switch(indexPath.section)
where you let the empty records 'fall through' to the next switch, like:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
switch (indexPath.section) {
case 0:
return <header cell>;
break;
case 1:
if(firstRecordHasData){
return <second cell>;
break;
}
case 2:
if(secondRecordHasData){
return <second cell>;
break;
}
case 3:
return <some other cell>;
break;
default:
return <a regular cell>;
break;
}
}
I was struggling with this for a while because I had to leave out sections in the middle of a grouped table. Tried with setting cell-, header- and footer heights to 0.0 but that didn't work. Couldn't just delete certain sections because of the called methods depending on the selected row. This was going to be a huge if..else if...else if with multiple callings of subroutines.
Glad I thought of the good old switch method, maybe it helps you as well :-)
You probably need to remove the section itself from the data backing your table. I don't think there's anything that lets you just hide a section.
You can set the particular section rows height to 0. Also, with the section header if you want. Datasource would still be there, only not showing up.
Section Rows
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.section == 0) {
if (_shouldHidden) {
return 0.0;
}
else {
return 55.0;
}
}
else {
return 55.0;
}
}
If you return 0 for the height of the section, Apple API will ignore it. So just return a small value greater than 0.
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
if (section == 0) {
return 1;
}
return 44;
}
Also implement view for header and return nil for the section you don't want to show.
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
if (section == 0 && !self.personaCells.count) {
return nil;
}
UIView *headerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, tableView.frame.size.width, 44)];
UILabel *headerLabel = [[UILabel alloc] initWithFrame:CGRectMake(15, 20, headerView.frame.size.width, 20)];
NSString *headerTitle = #"SAMPLE TITLE";
headerLabel.text = headerTitle;
[headerView addSubview:headerLabel];
return headerView;
}
Try like this:-
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
CGFloat headerHeight=10.f;
if (section==0)
{
headerHeight=0.01f;
}
else
{
headerHeight=50.0f;
}
return headerHeight;
}
For the case of static table, that is, the table sections and cells are configured in Storyboard. The following are my strategies to hide a specified section depending conditions.
Step one: implement two func defined in UITableViewDelegate
- heightForRowAt
- heightForHeaderInSection
For example, here are swift codes:
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
{
// if indexPath contains the specified section AND
// the condition for hiding this section is `true`
// return CGFloat(0)
// else
// return super.tableView(tableView, heightForRowAt: indexPath)
}
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat
{
// similar logic to set header height
}
Step two: define a func to set cells hidden for specific section and call it from viewWillAppear:
private func setSectionVisible()
{
/*
if condition for visible is true
let index = IndexPath(row:..., section:...)
let cell = self.tableView.cellForRow(at: index)
cell.isHiden = true
*/
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.setSectionVisible()
}
In case you need to reload tableview, you may need to call setSectionVisible() again.
I think this strategy may work for dynamic data from DataSource. In this way, you can control when to make specific section visible or hidden.