what is scrollViewDidScroll means? - uitableview

I want to know this override function named "scrollViewDidScroll"means because i am now currently stuck at that which that doesn't work.Here is my code.I really want to know each steps by steps of the code i describe below.
override func scrollViewDidScroll(scrollView: UIScrollView) {
// UITableView only moves in one direction, y axis
let currentOffset = scrollView.contentOffset.y //?
let maximumOffset = scrollView.contentSize.height - scrollView.frame.size.height //?
// Change 10.0 to adjust the distance from bottom
if (maximumOffset - currentOffset) <= 40.0 { //?
loadSegment(currentPage, count: currentCount) // This is the function that load 15 results per page each call from api.
}
}
I am now currently stuck at page 2 with "Loading" when i scroll down after getting 30 results which mean this method is not working after i scroll first from page 2.When i scroll down from page 2 to get more results,it stuck at loading which mean this method dont work.So loadSegment don't call because it dont pass to if condition.
Any help with code explanation?Thank You

If scrollViewDidScroll isn't firing, you should probably check if tableview.delegate = self; or tableview.delegate = // whichever class scrollViewDidScroll is in Have you put an NSLog in scrollViewDidScroll?

Related

ScrollView inertia effect manually, iOS, Swift

I have UICollectionView which I'm dragging from code (don't ask me why it's very long story:)).
And my code is working pretty well:
func move(prevPoint: CGPoint, curPoint: CGPoint) {
let xDiff = curPoint.x - prevPoint.x
let yDiff = curPoint.y - prevPoint.y
let xSign = xDiff == 0 ? 1 : (xDiff / abs(xDiff))
let ySign = yDiff == 0 ? 1 : (yDiff / abs(yDiff))
let x = max(min(abs(xDiff), maxPickerStep), minPickerStep) * -xSign * xMultiplier
let y = max(min(abs(yDiff), maxPickerStep), minPickerStep) * -ySign
let offset = CGPoint(x: collectionView.contentOffset.x + x, y: collectionView.contentOffset.y)
let cell = (collectionView.visibleCells.first as? ColorsCollectionViewCell)
let innerOffset = cell?.colorCollectionView.contentOffset ?? .zero
let inset = (cell?.colorCollectionView.contentInset.top ?? 0) * 2
let innerYContentOffset = min(max(innerOffset.y + y, -inset), (cell?.colorCollectionView.contentSize.height ?? 0) - inset)
cell?.colorCollectionView.contentOffset = CGPoint(x: innerOffset.x, y: innerYContentOffset)
collectionView.contentOffset = offset
}
But in addition to scrolling, I want to achieve the same effect as in UICollectionView when scrollView moves by inertia after user takes away finger. Thanks.
First thing first, I think that moving the scroll view manually is most certainly a thing I would avoid.
Probably there is something much simpler to fulfill the behavior you need.
So I highly suggest you, and any other reader, to not go further in the reading of this post and, instead, go ahead and try to solve the problem that guided you here in the first place.
You could also ask another question here on Stack Overflow to maybe get help to try to avoid you to manually update the scrollView position.
So if you are still reading, this article is probably the way to go with implementing something that really feels like a UIScrollView. Doing anything else will probably really look and feel awful.
Basically it consists of using UIKit Dynamics to control the inertia.
So you can create an object that conforms to UIDynamicItem (with a non-zero CGRect), and change its center instead of the scrollView contentOffset, than use a UIDynamicAnimator and its UIDynamicBehavior to set up the inertia and to connect the changes during the animation to the corresponding contentOffset in the scrollView using the UIDynamicBehavior's action block.
Assuming that you have an item that is a UIDynamicItem, and an animator that is a UIDynamicAnimator, the handling of the panGesture recognizer would look something like this:
func handlGestureRecognizer(panGesture: UIPanGestureRecognizer) {
switch panGesture.state {
case .began:
self.animator.removeAllBehaviors()
case .changed:
// Update scroll view position
break
case .ended:
var velocity = panGesture.velocity(in: panGesture.view!)
velocity.x = -velocity.x
velocity.y = -velocity.y
// You probably need to check for out of bound velocity too, and also put velocity.x to 0 if the scroll is only scrolling vertically
// This is done to just save the current content offset and then change it alongside the animation from this starting point
item.center = scrollView.contentOffset
let decelerationBehavior = UIDynamicItemBehavior(items: [item])
decelerationBehavior.addLinearVelocity(velocity, for: item)
decelerationBehavior.resistance = 2.0
decelerationBehavior.action = {
// Connect the item center to the scroll contentOffset. Probably something like this:
scrollView.contentOffset = item.center
}
self.animator.addBehavior(decelerationBehavior)
default:
break
}
}
You than just need to play up with the values of the behavior and be careful with the velocity you put into the behavior having extra care in looking at the edge cases (if you scroll over the min/max for example)
PS: After all I've written, I still believe you should strongly consider not doing this and, instead, go with the standard scrollView scrolling, avoiding manual updates.
You can try to play with decelerationRate and see if it satisfies your needs.
collectionView.decelerationRate = UIScrollView.DecelerationRate(rawValue: 1)

