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;
}
}
}
Related
In my app i am creating view controller with mixed of UILabel and UITextview which want to be scrollable because the text are dynamic and also exceeds vertical screen size.
I am currently having Scrollview which has subviews as below. Views are created in Xcode 4.3 Storyboard.
UILabel1(Say Heading)
UITextView1(Dynamic text which can be any size)
UILabel2(Second Heading)
UITextView2(Dynamic text which can be any size)
and so on.
The problem is
When the UITextView1 has more content then it overlaps with UILabel2 which i don't want.
I would like to have UILabel1 on top of scrollView and UITextView1 below the UILabel1. UILabel2 below UITextView1 and so on.
What i have to do to achieve this?
EDIT
In Storyboard
![enter image description here][1]
In Simulator
![enter image description here][2]
Thanks for your help guys. Much appreciated.
Code
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
[scrollView setScrollEnabled:YES];
[self.view addSubview:cockTailNameLabel];
[self.view insertSubview:txtIngredients belowSubview:cockTailNameLabel];
[self.view insertSubview:scrollView belowSubview:cockTailNameLabel];
//[scrollView]
[self.cockTailNameLabel setText:self.passcockTailName];
[_txtUse setText:self.passUse];
[_txtUse setEditable:NO];
[_txtUse setUserInteractionEnabled:NO];
CGRect useFrame = _txtUse.frame;
useFrame.size.height = _txtUse.contentSize.height;
_txtUse.frame = useFrame;
[txtIngredients setText:self.passIngredients];
[txtIngredients setEditable:NO];
[txtIngredients setUserInteractionEnabled:NO];
CGRect ingredientFrame = txtIngredients.frame;
ingredientFrame.size.height = txtIngredients.contentSize.height;
txtIngredients.frame = ingredientFrame;
[txtRecipe setText:self.passReceipe];
[txtRecipe setEditable:NO];
[txtRecipe setUserInteractionEnabled:NO];
CGRect recipeFrame = txtIngredients.frame;
recipeFrame.size.height = txtRecipe.contentSize.height;
txtRecipe.frame = recipeFrame;
[scrollView insertSubview:_txtUse belowSubview:cockTailNameLabel];
[scrollView insertSubview:titleIngredients belowSubview:_txtUse];
[scrollView insertSubview:txtIngredients belowSubview:titleIngredients];
[scrollView insertSubview:btnReceipe belowSubview:txtIngredients];
[scrollView insertSubview:btnHome belowSubview:txtIngredients];
[scrollView insertSubview:txtRecipe belowSubview:btnHome];
[scrollView insertSubview:btnfacebookShare belowSubview:txtRecipe];
[scrollView insertSubview:btnTwitterShare belowSubview:txtRecipe];
/*[scrollView addSubview:_txtUse];
[scrollView addSubview:titleIngredients];
[scrollView addSubview:txtIngredients];
[scrollView addSubview:btnReceipe];
[scrollView addSubview:btnHome];
[scrollView addSubview:txtRecipe];
[scrollView addSubview:btnfacebookShare];
[scrollView addSubview:btnTwitterShare];*/
[scrollView setContentSize:CGSizeMake(320, 1000)];
NSLog(#"RecipeName :%# ",passcockTailName);
}
In Storyboard or IB you can rearrange them freely.
In code you do - (void)insertSubview:(UIView *)view belowSubview:(UIView *)siblingSubview.
In code (in viewDidLoad):
UIScrollView *scroll =[[UIScrollView alloc] initWithFrame:CGrectMake(0,0 320, 480)];
[self.view addSubview:scroll];
// code to init your UI object
UILabel *uilabel1 = [[UIlabel alloc] initWithFrame:CGrectMake(10,10, 100, 40)]; // example
uilabel1.font = [UIFont systemFontOfSize:10];
uilabel1.text = #"UILabel1";
uilabel1.backgroundColor = [UIColor clearColor];
//
//
UILabel *uilabel2 = [[UIlabel alloc] initWithFrame:CGrectMake(10, 10 + 10 + uilabel1.frame.origin.y, 100, 40)]; // example
uilabel2.font = [UIFont systemFontOfSize:10];
uilabel2.text = #"UILabel2";
uilabel2.backgroundColor = [UIColor clearColor];
//
//
[scroll addSubview:uilabel1];
[uilabel1 releale];
[scroll addSubview:uilabel2];
[uilabel2 releale];
//
//
// in end
float offset = 10.0 * 2; // offset between uiobjects, N - number of objects
scroll.contentSize = CGSizeMake (0, 0, uilabel1.frame.size.height + uilabel2.frame.size.height + offset, 320);
Note that you may set frame (and other properties) of yours uiobjects and adds it in order to descend.
You can tell next view to start from the end of the first view like so:
startYOfNextView = firstView.frame.origin.y position + firstView.frame.size.height;
Do the same for the rest of the other view. If your view has variable text length, you may need to precaculate the height of the string based on a specific font e.g.:
CGSize maxSize = CGSizeMake(widthOfView, 9999999);
CGSize expectedSize = CGSizeMake(stringVar sizeWithFont:[UIFont fontWithName:#"Arial"] withMaxSize:maxSize lineBreakMode:UILineBreakModeWordWrap);
then tell your dynamic view to use the height of expectedSize variable like so:
myDynamicView = [[UILabel alloc] initWithFrame:CGRectMake(..., expectedSize.height)];
Your issue is labels are coming on top of the textview (Overlapped), right?
In This case, you are modifying the height of the textview dynamically. If its just for display purpose you can set the text to a UILabel with numberOfLines = 0 instead of UITextview; As its added to scrollview adding a label will be fine.
Also you need to modify the origin.y of the remaining views so that it will be properly aligned. ie, secondView.origin.y = firstView.origin.y + firstView.size.height + offset. Likewise modify the origin of the remaining views wrt the just above view. So that its origin will lie outside the previous view frame.
In my case I had to project it over the parent view, so remember there is also something,
- (void)insertSubview:(UIView *)view aboveSubview:(UIView *)siblingSubview
I think adding a transparent UI button is the fastest solution. You could add it once and then show / hide whenever you need to disable all views below the current one (assuming the "current" view, say a popup, was added last to your superview).
Declare an instance variable UIButton* _parentDisablingOverlay; in your popup view, then add this in its init or layoutSubviews method:
_parentDisablingOverlay = [[UIButton alloc] initWithFrame:CGRectMake(0,0, self.superview.frame.size.width, self.superview.frame.size.height)];
[self.superview insertSubview:_parentDisablingOverlay belowSubview:self];
_parentDisablingOverlay.hidden = true;
Later on, when you want to show the popup and disable everything below:
- (void) open {
self.hidden = false;
_parentDisablingOverlay.hidden = false;
}
Similarly to close and re-enable everything:
- (void) close {
self.hidden = true;
_parentDisablingOverlay.hidden = true;
}
My UIScrollView is populated with a large content and then zoomed with "setZoomScale:animated", such that it fits into the scrollview frame. The view appears properly zoomed out, and properly positioned in the scrollview. However, the user is able to scroll outside the content (the scrollview background color shows). It seems that he can scroll as far as the original content size (as if the content was not zoomed out).
Strangely enough, after the user zooms manually the first time, everything works fine, and the scrollview is constrained to the content size again.
I have searched the web, and gone through the Apple docs and examples, but could not find anyone having the same problem. The Apple examples seem to show the same thing as I do.
My content is a custom view that is derived from UIView, and whose CALayer is replaced by a CATiledLayer (as in the Apple examples). The drawing I do myself in drawLayer:inContext:.
Here is a code snippet from MyScrollViewController:
- (void)loadView {
CGRect frame = [UIScreen mainScreen].applicationFrame;
UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:frame];
scrollView.maximumZoomScale=1.0;
scrollView.delegate = self;
... setup theContentView ...
scrollView.contentSize = theContentView.bounds.size;
scrollView.contentInset = UIEdgeInsetsMake(20, 20, 20, 20);
[scrollView addSubview:theContentView];
self.view = scrollView;
double zoomScale = 0.5; // hardcoded for testing
[scrollView setZoomScale:zoomScale animated:NO];
NSLog(#"zoomscale=%g contentview=%# contentsize=%#", zoomScale, NSStringFromCGSize(theContentView.bounds.size), NSStringFromCGSize(scrollView.contentSize));
[scrollView release];
}
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
return theContentView;
}
The NSLog statement shows a correctly set contentSize and zoomscale...
Hoping there is something obvious that I am missing...
I ended up setting the zoomScale on the next runloop (instead of directly from loadView), which worked:
- (void) loadView {
CGRect frame = [UIScreen mainScreen].applicationFrame;
UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:frame];
scrollView.maximumZoomScale=1.0;
scrollView.delegate = self;
... setup theContentView ...
scrollView.contentSize = theContentView.bounds.size;
scrollView.contentInset = UIEdgeInsetsMake(20, 20, 20, 20);
[scrollView addSubview:theContentView];
self.view = scrollView;
// set the zoom level on the next runloop invocation
[self performSelector:#selector(initialZoom) withObject:nil afterDelay:0];
[scrollView release];
}
- (void) initialZoom {
double zoomScale = 0.5; // hardcoded for testing
UIScrollView *scrollView = (UIScrollView *) self.view;
[scrollView setZoomScale:zoomScale animated:NO];
}
Another possibility, suggested by Maverick1st, is to set the zoomscale from viewDidAppear, rather than LoadView. That approach has the added advantage that it shows an animation of the zooming, indicating to the user that the view starts in a zoomed state.
- (void) viewDidAppear {
double zoomScale = 0.5; // hardcoded for testing
UIScrollView *scrollView = (UIScrollView *) self.view;
[scrollView setZoomScale:zoomScale animated:YES];
}
I'm new at this but you could try clipping subviews. I created a scroll view with interface builder, and there in the attributes inspector was a checkbox clip subviews, so i think you should do something like that, only programmatically. Hope that helps.
I'm a newbe on iOS development, I'm trying to make a scroll header like Engadget's app for iPhone, i created a custom UIView to act as a subview for UIScrollView, the algorithm for placing the subviews seems to be ok as I can Scroll the subviews, programatically i change the background color of the views, but the problem is that I can't see anything of the content, just gray backgrounds what am I doing wrong?
Thanks in advance.
This is my code:
Promoted is a UIView with two labels and a UIImageView.
- (void)layoutScrollSubViews
{
promoted *view = nil;
NSArray *subviews = [sView1 subviews];
// reposition all image subviews in a horizontal serial fashion
CGFloat curXLoc = 0;
for (view in subviews)
{
if ([view isKindOfClass:[promoted class]] && view.tag > 0)
{
CGRect frame = view.frame;
frame.origin = CGPointMake(curXLoc, 0);
frame.size.height = kScrollObjHeight;
frame.size.width = kScrollObjWidth;
view.frame = frame;
curXLoc += (kScrollObjWidth);
}
}
// set the content size so it can be scrollable
[sView1 setContentSize:CGSizeMake((kNumImages * kScrollObjWidth), [sView1 bounds].size.height)];
}
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad{
NSUInteger i;
for (i = 1; i <= kNumImages; i++)
{
promoted *p = [[promoted alloc] initWithFrame:CGRectMake(0, 0, 320, 187)];
p.title.text = #"Test 1";
p.num.text = #"1/1";
p.num.backgroundColor = [UIColor whiteColor];
// setup each frame to a default height and width, it will be properly placed when we call "updateScrollList"
p.backgroundColor = [UIColor colorWithRed:(.9/i) green:(.9/i) blue:(.9/i) alpha:1];
p.tag = i; // tag our images for later use when we place them in serial fashion
[sView1 addSubview:p];
[p release];
}
[self layoutScrollSubViews];
[super viewDidLoad];
}
You can do two things:
1. Add your image view and labels directly to scrollView
2. also alloc your label and ImageView before adding them to your UIView(and don't forgot to release them when requirement is finish, in respective scope)
As per my experience, UIScrollView is a weird buggy control released by apple. Don't know why it behaves abnormal when added through nib file. I always try to create UIScrollView programmatically and add subviews to it by allocating them programmatically as well.
May be it would sound strange, but it works for me most of the time!
I have a scrollview that automatically generates a series of subviews containing imageviews. The concept is that it's pretty much a custom PDF style reader, where each page is loaded in to an imageview as an image. There are three "layers" - the outer UIScrollView, the automatically generated subViews and the imageview inside the subview.
I've been having some trouble with this, I've asked the question previously, but unfortunately, the core of my question was in the wrong place. Here is my second attempt:
On rotate, everything is rotated as needed. Unfortunately, this is what I'm getting:
Obviously, I would like Image 1 to be centred and for there to be no hint of image 2 until you flick the view across.
Here is my code for setting up the view:
- (void)loadView {
[self setUpView];
}
- (void)setUpView {
//INITIALISE PAGING SCROLL VIEW
CGRect pagingScrollViewFrame = [[UIScreen mainScreen] bounds];
pagingScrollViewFrame.origin.x -= 10;
pagingScrollViewFrame.size.width += 20;
pagingScrollView = [[UIScrollView alloc] initWithFrame:pagingScrollViewFrame];
pagingScrollView.contentMode = UIViewContentModeScaleAspectFit;
//CONFIGURE PAGING SCROLL VIEW
pagingScrollView.pagingEnabled = YES;
pagingScrollView.backgroundColor = [UIColor blackColor];
pagingScrollView.contentSize = CGSizeMake(pagingScrollViewFrame.size.width*7, pagingScrollViewFrame.size.height);
//ACTIVATE PAGING SCROLL VIEW
self.view = pagingScrollView;
//ADD PAGES TO SCROLL VIEW
for (int i = 0; i < 7; i++){
ImageScrollView *page = [[[ImageScrollView alloc] init] autorelease];
[self configurePage:page forIndex:i];
[pagingScrollView addSubview:page];
}
}
How do I re-define the size of the frame? What function should I call etc. How do I centre the image?
I'm new to this so I apologise for the ambiguity of my question.
Cheers
I've figured it out. It was the Frame of the individual pages that I needed to change, so (with the help gained from another question) I wrote a re-orient method.
The first step to this came because I figured out that I needed to iterate through and re-size each frame for each of my pages and then re-apply the frames to the view. For this to happen I needed to create an array which I would fill in the For Loop above. I have 7 total pages.
Then I could (on rotate) call the below re-orient method to re-size each view:
- (void) reOrient{
if(UIInterfaceOrientationIsPortrait(self.interfaceOrientation)){
CGRect f;
int i = 0;
for (ImageScrollView *page in pageArray) {
f = page.frame;
f.size.width = 320;
f.origin.x = (f.size.width * i);
page.frame = f;
pagingScrollView.contentSize = CGSizeMake(f.size.width * 7, 480);
i++;
}
}else{
CGRect f;
int i = 0;
for (ImageScrollView *page in pageArray) {
f = page.frame;
f.size.width = 480;
f.origin.x = (f.size.width * i);
page.frame = f;
pagingScrollView.contentSize = CGSizeMake(f.size.width * 7, 480);
i++;
}
}
}
This worked for me:
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
[super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
//get current page based on offset before rotation
NSInteger currentPage = self.scrollView.contentOffset.x / self.scrollView.bounds.size.width;
//set anticipated scrollview content size by switching width and height (they will be switched after rotation)
[self.scrollView setContentSize:CGSizeMake(totalPages * self.view.bounds.size.height, self.view.bounds.size.width)];
//set the anticipated content offset based on height before rotation
[self.scrollView setContentOffset:CGPointMake(currentPage * self.scrollView.bounds.size.height, 0.0f)];
}
Basically I am setting the content offset for the new scrollview content size before rotating (I'm switching height and width for the anticipated height and width).
This worked for me when rotating a full-screen paged UIScrollView.
All of the paged subviews have the following autoresizing mask so that they neatly fall into place after rotation:
UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleRightMargin
I want to add some text which is a long text lets say a story and I want it to be presented in a moving up fashion. That is it will be displayed in a box and the text will be moved line by line to show the whole story. So one line gets out of the box from top and other comes from the bottom. Like a HTML marquee behavior.
You should look into whether or not UIScrollView's contentOffset property is animatable using UIView animation blocks.
If it's not, then you could try using an NSTimer and in its function, either set the contentOffset or call [scrollView scrollRectToVisible:desiredContentFrame animated:YES]; where desiredContentFrame.origin.y is just a little bit lower than what you previously had it at (depending on the scroll speed you want). Then play around with the firing frequency of your timer and the desiredContentFrame.origin.y's delta to see what looks best and has the best performance.
Here's a quick hack showing both ways:
- (void)startAnimTimer
{
currentRect = CGRectMake(0, 0, 1, 1);
[NSTimer scheduledTimerWithTimeInterval:0.05 target:self selector:#selector(doScroll) userInfo:nil repeats:YES];
}
- (void)startAnimUIView
{
[UIView beginAnimations:#"scrollUIViewAnim" context:nil];
[UIView setAnimationDuration:20.0];
[UIView setAnimationCurve:UIViewAnimationCurveLinear];
CGPoint newContentOffset = CGPointMake(0, scrollView.contentSize.height);
scrollView.contentOffset = newContentOffset;
[UIView commitAnimations];
}
- (void)loadView {
[super loadView];
self.scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(5, 5, 200, 200)];
scrollView.contentSize = CGSizeMake(200, 1000);
scrollView.backgroundColor = [UIColor magentaColor];
CGFloat y = 10;
CGFloat yDelta = 21;
for(int i = 0; i < 47; i++)
{
// Add some UILabels to the scrollview just so we can see it animating
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(10, y, 180, 20)];
label.text = [NSString stringWithFormat:#"Cheezburger %d", i];
[scrollView addSubview:label];
[label release];
y += yDelta;
}
[self.view addSubview:scrollView];
// Comment out one of these
//[self startAnimTimer];
[self startAnimUIView];
}
- (void)doScroll
{
CGFloat scrollDelta = 1.0f;
CGRect newRect = CGRectMake(0, currentRect.origin.y + scrollDelta, currentRect.size.width, currentRect.size.height);
// Scroll the scrollview to the next position
[scrollView scrollRectToVisible:newRect animated:YES];
currentRect = newRect;
}
This method may help (or not):
- (void)scrollRectToVisible:(CGRect)rect animated:(BOOL)animated
If not, just make a big UILabel and animate its frame using a CABasicAnimation.
I think you want to look at the Scroll View (UIScrollView) in the iPhone SDK.
http://developer.apple.com/IPhone/library/documentation/UIKit/Reference/UIScrollView_Class/Reference/UIScrollView.html
Depending on your implementation, you may not be able to use it "out of the box" but it should provide the functionality you seem to be describing. I'm not sure it will do the auto-scroll thing but that should be pretty easy to implement... maybe.... But, I'm still confident it could be done.