scrolling the scrollview at the bottom programmatically - iphone - iphone

I am dynamically adding some views in scrollview and increasing the contentsize of scrollview too but I want to scroll the scrollview at the bottom of its height.
scrollRectToVisible is not helpful to me. It just scrolls to visible view of my iphone screen but I want to reach the bottom of contentsize of scrollview.
Can anyone give me some sample code?
Thanks,
Naveed Butt

I modified #jer's solution a little:
if([yourScrollView contentSize].height > yourScrollView.frame.size.height)
{
CGPoint bottomOffset = CGPointMake(0, [yourScrollView contentSize].height - yourScrollView.frame.size.height);
[yourScrollView setContentOffset:bottomOffset animated:YES];
}
This will scroll the content to the bottom of the UIScrollView, but only when it needs to be done (otherwise you get a weird up/down jumping effect)
Also I noticed if you don't minus the hight of the scrollview itself from the height of the content it scrolls the content up past where is visible.

Use something like this instead:
CGPoint bottomOffset = CGPointMake(0, [yourScrollView contentSize].height);
[yourScrollView setContentOffset:bottomOffset animated:YES];
If you don't want it animated, just change YES to NO.

CGFloat yOffset = scrollView.contentOffset.y;
CGFloat height = scrollView.frame.size.height;
CGFloat contentHeight = scrollView.contentSize.height;
CGFloat distance = (contentHeight - height) - yOffset;
if(distance < 0)
{
return ;
}
CGPoint offset = scrollView.contentOffset;
offset.y += distance;
[scrollView setContentOffset:offset animated:YES];

In case, if you want the Swift version:
scrollView.setContentOffset(CGPointMake(0, max(scrollView.contentSize.height - scrollView.bounds.size.height, 0) ), animated: true)
Hope this helps!

This is more reliable
CGSize contentSize = scrollview.contentSize;
[scrollview scrollRectToVisible: CGRectMake(0.0,
contentSize.height - 1.0,
contentSize.width,
1.0)
animated: YES];

Related

How can I zoom a UIScrollview to a fixed point?