'scrollViewDidScroll' does not catch movement continuously

I am writing program to move an UIView named "myView" while scrolling a UITableView, Look at the pic below:
"myView" will change its 'y-coordinate' along with the change of the contentoffset of tableview, the main code I write is:
func scrollViewDidScroll(scrollView: UIScrollView) {
let off_y = scrollView.contentOffset.y
let new_y = originalMyViewY - off_y
if new_y <= 0 {
print("bad new_y : \(new_y)")
return
}
else {
print("right new_y: \(new_y)")
}
var frame = self.myView.frame
frame.origin.y = new_y
self.myView.frame = frame
}
This works perfectly when I scroll the tableview at a normal speed. However, if I scroll the tableview very fast, 'myview' will stop before reaching the top edge. This is what the log shows:
right new_y: 56.0
bad new_y : -92.5
bad new_y : -153.5
bad new_y : -206.0
It seems that the delegate method scrollViewDidScroll did not catch every scroll movement which led to a value jump.
I want to know the reason and how to fix it, thanks!
The likely reason for this occurring is because the touches that are being received by your app are occurring faster than the refresh rate of the screen. Thus certain intermediate scroll points will be missed.
Refresh Rate
iOS devices typically run at 60hz or 60 frames per second. This means that every 1/60s or 0.016666 seconds the screen and digitizer will attempt to refresh the current state of touches.
If your finger moves faster than 0.5 display points in that time period than the next scrollViewDidScroll delegate method will report the newer location and never report the intermediate locations.
Solution
Instead of using the values directly from the delegate method, I would recommend "clamping" the result to your expected range. In this instance it looks like you don't want to receive negative numbers. Meaning if you want the origin y value to be at minimum 0 then you should take the value from the delegate and if it is less than 0 set your View's origin to 0 otherwise use your calculated result.
Example:
func scrollViewDidScroll(scrollView: UIScrollView) {
let off_y = scrollView.contentOffset.y
var new_y = originalMyViewY - off_y // make this mutable so we can change it in the <= 0 block
if new_y <= 0 {
print("y is less than 0, clamp y origin to 0")
new_y = 0
}
else {
print("right new_y: \(new_y)")
}
var frame = self.myView.frame
frame.origin.y = new_y
self.myView.frame = frame
}
Note: This shouldn't result in jumpy resizing of your view as scrollView should already be reporting this scrollViewDidScroll at near the screen refresh rate.

UICollectionView dynamic data source items adding to the beginning of the collection view

