How to layout a view whose frame depends of the orientation - iphone

Consider a view that has to aligned to the bottom of the screen and whose width must fill the screen.
Additionally, the height of the view depends of the orientation. In portrait orientation the height of the view must be 200px and in landscape the height must be 100px.
What is the best way to do this without assuming anything about the parent view size (ie: don't know if it has status bar or not, maybe there's a tab bar, etc.)?
This is my current code, that doesn't work:
- (void)viewDidLoad
{
[super viewDidLoad];
myView = [[UIView alloc] init];
CGSize myViewSize = CGSizeMake(self.view.frame.size.width, 200);
// Align bottom
float myViewY = self.view.frame.origin.y + self.view.frame.size.height - myViewSize.height;
myView.frame = CGRectMake(0, myViewY, myViewSize.width, myViewSize.height);
myView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin;
myView.backgroundColor = [UIColor redColor];
[self.view addSubview:myView];
}
- (void) willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
[super willAnimateRotationToInterfaceOrientation:toInterfaceOrientation duration:duration];
BOOL landscape = UIInterfaceOrientationIsLandscape(toInterfaceOrientation);
myView.frame = CGRectMake(myView.frame.origin.x, myView.frame.origin.y, myView.frame.size.width, landscape ? 100 : 200);
}

Try using UIWindow object as given below:
[[UIApplication sharedApplication] keyWindow].screen.applicationFrame.size.height
Apple documentation for applicationFrame reads as below:
This property contains the screen bounds minus the area occupied by the status bar, if it is visible. Using this property is the recommended way to retrieve your application’s initial window size. The rectangle is specified in points

Related

Overlapping UITextView border

I need to support Landscape and Portrait orientation for my app, and I venture into using a single UIView.
When I first simulate the application, it displays the result without a problem. However, when I change orientation to landscape, the problem occurs.
The first time I run the application in portrait:
When I change the orientation to landscape:
Notice the lower left corner, near the Information tab.
When I change back the orientation to portrait:
It gets worse.
The code I am using,
- (void)updateLayoutForNewOrientation:(UIInterfaceOrientation)toInterfaceOrientation
{
CGFloat height = CGRectGetHeight([[UIScreen mainScreen] bounds]);
CGFloat width = CGRectGetWidth([[UIScreen mainScreen] bounds]);
if (UIInterfaceOrientationIsPortrait(toInterfaceOrientation)) {
NSLog(#"Portrait");
[self iPhoneUserInterfacePortrait:width height:height];
}
else if (UIInterfaceOrientationIsLandscape(toInterfaceOrientation)) {
NSLog(#"Landscape");
[self iPhoneUserInterfaceLandscape:width height:height];
}
}
- (void)iPhoneUserInterfacePortrait:(CGFloat)width height:(CGFloat)height
{
UITextView *descriptionTextView = [[UITextView alloc] initWithFrame:CGRectMake(0.0, 0.0, width - 100.0, height - 300.0)];
[self makeBorder:descriptionTextView];
[self setInformation:descriptionTextView];
[self.view addSubview:descriptionTextView];
}
- (void)iPhoneUserInterfaceLandscape:(CGFloat)width height:(CGFloat)height
{
UITextView *descriptionTextView = [[UITextView alloc] initWithFrame:CGRectMake(0.0, 0.0, width + 230, height - 368.0)];
[self makeBorder:descriptionTextView];
[self setInformation:descriptionTextView];
[self.view addSubview:descriptionTextView];
}
- (void)makeBorder:(UITextView *)descriptionTextView
{
descriptionTextView.layer.borderWidth = 3.0;
descriptionTextView.layer.cornerRadius = 15.0;
descriptionTextView.layer.borderColor = [[UIColor grayColor] CGColor];
[self.view addSubview:descriptionTextView];
}
You're adding several views while you want just one.
[self.view addSubview:descriptionTextView];
To get rid of this line, you could add field descriptionTextView to your ViewController subclass and change frame of that text view without adding/removing it from self.view.
Also you should try to play with AutoResizing masks to see if you can get needed results without actually changing frame manually.
And you should be careful with that constants: try your app on both 3.5 and 4.0 inch simulator devices.

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;
}
}
}

