How to always makes a UIScrollView scrollable? - objective-c

I have a UIScrollView. The scrollview.contentSize is set to the height of all subviews in the UIScrollView. When the height of the contentSize if greater than the height of the UIScrollView, it is perfectly scrolling when dragging the view. When the height of the contentSize is less than the height of the UIScrollView nothing is happening when I drag the view.
I think that is standard behavior, since there is really nothing to scroll. But I would like, that the UIScrollView is moving a bit anyway.
A possible solution is to ensure, that the height of the contentSize is never less than the frame.height plus a small margin (i.e. 5px):
CGSize scrollSize = self.frame.size;
int defaultHeight = self.frame.size.height + 5;
int contentHeight = self.webView.frame.origin.y + self.webView.frame.size.height;
scrollSize.height = MAX(defaultHeight, contentHeight);
[self.scrollView setContentSize:scrollSize];
Is there a less hackish way to do this?

There are two properties on UIScrollView that sound like what you want:
#property(nonatomic) BOOL alwaysBounceVertical; // default NO. if YES and bounces is YES, even if content is smaller than bounds, allow drag vertically
#property(nonatomic) BOOL alwaysBounceHorizontal; // default NO. if YES and bounces is YES, even if content is smaller than bounds, allow drag horizontally
Simply set one or both of them to YES and you will get the bounce effect.
This can also be set in IB by checking the "Bounce Horizontally" and/or "Bounce Vertically" box(s):

In swift:
scrollView.alwaysBounceVertical = true
scrollView.alwaysBounceHorizontal = true

Related

Can't move my UIScrollView

I have a content size 1000x10000 and it's center is on the center of the viewcontroller.
I want to push a button for it to turn pages. In the next code I've tried almost any number in the origin.x but nothing changes the scroll view.
I don't understand the math of it but when I set origin.x=300 and origin.y=100 the view will move up a bit but that's it.
CGRect frame=scroller.frame;
frame.origin.x=ANY NUMBER HERE;
frame.origin.y=0;
[scroller scrollRectToVisible:frame animated:YES];
How can I set it to move the view from right to left
Try this,
[scroller setContentOffset:CGPointMake(300, 0) animated:YES];
setContentOffset:animated:
Sets the offset from the content view’s origin that corresponds to the receiver’s origin.
You should be creating the UIScrollView's frame to the size of the UIViewController like so:
UIScrollView *scroller = [[UIScrollView alloc] initWithFrame:self.view.frame];
Then set the scrollers CONTENT VIEW to the 1000x10000 size like so:
scroller.contentSize = CGSizeMake(1000, 10000);
The contentSize is what makes a UIScrollview able to scroll, as long as the contentsize is larger than the scrollers frame.
If you want to make it scrollable, then contentSize has to be greater than frame.size, otherwise there isn't space to scroll.
Then ensure that scrollEnabled is set to YES (by default it is, so unless that you've changed it it's already set to YES).

UIScrollView: handling of dynamic content?