I am creating a control for showing the timeline. Initially , it should show the year. When zooming in and the zoomScale reached a specific value, it will show the value of the individual months, and In the next zoom level, it should show the real values for the individual days.
I have creatd a scrollview and added the layers (for showing the month/year values) to this. After overriding the pinch guesture, I am able to do the zooming (Normal UIScrollview zooming). But I need to zoom it to a particular point. ie, Suppose i am zooming January, I need to keep it in the same position(for creating an effect like moving inside of January). Any suggestions to achieve this?
Is my way is currect? else please help me to start this.
Use This bellow method for your requirement,
- (void)zoomToRect:(CGRect)rect animated:(BOOL)animated
just call this above method with your selected frame or point , like if select January then just take the point ( position) of january button or view and just call above method with that frame..
for more information see this link UIScrollView_Class
i hope this helpful to you...
Here’s what it looks like:
#implementation UIScrollView (ZoomToPoint)
- (void)zoomToPoint:(CGPoint)zoomPoint withScale: (CGFloat)scale animated: (BOOL)animated
{
//Normalize current content size back to content scale of 1.0f
CGSize contentSize;
contentSize.width = (self.contentSize.width / self.zoomScale);
contentSize.height = (self.contentSize.height / self.zoomScale);
//translate the zoom point to relative to the content rect
zoomPoint.x = (zoomPoint.x / self.bounds.size.width) * contentSize.width;
zoomPoint.y = (zoomPoint.y / self.bounds.size.height) * contentSize.height;
//derive the size of the region to zoom to
CGSize zoomSize;
zoomSize.width = self.bounds.size.width / scale;
zoomSize.height = self.bounds.size.height / scale;
//offset the zoom rect so the actual zoom point is in the middle of the rectangle
CGRect zoomRect;
zoomRect.origin.x = zoomPoint.x - zoomSize.width / 2.0f;
zoomRect.origin.y = zoomPoint.y - zoomSize.height / 2.0f;
zoomRect.size.width = zoomSize.width;
zoomRect.size.height = zoomSize.height;
//apply the resize
[self zoomToRect: zoomRect animated: animated];
}
#end
Looking at it, it should be pretty straightforward to figure out how it works. If you see anything wrong with it, let me know in the comments.
Thanks :)
source: http://www.tim-oliver.com/2012/01/14/zooming-to-a-point-in-uiscrollview/
I had same problem,
I was having a world map image, & i wanted to zoom the map to certain location, India
This is how i fixed it.
I found out the position of India, in terms of CGRect(X,Y) [CGRect(500,300)]
& following code in DidLoad
[self.scrollViewForMap setZoomScale:2.0 animated:YES]; //for zooming
_scrollViewForMap.contentOffset = CGPointMake(500,300); //for location
This solved my problem. :)
- (void)pinchDetected:(UIPinchGestureRecognizer *)gestureRecognizer {
if([gestureRecognizer state] == UIGestureRecognizerStateBegan) {
// Reset the last scale, necessary if there are multiple objects with different scales
lastScale = [gestureRecognizer scale];
}
if ([gestureRecognizer state] == UIGestureRecognizerStateBegan ||
[gestureRecognizer state] == UIGestureRecognizerStateChanged) {
CGFloat currentScale = [[[gestureRecognizer view].layer valueForKeyPath:#"transform.scale"] floatValue];
// Constants to adjust the max/min values of zoom
const CGFloat kMaxScale = 2.2;
const CGFloat kMinScale = 0.64;
CGFloat newScale = 1 - (lastScale - [gestureRecognizer scale]);
newScale = MIN(newScale, kMaxScale / currentScale);
newScale = MAX(newScale, kMinScale / currentScale);
CGAffineTransform transform = CGAffineTransformScale([[gestureRecognizer view] transform], newScale, newScale);
[gestureRecognizer view].transform = transform;
[gestureRecognizer setScale:1.0];
lastScale = [gestureRecognizer scale]; // Store the previous scale factor for the next pinch gesture call
}
}
What worked for me (Swift 4):
func zoomIn(with gestureRecognizer: UIGestureRecognizer?) {
guard let location = gestureRecognizer?.location(in: gestureRecognizer?.view)
let rect = CGRect(x: location.x, y: location.y, width: 0, height: 0)
scrollView.zoom(to: rect, animated: true)
}
Or in other words creating a CGRect with zero width and height will cause the UIScrollView to zoom to the rects origin at maximumZoomScale

Is it possible to add fixed content to a UIScrollView?

I want to create a subclass of UITableView or UIScrollView that will have some shading at the top when the content offset is > 0 to indicate that the content is scrollable. (See image attached)
The way I'm implementing it right now is using the UIViewController that is the delegate of the tableView. I simply have a GradientView on top of the tableView, and I intercept scrollViewDidScroll: to animate the visibility of that top gradient.
My problem with this implementation is that it's not "clean". I want my UIViewControllers to take care of logic, and not to deal with applying gradients and stuff. I wish I could just drop a subclass of UITableView that will do that for me.
The challenge for me is that I can't figure out how the tableView could add to itself a fixed content on top of the scrollable content.
Another question is what method/s of UIScrollView should I override to intercept the scrolling event. Obviously I don't want the tableView to be the delegate of itself...
Any ideas?
Thanks!
Ok, so I found the solution on Apple's WWDC 2011 Session 104 video - Advanced Scroll View Techniques.
There is a whole section in this video about "Stationary Views" inside a scroll view.
According to Apple, the way to go here is to override layoutSubviews and put there all the code to position whatever you want - wherever you want.
I tried it and it's actually pretty easy and it's working as expected.
So for example if I would like a shadowed header on top of the table when the content is being scrolled, this is the code I should write:
-(void) layoutSubviews
{
[super layoutSubviews];
[self positionTopShadow];
}
-(void) positionTopShadow
{
CGFloat yOffset = self.contentOffset.y;
// I'm doing some limiting so that the maximum height of the shadow view will be 40 pixels
yOffset = MIN(yOffset, 40);
yOffset = MAX(0, yOffset);
CGRect frame = self.topShadowView.frame;
// The origin should be exactly like the content offset so it would look like
// the shadow is at the top of the table (when it's actually just part of the content)
frame.origin = CGPointMake(0, self.contentOffset.y);
frame.size.height = yOffset;
frame.size.width = self.frame.size.width;
self.topShadowView.frame = frame;
if (self.topShadowView.superview == nil)
{
[self addSubview:self.topShadowView];
}
[self bringSubviewToFront:self.topShadowView];
}
I've managed to figure out a much simpler way of doing this then what Avraham did.
I use the fact that the UIScrollView calls scrollViewDidScroll: ever pixel the scrolling changes to set the object at the location of the offset. Below is my full code to keep a gray bar at the top of the scrollview as you move around:
- (void)viewDidLoad {
UIScrollView* scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(5.0, 50.0, self.bounds.size.width - 15.0, self.bounds.size.height - 60.0)];
[scrollView setBackgroundColor:[UIColor colorWithRed:251.0/255.0 green:251.0/255.0 blue:251.0/255.0 alpha:1.0]];
[scrollView setContentSize:CGSizeMake(scrollView.frame.size.width + 500, 1000.0)];
[scrollView setDelegate:self];
[self addSubview:scrollView];
UIView* header = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, scrollView.contentSize.width, 40.0)];
[header setTag:100];
[header setBackgroundColor:[UIColor darkGrayColor]];
[scrollView addSubview:header];
}
-(void)scrollViewDidScroll:(UIScrollView *)scrollView {
UIView* header = [self viewWithTag:100];
[header setFrame:CGRectMake(0.0, scrollView.contentOffset.y, header.bounds.size.width, header.bounds.size.height)];
}
You could try using viewForHeaderInSection method of tableView for the shaded view(and also heightForHeaderInSection)... Make the shaded portion as a header.That way there is a fixed content on top of the scrollable content.
#define kImageOriginHight 300
- (void)scrollViewDidScroll:(UIScrollView *)scrollView1{
CGFloat yOffset = scrollView1.contentOffset.y;
// NSLog(#" y offset := %f", yOffset);
//zoom images and hide upper view while scrooling to down position
if (yOffset < 0) {//-kImageOriginHight
CGRect f = imgV.frame;
f.origin.y = yOffset;
f.size.height = -yOffset + kImageOriginHight;
imgV.frame = f;
//viewTableUpperView.alpha = 1.5 - (yOffset/-kImageOriginHight);
//viewTableUpperView.userInteractionEnabled = NO;
if(yOffset+0.5 == -kImageOriginHight){
[UIView animateWithDuration:0.1 animations:^{
//viewTableUpperView.alpha = 1.0;
}];
//viewTableUpperView.userInteractionEnabled = YES;
}
}
}

