I am trying to make a wheel rotate smoothly based on scrolling of a UIScrollView. I would want it to ease out when it almost stops scrolling the scrollview.
I tried much but failed so far, my current code is this:
#pragma mark - Scrollview delegate
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
CGFloat pageWidth = scrollView.frame.size.width;
int page = floor((scrollView.contentOffset.x - pageWidth / 2) / pageWidth) + 1;
NSLog(#"Offset: %# Page: %i", NSStringFromCGPoint(scrollView.contentOffset), page);
UIImageView* leftWheelImageView = (UIImageView*)[self.carImageView viewWithTag:10];
// Calculate speed of scrolling and turn the wheels to it
CGPoint currentOffset = scrollView.contentOffset;
NSTimeInterval currentTime = [NSDate timeIntervalSinceReferenceDate];
if(lastTime - currentTime > 0.1) {
CGFloat distance = currentOffset.x - lastOffset.x;
CGFloat scrollSpeedNotAbs = (distance * 10) / 1000; //in pixels per millisecond
CGFloat scrollSpeed = fabsf(scrollSpeedNotAbs);
leftWheelImageView.transform = CGAffineTransformMakeRotation(DEGREES_TO_RADIANS(2*3.14*99));
NSLog(#"Rotate wheel");
}
lastOffset = currentOffset;
lastTime = currentTime;
}
Could anyone push me in the right direction :-)?
You're setting your offset and time incorrectly; as it is, lastOffset and lastTime are being changed to currentOffset and currentTime every time you get the didScroll message. So your if statement will only be triggered if there's more than .1 seconds between each message. Since you said you wanted your scrolling to be smooth, it seems unlikely that this is what you were intending. Maybe move your assignments inside of your if statement? I'll see if I can find another method for you to use.
Why don't you use the library for doing this ? I have done this using carousel library . Please refer following link
https://github.com/nicklockwood/iCarousel
this is easy and better to implement.
Related
I don't know if I'm thinking right or not, but my issue that I have custom cells (that look like as a cars) in UITableView, each cell contain 2 UIImageViews (wheel images), I need to rotate the Wheel Images when UITableView scrolling either downward of upward, so can I point all wheel images to one image, and then rotate this image immediately when UITableView is scrolling?
This is my code
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
CGPoint currentOffset = scrollView.contentOffset;
if (currentOffset.y > self.lastContentOffset.y)
{
self.wheelImage.transform = CGAffineTransformRotate(self.wheelImage.transform,- M_PI / 4);
}
else
{
self.wheelImage.transform = CGAffineTransformRotate(self.wheelImage.transform, M_PI / 4);
}
self.lastContentOffset = currentOffset;
}
Sorry for my bad english, and thanks in advance.
Here is the best answer I have found till now, and it is work just fine, but I need to make it to more realistic (apply physical formula):
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
int fragment = 20;
for(UITableViewCell *cell in [self.showsTable visibleCells]){
if ([cell isKindOfClass:[ShowCell class]]){
CGPoint currentOffset = scrollView.contentOffset;
if (currentOffset.y > self.lastContentOffset.y){
[[(ShowCell*)cell wheel1Image] setTransform:CGAffineTransformRotate([(ShowCell*)cell wheel1Image].transform, M_PI / fragment)];
[[(ShowCell*)cell wheel2Image] setTransform:CGAffineTransformRotate([(ShowCell*)cell wheel2Image].transform, M_PI / fragment)];
}
else{
[[(ShowCell*)cell wheel1Image] setTransform:CGAffineTransformRotate([(ShowCell*)cell wheel1Image].transform,- M_PI / fragment)];
[[(ShowCell*)cell wheel2Image] setTransform:CGAffineTransformRotate([(ShowCell*)cell wheel2Image].transform,- M_PI / fragment)];
}
self.lastContentOffset = currentOffset;
}
}
}
I believe you can't do it such a way. What you need is to call reloadData for your UITableView from scrollViewDidScroll method. Calling reloadData will call cellForRawAtIndexMethod, and there in that method you should update transform of your wheels for every cell accordingly to current offset.
I Have four pages in single uiscrollView and pageing is enabled. Each page may have different height, I Tried to increase the content size of scrollview in scrollViewDidEndDecelerating delegate but it does't help me.
Can any one suggest how to increment contentsize of scrollview in each page differently?
Thanks in Advance.
That is not possible, the content size is the bounds of the scroll view, it is a rectangle, how could it change for each page? Why not scale the pages so they are the same size and use zooming?
I don't think you can do that natively. What you can try however is to disable paging and do it manually.
There is a useful delegate method for that:
-(void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset;
This let's you set where the scrollView will end its scrolling.
-(void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset{
float offsetY = floorf((*targetContentOffset).y);
float yGoto = 0.0;
// Find out, based on that offsetY in which page you are
// and set yGoto accordingly to the start of that page
// In the following example my pages are 320px each
// I start by only allowing to go 1 page at a time, so I limit
// how far the offsetY can be from the current scrollView offset
if(offsetY > scrollView.contentOffset.y + 160){
// Trying to scroll to more than 1 page after
offsetY = scrollView.contentOffset.y + 160;
}
if(offsetY < scrollView.contentOffset.y - 160){
// Trying to scroll to more than 1 page before
offsetY = scrollView.contentOffset.y - 160;
}
if(offsetY < 0){
// Trying to scroll to less than the first element
// This is related to the elastic effect
offsetY = 0;
}
if(offsetY > scrollView.contentSize.height-320){
// Trying to scroll to more than the last element
// This is related to the elastic effect
offsetY = scrollView.contentSize.height - 1;
}
// Lock it to offsets that are multiples of 320
yGoto = floorf(offsetY);
if((int)offsetY % 320 > 160){
int dif = ((int)offsetY % 320);
yGoto = offsetY + 320 - dif;
}else{
int dif = ((int)offsetY % 320);
yGoto = offsetY - dif;
}
yGoto = floorf(yGoto); // I keep doing this to take out non integer part
scrollView.decelerationRate = UIScrollViewDecelerationRateFast;
(*targetContentOffset) = CGPointMake(scrollView.contentOffset.x,yGoto);
}
Hope it helps!
I have a UICollectionView with 6 pages, and paging enabled, and a UIPageControl. What I want is, when I came to the last page, if I drag to right, UICollectionView reloads from first page seamlessly.
- (void)scrollViewDidEndDecelerating:(UIScrollView *)sender
{
// The key is repositioning without animation
if (collectionView.contentOffset.x == 0) {
// user is scrolling to the left from image 1 to image 10.
// reposition offset to show image 10 that is on the right in the scroll view
[collectionView scrollRectToVisible:CGRectMake(collectionView.frame.size.width*(pageControl.currentPage-1),0,collectionView.frame.size.width,collectionView.frame.size.height) animated:NO];
}
else if (collectionView.contentOffset.x == 1600) {
// user is scrolling to the right from image 10 to image 1.
// reposition offset to show image 1 that is on the left in the scroll view
[collectionView scrollRectToVisible:CGRectMake(0,0,collectionView.frame.size.width,collectionView.frame.size.height) animated:NO];
}
pageControlUsed = NO;
}
It doesn't work like I want. What can I do?
Here's what I ended up with for my UICollectionView (horizontal scrolling like the UIPickerView):
#implementation UIInfiniteCollectionView
- (void) recenterIfNecessary {
CGPoint currentOffset = [self contentOffset];
CGFloat contentWidth = [self contentSize].width;
// don't just snap to center, since this might be done in the middle of a drag and not aligned. Make sure we account for that offset
CGFloat offset = kCenterOffset - currentOffset.x;
int delta = -round(offset / kCellSize);
CGFloat shift = (offset + delta * kCellSize);
offset += shift;
CGFloat distanceFromCenter = fabs(offset);
// don't always recenter, just if we get too far from the center. Eliza recommends a quarter of the content width
if (distanceFromCenter > (contentWidth / 4.0)) {
self.contentOffset = CGPointMake(kCenterOffset, currentOffset.y);
// move subviews back to make it appear to stay still
for (UIView *subview in self.subviews) {
CGPoint center = subview.center;
center.x += offset;
subview.center = center;
}
// add the offset to the index (unless offset is 0, in which case we'll assume this is the first launch and not a mid-scroll)
if (currentOffset.x > 0) {
int delta = -round(offset / kCellSize);
// MODEL UPDATE GOES HERE
}
}
}
- (void) layoutSubviews { // called at every frame of scrolling
[super layoutSubviews];
[self recenterIfNecessary];
}
#end
Hope this helps someone.
I've been using the Street Scroller sample to create an infinite scroller for images. That works fine until I wanted to set pagingEnabled = YES; Tried tweaking around the recenterIfNecessary code and finally realized that it's the contentOffset.x that has to match the frame of the subview that i want visible when paging stops. This really isn't going to work in recenterIfNecessary since you have no way of knowing it will get called from layoutSubviews. If you do get it adjusted right, the subview may pop out from under your finger. I do the adjustment in scrollViewDidEndDecelerating. So far I haven't had problems with scrolling fast. It will work and simulate paging even when pagingEnabled is NO, but it looks more natural with YES.
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
[super scrollViewDidEndDecelerating:scrollView];
CGPoint currentOffset = [self contentOffset];
// find the subview that is the closest to the currentOffset.
CGFloat closestOriginX = 999999;
UIView *closestView = nil;
for (UIView *v in self.visibleImageViews) {
CGPoint origin = [self.imageContainerView convertPoint:v.frame.origin toView:self];
CGFloat distanceToCurrentOffset = fabs(currentOffset.x - origin.x);
if (distanceToCurrentOffset <= closestOriginX) {
closestView = v;
closestOriginX = distanceToCurrentOffset;
}
}
// found the closest view, now find the correct offset
CGPoint origin = [self.imageContainerView convertPoint:closestView.frame.origin toView:self];
CGPoint center = [self.imageContainerView convertPoint:closestView.center toView:self];
CGFloat offsetX = currentOffset.x - origin.x;
// adjust the centers of the subviews
[UIView animateWithDuration:0.1 animations:^{
for (UIView *v in self.visibleImageViews) {
v.center = [self convertPoint:CGPointMake(v.center.x+offsetX, center.y) toView:self.imageContainerView];
}
}];
}
I have not used UICollectionView for infinite scrolling, but when doing it with a UIScrollView you first adjust your content offset (instead of using scrollRectToVisible) to the location you want. Then, you loop through each subview in your scroller and adjust their coordinates either to the right or left based on the direction the user was scrolling. Finally, if either end is beyond the bounds you want them to be, move them to the far other end. Their is a very good WWDC video from apple about how to do infinite scrolling you can find here: http://developer.apple.com/videos/wwdc/2012/
Does anyone know how to determine which direction a scroll view will move when the user lifts their finger, i.e. when scrollViewDidEndDragging gets called?
Specifically, when the scroll view is set to paging.
Most of the time, I can just track and check the contentOffset in scrollViewDidScroll, however, there are special cases where the user flicks the screen quickly. In some cases a LEFT-RIGHT-LEFT move will scroll to the next page, and in others, the same pattern will remain on the same page.
I'm guessing it has something to do the with acceleration (difference between the last few points) of the touch.
(I'm on iOS4.3 on an iPad)
You don't use timer, which is very expensive operation. Lock the direction of your scrollView first to move only up/down and left/right [I guess that's what you want]. And then use following code to determine...
CGPoint start, end;
- (void)scrollViewWillBeginDragging:(UIScrollView *)sender {
start = self.scrollView.contentOffset;
}
- (void)scrollViewDidEndDragging:(UIScrollView *)sender willDecelerate:(BOOL)decelerate {
end = self.scrollView.contentOffset;
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)sender {
NSLog(#"%d %d",(int)start.x,(int)start.y);
NSLog(#"%d %d",(int)end.x,(int)end.y);
}
Here is my solution works fine for me hope it helps
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
CGFloat pageWidth = scrollView.frame.size.width;
//Old Solution
//Switch the indicator when more than 30% of the previous/next page is visible
//You can vary the percentage
//int page = floor((scrollView.contentOffset.x - pageWidth * 0.3) / pageWidth) + 1;
//New Solution
_pageControl.currentPage = (int)scrollView.contentOffset.x / (int)pageWidth;
}
I quit the old solution as the new one seems more slick
Implement the following UIScrollViewDelegate Method.
You can compare current content offset and targetContentOffset then get the direction.
-(void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset{
CGPoint newOffset = CGPointMake(targetContentOffset->x, targetContentOffset->y);
if (newOffset.x > scrollView.contentOffset.x) {
NSLog(#"Scrolling direction is left");
}else{
NSLog(#"Scrolling direction is right");
}
}
This is a tricky one, iOS 5 has the answer, but that no good for you.
Perhaps a hacky workaround is to set a NSTimer for a small time interval (say 0.1 seconds) after scrollViewDidEndDragging finished. Then compare the content offsets.
If you want to know if the scroll view will actually go to the next page, perhaps you could check if the content offset has gone more than 1/2 way. You could read the content offset on scrollViewDidScroll: and do a bit of maths to determine if its more than 1/2 way.
In addition to saurin response.
Her solution work fine but if you are using pagination and you are in the end of scroll content size you will have to test if we really have a scroll to the next 'page' or not:
CGPoint start, end;
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
start = scrollView.contentOffset;
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
end = scrollView.contentOffset;
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
if (start.x > end.x && end.x>0)
{
NSLog(#"on the previous left page");
}
if (start.x < end.x && end.x < scrollView.contentOffset.x)
{
NSLog(#"on the next right page");
}
}
When somebody does a wipe gesture to scroll the content from left to right, I would like to have a background image scrolling into the same direction, but at a different speed. Much like what these classic games did do 20 years ago (remember that, anybody????)
I accomplished this by using two UIScrollView instances. The first is where the actual content is displayed, and the second (which is behind the first in z-order) is where I have my slower-moving background. From there the top UIScrollView has a delegate attached to it that gets notified when the contentOffset changes. That delegate, in turn, programatically sets the contentOffset of the background scroller, multiplied against a constant to slow the scroll down relative to the foreground. So, for instance, you might have something like:
// Defined as part of the delegate for the foreground UIScrollView
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
UIScrollView* scroll_view(static_cast<UIScrollView*>(bkg_scroller_m.view));
CGPoint offset(scrollView.contentOffset);
offset.x = offset.x / 3;
offset.y = offset.y / 3;
// Scroll the background scroll view by some smaller offset
scroll_view.contentOffset = offset;
}
You can easily do this by implementing scroll view did scroll with a UIImageView under it...
You'll end up with something like this... with the backgroundImageView being a UIImageView added to the view before the subview... you can layer as much image views as you want without performance issues
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
float factor = scrollView.contentOffset.x / (scrollView.contentSize.width - 320);
if (factor < 0) factor = 0;
if (factor > 1) factor = 1;
CGRect frame = backgroundImageView.frame;
frame.origin.x = factor * (320 - backgroundImageView.frame.size.width);
backgroundImageView.frame = frame;
}
You can do it with CoreAnimation. You'll want to hook into the scrollViewDidEndDragging:willDecelerate: and scrollViewWillBeginDecelerating: UIScrollViewDelegate methods. Then begin an Animation on your image by changing the center position. See this SO article for more on animations.
For example you have multiple scrollviews, want them scroll difference speed. here is the modification code base on Salamatizm answer:
CGSize screenSize = [[UIScreen mainScreen] bounds].size;
float factor = scrollView.contentOffset.x / (scrollView.contentSize.width - screenSize.width);
if (factor < 0) factor = 0;
if (factor > 1) factor = 1;
CGSize parralaxSize = self.parralaxBackgroundView.contentSize;
CGPoint parallaxOffset = CGPointMake(-(factor * (screenSize.width - parralaxSize.width)), 0);
[self.parralaxBackgroundView setContentOffset:parallaxOffset animated:NO];