I've a Storyboard with a UIScrollView which contains two UILabels, a UIImageView and a UITextView. The content of the UIImageView and UITextView is dynamic and so are their height.
Currently I'm doing this inside my viewDidLoad to adjust the size of the UITextView after the dynamic text is inserted:
CGRect frame = self.textView.frame;
frame.size.height = self.textView.contentSize.height;
self.textView.frame = frame;
Is this the way to change its height?
My next problem is to set the content size for the UIScrollView, to activate the scrolling. Is there a smart way to get the height of all its content or do I have to calculate the height for each element and set the sum of this as the content size of the UIScrollView?
IF you had no space in between your objects, you could make a for loop in your scrollView.subviews and add up all the heights to set as the contentSize.
As you probably don't have everything tight together, you're probably better by getting the bottom most object and adding up it's frame.origin.y and it's frame.size.height (maybe you want to have some extra space in here, but that's up to you) and that will give you your contentSize.height to keep everything in there.

Why do updates on "contentOffset" of a UIScrollView make no effect?

I have a UIScrollView which is scrollable both vertically and horizontally. This view is filled with lots of buttons, each of them with its own width (but all with the same height).
When one of these buttons gets tapped, a slider-like interface is brought to life. If this interface goes over the selected button, the whole scroll view must be scrolled so that the button becomes visible once again.
My app behaves as expected when the Y coordinate of the scroll view's content offset is set to a limit (this limit can be 0 or the view's height). But if the content offset is located in an intermediate vertical position, the scrolling just doesn't seem to happen.
At first, I tried the following approach:
CGPoint newOffset = CGPointMake(self.scrollView.contentOffset.x + horizontalVar,
self.scrollView.contentOffset.y);
[self.scrollView setContentOffset: newOffset animated: YES];
Which didn't work, as I mentioned.
Then, I tried to manually animate the view, using its property setter:
[UIView animateWithDuration: 0.3 animations: ^{
CGPoint newOffset = CGPointMake(self.scrollView.contentOffset.x + horizontalVar, self.scrollView.contentOffset.y);
self.scrollView.contentOffset = newOffset;
}];
That approach produced the following result: if the scroll view is in an intermediate vertical position when one of its buttons gets tapped, the content offset update causes a visual change, but the view almost immediately returns to its original state.
I have no other clues on the subject. Could you please help me?
Maybe it's because you didn't set the ContentSize of your scrollView.

Scrollable UINavigationBar similar to Mobile Safari

My application uses a UINavigationController and the final view (detail view) lets you view an external website within the application using a UIWebView.
I'd like to free up some additional screen real estate when the user is viewing a webpage and wanted to emulate how Safari on iPhone works where their URL bar at the top scrolls up and off the screen when you're viewing content in the UIWebView that's below the fold.
Anyone have ideas on how to achieve this? If I set the navigationBarHidden property and roll my own custom bar at the top and set it and a UIWebView within a UIScrollView then there are scrolling issues in the UIWebView as it doesn't play nicely with other scrollable views.
Based on #Brian suggestion I made this code:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
CGFloat height = navigationBar.frame.size.height;
CGFloat y = scrollView.bounds.origin.y;
if (y <= 0) {
CGRect frame = navigationBar.frame;
frame.origin.y = 0;
navigationBar.frame = frame;
} else if (tableView.contentSize.height > tableView.frame.size.height) {
CGFloat diff = height - y;
CGRect frame = navigationBar.frame;
frame.origin.y = -y;
navigationBar.frame = frame;
CGFloat origin = 0;
CGFloat h = height; // height of the tableHeaderView
if (diff > 0) {
origin = diff;
h = y;
}
frame = tableView.frame;
frame.origin.y = origin;
frame.size.height = tableView.superview.frame.size.height - origin;
tableView.frame = frame;
CGRect f = CGRectMake(0, 0, tableView.frame.size.width, h);
UILabel* label = [[UILabel alloc] initWithFrame:f];
tableView.tableHeaderView = label;
[label release];
}
}
My code has a UITableView but should work with any scrollable component. If you have other components than the navigationBar and the UIScrollView subclass, you should change the way the height of the scrollable component is calculated. Something like this:
frame.size.height = tableView.superview.frame.size.height - origin - otherComponentsHeight;
I needed to add a dumb tableHeaderView to have the desired behaviour. The problem was that when scrollViewDidScroll: is called the content has an offset, but the apparience in Mobile Safari is that the content is not scrolled until the navigationBar fully disappears. I tried first changing the contentOffset.y to 0, but obviously it didn't work since all the code relies on the scrolling mechanism. So I just added a tableHeaderView whose height is exactly the scrolled offset, so the header is never really seen, and the content appears to not scroll until the navigationBar fully disappears.
If you don't add the dumb tableHeaderView, then the scrollable component appears to scroll behind the navigationBar.
With the tableHeaderView, the scrollable component is actually scrolling (as seen in the scrollbar), but since there is a tableHeaderView whose height is exactly the same than the scrolled offset, the scrollable content appears to not be scrolling until the navigationBar fully disappears:
Have a delegate for the scrolling events in the UIWebView and when you initially start scrolling the UIWebView, have the UIWebView increase in height and have it's Y position decrease at the same time while simultaneously shifting the nav bar up in the Y direction. Once the nav bar has been completely shifted out of view, stop increasing the size of the UIWebView and just allow normal scrolling to occur.
This will give the illusion of the nav bar being part of the UIWebView as it scrolls off the screen.
Also, you'll need to do the reverse when you are scrolling in the opposite direction and are reaching the top of the content of the UIWebView.
Can't give you a straight answer, but have a look at iWebKit. Maybe that provides a solution. The demo, at least, contains a "full screen" item.

