How to add a scroll view to an ios app - objective-c

I have a tabbed application that I would like to have a scroll view on. I already have a few text fields and labels on that tab. The problem is, the keyboard hides some of the text fields. How would I add a vertical scroll to prevent that?

You can accomplish what you are looking for without a scrollview.
In you delegate method for you textfield, You can change the frame of the viewController's view.
something like
- (void)textFieldDidBeginEditing:(UITextField *)textField;
{
CGRect newFrame = self.view.frame;
newFrame.origin.y = - 40; // move the view up to the point your textfield is visible
self.view.frame = newFrame;
}
Then in Set it back
- (void)textFieldDidEndEditing:(UITextField *)textField;
{
CGRect newFrame = self.view.frame;
newFrame.origin.y = 0;
self.view.frame = newFrame;
}
Usually I animate this with 0.33 seconds duration.

The solution requires quite a bit of code, but here's the general idea of what you need:
You will need to add the text fields (and everything else for consistency) to a scrollView.
You need to setup the scroll view to have vertical scrolling space only, but set the scrollEnabled to false so that the user can't scroll it manually.
Then you need to listen to UIKeyboardWillShowNotification and UIKeyboardWillHideNotification and manually scroll it up/down as required.

Related

How to programmaticaly add an UIActivityIndicatorView to a view on the bottom right corner

