I have a view controller that contains a navigation bar, a table view and a tool bar. I included the UITableViewDelegate in the view controller, and correctly assigned the table's data source and delegate to the view controller through the storyboard. The table view loads its data from a remote database, once the table scrolls to the last cell more data is loaded into the table. I achieved this by using the scrollViewDidScroll and indexPathForRowAtPoint methods as outlined in the following post: How to know when UITableView did scroll to bottom in iPhone. However, when I run the app and scroll through the table the only index path returned by indexPathForRowAtPoint is the one that was located at the specified point at the time of table load. Here is the code and the output I get when I scroll:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
CGPoint bottomPoint = CGPointMake(160, 430);
CGPoint topPoint = CGPointMake(160, 10);
NSLog(#"%d", [[_tableView indexPathForRowAtPoint:bottomPoint] row]);
}
Every time I scroll the following is outputted:
2013-06-08 00:56:45.006 Coffee[24493:907] 3
2013-06-08 00:56:45.012 Coffee[24493:907] 3
2013-06-08 00:56:45.040 Coffee[24493:907] 3
2013-06-08 00:56:45.069 Coffee[24493:907] 3
2013-06-08 00:56:45.088 Coffee[24493:907] 3
2013-06-08 00:56:45.105 Coffee[24493:907] 3
2013-06-08 00:56:45.135 Coffee[24493:907] 3
2013-06-08 00:56:45.144 Coffee[24493:907] 3
2013-06-08 00:56:45.173 Coffee[24493:907] 3
2013-06-08 00:56:45.180 Coffee[24493:907] 3
Where 3 is the indexPath.row of the cell the bottom point is on when the controller loads. What am I doing wrong and why is this happening? Does it have anything to do with the fact that the UITableView is located inside a parent view controller?
bottomPoint is looking for a location inside your scrollView. Your scrollView contains a table, and all the cells are in the SAME PLACE all the time, relative to your scrollView. That is why you always get the same cell at that point.
When you scroll, the cells don't move in the scrollView, the scrollView "moves" relative to its parent. This is done by having its contentOffset changed.
If you add your scrollView's y content offset to your bottomPoint, you'll get the point that you are probably really looking for in your scrollView.
like this:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
CGPoint bottomPoint = CGPointMake(160, 430) - scrollView.contentOffset.y;
CGPoint topPoint = CGPointMake(160, 10);
NSLog(#"%d", [[_tableView indexPathForRowAtPoint:bottomPoint] row]);
}
As you can see from the Apple's Documentation, the Method indexPathForRowAtPoint: is refer to a point in the local coordinate system of the receiver (the table view's bounds), BUT NO the table view's frame. As the bottomPoint you set is in the table view's frame coordinate system, you will not get the right indexPath. HalR's answer gives the bottomPoint in the table view's bounds coordinate system.
indexPathForRowAtPoint:
Returns an index path identifying the row and section at the given point.
- (NSIndexPath *)indexPathForRowAtPoint:(CGPoint)point
Parameters
point:
a point in the local coordinate system of the receiver (the table view's bounds).
Return Value
An index path representing the row and section associated with point or nil if the point is out of the bounds of any row.
FYI different between Frame and Bounds
Here is the class to implement the feature.
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
let flagView = UIView(frame: CGRect(x: 0, y: 100.0, width: self.view.frame.width, height: 20.0))
flagView.backgroundColor = .blue
self.view.addSubview(flagView)
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 20
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
cell.textLabel?.text = "Cell number: \(indexPath.row)"
return cell
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.contentOffset.y > 0 {
let currentPoint = CGPoint(x: self.view.frame.width - 100, y: 100.0 + scrollView.contentOffset.y)
print(" current index: \(String(describing: tableView.indexPathForRow(at: currentPoint)?.row))")
}
}
}
let bottomPoint = CGPoint(x: tableView.frame.midX, y: tableView.frame.maxY)
let converted = tableView.convert(point, from: tableView.superview)
let indexPathForBottomCell = tableView.indexPathForItem(at: converted)
This should work too, and may be easier to understand. All the additional spaces (like bottom inset) would be handled differently for each case.
Related
HI i have searched everywhere for a solution to my problem and cannot figure it out or found any other posts with the same problem.
I recently updated my Xcode project to Xcode 13 and my iPhone to iOS 15 and Apple made some significant changes to the navigation and tab bar structure which completely destroyed my app.
Need assistance on the correct structure on how to resolve this problem and am sure this will help lots of people who come across this same issue.
I have a ViewControllerA inside of a navigation controller which is nested inside of tab bar controller. User taps a button and segues to another ViewControllerB which is a modal VC that goes over full screen. User can then swipe down with a panGestureRecognizer.
As user swipes down there is a strange flicker effect which looks awful. The only solution I found is in storyboards to completely remove navigation bar and apply constraints to top of the view(I know this is possible but not scalable as I do not want to determine device size). But then view is covered by top bar.
I have attached video showing problem.
Please help as I do not know where to go from here.
THANK YOU
var initialTouchPoint: CGPoint = CGPoint(x: 0,y: 0)
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.delegate = self
tableView.estimatedRowHeight = 636
tableView.rowHeight = UITableView.automaticDimension
}
#IBAction func panGestureRecognizerHandler(_ sender: UIPanGestureRecognizer) {
let touchPoint = sender.location(in: self.view?.window)
if sender.state == UIGestureRecognizer.State.began {
initialTouchPoint = touchPoint
} else if sender.state == UIGestureRecognizer.State.changed {
if touchPoint.y - initialTouchPoint.y > 0 {
self.view.frame = CGRect(x: 0, y: touchPoint.y - initialTouchPoint.y, width: self.view.frame.size.width, height: self.view.frame.size.height)
}
} else if sender.state == UIGestureRecognizer.State.ended || sender.state == UIGestureRecognizer.State.cancelled {
if touchPoint.y - initialTouchPoint.y > 100 {
self.dismiss(animated: true)
} else {
UIView.animate(withDuration: 0.3, animations: {
self.view.frame = CGRect(x: 0, y: 0, width: self.view.frame.size.width, height: self.view.frame.size.height)
})
}
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "SwipeDownCell", for: indexPath) as! SwipeDownTableViewCell
cell.postImageView.image = UIImage(named: "food")
cell.postImageView.contentMode = .scaleAspectFill
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
https://streamable.com/924xoo
I have the same issue. It is caused by a combination of safeArea and the modalPresentationStyle of the parent view controller. In my case it is UIModalPresentationOverCurrentContext.
If you use other presentation styles, e.g. UIModalPresentationPopover, the flickering disappear.
As this is not appropriate in my case, I found an easy workaround.
You need to set the top constraint of your first item in the child view to refer the superview, not the safe area!
To better support devices without the notch, you can use following helper method implemented in AppDelegate.
- (UIEdgeInsets)getSafeAreaInsets {
if (#available(iOS 11.0, *)) {
UIEdgeInsets insets = self.window.safeAreaInsets;
return insets;
} else {
return UIEdgeInsetsZero;
}
Call this in the viewDidLoad of the child view and check if the bottom edge is 0.
//classic devices without notch
if ([(AppDelegate*)[[UIApplication sharedApplication] delegate] getSafeAreaInsets].bottom == 0) {
self.labelHeaderContraint.constant = 32;
}
In My application I have top navigation bar and a tableview below the navigation bar. I have CollectionViewCell with two rows which added inside the UITableViewHeader programmatically. When ever I scroll the the TableView to top, i want the header to stop just below the navigation bar, and update the TableView Header height so I can show only one row. I just want to do an animation (like Shrinked)when the TableViewHeader sticks to the navigationbar the two collectionview rows should turn into one row by decreasing the Header Height. How can I do it programmatically
Below is my code for showing CustomHeaderView
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = UIView.init(frame: CGRect(x: 0, y: 0, width: self.view.frame.width, height: 183))
let headerCell = tableView.dequeueReusableCell(withIdentifier: kLastPlayedidentifier) as! LastPlayedTVC
headerCell.frame = headerView.frame
headerCell.category = lastPlayedData
headerView.addSubview(headerCell)
return headerView
}
Also i'm checking for the scroll position to set the tableview header height progmmatically which isn't successful for me.
func scrollViewDidScroll(_ scrollView: UIScrollView) {
print(scrollView.contentOffset)
if scrollView.contentOffset.y > 237 //This value is to check when the header reached the top position {
//Condition to check and animate the headerview height to make collectionview cell two rows into one rows.
}
How can I achieve the TableViewHeader height update when header sticks on top while scrolling.
Any help is appreciated.
What you are looking for is "sticky header"
and you want to change the header as well.
Sticky part is built in automatically I think if you just use UITableViewController(style: .plain), if that doesn't work for you, you can just google sticky header and there are lots of answers.
the part about changing the height or animating it. you are doing it right, just do something like:
// update your viewForHeader method to account for headerRows variable above
// update your viewForHeader method to account for headerRows variable above
// default 2, you modify this in your scroll
var headerRows = 2
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let height = headerRows == 2 ? 183 : 91
let headerView = UIView.init(frame: CGRect(x: 0, y: 0, width: self.view.frame.width, height: height))
let headerCell = tableView.dequeueReusableCell(withIdentifier: kLastPlayedidentifier) as! LastPlayedTVC
headerCell.frame = headerView.frame
headerCell.category = lastPlayedData
headerView.addSubview(headerCell)
return headerView
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
print(scrollView.contentOffset)
if scrollView.contentOffset.y > 237 {
updatedHeader.frame.size.height = 40
self.tableviewObj.tableHeaderView = updatedHeader
headerRows = 1 } else {
headerRows = 2
}
self.tableView.reloadSectionHeaders()
}
If you want to do some animating instead, what you would do is store reference to your headerView in a variable of your view controller and inside your scrollViewDidScroll animate it using UIView.animate{...}
hope this helps man.
I've set up a scrollView inside my custom cell. Inside my tableViewController I've used...
UIScrollViewDelegate
...Which fill delegate when the "scrollView" has moved. Though, I only want it to react when the image scroll view is moved.
It currently reacts to both, when the tablView scrolls and image scrollview scrolls.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
//other irrelevant cell setup....
scrollView = cell.imageScrollView
scrollView.pagingEnabled = true
scrollView.scrollEnabled = true
scrollView.delegate = self
scrollView.tag = indexPath.row
}
func scrollViewDidScroll(scrollView: UIScrollView) {
var pageWidth = scrollView.frame.size.width
var newPage = floor((scrollView.contentOffset.x - pageWidth / 2) / pageWidth) + 1
//prints: 0.0 , while tableView scrolls
//also, prints newPage when scrollView is moved
print(newPage)
}
I've tried to use sender but didn't know who to properly incorporate it. Any ideas? It seems that....
- (void)scrollViewDidScroll:(UIScrollView *)sender{}
... no longer exists.
You can try use isMemberOfClass(),like this:
if sender.isMemberOfClass(UITableView) {
// your logic
}
else sender.isMemberOfClass(UIScrollView)
{
// your logic
}
I need to do this app that has a weird configuration.
As shown in the next image, the main view is a UIScrollView. Then inside it should have a UIPageView, and each page of the PageView should have a UITableView.
I've done all this so far. But my problem is that I want the scrolling to behave naturally.
The next is what I mean naturally. Currently when I scroll on one of the UITableViews, it scrolls the tableview (not the scrollview). But I want it to scroll the ScrollView unless the scrollview cannot scroll cause it got to its top or bottom (In that case I'd like it to scroll the tableview).
For example, let's say my scrollview is currently scrolled to the top. Then I put my finger over the tableview (of the current page being shown) and start scrolling down. I this case, I want the scrollview to scroll (no the tableview). If I keep scrolling down my scrollview and it reaches the bottom, if I remove my finger from the display and put it back over the tebleview and scroll down again, I want my tableview to scroll down now because the scrollview reached its bottom and it's not able to keep scrolling.
Do you guys have any idea about how to implement this scrolling?
I'm REALLY lost with this. Any help will be greatly appreciate it :(
Thanks!
The solution to simultaneously handling the scroll view and the table view revolves around the UIScrollViewDelegate. Therefore, have your view controller conform to that protocol:
class ViewController: UIViewController, UIScrollViewDelegate {
I’ll represent the scroll view and table view as outlets:
#IBOutlet weak var scrollView: UIScrollView!
#IBOutlet weak var tableView: UITableView!
We’ll also need to track the height of the scroll view content as well as the screen height. You’ll see why later.
let screenHeight = UIScreen.mainScreen().bounds.height
let scrollViewContentHeight = 1200 as CGFloat
A little configuration is needed in viewDidLoad::
override func viewDidLoad() {
super.viewDidLoad()
scrollView.contentSize = CGSizeMake(scrollViewContentWidth, scrollViewContentHeight)
scrollView.delegate = self
tableView.delegate = self
scrollView.bounces = false
tableView.bounces = false
tableView.scrollEnabled = false
}
where I’ve turned off bouncing to keep things simple. The key settings are the delegates for the scroll view and the table view and having the table view scrolling being turned off at first.
These are necessary so that the scrollViewDidScroll: delegate method can handle reaching the bottom of the scroll view and reaching the top of the table view. Here is that method:
func scrollViewDidScroll(scrollView: UIScrollView) {
let yOffset = scrollView.contentOffset.y
if scrollView == self.scrollView {
if yOffset >= scrollViewContentHeight - screenHeight {
scrollView.scrollEnabled = false
tableView.scrollEnabled = true
}
}
if scrollView == self.tableView {
if yOffset <= 0 {
self.scrollView.scrollEnabled = true
self.tableView.scrollEnabled = false
}
}
}
What the delegate method is doing is detecting when the scroll view has reached its bottom. When that has happened the table view can be scrolled. It is also detecting when the table view reaches the top where the scroll view is re-enabled.
I created a GIF to demonstrate the results:
Modified Daniel's answer to make it more efficient and bug free.
#IBOutlet weak var scrollView: UIScrollView!
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var tableHeight: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
//Set table height to cover entire view
//if navigation bar is not translucent, reduce navigation bar height from view height
tableHeight.constant = self.view.frame.height-64
self.tableView.isScrollEnabled = false
//no need to write following if checked in storyboard
self.scrollView.bounces = false
self.tableView.bounces = true
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 20
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let label = UILabel(frame: CGRect(x: 0, y: 0, width: tableView.frame.width, height: 30))
label.text = "Section 1"
label.textAlignment = .center
label.backgroundColor = .yellow
return label
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = "Row: \(indexPath.row+1)"
return cell
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView == self.scrollView {
tableView.isScrollEnabled = (self.scrollView.contentOffset.y >= 200)
}
if scrollView == self.tableView {
self.tableView.isScrollEnabled = (tableView.contentOffset.y > 0)
}
}
Complete project can be seen here:
https://gitlab.com/vineetks/TableScroll.git
After many trials and errors, this is what worked best for me. The solution has to solve two needs 1) determine who's scrolling property should be used; tableView or scrollView? 2) make sure that the tableView doesn't give authority to the scrollView until it has reached the top of it's table/content.
In order to see if the scrollview should be used for scrolling vs the tableview, i checked to see if the UIView right above my tableview was within frame. If the UIView is within frame, it's safe to say the scrollView should have authority to scroll. If the UIView is not within frame, that means that the tableView is taking up the entire window, and therefor should have authority to scroll.
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.bounds.intersects(UIView.frame) == true {
//the UIView is within frame, use the UIScrollView's scrolling.
if tableView.contentOffset.y == 0 {
//tableViews content is at the top of the tableView.
tableView.isUserInteractionEnabled = false
tableView.resignFirstResponder()
print("using scrollView scroll")
} else {
//UIView is in frame, but the tableView still has more content to scroll before resigning its scrolling over to ScrollView.
tableView.isUserInteractionEnabled = true
scrollView.resignFirstResponder()
print("using tableView scroll")
}
} else {
//UIView is not in frame. Use tableViews scroll.
tableView.isUserInteractionEnabled = true
scrollView.resignFirstResponder()
print("using tableView scroll")
}
}
hope this helps someone!
None of the answers here worked perfectly for me. Each one had it's owned nuanced problem (needing to do a repeated swipe when one scrollview hit it's bottom, or the scroll indicator not looking correct, etc), so figured I'd throw in another answer.
Ole Begemann has a great write up on doing this exactly https://oleb.net/blog/2014/05/scrollviews-inside-scrollviews/
Despite being an old post, the concepts still apply to the current APIs. Additionally, there is a maintained (Xcode 9 compatible) Objective-C implementation of his approach https://github.com/eyeem/OLEContainerScrollView
If you are facing problem with the nested scrolling issue , here tis the simplest solution for it .
go to your design screen
select your scroll view and then disable bounce on scroll
if your view uses table view inside scroll view then disable bounce on scroll of the table view as well
run and check it is solved
check how to disable bounce on scroll of a scroll view
check how to disable bounce on scroll of a tableview view
I was struggling with this problem, too. There is a very simple solution.
In interface builder:
create simple ViewController
add a simple View, it will be our header, and constrain it to superview
it's the red view on the example below
I have added 12px from top, left and right, and set fixed height to 128px
embed a PageViewController, making sure it is constrained to the superview, and not the header
Now, here comes the fun part: for each page you add, make sure its tableView has an offset from top. Thats it. You can do if with this code, for example (assuming you use UITableViewController as a page):
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let tables = viewControllers.compactMap { $0 as? UITableViewController }
tables.forEach {
$0.tableView.contentInset = UIEdgeInsets(top: headerView.bounds.height, left: 0, bottom: 0, right: 0)
$0.tableView.contentOffset = CGPoint(x: 0, y: -headerView.bounds.height)
}
}
No messy scroll inside scroll inside table view, no mangling with delegates, no duplicated scrolls, perfectly natural behavior. If you can't see the header, it is probably because of the tableView background color. You have to set it to clear, for the header to be visible from under the tableView.
I think there are two options.
Since you know the size of the scroll view and the main view, you are unable to tell whether the scroll view hit the bottom or not.
if (scrollView.contentOffset.y >= (scrollView.contentSize.height - scrollView.frame.size.height)) {
// reach bottom
}
So when it hit; you basically set
[contentScrollView setScrollEnabled:NO];
and other way around for your tableView.
The other thing, which is more precise I think, is to add Gesture to your views.
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc]
initWithTarget:self action:#selector(respondToTapGesture:)];
// Specify that the gesture must be a single tap
tapRecognizer.numberOfTapsRequired = 1;
// Add the tap gesture recognizer to the view
[self.view addGestureRecognizer:tapRecognizer];
// Do any additional setup after loading the view, typically from a nib
So when you add Gesture, you can simply control the active view by changing setScrollEnabled in the respondToTapGesture.
I found an awesome library
MXParallaxHeader
In Storyboard just set UIScrollView class to MXScrollView then magic happens.
I used this class to handle my UIScrollView when I embed a UIPageViewController container view. even you can insert a parallax header view for more detail.
Also, this library provides Cocoapods and Carthage
I attached an image below which represent UIViewHierarchy.
MXScrollView Hierarchy
SWIFT 5
I had some trouble using Vineet's answer for when I could not guarantee the scrollView content offset (Y) due to various different screen sizes. To resolve this, I changed the first trigger event of when the tableView's scroll gets enabled.
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.bounds.contains(button.frame) {
tableView.isScrollEnabled = true
}
if scrollView == tableView {
self.tableView.isScrollEnabled = (tableView.contentOffset.y > 0)
}
}
The scrollView.bounds.contains will check if a given element's frame is FULLY within the scrollView's visible content. I set this to a button that I have below the tableView. You could set this to your tableVIew's frame instead if your only condition is that your tableView is fully visible.
I left the original implementation of when to disable the tableView's scroll and it works very well.
I tried the solution marked as the correct answer, but it was not working properly. The user need to click two times on the table view for scroll and after that I was not able to scroll the entire screen again. So I just applied the following code in viewDidLoad():
tableView.addGestureRecognizer(UISwipeGestureRecognizer(target: self, action: #selector(tableViewSwiped)))
scrollView.addGestureRecognizer(UISwipeGestureRecognizer(target: self, action: #selector(scrollViewSwiped)))
And the code below is the implementation of the actions:
func tableViewSwiped(){
scrollView.isScrollEnabled = false
tableView.isScrollEnabled = true
}
func scrollViewSwiped(){
scrollView.isScrollEnabled = true
tableView.isScrollEnabled = false
}
One easy trick, if you want to achieve it is replacing parent scrollview with normal container view.
Adding a pan gesture on container view, you can play with top constraint of first view to assign negative values. You can keep a check of page View's origin if it achieves to top you can start assigning that value on content offset of the pageView's child view. Until user achieves the table view in a state of top most view in container view, you can keep page tableView's scrolling disabled and allow scrolling manually by setting content offset.
So initially the page view height will be collapsed (or say out of screen) or less at bottom. Later on scrolling down it will expand to take more space.
Gesture will automatically stop responding if out of frames say on nav bar or other view outside container view.
Gestures are a key to user interactive transitions used in many apps. You can mimic scroll for a certain time with it.
In my case I'm using constraint for height like that:
self.heightTableViewConstraint.constant = self.tableView.contentSize.height
self.scrollView.contentInset.bottom = self.tableView.contentSize.height
Below code works great for me
As I wanted to show some header after some scroll and table view supposed to scroll
And in ViewDidLoad add
override func viewDidLoad() {
super.viewDidLoad()
mainScrollView.delegate = self
}
Change 265 to whatever number you want to stop upper scroll
extension AccountViewController: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
print(notebookTableView.contentOffset.y)
if notebookTableView.contentOffset.y < 265 {
if notebookTableView.contentOffset.y > 0 {
mainScrollView.setContentOffset(notebookTableView.contentOffset, animated: false)
} else {
mainScrollView.setContentOffset(CGPoint(x: 0.0, y: 0.0), animated: false)
}
} else {
mainScrollView.setContentOffset(CGPoint(x: 0.0, y: 265), animated: false)
}
}
}
CGFloat tableHeight = 0.0f;
YourArray =[response valueForKey:#"result"];
tableHeight = 0.0f;
for (int i = 0; i < [YourArray count]; i ++) {
tableHeight += [self tableView:self.aTableviewDoc heightForRowAtIndexPath:[NSIndexPath indexPathForRow:i inSection:0]];
}
self.aTableviewDoc.frame = CGRectMake(self.aTableviewDoc.frame.origin.x, self.aTableviewDoc.frame.origin.y, self.aTableviewDoc.frame.size.width, tableHeight);
Maybe brute-force, but working perfectly if cell heights are the same: by the way, I use auto layout.
for the tableView (or collectionView or whatever), set an arbitrary height in storyboard, and make an outlet to class. Wherever appropriate, (viewDidLoad() or...) set the tableView's height big enough so that tableView doesn't need to scroll. (need to know the number of rows in advance) Then only the outer scrollView will scroll nicely.
I want more space(10px) between each cell. How can I do this?
And I have added this code
tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
The best way for me, just add this in cellForRowAtIndexPath or in willDisplayCell
CGRect sizeRect = [UIScreen mainScreen].applicationFrame;
NSInteger separatorHeight = 3;
UIView * additionalSeparator = [[UIView alloc] initWithFrame:CGRectMake(0,cell.frame.size.height-separatorHeight,sizeRect.size.width,separatorHeight)];
additionalSeparator.backgroundColor = [UIColor grayColor];
[cell addSubview:additionalSeparator];
For Swift 3.0:
let screenSize = UIScreen.main.bounds
let separatorHeight = CGFloat(3.0)
let additionalSeparator = UIView.init(frame: CGRect(x: 0, y: self.frame.size.height-separatorHeight, width: screenSize.width, height: separatorHeight))
additionalSeparator.backgroundColor = UIColor.gray
self.addSubview(additionalSeparator)
You should add this to cell's method awakeFromNib() to avoid re-creation.
I have seen many clunky solutions like subclassing UITableView with hidden cells, and other less optimal ones incl. in this thread.
When initializing the UITableView, Set the rowHeight property of UITableView to a height that equals = cell height + desired separator/space height.
Do not use standard UITableViewCell class though, instead, subclass the UITableViewCell class and override its layoutSubviews method. There, after calling super (don't forget that), set the height of the cell itself to desired height.
BONUS UPDATE 18/OCT/2015:
You can be a bit smart about this. The solution above basically puts the "separator" at the bottom of the cell. What really happens is, the row height is managed by the TableViewController but the cell is resized to be a bit lower. This results in the separator/empty space being at the bottom. But you can also centre all the subviews vertically so that you leave the same space at the top and the bottom. For example 1pt and 1pt.
You can also create isFirst, isLast convenience properties on your cell subclass. You would set these to yes in the cellForRowAtIndexPath.
This way you can handle the edge cases for top and bottom separators inside the layoutSubviews method as this would have access to these properties.
This way you can handle the edge cases for top or bottom - because sometimes the design department wants N+1 separators while the number of cells is only N. So you have to either deal with the top one or the boot one in a special way. But it's best do this inside cells instead tableViewHeader or TableViewFooter.
I don't think it's possible using standard API. I guess you would need to subclass the UITableViewCell and add a view that simulates a separator at the bottom of the cell.
You may want to check this question, it seems related and has some sample code:
iPhone + UITableView + place an image for separator
In Swift
The easiest and shortest way for me was to add the snippet below in cellForRowAtIndexPath or in willDisplayCell:
override func tableView(tableView: UITableView,
willDisplayCell cell: UITableViewCell,
forRowAtIndexPath indexPath: NSIndexPath)
{
let additionalSeparatorThickness = CGFloat(3)
let additionalSeparator = UIView(frame: CGRectMake(0,
cell.frame.size.height - additionalSeparatorThickness,
cell.frame.size.width,
additionalSeparatorThickness))
additionalSeparator.backgroundColor = UIColor.redColor()
cell.addSubview(additionalSeparator)
}
this is quite old. Nevertheless I will post my approach.
Simply increase your cell height a bit and assign a mask layer to the cell, like that:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "...", for: indexPath)
// Configure the cell...
let maskLayer = CAShapeLayer()
let bounds = cell.bounds
maskLayer.path = UIBezierPath(roundedRect: CGRect(x: 2, y: 2, width: bounds.width-4, height: bounds.height-4), cornerRadius: 5).cgPath
cell.layer.mask = maskLayer
return cell
}
So in this example my seperator height will be 4.
Have fun!
You can do this entirely in the storyboard. Here is how:
go to the storyboard and select the tableview
Show the Size Inspector and from there set row height to say 140.
then show the Attributes Inspector and from there set your separator to Single Line and Style Plain and choose a color
then in the storyboard (or in Document Outline) select the cell
and again in the Size Inspector, under the Table View Cell, set custom Row Height to say 120.
That’s all. Your separator will be 20 units tall.
Kinda old thread, but since I only found hacky solutions in this thread,
here the solution that worked best for me (without additional UIView in every cell)
Swift 3:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//configure your cell...
cell.layer.shadowColor = UIColor.red.cgColor
cell.layer.shadowOffset = CGSize(width: 0, height: 1)
cell.layer.shadowOpacity = 1
cell.layer.shadowRadius = 0
cell.layer.masksToBounds = false
return cell
}
EDIT: Unfortunately this does not work if you scroll up in a table. I leave the answer here anyway, since it might be a solution if your table has limited content.
See Shadow on a UITableViewCell disappears when scrolling for more info.
For a table cell with height of 50 and a space of 5 pix between the rows. Width is 320.
Define the background of the cells to be clear:
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
cell.backgroundColor = [UIColor clearColor];
}
Set the height of the cells, this is the size of the row PLUS the delimiter:
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
return 55;
}
And define in cellForRowAtIndexPath a box, with the size of the row (MINUS delimiter) to draw in the background color:
UILabel *headerBackgroundLabel = [[UILabel alloc] initWithFrame:CGRectMake(0,0,320,50)];
backgroundBox.backgroundColor = [UIColor grayColor];
[cell addSubview:backgroundBox];
I do it a much simpler and more flexible way. Some may call it a hack. I call it pragmatic.
I hide the standard UITableViewSeparator. I then add a subview to my cell, using auto layout pin it to the top. Fix the height to what I desire. Pin it to the edges with a margin either side. Change it's background colour. I have a plain separator with the height i desire.
You may question how efficient this is having another UIView in the cell hierarchy. Is it really going to make a noticeable difference? Probably not - you've just taken the standard separator out of the table hierarchy anyway.
Swift 4
It's not possible to make the default separator higher. Instead you need to add a subview that will look as a separator to each cell (and optionally make the cell higher). You can do it for example in cellForRowAtIndexPath or in a UITableViewCell subclass.
In case you allow to select the cell, you need to add the subview for selected state as well, otherwise the separator would disappear when the cell is selected. That's why selectedBackgroundView is also configured.
Add this into your UITableViewController subclass:
override func viewDidLoad() {
super.viewDidLoad()
tableView.separatorStyle = .none
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.backgroundView = UIView(backgroundColor: .white)
cell.backgroundView?.addSeparator()
cell.selectedBackgroundView = UIView(backgroundColor: .blue)
cell.selectedBackgroundView?.addSeparator()
// configure the cell
return cell
}
Add this extensions into the same file at the bottom:
private extension UIView {
convenience init(backgroundColor: UIColor) {
self.init()
self.backgroundColor = backgroundColor
}
func addSeparator() {
let separatorHeight: CGFloat = 2
let frame = CGRect(x: 0, y: bounds.height - separatorHeight, width: bounds.width, height: separatorHeight)
let separator = UIView(frame: frame)
separator.backgroundColor = .gray
separator.autoresizingMask = [.flexibleTopMargin, .flexibleWidth]
addSubview(separator)
}
}
Here's an option that might work for some people
cell.layer.borderColor = UIColor.white.cgColor
cell.layer.borderWidth = 4.0
cell.layer.masksToBounds = true
The easier and safest solution to this problem is to turn off the table separator and use a UITableViewCell as a separator of variable height. Sure, you'll have to do some index math to figure out where items are, but really it's odd / even.
It won't break and you get the benefit of recyclable cells (no extraneous views to clean up).
First make tableview separator none from the storyboard. Then add UILabel/UIView at bottom of cell of height(you needed) using storyboard or Xib
For Swift 4
Implemented King-Wizard's solution to Swift 4:
public func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let additionalSeparatorThickness = CGFloat(4)
let additionalSeparator = UIView(frame: CGRect(x: 0,
y: cell.frame.size.height - additionalSeparatorThickness, width: cell.frame.size.width, height: additionalSeparatorThickness))
additionalSeparator.backgroundColor = UIColor.groupTableViewBackground
cell.addSubview(additionalSeparator)
}
This is the easiest solution I've found:
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
" "
}
then just set the height to whatever you want:
tableView.sectionHeaderHeight = 30.0
I came across a way that has allowed me to effectively change the gap between cells.
In Interface builder I set the row height to be 46.
In the cellForRowAtIndexPath method of my TableView delegate I set the frame of the cell to be a smaller value.
cell.frame=CGRectMake(44,0,tableView.bounds.size.width,44)
This gives me a cell with a height of 44 that fits the width of the tableView but the space provided for the row will be 46 as defined in IB.
I was filling the cell programmatically anyway so this suited me fine.
You should implement
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
delegate method. and return 100.0 there.