UIViewController not filling screen

I have a UINavigationController that pushes on another UIViewController. In this UIViewController I am going to show a UITableView when in portrait mode and another view in landscape mode.
Therefore in my viewDidLoad I am creating UIView and then adding 2 ViewControllers to this. My problem is that when it loads up I get the following white margin at the top.
I think this is because of (in my Step 3 below ) the
CGRect appFrame = [[UIScreen mainScreen] applicationFrame];
[[self view] setFrame:appFrame];
is not returning the full screen, minus the navigation bar. Is this right? If so how can I make it return the full size so there is no white margin?
- (void)viewDidLoad
{
[super viewDidLoad];
// Step 1 - Set up the tableDataView for vertical layout
TableDataViewController *tableController = [[TableDataViewController alloc] init];
self.tableDataViewController = tableController;
[tableController release];
// Step 2 - Set up the graphView for horizontal layout
GraphViewController *graphController = [[GraphViewController alloc]
initWithNibName:#"GraphViewController" bundle:nil];
self.graphViewController = graphController;
[graphController release];
// Step 3 - Get the screen size
CGRect appFrame = [[UIScreen mainScreen] applicationFrame];
[[self view] setFrame:appFrame];
// Step 4 - Add the vertical view to the containerView
// and then add the containerView to this controller
containerView = [[[UIView alloc] initWithFrame:appFrame] autorelease];
[[self view] addSubview:containerView];
[containerView addSubview:[tableDataViewController view]];
// Step 5 - Add to the active view so we can test for it later
activeView = [tableDataViewController view];
}
Many thanks
Mike
I think you have an issue with your frame offsets. With the navigation bar enabled the rect you get in appFrame has a y offset of 44.f (the navigation bar's height) - check with NSLog and see if that's true.
Because you are setting the frame of a view that will be placed at the origin it should have x and y origins set to zero. You can do this in a safer manner by checking
CGFloat navHeight = navController.navigationBarHidden ? 0 :
navController.navigationBar.frame.size.height;
In fact I think using the bounds property of [UIScreen mainScreen] may be a better overall solution. It will come with the origin and size set correctly and you shouldn't need to check the presence of the navigation bar.
Check what's going on:
CGRect screenBounds = [[UIScreen mainScreen] bounds]
NSLog(#"%#", NSStringFromCGRect(screenBounds));
CGRect screenFrame = [[UIScreen mainScreen] applicationFrame]
NSLog(#"%#", NSStringFromCGRect(screenFrame));
There are a couple things going on. Above all, frame describes a view's location in its superview's coordinate system.
containerView seems to be added as a subview of a custom view, and its coordinate system origins below the navigation bar, not the application frame.
You want to fill the custom view, so the frame of containerView should be something like
CGRectMake(0.0, 0.0, self.view.bounds.width, self.view.bounds.height);

How to make UIScrollView respect the layout of the containing UIView?

I'm using a UIView to control the layout of my view (along with a view controller). I want UIScrollView to only use half of the vertical screen. That works fine if I use the upper half of the screen, but not the bottom half.
Here's the relevant code from the UIViewController:
- (void)loadView {
CGRect fullFrame = [[UIScreen mainScreen] applicationFrame];
//trying to put the scroll view on the bottom half of the screen, but does not work.
CGRect halfFrame = CGRectMake(0, fullFrame.size.height / 2 ,
fullFrame.size.width, fullFrame.size.height / 2);
//use this instead for the scroll view to go to the top half of the screen (and work properly)
//CGRect halfFrame = CGRectMake(0, 0 , fullFrame.size.width, fullFrame.size.height / 2);
UIScrollView* sv = [[UIScrollView alloc] initWithFrame:halfFrame];
[sv setContentSize:CGSizeMake(3 * halfFrame.size.width, halfFrame.size.height)];
CGRect stencilFrame = halfFrame;
UIView *leftView = [[UIView alloc] initWithFrame:stencilFrame];
stencilFrame.origin.x += stencilFrame.size.width;
UIView *centerView = [[UIView alloc] initWithFrame:stencilFrame];
stencilFrame.origin.x += stencilFrame.size.width;
UIView *rightView = [[UIView alloc] initWithFrame:stencilFrame];
//mix up the colors
[leftView setBackgroundColor:[UIColor redColor]];
[centerView setBackgroundColor:[UIColor greenColor]];
[rightView setBackgroundColor:[UIColor blueColor]];
//add them to the scroll view
[sv addSubview:leftView];
[sv addSubview:centerView];
[sv addSubview:rightView];
//turn on paging
[sv setPagingEnabled:YES];
UIView *containerView = [[UIView alloc]initWithFrame:fullFrame];
[containerView addSubview:sv];
[self setView:containerView];
}
Thank you in advance for any advice or help.
I figured it out. The crux of the problem is that views within the scroll view are initialized with the same frame as the scroll view itself. When the scrollView is initialized with halfFrame, the origin is (0, half the full screen size), which is ok since that is relative to the application window itself. However, the views that are put inside the scrollView (like leftView) are initialized to halfFrame, but in this case the origin is relative to the scrollView, effectively placing them off the screen. Setting the origin to (0,0) fixes this:
CGRect stencilFrame = CGRectMake(0, 0, fullFrame.size.width , fullFrame.size.height / 2);
contentSize must contain the rectangle of the view inside the scroll view. That is, the total size of all scrollable controls within. The frame of the UIScrollView decides how much scrolling is needed to let the user browse everything.
You don't have the "full frame" available if you have a nav bar or a tab bar. In general, code that uses [UIScreen mainScreen] for layout information is probably wrong.
Additionally, the status bar can change size if (for example) a call is in progress or tethering is enabled.
Instead, use any sane value for full frame and enable autoresizing:
CGRect fullFrame = {{0,0}, {320,480}};
...
sv.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleTopMargin;
EDIT: You also probably need to subclass UIScrollView and implement -setFrame: so that it also sets the content size and -layoutSubviews to do the correct layout.

Landscape Screen shifted to right in 20px

I am trying to rotate the screen on the iphone. For example,
First state of screen : is "Portrait".
Then it calls "Landscape" screen. Following code is used for making Landscape screen :
- (void)viewDidLoad {
[super viewDidLoad];
[UIApplication sharedApplication].statusBarOrientation = UIInterfaceOrientationLandscapeRight;
CGFloat angle = 90 * M_PI / 180;
self.view.transform = CGAffineTransformMakeRotation(angle);
self.view.center = [nRangeAppDelegate sharedAppDelegate].window.center;
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, 20, 480, 44)];
label.textAlignment = UITextAlignmentLeft;
label.numberOfLines = 2;
label.text = #"12345\n67890";
[self.view addSubview:label];
}
But above code shifted to right in 20 px.
How to make 480x320 view at point 0,0 no shifting ?
Thanks in advance.
Update:
My application navigation based application. All UIViewControllers have one UIView. When call the the view I am trying rotate, this code is used : MyRoratedController *myCtrlr = [[MyRoratedController alloc] initWithNibName:#"MyRoratedController" bundle:nil]; [navigationController pushViewController: myCtrlr animated:NO]; I've changed CGPointMake(160.0, 240.0) by many different values. But no changes.
Your window is shifted because the appDelegate window is not centred on the screen as it is drawn below the status bar
Try this:
self.view.center = CGPointMake(160.0, 240.0);
You must make sure you are adding this view to a normal viewcontroller (ie not a nav controller or similar). The easiest way to do this is to add it to the main application window, like so:
MyRoratedController *myCtrlr = [[MyRoratedController alloc]
initWithNibName:#"MyRoratedController" bundle:nil];
[window addSubview:[myCtrlr view]];
Note that if your app has a status bar shown you will have to change the 160 above to 150