I'm making collection view with a dynamic content loading ability. In my case, I'm making calendar. Current date will be in the visible part (after loading). If you scroll up, the older dates will shown and if you scroll down you'll see next dates.
My collection view use standard flow layout. I have 7 cells in a row and 4 visible rows.
I have a problem with adding older dates (e.g. cells that appears if you scroll up). What I'm doing: first I implement scroll view method for detecting scroll event.
startOffsetY is contentOffset.y value set in viewDidLoad. It's not equal to 0 because I set contentInset. upd is just a flag that means that new update is could be start.
func scrollViewDidScroll(scrollView: UIScrollView) {
if scrollView.contentOffset.y <= startOffsetY - 20 && upd {
calendar.updateDataSource()
}
}
Next, I calculate dates for previous time slice (about 4 weeks, I also tried 1 week) from the first date that is in my data source array.
After that, I calculate indexPath array and make update to collection view:
var indexes = [NSIndexPath]()
for n in 1...4 {
for i in reverse(1...7) {
indexes.append(NSIndexPath(forItem: n * 7 - i, inSection: 0))
}
}
self.calendarCollectionView.performBatchUpdates({ () -> Void in
self.calendarCollectionView.insertItemsAtIndexPaths(indexes)
}, completion: { (finish) -> Void in
self.upd = true
})
but, I have visible lags when rows added and scrolling is in progress.
I tried different strategies: I used reloadData() and it was ideal (on the simulator) and extremely laggy on my iPhone 4S (this was the first time in my experience when simulator was faster than the device). From this point, I figure out, that animation of inserting items might be the problem. I tried to wrap performBatchUpdates into the UIView.performWithoutAnimation block, but with no luck also.
I'm really looking for some help, I don't looking for a ready made solutions except they work as I describe (scroll up'n'down and load content) and I can look how it works. Once again, scrolling already loaded items is not a problem, the problem is a lagging when content is add at the begging of my data array.
EDIT
Code provided by #teamnorge in Swift
func scrollViewDidScroll(scrollView: UIScrollView) {
let currentOffset = scrollView.contentOffset.y
let contentHeight = scrollView.contentSize.height
var offset : CGFloat!
println(currentOffset)
/* 0.125 - 1/8, 0.5 - 1/2 */
if currentOffset < contentHeight * 0.125 {
offset = contentHeight * 0.125 - currentOffset
// reload content here
scrollView.contentOffset = CGPoint(x: 0, y: currentOffset + contentHeight * 0.5 + offset - CGFloat(cellHeight))
}
/* 0.75 - 6/8, 0.5 - 1/2 */
if currentOffset > contentHeight * 0.75 {
offset = currentOffset - contentHeight * 0.75
// reload content here
scrollView.contentOffset = CGPoint(x: 0, y: currentOffset - contentHeight * 0.5 - offset + CGFloat(cellHeight))
}
It works very nice, but need to play with contentOffset y formula because right now trick works only when you scroll up. Will fix this tomorrow and add calendar date calculations.
EDIT 2
reloading data ruins everything in all of my prototypes. Including the one I made previously. Found something that makes lags a bit lower but still very noticeable and totally unacceptable. Here are these things:
remove autolayout from cell prototype in the storyboard
add this code to the cell:
cell.layer.shouldRasterize = true
cell.layer.rasterizationScale = UIScreen.mainScreen().scale
I think that invalidation of the layout results in these lags. So, I maybe I need to make custom flow layout? And if so what recommendations can you give.
That's actually the very interesting topic. I'll try to explain the idea I came up with while working on another Calendar app.
In my case it was not a Calendar View but Day View, hours 00:00 - 24:00 listed from top to bottom, so I had UITableView and not UICollectionView but it's not that important in this case.
The language used was not Swift but Objective-C, so I just try to translate code samples.
Instead of manipulation with the data source I created UITableView with the fixed amount of rows, in my case to store exactly two days (48 rows, two days of 24 hours each). You could choose the amount of rows containing in two full screens.
The important thing is that total amount of rows must be a multiple of 8.
Then you need a formula to calculate what's the day number for each particular cell based on what's inside the first visible row.
The idea is that UICollectionView is in fact UIScrollView so when we scroll down or up we can handle the corresponding event and calculate the visible offsets.
To simulate the infinitive scrolling we handle the scrollViewDidScroll and check if you just passed the 1/8 of the UIScrollView height scrolling up, move your UIScrollView to the 1/2 of height plus the exact offset so it moves to the "second screen" smoothly. And back, if you passed the 6/8 of the UIScrollView height while scrolling down, move the UIScrollView up to 1/2 of its height minus offset.
When you do this you see scrolling indicator jumps up and down which is very confusing so we have to hide it, just put somewhere in viewDidLoad:
calendarView.showsHorizontalScrollIndicator = false
calendarView.showsVerticalScrollIndicator = false
where calendarView is your UICollectionView instance name.
Here is the code (translated from Objective-C right here, not tried in the real project):
func scrollViewDidScroll(scrollView_:UIScrollView) {
if !enableScrollingHandling {return}
var currentOffsetX:CGFloat = scrollView_.contentOffset.x
var currentOffSetY:CGFloat = scrollView_.contentOffset.y
var contentHeight:CGFloat = scrollView_.contentSize.height
var offset:CGFloat
/* 0.125 - 1/8, 0.5 - 1/2 */
if currentOffSetY < (contentHeight * 0.125) {
enableScrollingHandling = false
offset = (contentHeight * 0.125) - currentOffSetY
// #todo: your code, specify which days are listed in the first row (2nd screen)
scrollView_.contentOffset = CGPoint(x: currentOffsetX, y: currentOffset +
contentHeight * 0.5 + offset - CGFloat(kRowHeight))
enableScrollingHandling = true
return
}
/* 0.75 - 6/8, 0.5 - 1/2 */
if currentOffSetY > (contentHeight * 0.75) {
enableScrollingHandling = false
offset = currentOffSetY - (contentHeight * 0.75)
// #todo: your code, specify which days are listed in the first row (1st screen)
scrollView_.contentOffset = CGPoint(x: currentOffsetX, y: currentOffset -
contentHeight * 0.5 - offset + CGFloat(kRowHeight))
enableScrollingHandling = true
return
}
}
Then in your collectionView:cellForItemAtIndexPath: based on what's in the first row (is this content of the first screen or second screen) and what are the current visible days (formula) you just update the cell content.
This formula could also be a tricky part and may require some workaround especially if you decide to put Month Names in between months. I do also have some ideas on how to organise it, so if you encounter any problem we can discuss it here.
But such an approach to simulate infinitive scrolling with "two screens loop and jumps in between" works really like a charm, very smooth scrolling like behaviour tested on older phones also.
PS: kRowHeight is just a constant the height of the exact row (cell) it's needed for precise and smooth scrolling behaviour it could be skipped I think.
UPDATE:
Important notice, I skipped this from original code, but see this is important. When you manually set the contentOffset you also triggers the scrollViewDidScroll event, to prevent this you need to temporary disable your scrollViewDidScroll event processing. You can do it by adding, for example, state variable enableScrollHandling and change its state true/false, see updated code.

Calculate Imageview Frame with Scrollview's contentOffset

I am trying to calculate the image frame in my scrollViewDidScroll. When i pull down or up my tableview, i want to change my imageview frames.
if (scrollView.contentOffset.y > 0 && scrollView.contentOffset.y <= 60) {
userImageViewHeightConstraint.constant = 50 - scrollView.contentOffset.y /6;
userImageViewWidthConstraint.constant = 50 - scrollView.contentOffset.y /6;
userImageView.layer.cornerRadius = userImageViewHeightConstraint.constant/2;
}
I'm calculating with like that but, i used to a condition for between 0 to 60 content off set. If i use slowly my tableview, it working, but when i pull fast, it is jumping another content off set and it is not entering if condition and my image frames are not changing,i want to change these frames step by step, so what should i do for calculate it?
Sorry for my bad english :). Thanks for your advice and answers.
scrollViewDidScroll: is not called for each point change in the offset. It skips some points when scrolled fast.
A crude solution would be to add 2 more conditions:
if (scrollView.contentOffset.y <=0)
if (scrollView.contentOffset.y > 60)
Set the constraints to the minimum and maximum value you would expect from your formula.