I'm trying to programmatically add and position an UIActivityIndicatorView to the bottom right corner of my main view. My app can rotate.
Right now in my viewDidLoad-method I have this code:
[super viewDidLoad];
UIActivityIndicatorView *iv = [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
[iv startAnimating];
int margin = 14;
iv.frame = CGRectMake(
self.view.frame.size.width - iv.frame.size.width - margin,
self.view.frame.size.height - iv.frame.size.height - margin,
iv.frame.size.width,
iv.frame.size.height );
iv.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin;
[self.view addSubview:iv];
When the app is starting in portrait, the activity-indicator is positioned correctly. As soon as I rotate the device (or start the app in landscape) the positioning of the activityindicator is wrong.
How can this be fixed?
You also need a flexible top margin, so that the view will slip to up or down as the height of the main view changes. Also, the parent view needs  to autoresizesSubviews set to YES (which is default YES, so you are probably ok).
distanceLabel.autoresizingMask = (UIViewAutoresizingFlexibleLeftMargin|UIViewAutoresizingFlexibleTopMargin);
On your view controller, override the method shouldAutorotateToInterfaceOrientation and correct the position when orientation changes.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation;

GMGridView appears offscreen, but overlaps with onscreen view

I am using a custom controller for transitions (could not use navigation controller due to inherent cycles in project, which would allow the navigation controllers stack to grow unbounded [which I assume would cause memory issues]). I am emulating a navigation controllers sliding animation (when transitioning to new screens) using UIView animateWithDuration:animations:completion:
When a button triggering the transition is pressed, the frame of the new view I want to transition to is set to an offscreen position. In the animation for the transition (UIView animateWithDuration:animations:completion:), the view currently on screen has its frame set to an offscreen position, and the new view is set to an onscreen position.
This is inside my custom controller for transitions:
CGFloat windowWidth = self.mainView.frame.size.width;
CGFloat windowHeight = self.mainView.frame.size.height;
CGRect offScreenLeft = CGRectMake(-1*windowWidth, 0.0, windowWidth, windowHeight);
CGRect onScreen = self.mainView.frame;
CGRect offScreenRight = CGRectMake(windowWidth, 0.0, windowWidth, windowHeight);
if (direction == TransitionDirectionForward)
{
if (dragBackgroundOnscreen){
[self.mainView addSubview:self.backgroundView];
[self.mainView sendSubviewToBack:self.backgroundView];
self.backgroundView.frame = offScreenRight;
}
self.currentViewController.view.frame = offScreenRight;
[UIView animateWithDuration:0.65
animations:^{
oldViewController.view.frame = offScreenLeft;
if (dragBackgroundOffscreen)
self.backgroundView.frame = offScreenLeft;
else if (dragBackgroundOnscreen)
self.backgroundView.frame = onScreen;
self.currentViewController.view.frame = onScreen;
}
completion:^(BOOL finished){
[oldViewController.view removeFromSuperview];
if (dragBackgroundOffscreen)
[self.backgroundView removeFromSuperview];
[oldViewController willMoveToParentViewController:nil];
[oldViewController removeFromParentViewController];
[self.currentViewController didMoveToParentViewController:self];
}];
}
else if (direction == TransitionDirectionBackward)
{
if (dragBackgroundOnscreen){
[self.mainView addSubview:self.backgroundView];
[self.mainView sendSubviewToBack:self.backgroundView];
self.backgroundView.frame = offScreenLeft;
}
self.currentViewController.view.frame = offScreenLeft;
[UIView animateWithDuration:0.65
animations:^{
oldViewController.view.frame = offScreenRight;
if (dragBackgroundOffscreen)
self.backgroundView.frame = offScreenRight;
else if (dragBackgroundOnscreen)
self.backgroundView.frame = onScreen;
self.currentViewController.view.frame = onScreen;
}
completion:^(BOOL finished){
[oldViewController.view removeFromSuperview];
if (dragBackgroundOffscreen)
[self.backgroundView removeFromSuperview];
[oldViewController willMoveToParentViewController:nil];
[oldViewController removeFromParentViewController];
[self.currentViewController didMoveToParentViewController:self];
}];
}
I also want the background (self.backgroundView) to remain static unless moving to a screen that has its own background (i.e. I dont want the background to slide if the new views background is the same background).
I am using TransitionDirectionBackward and TransitionDirectionForward just to differentiate between sliding left and sliding right.
Everything works great, except when transitioning involves a GMGridView. the problem when the Gridviews frame is set to an offscreen frame its really setting its currently selected page's frame to that offscreen frame. Other pages of the gridview are not bounded by this frame, so they can appear on screen even before the transition. I tried setting the frame and bounds property on the GridView's viewcontroller's view, but I can still get a page of the gridview appearing onscreen before the transition animation.
Anyone see a solution to this? I was trying to find a way to clip the view of the GridView during the transition so pages dont appear except for the currently selected page, but havent found anything useful.
UPDATE: I found a possible fix by setting alpha = 0.0 for cells that are visible but shouldnt be (later setting alpha = 1.0 when the transition animation is complete). However, I need to know which cells to do this for. I need a way to access the page that the GMGridView is currently on so I can set the adjacent page's cells to have an alpha of 0.0.
UPDATE: Found a way to get it to work by using myGridView convertPoint:(A cgpoint i found by trial and error to be on the first cell of a page.) fromView:myGridView.window. NOTE: I needed an if/else if to check if i was in lanscape left or landscape right since the window coordinates do not rotate when the device is rotated. with this i was able to get the index of the cell at the point on the screen i had specified and then set the previous page to be transparent until after the transition animation.
I would still like to know if there is a way of "clipping" the gridview so that if the cells could be opaque, but just never displayed....?
I think I over complicated the problem. I was looking for the clipsToBounds method of a UIView (although I could have sworn I tried that before). In any case, its working now!

Slide all views up/down as statusBar is hidden/shown

I’m letting the user show/hide the statusBar at will, and I want all the views to slide down/up with it. I assumed setting the autoresizing mask would take care of this. I’ve added the navigation controller programmatically, so I did this:
[self.view setAutoresizingMask:UIViewAutoresizingFlexibleHeight];
[self.navigationController.view setAutoresizingMask:UIViewAutoresizingFlexibleHeight];
[self.navigationController.navigationBar setAutoresizingMask:UIViewAutoresizingFlexibleTopMargin];
This has no effect.
I printed the frame rects of self.view and self.navigationController.view before and after hiding the statusBar, and they remain exactly the same height.
Since autoresizesSubviews defaults to YES, I doubt that is the problem. I must not be setting the autoresizing mask correctly. Does anybody know what I'm doing wrong?
The answer seems to be that the container view's autoresizing mask is simply not coordinated with the status bar. There’s no choice but to write code adjusting the layout.
Since Apple didn’t provide automatic coordination between the statusBar and other elements, you’d think they’d let us do it ourselves. But, no, we are not permitted to set the statusBar frame directly. The only way to animate statusBar repositioning is via UIStatusBarAnimationSlide, which uses its own timeline and will never match other animations.
However, we can control the timing of a statusBar fade and slide the container view along with it. The subviews' autoresize masks will do the rest. This actually looks pretty good, and the coding, although complicated by some weird framing behavior, is not too painful:
UIApplication *appShared = [UIApplication sharedApplication];
CGFloat fStatusBarHeight = appShared.statusBarFrame.size.height; // best to get this once and store it as a property
BOOL bHideStatusBarNow = !appShared.statusBarHidden;
CGFloat fStatusBarHeight = appDelegate.fStatusBarHeight;
if (bHideStatusBarNow)
fStatusBarHeight *= -1;
CGRect rectView = self.view.frame; // must work with frame; changing the bounds won't have any effect
// Determine the container view's new frame.
// (The subviews will autoresize.)
switch (self.interfaceOrientation) {
case UIInterfaceOrientationPortrait:
case UIInterfaceOrientationPortraitUpsideDown:
rectView.origin.y += fStatusBarHeight;
rectView.size.height -= fStatusBarHeight;
break;
case UIInterfaceOrientationLandscapeLeft:
case UIInterfaceOrientationLandscapeRight:
// In landscape, the view's frame will sometimes complement the orientation and sometimes not.
/*
Specifically, if view is loaded in landscape, its frame will reflect that; otherwise, the frame will always be in portrait orientation.
This is an issue only when the navBar is present. Regular view controllers can count on the frame staying in portrait orientation no matter what.
But regular view controllers have another oddity: In the upside-down orientations, you should adjust the height only; the origin takes care of itself.
*/
if (rectView.size.width < rectView.size.height) {
rectView.origin.x += fStatusBarHeight;
rectView.size.width -= fStatusBarHeight;
}
else {
rectView.origin.y += fStatusBarHeight;
rectView.size.height -= fStatusBarHeight;
}
break;
default:
break;
}
// The navBar must also be explicitly moved.
CGRect rectNavBar = [self.navigationController.navigationBar frame];
rectNavBar.origin = CGPointMake(0.0f, rectNavBar.origin.y + fStatusBarHeight);
// Perform the animated toggling and reframing.
[UIView animateWithDuration:kAnimDurationToggleStatusBar animations:^{
[appShared setStatusBarHidden:bHideStatusBarNow]; // you can add withAnimation:UIStatusBarAnimationSlide here and it will work, but the timing won't match
[self.navigationController.navigationBar setFrame:rectNavBar];
[self.view setFrame:rectView];
}];
There’s no need to do anything to the toolbar, which remains glued to the bottom of the screen -- as long as you have not set the window frame to mainScreen.bounds.
One snag is how to get the statusBar height when you want to re-display it, since statusBarFrame returns a 0-area rect if it is currently hidden. It turns out that doing a preliminary show/hide, without animation, just to get the rect, works fine. There’s no visible flash.
Also, if you are using a xib/nib, be sure that its view's statusBar is set to None.
Maybe some day Apple will enhance the statusBar layout behavior. Then all this code, for every view controller, will have to be redone...

Changing TabView: Resize TabView & Window

I made an application which have a preferences window with 2 tabs.
The first tab have a lots of prefs settings in it, but the second one is very small...
I'd like that the tabview & the window resize when we switch between these 2 tabs.
I act like that, but it doesn't seems to work, when I switch view the "Networks settings" tab is being reduced and disapear (like if the height was going from origin to 0 with animation).
Here is my code (.m):
- (void)tabView:(NSTabView *)tabView
didSelectTabViewItem:(NSTabViewItem *)tabViewItem
{
NSRect frame;
int height;
if ([[tabViewItem identifier] isEqualTo:#"Panel settings"]) {
height = 400;
} else if ([[tabViewItem identifier] isEqualTo:#"Network settings"]) {
height = 200;
}
frame = [[tabView window] frame];
frame.size.height = height;
frame.origin.y += height;
[[tabView window] setFrame:frame display:YES animate:YES];
}
Note that I linked the tab view to delegate.
My window is linked to the NSWindow * PrefWindow referencing outlet.
Thanks for your help!
I still think the problem is with the way your springs and struts are set in IB -- make sure you set them for not only for the tab view itself, but also for the individual objects in the view of the tab view item. I noticed that the default settings for my subviews had them all with the top strut set which made any subviews near the bottom of the view disappear when the window shrunk.

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.