UITableView automatically drop down with pagination - ios

I've got a huge legacy project on objective-c and trying to implement new features before we starting to move it to swift.
So I'm working on chat right now and creating "Search" like in telegram.
After we got 200status from server we got 41 message in chat. Im set user screen to the middle of this messages, and need to implement pagination in both sides.
I've successfully added both features, and need to resolve last problem:
When user scroll UP or down to the another messages, I've got check in scrollViewDidScroll:
CGPoint offset = scrollView.contentOffset;
CGSize size = scrollView.contentSize;
CGFloat height = _tableView.frame.size.height;
if (offset.y > size.height - height) {
if((isLoadingMessages == NO) && (loadedMessagesUp % MAX_LOADING_MESS == 0)){
_isFromSearch = false;
NSInteger create = [[[_messagesArray lastObject] objectForKey:#"create"] integerValue];
_dateForPagination = create;
scrollDirectionDown = false;
[self loadMessages];
NSLog(#"pagination up");
}
}
if(offset.y < 10) {
if((isLoadingMessages == NO) && (loadedMessagesDown % MAX_LOADING_MESS == 0)){
_isFromSearch = false;
NSInteger create = [[[_messagesArray firstObject] objectForKey:#"create"] integerValue];
_dateForPagination = create;
scrollDirectionDown = true;
[self loadMessages];
NSLog(#"pagination down");
}
}
So, the question is:
When I've scrolledUP, my [_tableVIew contentOffset] calculated automatically (or I can't find where it happened, but I've check all project with cmd+f).
But when user scrollDOWN, to the new messages, table view added messages with method:
if (scrollDirectionDown) {
_paginationMessagesArray = [[[_paginationMessagesArray reverseObjectEnumerator]allObjects]mutableCopy];
[_paginationMessagesArray addObjectsFromArray: _messagesArray];
_messagesArray = _paginationMessagesArray;
}
And after this dropped my tableview to the [tableView offset] = 0 (equal indexPath.row = 0). So this automatically calls method with pagination and again dropped page to first element.
I think that I need to set new contentOffset after [tableview reloadData]. But I can't calculate it.
Can somebody help: how to calculate offset from to tableView minY to the tableView currentY ? Or I do everything wrong?
I suppose that I can't find some method in this terrible project, but I hope you can help to me with this situation.
in this picture I need to calculate offset from bottom of black square to the button of blue square. Because after [tableView reloadData] black square (which is screen of the phone) immediately drops to the bottom of blue Screen and turning pagination again. And it repeating and repeating..
THanks a lot, mates!

I found answer by changing my search in google :D 3 days I've spent on this.
UIScrollView disable vertical bounce only at bottom

Related

How to properly add items in the beginning of horizontal UICollectionView?

I'm trying to achieve next behavior:
I add few items in the beginning of UICollectionView
Items are added with proper animation, but I keep content offset on current center cell (it will be first cell before adding new items).
How I'm doing it now:
[self.collectionView performBatchUpdates:^{
CGPoint contentOffset = self.collectionView.contentOffset;
contentOffset.x += kMyCollectionViewCellWidth * firstIndex;
((MyCollectionViewLayout *)self.collectionView.collectionViewLayout).proposedContentOffset = contentOffset;
[self.collectionView insertItemsAtIndexPaths:indexesToAdd];
} completion:nil];
And in MyCollectionViewLayout.m:
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset {
if (self.proposedContentOffset.x != NSNotFound) {
proposedContentOffset = self.proposedContentOffset;
CGPoint zeroPoint = CGPointZero;
zeroPoint.x = NSNotFound;
self.proposedContentOffset = zeroPoint;
}
return proposedContentOffset;
}
Though, on slow devices I can see weird animation of adding this cells. New cells appears above old ones and moves left, when I want them appear in the left immediately or at least under old cells.

positioning uicontrol to create infinite scroll

I'm a beginner at objective C learning to program, and also beginner at asking questions on this site, please bear with me.
I am currently trying to draw a column of boxes (UIControls) on the screen, and be able to scroll them upward or downward infinitely. So when one goes off the bottom of the screen its shifted to the bottom and reused.
I know there must be a lot of mistakes in the code. But the gist of what I am trying to do is: The boxes are all in an array (imArray). When a box scrolls off the bottom of the screen its taken off the end of the array, and inserted at the beginning. Then the box inserts itself graphically into the top of the column.
The first if statement deals with scrolling off the bottom of the screen, and it works fine. But the second if statement, where i try to do the opposite with similar code works only when i scroll slowly, when i scroll quickly the spacing between boxes becomes uneven, and sometimes a box just locks up on the screen and stops moving.
Any help is appreciated, and I will try to provide any more clarity that may be needed.
-(BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
{
CGPoint pt = [touch locationInView:self];
int yTouchEnd = pt.y;
int yTouchChange = yTouchEnd - yTouchStart;
//iterate through all boxes in imArray
for(int i = 0; i < self.numberOfSections; i++)
{
//1. get box
STTimeMarker *label = self.imArray[i];
//2. calculate new label transform
label.transform = CGAffineTransformTranslate(label.startTransform, 0, yTouchChange);
CGRect frame = label.frame;
//3. if the box goes out of the screen on the bottom
if (frame.origin.y > [[UIScreen mainScreen]bounds].size.height)
{
//1. move box that left the screen to to beginning of array
[self.imArray removeObjectAtIndex:i];
[self.imArray insertObject:label atIndex:0];
//2. get y value of box closest to top of screen.
STTimeMarker *labelTwo = self.imArray[1];
CGRect frameTwo =labelTwo.frame;
//3. put box that just left the screen in front of the box I just got y value of.
frame.origin.y = frameTwo.origin.y - self.container.bounds.size.height/self.numberOfSections;
label.frame=frame;
}
//1. if the box goes out of the frame on the top
// (box is 40 pixels tall)
if (frame.origin.y < -40)
{
[self.imArray removeObjectAtIndex:i];
[self.imArray addObject:label];
STTimeMarker *labelTwo = self.imArray[self.numberOfSections-1];
CGRect frameTwo =labelTwo.frame;
frame.origin.y = frameTwo.origin.y + self.container.bounds.size.height/self.numberOfSections;
label.frame=frame;
}
}
return YES;
}
If I understand what you are trying to do correctly, I think you want to come at this a different way. Your data model (the array) does not need to change. All that is changing as you scroll is the view, what is displaying on screen. The simplest way to achieve the appearance of an infinite scroll would be to use a UITableView and give it some large number of cells. Then your cellForRowAtIndexPath: method will return the cell for the correct position using the mod operator (%). Untested code:
- (NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section {
return 99999;
}
- (UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath {
NSInteger moddedRow = indexPath.row % [self.imArray count];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kSomeIdentifierConst forIndexPath:[NSIndexPath indexPathForRow:moddedRow inSection:indexPath.section]];
return [self configureCellWithData:self.imArray[moddedRow]];
}
This may not be sufficient for your purposes if you need true infinite scroll, but should work for most purposes.

how to determine what pan view is in what index

I have an app with 3 uiimageviews on the screen. each one has a uipangesture connected to it. I am having trouble determining which pan view goes to which box. (see image below.) The white boxes determine if a pan view is in them by CGRectContainsPoint(). My main problem is getting the text out of the labels that are in the image views. Since they will always be different, how can I know from a 1, 2, 3 kind of indexing which pan's view is in which box?
Here is my code.
if (pan.state == UIGestureRecognizerStateEnded)
{
++_countSoFar; // _countSoFar is an iVar
if (CGRectContainsPoint(self.view1.frame, pan.view.center)) {
pan.view.tag = 1;
}
else if(CGRectContainsPoint(self.view2.frame, pan.view.center)) {
pan.view.tag = 2;
}
else pan.view.tag = 3;
NSLog(#"counts = %d", _countSoFar);
}
if (_countSoFar == carLevels)
{
NSString *s1 = [[self.view viewWithTag:pan.view.tag].subviews[0] text];
_countSoFar = 0; //reset count
}
The problem with my code is I can only get 1 text value because the subviews only has 1 per view...I can't figure this out. Any help will be appreciated!
Here is the image:
You have two options:
Set the property of UIImageView and on the gesture recongnizer event handler, get the attached UIImageView and identify the relevant content based on the index you set.
Subclass the UIPanGestureRecognizer, add a new field called index and use that to capture the gestures and then the index from that

How to detect scrolling to a new section in a UICollectionView?

I am implementing an infinite-scrolling calendar. My issue is that I would like to set the current month as the title in the navigation bar and it should update while scrolling - once you pass the section header view the title should update in the nav bar.
A possible solution would be to set the view title in the method called - (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath so that, when I calculate a new section Header, it also updates the title. The problem with this is that the title changes when the new section is at the bottom of the page.
Is there a way to know the "current section" of UICollectionView once the user has scrolled to it? Or can you think of a way to improve my current solution?
To help the readers of this post, I posted my own sample code for this question at this GitHub repo.
I have been pondering an algorithm that would allow you to know when the user has scrolled past a section header in order to update the title, and after some experimentation I have figured out how to implement the desired behavior.
Essentially, every time the scroll position changes you need to know what section the user is on and update the title. You do this via scrollViewDidScroll on the UIScrollViewDelegate - remembering a collection view is a scroll view. Loop over all the headers and find the one that's closest to the current scroll position, without having a negative offset. To do that, I utilized a property that stores an array of each section header's position. When a header is created, I store its position in the array at the appropriate index. Once you've found the header that's closest to your scroll position (or the index location of said header), simply update the title in the navigation bar with the appropriate title.
In viewDidLoad, fill the array property with NSNull for each section you have:
self.sectionHeaderPositions = [[NSMutableArray alloc] init];
for (int x = 0; x < self.sectionTitles.count; x++) {
[self.sectionHeaderPositions addObject:[NSNull null]];
}
In collectionView:viewForSupplementaryElementOfKind:atIndexPath:, update the array with the position of the created header view:
NSNumber *position = [NSNumber numberWithFloat:headerView.frame.origin.y + headerView.frame.size.height];
[self.sectionHeaderPositions replaceObjectAtIndex:indexPath.section withObject:position];
In scrollViewDidScroll:, perform the calculations to determine which title is appropriate to display for that scroll position:
CGFloat currentScrollPosition = self.collectionView.contentOffset.y + self.collectionView.contentInset.top;
CGFloat smallestPositiveHeaderDifference = CGFLOAT_MAX;
int indexOfClosestHeader = NSNotFound;
//find the closest header to current scroll position (excluding headers that haven't been reached yet)
int index = 0;
for (NSNumber *position in self.sectionHeaderPositions) {
if (![position isEqual:[NSNull null]]) {
CGFloat floatPosition = position.floatValue;
CGFloat differenceBetweenScrollPositionAndHeaderPosition = currentScrollPosition - floatPosition;
if (differenceBetweenScrollPositionAndHeaderPosition >= 0 && differenceBetweenScrollPositionAndHeaderPosition <= smallestPositiveHeaderDifference) {
smallestPositiveHeaderDifference = differenceBetweenScrollPositionAndHeaderPosition;
indexOfClosestHeader = index;
}
}
index++;
}
if (indexOfClosestHeader != NSNotFound) {
self.currentTitle.text = self.sectionTitles[indexOfClosestHeader];
} else {
self.currentTitle.text = self.sectionTitles[0];
}
This will correctly update the title in the nav bar once the user scrolls past the header for a section. If they scroll back up it will update correctly as well. It also correctly sets the title when they haven't scrolled past the first section. It however doesn't handle rotation very well. It also won't work well if you have dynamic content, which may cause the stored positions of the header views to be incorrect. And if you support jumping to a specific section, the user jumps to a section whose previous section's section header hasn't been created yet, and that section isn't tall enough such that the section header is underneath the nav bar (the last section perhaps), the incorrect title will be displayed in the nav bar.
If anyone can improve upon this to make it more efficient or otherwise better please do and I'll update the answer accordingly.
change below line in method viewForSupplementaryElementOfKind :
self.title = [df stringFromDate:[self dateForFirstDayInSection:indexPath.section]];
to this:
if(![[self dateForFirstDayInSection:indexPath.section-1] isKindOfClass:[NSNull class]]){
self.title = [df stringFromDate:[self dateForFirstDayInSection:indexPath.section-1]];
}
Hope it will help you.
Yes, the problem is that footer and header are not exists in visibleCells collection. There is other way to detect scroll for section header/footer. Just add a control there and find the rect for it. Like this:
func scrollViewDidScroll(scrollView: UIScrollView) {
if(footerButton.tag == 301)
{
let frame : CGRect = footerButton.convertRect(footerButton.frame, fromView: self.view)
//some process for frame
}
}
No solution here is fulfilling, so I came up with my own that I want to share, fully. If you use this, you have to make some pixel adjustments, though.
extension MyViewControllerVC: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView == self.myCollectionView {
let rect = CGRect(origin: self.myCollectionView.contentOffset, size: self.cvProductItems.bounds.size)
let cellOffsetX: CGFloat = 35 // adjust this
let cellOffsetAheadY: CGFloat = 45 // adjust this
let cellOffsetBehindY: CGFloat = 30 // adjust this
var point: CGPoint = CGPoint(x: rect.minX + cellOffsetX, y: rect.minY + cellOffsetAheadY) // position of cell that is ahead
var indexPath = self.myCollectionView.indexPathForItem(at: point)
if indexPath?.section != nil { // reached next section
// do something with your section (indexPath!.section)
} else {
point = CGPoint(x: rect.minX + cellOffsetX, y: rect.minY - cellOffsetBehindY) // position of cell that is behind
indexPath = self.myCollectionView.indexPathForItem(at: point)
if indexPath?.section != nil { // reached previous section
// do something with your section (indexPath!.section)
}
}
}
}
}
UICollectionView inherits UIScrollView, so we can just do self.myCollectionView.delegate = self in viewDidLoad() and implement the UIScrollViewDelegate for it.
In the scrollViewDidScroll callback we will first get the point of a cell below, adjust cellOffsetX and cellOffsetAheadY properly, so your section will be selected when the cell hits that point. You can also modify the CGPoint to get a different point from the visible rect, i.e for x you can also use rect.midX / rect.maxX and any custom offset.
An indexPath will be returned from indexPathForItem(at: GCPoint) when you hit a the cell with those coordinates.
When you scroll up, you might want to look ahead, possibly ahead your UICollectionReusableView header and footer, for this I also check the point with negative Y offset set in cellOffsetBehindY. This has lower priority.
So, this example will get the next section once you pass the header and the previous section once a cell of the previous section is about to get into view. You have to adjust it to fit your needs and you should store the value somewhere and only do your thing when then current section changes, because this callback will be called on every frame while scrolling.

Changing the UITableView height based on scroll in iOS

I want to change the height of my UITableView based on the scrolled content. Right now I do it by getting the scrollViewDidScroll event and then getting scrolled value and then change the height. Here is my code (for simplification I omitted the irrelevant code):
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
float currentOffset = scrollView.contentOffset.y
double delta = currentOffset - storedOffset;
if(delta != 0)
{
delta = abs(delta);
CGRect newListFrame = myTableView.frame;
float newListHeight = newListFrame.size.height + delta;
newListFrame.size.height = newListHeight;
myTableView.frame = newListFrame;
}
storedOffset = currentOffset;
}
But this approach is wrong because with this approach my UITableView's content is scrolled only a little bit and that's not what I want. I just want to get the value of that list that would be scrolled without actually scrolling it. Is there any way to do that? I thing I could get raw finger moved event but can I get it on UITableVIew? Can I do something like this using a UITableView method?

Resources