Calling web service on Scrolling UITableView

Totally Confused !!!...
I have one web-service that accepts the pageNumber as Parameter and gives me some records as JSON response. (This service basically implements the functionality of Paging for Silverlight version.) I want to load these pages one by one when user scrolls the UITableView (means Load the next 20 entries (or second page) when user scrolls).
My Problem :
How and Where to call this service and How to calculate pageNumber ?
I searched about this but didn't get any Satisfied answer.
What I found is :
How to call web service after each certain number of data received and load it into table view
Load more data from a web service when the user scrolls the UITableView
how to add elements to tableview on scrolling iphone?
I would love to here your responses in Objective-C Language.
Any Suggestions ?
Take one integer set it to 0. Now Used Pull to refresh functionality (Pull To refresh exmaple).
Now in API side you have to set the two extra parameter like pagenumber and pageSize
At very first time you call API with pageNumber=0 and pageSize=20
Now when you pull the table then you have a particular method in which you have to call the API with pageNumber++ and pageSize=20 again and whatever you get in the response add into your NSMutableArray. If you found nothing then remove pull to refresh option
I solved my Question with this Code :
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
CGPoint offset = scrollView.contentOffset;
CGRect bounds = scrollView.bounds;
CGSize size = scrollView.contentSize;
UIEdgeInsets inset = scrollView.contentInset;
float y = offset.y + bounds.size.height - inset.bottom;
float h = size.height;
float reload_distance = 10;
if(y > h + reload_distance)
{
lastPageNumber++;
[[NSUserDefaults standardUserDefaults] setInteger:lastPageNumber forKey:#"PageNumber"];
// Call the method.
}
}
Hope, it will be helpful for future Visitors.
To make an infinite scroller, you need to know when to start fetching the new items. There are many ways to do this, but the best place is to hook into the UITableViewDelegate's methods. When cellForRowAtIndexPath: is called, check if the row is the last in your data store. If it is, make the call for the new data, then append it to the old data and refresh your view. You can also hook into the table views scroller to see when it it X percent scrolled to the bottom to make this call.
In .h :
int lastPageNumber;
In .m :
In cellForRowAtIndexPath delegate method
int rowNum =indexPath.row;
if(rowNum%20==0)
{
if(rowNum/20>lastPageNumber)
{
//call webService
lastPageNumber++;
}
}

Resources