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.
Related
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/
So I have a UIScrollview that scrolls horizontally with paging through a bunch of different UIImageViews containing user avatars. But, I want to only load the avatars from their URL IFF they are actually showing on the screen (4 are showing at a time, but I can scroll through like 30 or more or less). Is there a way of detecting which UIImageView/ which page it is on? Or does anyone have any general recommendations or ideas for how I should go about this problem? Thank you in advance.
-(void)scrollViewDidScroll:(UIScrollView *)scrollView{
-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
CPoint offset = scrollView.contentOffset;
int index = offset.x / pageWidth;
switch (index) {
case 0:
//first ImageView
break;
case 1:
//second ImageView
break;
default:
break;
}
}
Or If you tag your imageViews (starting at 1) you can retrieve them via:
-(void)scrollViewDidScroll:(UIScrollView *)scrollView{
-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
CPoint offset = scrollView.contentOffset;
int index = offset.x / pageWidth;
UIImageView *visibileImageView = (id)[scrollView viewWithTag:index+1];
}
You can use scrollViewDidScroll to detect the position at any point if you use the modulus:
int index = (offset.x - offset.x % pageWidth)/pageWidth;
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");
}
}
I have a UIView inside a UIScrollView. Whenever the UIScrollView zoom changes, I want to redraw the entire UIView at the new zoom level.
In iOS < 3.2, I was doing this by resizing the UIView within the UIScrollView to make it the new size, and then setting the transform back to Identity, so that it wouldn't try to resize it further. However, with iOS >= 3.2, changing the identity also changes the UIScrollView's zoomScale property.
The result is that whenever I zoom (say 2x), I adjust the embedded UIView to be the appropriate size, and redraw it. However now (since I reset the transform to Identity), the UIScrollView thinks its once again at zoomScale 1, rather than zoomScale 2. So if I have my maxZoomScale set at 2, it will still try zooming further, which is wrong.
I thought about using the CATiledLayer, but I don't think this is sufficient for me, since I want to redraw after every zoom, not just at certain zoom thresholds like it tries to do.
Does anyone know how to do the proper redrawing of the UIView on a zoom?
Tom,
Your question is a bit old, but I came up with a solution for this, so I figured I'd put in an answer in case it helps you or anyone else. The basic trick is to reset the scroll view's zoomScale to 1, and then adjust the minimumZoomScale and maximumZoomScale so that the user can still zoom in and out as expected.
In my implementation, I've subclassed UIScrollView and set it to be its own delegate. In my subclass, I implement the two delegate methods you need for zooming (shown below). contentView is a property I added to my UIScrollView subclass that in order to give it the view that actually displays the content.
So, my init method looks something like this (kMinimumZoomScale and kMaximumZoomScale are #define's at the top of the class):
- (id)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
self.autoresizesSubviews = YES;
self.showsVerticalScrollIndicator = YES;
self.showsHorizontalScrollIndicator = NO;
self.bouncesZoom = YES;
self.alwaysBounceVertical = YES;
self.delegate = self;
self.minimumZoomScale = kMinimumZoomScale;
self.maximumZoomScale = kMaximumZoomScale;
}
return self;
}
Then I implement the standard UIScrollView delegate methods for zooming. My ContentView class has a property called zoomScale that tells it what scale to use for displaying its content. It uses that in its drawRect method to resize the content as appropriate.
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)aScrollView {
return contentView;
}
- (void)scrollViewDidEndZooming:(UIScrollView *)aScrollView withView:(UIView *)view atScale:(float)scale {
CGFloat oldZoomScale = contentView.zoomScale;
CGSize size = self.bounds.size;
// Figure out where the scroll view was centered so that we can
// fix up its offset after adjusting the scaling
CGPoint contentCenter = self.contentOffset;
contentCenter.x += size.width / (oldZoomScale * scale) / 2;
contentCenter.y += size.height / (oldZoomScale * scale) / 2;
CGFloat newZoomScale = scale * oldZoomScale;
newZoomScale = MAX(newZoomScale, kMinimumZoomscale);
newZoomScale = MIN(newZoomScale, kMaximumZoomscale);
// Set the scroll view's zoom scale back to 1 and adjust its minimum and maximum
// to allow the expected amount of zooming.
self.zoomScale = 1.0;
self.minimumZoomScale = kMinimumZoomScale / newZoomScale;
self.maximumZoomScale = kMaximumZoomScale / newZoomScale;
// Tell the contentView about its new scale. My contentView.zoomScale setter method
// calls setNeedsDisplay, but you could also call it here
contentView.zoomScale = newZoomScale;
// My ContentView class overrides sizeThatFits to give its expected size with
// zoomScale taken into account
CGRect newContentSize = [contentView sizeThatFits];
// update the content view's frame and the scroll view's contentSize with the new size
contentView.frame = CGRectMake(0, 0, newContentSize.width, newContentSize.height);
self.contentSize = newContentSize;
// Figure out the new contentOffset so that the contentView doesn't appear to move
CGPoint newContentOffset = CGPointMake(contentCenter.x - size.width / newZoomScale / 2,
contentCenter.y - size.height / newZoomScale / 2);
newContentOffset.x = MIN(newContentOffset.x, newContentSize.width - size.width);
newContentOffset.x = MAX(0, newContentOffset.x);
newContentOffset.y = MIN(newContentOffset.y, newContentSize.height - .size.height);
newContentOffset.y = MAX(0, newContentOffset.y);
[self setContentOffset:newContentOffset animated:NO];
}
In my case, I have an image view and on top of the image view I have several other imageviews. This is the implementation I came up with and works fine:
- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(double)scale{
NSLog(#"finished zooming");
NSLog(#"position x :%f",pin1.frame.origin.x);
NSLog(#"position y :%f",pin1.frame.origin.y);
CGRect frame = pin1.frame;
frame.origin.x = pin1.frame.origin.x * scale;
frame.origin.y = pin1.frame.origin.y * scale;
pin1.frame = frame;
NSLog(#"position x new :%f",pin1.frame.origin.x);
NSLog(#"position y new:%f",pin1.frame.origin.y);
}
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];