How do I pan the image inside a UIImageView?

I have a UIImageView that is displaying an image that is wider and taller than the UIImageView is. I would like to pan the image within the view using an animation (so that the pan is nice and smooth).
It seems to me that I should be able to just adjust the bounds.origin of the UIImageView, and the image should move (because the image should paint inside the view with that as its origin, right?) but that doesn't seem to work. The bounds.origin changes, but the image draws in the same location.
What almost works is to change the contentsRect of the view's layer. But this begins as a unit square, even though the viewable area of the image is not the whole image. So I'm not sure how I would detect that the far edge of the image is being pulled into the viewable area (which I need to avoid, since it displays by stretching the edge out to infinity, which looks, well, sub-par).
My view currently has its contentsGravity set to kCAGravityTopLeft via Interface Builder, if that makes a difference (Is it causing the image to move?). No other options seemed to be any better, though.
UPDATE: to be clear, I want to move the image inside the view, while keeping the view in the same spot.
I'd highly recommend enclosing your UIImageView in a UIScrollView. Have the UIImageView display the full image, and set the contentSize on the UIScrollView to be the same as your UIImageView's size. Your window into the image will be the size of the UIScrollView, and by using scrollRectToVisible:animated: you can pan to particular areas on the image in an animated fashion.
If you don't want scroll bars to appear, you can set the showsHorizontalScrollIndicator and showsVerticalScrollIndicatorproperties to NO.
UIScrollView also provides pinch-zooming functionality, which may or may not be useful to you.
Brad Larson pointed me down the right road with his suggestion to put the UIImageView inside a UIScrollView.
In the end I put the UIImageView inside of a UIScrollView, and set the scrollView's contentSize and the imageView's bounds to be the same size as the image in the UIImage:
UIImage* image = imageView.image;
imageView.bounds = CGRectMake(0, 0, image.size.width, image.size.height);
scrollView.contentSize = image.size;
Then, I can animate the scrollView's contentOffset to achieve a nice panning effect:
[UIView beginAnimations:#"pan" context:nil];
[UIView setAnimationDuration:animationDuration];
scrollView.contentOffset = newRect.origin;
[UIView commitAnimations];
In my particular case, I'm panning to a random space in the image. In order to find a proper rect to pan to and a proper duration to get a nice constant speed, I use the following:
UIImage* image = imageView.image;
float xNewOrigin = [TCBRandom randomIntLessThan:image.size.width - scrollView.bounds.size.width];
float yNewOrigin = [TCBRandom randomIntLessThan:image.size.height - scrollView.bounds.size.height];
CGRect oldRect = scrollView.bounds;
CGRect newRect = CGRectMake(
xNewOrigin,
yNewOrigin,
scrollView.bounds.size.width,
scrollView.bounds.size.height);
float xDistance = fabs(xNewOrigin - oldRect.origin.x);
float yDistance = fabs(yNewOrigin - oldRect.origin.y);
float hDistance = sqrtf(powf(xDistance, 2) + powf(yDistance, 2));
float hDistanceInPixels = hDistance;
float animationDuration = hDistanceInPixels / speedInPixelsPerSecond;
I'm using a speedInPixelsPerSecond of 10.0f, but other applications might want to use a different value.