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
}
}
Related
Not sure it's possible, but I have a scrollview with a bunch of subviews for content. When a particular subview is scrolled to and begins getting scrolled up off screen, I'd like to create the 'rubberband' effect, where the scrolling off screen becomes incrementally less as the scrolling continues (eventually after some threshold, the rubberband would 'snap', and the user would get past that particular subview. For now just focusing on the first part.)
My current attempt is to reset the contentOffset when the view's top goes above top of the scrollview, with a smaller distance between the scrollview top and subview:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (scrollView.contentOffset.y > _myview.frame.origin.y) {
[scrollView setContentOffset:CGPointMake(0, _myview.frame.origin.y + ((scrollView.contentOffset.y - _myview.frame.origin.y) * .7))];
}
}
When running it, the subview 'sticks' to the top of the scrollview, never going past, and not having the rubberband effect. Any thoughts or suggestions? Is this even possible? Thanks.
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.
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];
}
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) ")
}
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.