I have one collection view configured as follow
superCollectionView!.alwaysBounceHorizontal = false
superCollectionView!.alwaysBounceVertical = false
if #available(iOS 10.0, *) {
superCollectionView!.refreshControl = refreshControl
} else {
superCollectionView!.backgroundView = refreshControl
}
but bounce effect is still there.
I want to remove bounce from bottom...
If you want to remove bouncing only from the bottom (For letting the refreshControl to be available), I'd suggest to handle it in scrollViewDidScroll: method to check if the scroll view contentOffset.y has been reached to bottom of the scroll view (logically, it is the content size of the scroll view minus the height of the visible frame of the scroll view), as follows:
Solution:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.contentOffset.y >= scrollView.contentSize.height - scrollView.frame.size.height {
scrollView.setContentOffset(CGPoint(x: scrollView.contentOffset.x, y: scrollView.contentSize.height - scrollView.frame.size.height), animated: false)
}
}
Output:
After implementing scrollViewDidScroll as mentioned above, it should be behaves like:
Also:
What about achieving the opposite?
Referring to the above description, preventing the top bouncing would be:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.contentOffset.y < 0 {
scrollView.setContentOffset(CGPoint(x: scrollView.contentOffset.x, y: 0), animated: false)
}
}
Related
I need my Navigation bar to be scroll up and down while tableview is scrolling up and down.
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let defaultOffset = view.safeAreaInsets.top
let offset = scrollView.contentOffset.y + defaultOffset
navigationController?.navigationBar.transform = .init(translationX: 0, y: min(0, -offset))
}
It should work and I got this problem once, so this code helped me...Might it works for you too..
I have a textField and when I tap it a tableView appear below. When I scroll the tableView down, say 25% of the height of the tableView I want to hide it. Is it possible ? I am using the scrollViewWillBeginDragging function but its not what I want.
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
let translation = scrollView.panGestureRecognizer.translation(in: scrollView.superview!)
if translation.y > 550 {
self.animateTableView(shouldShow: false)
}
}
Use this UIScrollViewDelegate method:-
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let bottomEdge: CGFloat = scrollView.contentOffset.y + scrollView.frame.size.height
let contentSize = scrollView.contentSize.height * 0.25
if bottomEdge >= contentSize {
/* Code to hide tableView */
}
}
I currently have a UITableView whose HeaderView is a UIScrollView. I am using this as a sort of carousel that displays constantly rotating information triggered to move on a timer every two seconds. What I am trying to do is detect when the user has moved the scroll view on their own. Here is the code I have to handle the ScrollView movement right now.
override func viewDidLoad() {
var myTimer = Timer.scheduledTimer(timeInterval: 2, target: self, selector: #selector(runTimedCode), userInfo: nil, repeats: true)
}
func runTimedCode() {
var itemCount = newsArray.count // how many items are in the news reel
if currentPage < itemCount {
let newX = CGFloat(currentPage) * self.view.frame.width // calculate next page position
featuredScrollView.setContentOffset(CGPoint(x: newX, y: 0), animated: true)
currentPage += 1
} else {
// end of items
currentPage = 0
let newX = CGFloat(currentPage) * self.view.frame.width // calculate next page position
print("new x = \(newX)")
featuredScrollView.setContentOffset(CGPoint(x: newX, y: 0), animated: true)
}
}
However the scrollViewDidScroll function is only called when the tableview is moved. Is there any way to detect when the ScrollView above the tableView is scrolled? I'm completely stuck on this any help is appreciated. Thanks!
First, make sure that you have the delegate set up so that your view controller gets events from both the scroll view and the table view.
Next, keep references to make sure that you can distinguish the two views.
Finally, check which reference is triggering the viewdidscroll function and do your code there.
let view1:UIScrollView!
let view2:UITableView!
view1.delegate = self
view2.delegate = self
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView == view1 {
// Do something
}
if scrollView == view2 {
// Do something
}
}
I want to have my tableViewHeaders visible as the user scrolls by pinning to the top which is the current behaviour in my tableView. However, when the tableView stops scrolling, I want to remove these 'pinned' headers. I am achieving this in my collectionView project using the following in my scrollView delegate methods:
if let cvl = chatCollectionView.collectionViewLayout as? UICollectionViewFlowLayout {
cvl.sectionHeadersPinToVisibleBounds = false
cvl.invalidateLayout()
}
Is there a similar way to hide a tableView's 'pinned' (sticky) headers? I am using a tableViewController.
This is my solution to this issue. I wonder if there is a simpler way to do this though.
Please note, this will only work if your header is a UITableViewHeaderFooterView. Not if you are using a UITableViewCell for a header. If you are using a UITableViewCell, tableView.headerView(forSection: indexPathForVisibleRow.section) will return nil.
In order to hide the pinned headers when the tableView stops scrolling and have them re-appear when the tableView starts scrolling again, override these four scrollView delegate methods.
In the first two (scrollViewWillBeginDragging and scrollViewWillBeginDecelerating), get the section header for the first section of the visible rows and make sure it is not hidden.
In the second two delegate methods, do a check to see that for each of the visible rows, the header frame for that row is not overlapping the frame for the row cell. If it is, then this is a pinned header and we hide it after a delay. We need to ensure that the scrollView is not still dragging before removing the pinned header as will be the case when the user lifts their finger but the scroll view continues to scroll. Also because of the time delay, we check that the scrollView is not dragging before removing it in case the user starts scrolling again less than 0.5 seconds after the scroll stops.
override func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
showPinnedHeaders()
}
override func scrollViewWillBeginDecelerating(_ scrollView: UIScrollView) {
showPinnedHeaders()
}
override func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
removePinnedHeaders()
}
override func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
removePinnedHeaders()
}
private func showPinnedHeaders() {
for section in 0..<totalNumberOfSectionsInYourTableView {
tableView.headerView(forSection: section)?.isHidden = false
}
}
private func removePinnedHeaders() {
if let indexPathsForVisibleRows = tableView.indexPathsForVisibleRows {
if indexPathsForVisibleRows.count > 0 {
for indexPathForVisibleRow in indexPathsForVisibleRows {
if let header = tableView.headerView(forSection: indexPathForVisibleRow.section) {
if let cell = tableView.cellForRow(at: indexPathForVisibleRow) {
if header.frame.intersects(cell.frame) {
let seconds = 0.5
let delay = seconds * Double(NSEC_PER_SEC)
let dispatchTime = DispatchTime.now() + Double(Int64(delay)) / Double(NSEC_PER_SEC)
DispatchQueue.main.asyncAfter(deadline: dispatchTime, execute: {
if !self.tableView.isDragging && header.frame.intersects(cell.frame) {
header.isHidden = true
}
})
}
}
}
}
}
}
}
Additionally add removePinnedHeaders() to viewDidAppear() and any other rotation or keyboard frame change methods that will scroll your tableView.
I know the Apple documentation has the following delegate method:
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView; // called when scroll view grinds to a halt
However, it doesn't necessarily mean you are at the bottom. Cause if you use your finger, scroll a bit, then it decelerates, but you are not actually at the bottom of your scroll view, then it still gets called. I basically want an arrow to show that there is more data in my scroll view, and then disappear when you are at the bottom (like when it bounces). Thanks.
I think what you might be able to do is to check that your contentOffset point is at the bottom of contentSize. So you could probably do something like:
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
float bottomEdge = scrollView.contentOffset.y + scrollView.frame.size.height;
if (bottomEdge >= scrollView.contentSize.height) {
// we are at the end
}
}
You'll likely also need a negative case there to show your indicator when the user scrolls back up. You might also want to add some padding to that so, for example, you could hide the indicator when the user is near the bottom, but not exactly at the bottom.
So if you want it in swift, here you go:
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
if (scrollView.contentOffset.y >= (scrollView.contentSize.height - scrollView.frame.size.height)) {
//reach bottom
}
if (scrollView.contentOffset.y < 0){
//reach top
}
if (scrollView.contentOffset.y >= 0 && scrollView.contentOffset.y < (scrollView.contentSize.height - scrollView.frame.size.height)){
//not top and not bottom
}
}
I Think #bensnider answer is correct, But not exart. Because of these two reasons
1. - (void)scrollViewDidScroll:(UIScrollView *)scrollView{}
This method will call continuously if we check for if (bottomEdge >= scrollView.contentSize.height)
2 . In this if we go for == check also this condition will valid for two times.
(i) when we will scroll up when the end of the scroll view touches the bottom edge
(ii) When the scrollview bounces back to retain it's own position
I feel this is more accurate.
Very few cases this codition is valid for two times also. But User will not come across this.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if (scrollView.contentOffset.y == roundf(scrollView.contentSize.height-scrollView.frame.size.height)) {
NSLog(#"we are at the endddd");
//Call your function here...
}
}
The accepted answer works only if the bottom contentInset value is non-negative. A slight evolution would consider the bottom of the contentInset regardless of it's sign:
CGFloat bottomInset = scrollView.contentInset.bottom;
CGFloat bottomEdge = scrollView.contentOffset.y + scrollView.frame.size.height - bottomInset;
if (bottomEdge == scrollView.contentSize.height) {
// Scroll view is scrolled to bottom
}
Actually, rather than just putting #bensnider's code in scrollViewDidScroll, this code (written in Swift 3) would be better performance-wise:
func scrollViewDidEndDragging(_ scrollView: UIScrollView,
willDecelerate decelerate: Bool) {
if !decelerate {
checkHasScrolledToBottom()
}
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
checkHasScrolledToBottom()
}
func checkHasScrolledToBottom() {
let bottomEdge = scrollView.contentOffset.y + scrollView.frame.size.height
if bottomEdge >= scrollView.contentSize.height {
// we are at the end
}
}
It work in Swift 3:
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.contentOffset.y == (scrollView.contentSize.height - scrollView.frame.size.height) {
loadMore()
}
}
See what items are currently displayed in the UIView, using something like indexPathsForVisibleRows and if your model has more items than displayed, put an arrow at the bottom.