UITableView won't scroll after editing view frame and origin

I'm trying to implement a UITextView in a table cell at the bottom of a table view.
I've tried the suggestions here Making a UITableView scroll when text field is selected, and other solutions as well, but they're a bit different because I have to artificially add extra height to the current view in order to create space for the keyboard.
Here's what I added to the previous solution in order to port it to my app.
-(void) keyboardWillShow:(NSNotification *)note {
CGRect frame = self.view.frame;
frame.size.height += keyboardHeight;
frame.origin.y -= keyboardHeight;
self.view.frame = frame;
}
-(void) keyboardWillHide:(NSNotification *)note
{
CGRect frame = self.view.frame;
frame.size.height -= keyboardHeight;
frame.origin.y += keyboardHeight;
}
Doing this will correctly add the height to the view and scroll to the cell, but after restoring the original view's height, scrolling beyond the current visible view becomes impossible, even though there is valid content outside of the boundaries (I see the text view before the scroll bar bounces back).
If I try to save the tableview's frame or bounds (not the view) in keyboardWillShow and restore them in keyboardWillHide, the scrolling will be restored, but the view will be cut in half.
Are there any remedies to this besides hard-coding the additional height to the bottom of the view?
I was able to solve my problem of the locked scrolling by removing the code that edits the view's origin. In addition, I implemented scrolling to the bottom cell by using the tableview's contentSize property in my calculations.
-(void) keyboardWillShow:(NSNotification *)note
{
if(!isKeyboardShowing)
{
isKeyboardShowing = YES;
CGRect keyboardBounds;
[[note.userInfo valueForKey:UIKeyboardBoundsUserInfoKey] getValue: &keyboardBounds];
CGFloat keyboardHeight = keyboardBounds.size.height;
CGRect frame = self.view.frame;
frame.size.height += keyboardHeight;
self.view.frame = frame;
CGPoint scrollPoint = frame.origin;
scrollPoint.y += _tableView.contentSize.height - keyboardHeight;
[_tableView setContentOffset:scrollPoint animated:YES];
}
}

