Objective-C : Endless UIScrollView without pagingEnabled - objective-c

In my iPhone app there is a scrollview pagingEnabled=NO which can contain up to 200 subviews (150 x 150) and the challenge is to do lazy loading and simulate endless scrolling (without bouncing) in horizontal directions.
Is there a solution or an alternative for this request?

Lazy loading of a scroll view is demonstrated in one of Apple's sample code projects: PageControl.
To fake endless scrolling what I'd suggest is to make your scroll view very wide to begin with, wider than an average person would scroll in one set of scrolling behaviors. Then in your delegate methods -scrollViewDidEndScrollingAnimation:, -scrollViewDidEndDragging:willDecelerate: and -scrollViewDidEndDecelerating:, one or more of which will be called after the user is done scrolling, reposition your content to exist in the center of the scroll view and update your contentOffset point without animating.
For that to work visually you would need to also disable the horizontal scroller. You'll also need to consider how to determine what view to draw at a particular contentOffset with this method since you won't be able to just divide the contentOffset.x by the scroll view's bounds anymore to find out where you are.

Hello I found the way to do it.
I have a master array with all the subviews (in my case they are images, so I store the names).
The scrollview only has 3 subviews: left, current, right.
Paging is enabled, so the user cant really spin more that one view left/right at any time.
What I do is:
1) track his current position on the master array. If he moves left, subtract one; right add one. Like this:
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
[some code to determine current page, based on contentOffset]
if (page == 0){
NSLog(#"Going left");
if (currentPage > 0){
currentPage--;
} else {
//cycle to last
currentPage = [images count] -1;
}
} else if (page == 2){
NSLog(#"Going right");
if (currentPage < ([images count] -1)){
currentPage++;
} else {
//cycle to first
currentPage = 0;
}
} else{
NSLog(#"Not moving");
}
2) after the user moves, I reload 3 new images, like this:
//updates the 3 views of the scrollview with a new center page.
-(void) updateScrollViewForIndex:(NSInteger)newCenterPage{
//fist clean scroll view
for (UIView *sView in [scroll subviews]){
[sView removeFromSuperview];
}
NSInteger imgCount = [images count];
//set center view
[self loadImageIndex:newCenterPage atPosition:1];
//set left view
if (newCenterPage > 0){
[self loadImageIndex:newCenterPage-1 atPosition:0];
} else {
//its the first image, so the left one is the last one
[self loadImageIndex:imgCount-1 atPosition:0];
}
//set right view
if (newCenterPage < imgCount-1){
[self loadImageIndex:newCenterPage+1 atPosition:2];
} else {
//its the last image, so ther right one is the first one
[self loadImageIndex:0 atPosition:2];
}
}
3) Finally re-center the scroll view to the center view again:
[scroll setContentOffset:CGPointMake(1 * viewWidth, 0)];
Hope this helps, although "the man with the plan" is Mr. Clark, who pointed the way.
Gonso

Matt Gallagher has a blog post which describes a solution to this exact problem. I've used it and it works great.
The UIScrollView and UIPageControl in Cocoa Touch allow for user interfaces with multiple panning pages. The sample project that Apple provides (PageControl) keeps all child views for every page in a lazily loaded array. I'll show you how you can implement this using just two child views, no matter how many virtual pages you wish to represent.
It works by shuffling around the child views and reseting their content when necessary. I used this for displaying flash cards, where there could be anywhere from 3 to 3,000 items. Although it's set up right now for paging, I'm sure you could get it to work with regular scrolling.

Related

Scrolling a UIScrollView in a UIScrollView

If I have a UIScrollView in a UIScrollview, but I only want the contained scrollview to recieve events and work when the parent scroll view Y Offset is 0.
How does one implement this?
Caveat I didn't mention, using self.ptViewController.collectionView.userInteractionEnabled = YES;
Does not help, as the events don't begin passing to the scrollview until the user has released their finger due presumably to the change in FirstResponder.
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView //Or – scrollViewDidScroll:
{
if (scrollView==scrollViewA && scrollViewA.contentOffset.y == 0)
{
scrollViewB.userInteractionEnabled = YES;
}
}
As far as I know, you cannot transfer the first responders between the different scrollviews so you would not have to take your finger off and on again.
However you can do something else:
Once you reach the y=0 point in your parent scrollview, you can then use the touchesMoved event to programatically scroll your contained scrollView.
Basically, you'd do a hit test to make sure the touch is in the contained scrollView, check if the container scrollView contentOffset y position is 0 and then scroll your container scrollView.
Something like:
- (void) touchesMoved: (NSSet *)touches withEvent:(UIEvent *)event
{
if (scrollViewContainer.contentOffset.y == 0)
{
if ([self hitTest:[[touches anyObject] locationInView:containedScrollView] withEvent:nil])
{
//programatically scroll your contained scrollView
}
}
}
This code is just a sample to better understand what I mean. You should adjust it to your own needs.
So, after trying the other suggestions, I found I wasn't able to replicate the fluidity needed to seamlessly scroll as if it were one table.
So long story short, a Scrollview in a scrollview, may be a bad idea if you don't want the user to take their finger off the screen while scrolling between the two.
Thus don't bother, use a UICollectionView and define precisely how each section/header/footer/row cell should be handled.
So what I did was simply add the necessary attribute changes in the Collection Flow Layout, to ensure that all sections and cells were being handled appropriately.
Due to the nature of what I was trying to achieve the flow layout required a lot of math and code to ensure all constraints were handled.

Setting content offset of dynamically expanding scroll view

My app dynamically generates items onto a scroll view. Since there is not definite height for the scroll view, it keeps adding to its height after a condition. However, I can only fit 12 items on the screen, so I want it to scroll down automatically to the lowest part of the screen when items%12==0;. After doing some research, I learned about contentOffset. However, I'm not getting the behavior I want, it scrolls all the way to the top and freezes, until I add 12 more items to the screen. Here are my conditionals.
if (itemsLength %2 ==0) {
xPos = 20;
yPos += 60;
[scrollView setContentSize:CGSizeMake(self.view.frame.size.width,scrollViewHeight+=50)];
NSLog(#"%i",scrollViewHeight);
}
if (itemsLength %12 == 0) {
CGPoint bottomOffset = CGPointMake(0,self.scrollView.contentSize.height - self.scrollView.bounds.size.height);
[scrollView setContentOffset:bottomOffset animated:YES];
}
Thanks!
~Carpetfizz
EDIT: Is there a way to scroll to a certain point in my Scrollview, without changing the content offset? Doing so ruins the rest of my code.
Try this
if (itemsLength %12 == 0) {
[scrollView scrollRectToVisible:CGRectMake(0,scrollViewHeight-50,320,50) animated:YES];
}

Detecting UIScrollView Position

I am trying to use a UIScrollView as a navigation device in my iOS application. Basically, I want the application to load specific content, based on the position of a paging-enabled UIScrollView. It is sort of like a navigation wheel.
Currently, I have a two-page (2*320) scrollview, and I am using the scrollViewDidEndDragging delegate method and contentoffset.x to load the appropriate content. I try to determine the position of the scrollview as such:
if (scrollView.contentOffset.x < 320) {
// Load content 1
}
else if (scrollView.contentOffset.x >= 320) {
// Load content 2
}
My problem is that I either do not understand how to work with contentoffset or it is not suitable to deal with my problem. Either way, I am stuck. Can someone let me know where I went wrong and/or show me a more efficient way to determine scrollview position?
I am stupid, I forgot to mention what the problem is... Basically, I cannot track the position of the scrollview. When I move to the second page it only registers changes if I go over the 620 limit (bounce rightward). But when I go back to the starting position (page 1), it does register the changes. In other words, my tracking is not accurate.
Delegate and everything else is working fine, I log them!
It's hard to say because you never specifically mentioned what the problem is, but I'll take a couple guesses at it.
If the delegate method isn't being called at all you need to remember to set the scroll views delegate:
[myScrollView setDelegate:self];
Otherwise, the problem with using scrollViewDidEndDragging to detect the scroll views offset is that it will check the offset at the point where you stop dragging which could be within the destination pages rect, or within the starting pages rect. As an alternative, I'd suggest you use scrollViewDidEndDecelerating to check the content offset as it will be called as soon as the scroll view comes to a stop at its destination page.
-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
if (scrollView.contentOffset.x < 320) {
NSLog(#"%#",NSStringFromCGPoint(scrollView.contentOffset));
}
else if (scrollView.contentOffset.x >= 320) {
NSLog(#"%#",NSStringFromCGPoint(scrollView.contentOffset));
}
}
I think this could help you :). With scrollViewDidScroll you always get the exact position of your scrollView:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if (scrollView.contentOffset.x < 320)
{
// Load content 1
}
else if (scrollView.contentOffset.x >= 320)
{
// Load content 2
}
}
NSLog(#"Position: %g", scrollView.contentOffset.x);
Do not forget the delegate UIScrollViewDelegate in your .h file.
Here a solution for getting the current position (page) as integer Value:
Use scrollViewDidEndDecelerating: methode:
-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
NSLog(#"%i", (int)(_scrollView.contentOffset.x / _scrollView.frame.size.width));
}
If for any reason, you'd like to be notified before the view ended decelerating; for example, to perform animations. You can also use this:
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
// Where the scroll will end (vertically)
let offSetY = targetContentOffset.pointee.y
// I've got a scroll view with paging enabled so I use this to check which "page" is selected.
if offSetY == firstPageView.frame.origin.y {
// Do something…
} else if offSetY == secondPageView.frame.origin.y {
// Do something…
}
}
Using UIScrollViewDelegate,
func scrollViewWillBeginDragging(scrollView: UIScrollView){
print("dragging begins")
print("Position: \(scrollView.contentOffset.x) , \(scrollView.contentOffset.y) ")
}

Detect UIScrollview scrolling past bottom

I'm trying to create functionality similar to Reeder where you are introduced to new content if you scroll below or above the content in a scroll view.
I thought I was heading in a good place with scrollViewWillEndDragging:withVelocity:targetContentOffset:
However, this just stops at the bottom.
Is there any way to detect if you are scrolling beyond the bottom or top (this is when the vertical scrollbar begins to get smaller)
Thanks!
posting comment as answer:
Check the contentOffset in scrollviewDidScroll and the moment the contetOffset is beyond your bottom/top point, add new stuff to the scrollview, and increase the content size
Maybe better solution would be something like this:
-(void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {
if (targetContentOffset->y == 0 && scrollView.contentOffset.y < - someOffset) { //use someOffset if you dont want to reload immedeately on scroll to top
//dragged past top
}
if (targetContentOffset->y == scrollView.contentSize.height && scrollView.contentOffset.y > scrollView.contentSize.height + someOffset) {
//dragged past bottom
}
}

Infinite horizontal scrolling UIScrollView

I have a UIScrollView whose content size is 1200x480. I have some image views on it, whose width adds up to 600. When scrolling towards the right, I simply increase the content size and set the offset so as to make everything smooth (I then want to add other images, but that's not important right now). So basically, the images currently in the viewport remain somewhere on the left, eventually to be removed from the superview.
Now, the problem that I have happens when scrolling towards the left. What I do is I move the images to the end of the content size (so add 600 to each image view's origin.x), and then set the content offset accordingly. It works when the finger is on the screen and the user drags (scrollView.isTracking = YES). When the user scrolls towards the left and lets go (scrollView.isTracking = NO), the image views end up moving too fast towards the right and disappear almost instantly. Does anyone know how I could have the images move nicely and not disappear even when the user's not manually dragging the view and has already let go?
Here's my code for dragging horizontally:
-(void) scrollViewDidScroll:(UIScrollView *)scrollView {
CGPoint offset = self.scrollView.contentOffset;
CGSize size = self.scrollView.contentSize;
CGPoint newXY = CGPointMake(size.width-600, size.height-480);
// this bit here allows scrolling towards the right
if (offset.x > size.width - 320) {
[self.scrollView setContentSize:CGSizeMake(size.width+320, size.height)];
[self.scrollView setContentOffset: offset];
}
// and this is where my problem is:
if (offset.x < 0) {
for (UIImageView *imageView in self.scrollView.subviews) {
CGRect frame = imageView.frame;
[imageView setFrame:CGRectMake
(frame.origin.x+newXY.x, frame.origin.y, 200, frame.size.height)];
}
[self.scrollView setContentOffset:CGPointMake(newXY.x+offset.x, offset.y)];
}
}
EDIT: This is now working - I had a look at StreetScroller and it's all good now.
However, I now want to zoom in on the scrollview, but viewForZoomingInScrollView is never called. Is it not possible to zoom in on a scrollview with a large content size?
There are some approaches floating around here. Just use the site search …
If you want an more "official" example created by Apple take a look at the StreetScroller Demo. For some more information about this example take a look at last years WWDC session no. 104 Advanced Scroll View Techniques.
There is also an UIScrollView subclass on Github called BAGPagingScrollView, which is paging & infinite, but it has a few bugs you have to fix on your own, because it's not under active development (especially the goToPage: method leads to problems).