How can I use scrollRectToVisible to scroll to the center of an image?

I have a UIScrollView with zooming and panning. I want the image to scroll to the center after a user command. My problem is in calculating the size and location of a frame that is in the center of the image.
Does anyone know how to calculate the correct frame for the center of my image? The problem is that if the zoomScale is different the frame changes.
Thanks!
Here's maybe a bit better code in case anyone is in need ;-)
UIScrollView+CenteredScroll.h:
#interface UIScrollView (CenteredScroll)
-(void)scrollRectToVisibleCenteredOn:(CGRect)visibleRect
animated:(BOOL)animated;
#end
UIScrollView+CenteredScroll.m:
#implementation UIScrollView (CenteredScroll)
-(void)scrollRectToVisibleCenteredOn:(CGRect)visibleRect
animated:(BOOL)animated
{
CGRect centeredRect = CGRectMake(visibleRect.origin.x + visibleRect.size.width/2.0 - self.frame.size.width/2.0,
visibleRect.origin.y + visibleRect.size.height/2.0 - self.frame.size.height/2.0,
self.frame.size.width,
self.frame.size.height);
[self scrollRectToVisible:centeredRect
animated:animated];
}
#end
Based on Daniel Bauke answer, I updated his code to include zoom scale :
#implementation UIScrollView (jsCenteredScroll)
-(void)jsScrollRectToVisibleCenteredOn:(CGRect)visibleRect
animated:(BOOL)animated
{
CGPoint center = visibleRect.origin;
center.x += visibleRect.size.width/2;
center.y += visibleRect.size.height/2;
center.x *= self.zoomScale;
center.y *= self.zoomScale;
CGRect centeredRect = CGRectMake(center.x - self.frame.size.width/2.0,
center.y - self.frame.size.height/2.0,
self.frame.size.width,
self.frame.size.height);
[self scrollRectToVisible:centeredRect
animated:animated];
}
#end
private func centerScrollContent() {
let x = (imageView.image!.size.width * scrollView.zoomScale / 2) - ((scrollView.bounds.width) / 2)
let y = (imageView.image!.size.height * scrollView.zoomScale / 2) - ((scrollView.bounds.height) / 2)
scrollView.contentOffset = CGPointMake(x, y)
}
Okay, got it working. Here's the code incase anyone is in need:
CGFloat tempy = imageView.frame.size.height;
CGFloat tempx = imageView.frame.size.width;
CGRect zoomRect = CGRectMake((tempx/2)-160, (tempy/2)-240, myScrollView.frame.size.width, myScrollView.frame.size.height);
[myScrollView scrollRectToVisible:zoomRect animated:YES];

Is it Possible to Center Content in a UIScrollView Like Apple's Photos App?

I have been struggling with this problem for some time and there seems to be no clear answer. Please let me know if anyone has figured this out.
I want to display a photo in a UIScrollView that is centered on the screen (the image may be in portrait or landscape orientation).
I want to be able to zoom and pan the image only to the edge of the image as in the photos app.
I've tried altering the contentInset in the viewDidEndZooming method and that sort of does the trick, but there's several glitches.
I've also tried making the imageView the same size as the scrollView and centering the image there. The problem is that when you zoom, you can scroll around the entire imageView and part of the image may scroll off the view.
I found a similar post here and gave my best workaround, but no real answer was found:
UIScrollView with centered UIImageView, like Photos app
One elegant way to center the content of UISCrollView is this.
Add one observer to the contentSize of your UIScrollView, so this method will be called everytime the content change...
[myScrollView addObserver:delegate
forKeyPath:#"contentSize"
options:(NSKeyValueObservingOptionNew)
context:NULL];
Now on your observer method:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
// Correct Object Class.
UIScrollView *pointer = object;
// Calculate Center.
CGFloat topCorrect = ([pointer bounds].size.height - [pointer viewWithTag:100].bounds.size.height * [pointer zoomScale]) / 2.0 ;
topCorrect = ( topCorrect < 0.0 ? 0.0 : topCorrect );
topCorrect = topCorrect - ( pointer.frame.origin.y - imageGallery.frame.origin.y );
// Apply Correct Center.
pointer.center = CGPointMake(pointer.center.x,
pointer.center.y + topCorrect ); }
You should change the [pointer
viewWithTag:100]. Replace by your
content view UIView.
Also change imageGallery pointing to your window size.
This will correct the center of the content everytime his size change.
NOTE: The only way this content don't works very well is with standard zoom functionality of the UIScrollView.
This code should work on most versions of iOS (and has been tested to work on 3.1 upwards).
It's based on the Apple WWDC code for the PhotoScroll app.
Add the below to your subclass of UIScrollView, and replace tileContainerView with the view containing your image or tiles:
- (void)layoutSubviews {
[super layoutSubviews];
// center the image as it becomes smaller than the size of the screen
CGSize boundsSize = self.bounds.size;
CGRect frameToCenter = tileContainerView.frame;
// center horizontally
if (frameToCenter.size.width < boundsSize.width)
frameToCenter.origin.x = (boundsSize.width - frameToCenter.size.width) / 2;
else
frameToCenter.origin.x = 0;
// center vertically
if (frameToCenter.size.height < boundsSize.height)
frameToCenter.origin.y = (boundsSize.height - frameToCenter.size.height) / 2;
else
frameToCenter.origin.y = 0;
tileContainerView.frame = frameToCenter;
}
Here is the best solution I could come up with for this problem. The trick is to constantly readjust the imageView's frame. I find this works much better than constantly adjusting the contentInsets or contentOffSets. I had to add a bit of extra code to accommodate both portrait and landscape images.
If anyone can come up with a better way to do it, I would love to hear it.
Here's the code:
- (void) scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale {
CGSize screenSize = [[self view] bounds].size;
if (myScrollView.zoomScale <= initialZoom +0.01) //This resolves a problem with the code not working correctly when zooming all the way out.
{
imageView.frame = [[self view] bounds];
[myScrollView setZoomScale:myScrollView.zoomScale +0.01];
}
if (myScrollView.zoomScale > initialZoom)
{
if (CGImageGetWidth(temporaryImage.CGImage) > CGImageGetHeight(temporaryImage.CGImage)) //If the image is wider than tall, do the following...
{
if (screenSize.height >= CGImageGetHeight(temporaryImage.CGImage) * [myScrollView zoomScale]) //If the height of the screen is greater than the zoomed height of the image do the following...
{
imageView.frame = CGRectMake(0, 0, 320*(myScrollView.zoomScale), 368);
}
if (screenSize.height < CGImageGetHeight(temporaryImage.CGImage) * [myScrollView zoomScale]) //If the height of the screen is less than the zoomed height of the image do the following...
{
imageView.frame = CGRectMake(0, 0, 320*(myScrollView.zoomScale), CGImageGetHeight(temporaryImage.CGImage) * [myScrollView zoomScale]);
}
}
if (CGImageGetWidth(temporaryImage.CGImage) < CGImageGetHeight(temporaryImage.CGImage)) //If the image is taller than wide, do the following...
{
CGFloat portraitHeight;
if (CGImageGetHeight(temporaryImage.CGImage) * [myScrollView zoomScale] < 368)
{ portraitHeight = 368;}
else {portraitHeight = CGImageGetHeight(temporaryImage.CGImage) * [myScrollView zoomScale];}
if (screenSize.width >= CGImageGetWidth(temporaryImage.CGImage) * [myScrollView zoomScale]) //If the width of the screen is greater than the zoomed width of the image do the following...
{
imageView.frame = CGRectMake(0, 0, 320, portraitHeight);
}
if (screenSize.width < CGImageGetWidth (temporaryImage.CGImage) * [myScrollView zoomScale]) //If the width of the screen is less than the zoomed width of the image do the following...
{
imageView.frame = CGRectMake(0, 0, CGImageGetWidth(temporaryImage.CGImage) * [myScrollView zoomScale], portraitHeight);
}
}
[myScrollView setZoomScale:myScrollView.zoomScale -0.01];
}
Here is how I calculate the the min zoom scale in the viewDidLoad method.
CGSize photoSize = [temporaryImage size];
CGSize screenSize = [[self view] bounds].size;
CGFloat widthRatio = screenSize.width / photoSize.width;
CGFloat heightRatio = screenSize.height / photoSize.height;
initialZoom = (widthRatio > heightRatio) ? heightRatio : widthRatio;
[myScrollView setMinimumZoomScale:initialZoom];
[myScrollView setZoomScale:initialZoom];
[myScrollView setMaximumZoomScale:3.0];