I have a UIView -- call it HomeView that contains a UITableView and a UISearchBar. That is, they are subviews. These are added in viewDidLoad for HomeView. A picture is worth a thousand words:
As can be seen from these screenshots, the first item in the UITableView is hidden once the UISearchBar is displayed. Here is the relevant code:
- (void) viewDidLoad
self.table_view = [UITableView.alloc.initWithFrame: self.view.bounds style:UITableViewStylePlain];
self.table_view.bounds.origin.y += 44;
self.table_view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
// ^^^ This is the code I believe is one source of my problem
// Triggered when search button in nav bar is pressed
// code to calculate delta here
// Here's how the view is animated. The search bar frame
// is dropped down (works), and ideally, the tableview
// drops down exactly the same amount at the same time.
// If the action is to hide, then the inverse happens
// because +delta+ will have been calculated as a
// negative number above.
[UIView animateWithDuration: 0.7
delay: 0.0
options: UIViewAnimationOptionCurveEaseIn
animations:^ {
self.searchBar.frame = CGRectOffset(#searchBar.frame, 0.0, delta);
}
completion:^(BOOL finished){
self.searchBarVisible = !self.searchBarVisible
self.searchBar.hidden = !self.searchBarVisible
edgeInsets = UIEdgeInsetsMake(0.0f, self.searchBarVisible ? delta : 0.0f, 0.0f, 0.0f)
[tableView setContentInset: edgeInsets]
}
];
What actually happens is that the search bar slides down as expected in response to the search button press and then slides up on next button press. However, the table view never adjusts.
The problem, as near as I can tell, is that in viewDidLoad, the enclosing HomeView has not been rendered, so the size is as-yet unknown. I can get the screen dimensions and use that, but I thought the auto resizing mask was better practice, right?
Are there obvious flaws in this, and is there a way to get the table view to animate down at the same time the search bar is?
Thanks
All right, let's try this again!
In your animation block, change the line where you make edgeInsets to:
edgeInsets = UIEdgeInsetsMake(self.searchBarVisible ? delta : 0.0f, 0.0f, 0.0f, 0.0f);
From what I can tell, the first argument is the top, and the one you had set was the left inset.
Related
I've got a VC with a table view. When an event occurs, I want to drop in a UIView from the top of the main view. When the user scrolls the table view, I want to re-layout the view so that the dropped in view "scrolls away". I figured I'd do this by moving the upperView's frame and resizing the table view (both in relation to the scroll offset). I've got it almost working as follows:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
CGFloat contentOffsetY = scrollView.contentOffset.y;
if (contentOffsetY > 0) {
CGFloat upperHeight = self.upperView.frame.size.height;
CGFloat fullTableHeight = self.view.frame.size.height;
CGFloat offsetY = (contentOffsetY < upperHeight)? -scrollView.contentOffset.y : -upperHeight;
self.upperView.frame = CGRectMake(0, offsetY, 320, upperHeight);
scrollView.frame = CGRectMake(0, upperHeight+offsetY, 320, fullTableHeight-(upperHeight+offsetY));
}
NSLog(#"%f", self.upperView.frame.origin.y);
}
The upper view origin starts at 0,0.
The problem is, after a little dragging back and forth, I lose the top few pixels of that upper view. It can't seem to get it's origin y back to zero. The logging reads negative values, and only gets to -1, with the most careful dragging. Has anybody done something like this? Much obliged if you can help.
It sounds like you always scroll the table view to the top when you show the drop-in view. Assuming that's the case, there is a better way to do this.
UITableView inherits the contentInset property from UIScrollView. The contentInset property defines a border on each edge of the scroll view. Each border has its own thickness, which is zero by default. These borders just affect how far the scroll view is willing to let the user scroll the content - they don't hide the content! If you set the top inset larger than zero, and give the scroll view a subview with a negative Y origin, that subview can be visible in the border, and will scroll with the rest of the scroll view's content.
So we'll set the table view's top inset to the height of the drop-in view, and add the drop-in view as a subview of the table view with its origin set to the negative of its height. This will make it fit perfectly on the screen above the first row of the table view, and it will scroll with the table view. When we detect that the drop-in view has been scrolled fully off-screen, we can just remove it from the table view and set the table view's top inset back to zero.
We'll need an instance variable that tracks the current state of the drop-in view:
typedef enum {
DropInViewStateHidden,
DropInViewStateAppearing,
DropInViewStateVisible
} DropInViewState;
#implementation ViewController {
DropInViewState _dropInViewState;
}
In my test project, I just used a button to trigger the drop-in view. Here's the action:
- (IBAction)dropIn {
if (_dropInViewState != DropInViewStateHidden)
return;
CGRect frame = self.dropInView.frame;
frame.origin.y = -frame.size.height;
self.dropInView.frame = frame;
[self.tableView addSubview:self.dropInView];
self.tableView.contentInset = UIEdgeInsetsMake(frame.size.height, 0, 0, 0);
[self.tableView setContentOffset:frame.origin animated:YES];
_dropInViewState = DropInViewStateAppearing;
}
When the table view scrolls, we check the state of the drop-in view. If it is in the “visible” state and has been scrolled off-screen, we hide it. There's a tricky bit because when we make the drop-in view visible, and scroll it onto the screen, we can receive scrollViewDidScroll: messages that would make us think the drop-in view has been hidden. That's why we start out in the DropInViewStateAppearing state, and transition to the DropInViewVisible state when we know the view has appeared.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
switch (_dropInViewState) {
case DropInViewStateHidden:
break;
case DropInViewStateVisible:
if (scrollView.contentOffset.y >= 0) {
// dropInView has been scrolled off-screen
self.tableView.contentInset = UIEdgeInsetsZero;
[self.dropInView removeFromSuperview];
_dropInViewState = DropInViewStateHidden;
break;
}
case DropInViewStateAppearing:
// When I first add dropInView to tableView and tell tableView
// to scroll to reveal dropInView, I may get a bunch of
// scrollViewDidScroll: messages with contentOffset.y >= 0.
// I don't want those messages to hide dropInView, so I sit in
// DropInViewStateAppearing until contentOffset.y goes negative,
// which means at least part of dropInView is actually on-screen.
if (scrollView.contentOffset.y < 0)
_dropInViewState = DropInViewStateVisible;
break;
}
}
Figured this out: The UITableView doesn't thoroughly message didScroll during the bounce. This is why I was missing a few pixels. Resizing during the bounce makes the bounce get mixed up and stop. This fix on my code above allows the bounce to work (by moving, not resizing the table) and makes sure the upper view is correctly placed during the bounce (when contentOffset.y <= 0).
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
CGFloat contentOffsetY = scrollView.contentOffset.y;
CGFloat upperHeight = self.upperView.frame.size.height;
CGFloat fullTableHeight = self.view.frame.size.height;
CGFloat offsetY = (contentOffsetY < upperHeight)? -scrollView.contentOffset.y : -upperHeight;
if (contentOffsetY > 0) {
self.upperView.frame = CGRectMake(0, offsetY, 320, upperHeight);
scrollView.frame = CGRectMake(0, upperHeight+offsetY, 320, fullTableHeight-(upperHeight+offsetY));
} else {
self.upperView.frame = CGRectMake(0, 0, 320, upperHeight);
scrollView.frame = CGRectMake(0.0, upperHeight, 320, scrollView.frame.size.height);
}
[super scrollViewDidScroll:scrollView];
}
I have a tabbarview application that has a button in one of the tabs. When Pressing that button, something will happen, and the user will be switched to another tab.
I made an animation in that button:
UIView * fromView = self.tabBarController.selectedViewController.view;
UIView * toView = [[self.tabBarController.viewControllers objectAtIndex:0] view];
[UIView transitionFromView:fromView
toView:toView
duration:0.6
options:(UIViewAnimationOptionTransitionCurlDown)
completion:^(BOOL finished) {
if (finished) {
self.tabBarController.selectedIndex = 0;
}
}];
Which I got from here. However the problem is that after animating, I seem to have a gap on the top of the screen that is about as high as the status bar. Does anyone know what's causing this? This gap quickly closes when the animation finishes (which is when we do self.tabBarController.selectedIndex = 0
By the way, the problem still persist if I swap the animation to something else or even without animation.
Additional info, here's the frame details:
from frame: x:0.000000, y:0.000000, w:320.000000, h:411.000000
to frame: x:0.000000, y:0.000000, w:320.000000, h:431.000000
The tab bar controller's area also covers the area underneath the status bar. So it's own client view has origin.y of 20.
Thus you need to set the incoming view frame correctly before invoking the transition.
I've found a very hacky way to do it:
CGRect to = fromView.superview.frame;
to.origin.y -= 20;
fromView.superview.frame = to;
Anyone that can explain to me why I had to do this and a more elegant way to do this will get the answer accepted.
Hey I have a view that has a button that when pressed should modally present a UIDatePicker. I have the picker showing up properly, but because it is on its own UIView, it is full height, and looks funny. All I want is to get a really quick date input from the user, more like a UIActionSheet than a UIView.
How can I make the UIPicker slide up modally only halfway and have some actions on a toolbar, like done etc?
Thanks
You could put it on another UIView which has a transparent background, or more simply, don't use presentModalViewController, but write your own routine to show it in the current view.
// Untested code:
// put this in your current UIViewController (the one where you were going to call presentModalViewController:)
- (void)showPicker:(UIPickerView *) picker{
CGRect startFrame = picker.frame;
CGRect endFrame = picker.frame;
// Set the start position to below the bottom of the visible frame:
startFrame.origin.y = self.view.frame.size.height;
// Set the end position to slid up by the height of the view, so it will just fit:
endFrame.origin.y = startFrame.origin.y - endFrame.size.height;
picker.frame = startFrame;
[self.view addSubView:picker];
[UIView beginAnimations]
picker.frame = endFrame;
[UIView commitAnimations];
}
You would of course need to add all the necessary code to keep a pointer to the picker and keep track of when to show and get rid of it.
My app's table view does not occupy the full screen height, as I've allowed 50px at the bottom for a banner.
When I begin typing in the search bar, the search results table view is larger; it fills all available screen space between the search bar and the tab bar. This means that the very last search result is obscured by the banner.
How do I specify the size of the table view used by UISearchDisplayController? There's no bounds or frame property that I can see.
EDIT TO ADD SCREENSHOTS:
This is how the table view is set up in IB. It ends 50px short of the synthesized tab bar.
(source: lightwood.net)
This is how content displays normally. I've scrolled to the very bottom here.
(source: lightwood.net)
This is how it displays when searching. Again, I've scrolled to the very bottom. If I disable the banner ad, I can see that the search display table spreads right down to the tab bar.
(source: lightwood.net)
The key to solving this one was finding out when to change the geometry of the table view. Calling:
[self.searchDisplayController.searchResultsTableView setFrame:someframe];
after creating the UISearchDisplayController was futile. The answer was this delegate method:
-(void)searchDisplayController:(UISearchDisplayController *)controller didShowSearchResultsTableView:(UITableView *)tableView {
tableView.frame = someframe;
}
Note, I had also tried -searchDisplayController:didLoadSearchResultsTableView but it did no good in there. You have to wait until it's displayed to resize it.
Also note that if you simply assign tableView.frame = otherTableView.frame, the search results table overlaps its corresponding search bar, so it is impossible to clear or cancel the search!
My final code looked like this:
-(void)searchDisplayController:(UISearchDisplayController *)controller didShowSearchResultsTableView:(UITableView *)tableView {
CGRect f = self.masterTableView.frame; // The tableView the search replaces
CGRect s = self.searchDisplayController.searchBar.frame;
CGRect newFrame = CGRectMake(f.origin.x,
f.origin.y + s.size.height,
f.size.width,
f.size.height - s.size.height);
tableView.frame = newFrame;
}
I updated the code to allow for deeper view hierarchies, but the initial frame of the semi-transparent cover view still takes up the entire window below the search bar.
-(void)searchDisplayController: (UISearchDisplayController*)controller
didShowSearchResultsTableView: (UITableView*)tableView
{
if ( [controller.searchBar.superview isKindOfClass: [UITableView class]] )
{
UITableView* staticTableView = (UITableView*)controller.searchBar.superview;
CGRect f = [tableView.superview convertRect: staticTableView.frame fromView: staticTableView.superview];
CGRect s = controller.searchBar.frame;
CGRect newFrame = CGRectMake(f.origin.x,
f.origin.y + s.size.height,
f.size.width,
f.size.height - s.size.height);
tableView.frame = newFrame;
}
}
I'm not sure I completely understand what you're describing, it would be nice to have a screenshot.
It sounds like what's happening is the UITableView is the size of the screen and the banner is overlapping the bottom 50 pixels of it. All UIView children have a frame, bounds, and center properties they inherit from their common UIView parent.
#interface UIView(UIViewGeometry)
// animatable. do not use frame if view is transformed since it will not correctly reflect the actual location of the view. use bounds + center instead.
#property(nonatomic) CGRect frame;
// use bounds/center and not frame if non-identity transform. if bounds dimension is odd, center may be have fractional part
#property(nonatomic) CGRect bounds; // default bounds is zero origin, frame size. animatable
#property(nonatomic) CGPoint center; // center is center of frame. animatable
#property(nonatomic) CGAffineTransform transform; // default is CGAffineTransformIdentity. animatable
// ... from UIView.h
You can manipulate these properties as you like, it sounds like you simply need to adjust the bounds to be 50 pixels smaller than the screen, and do some math to calculate your new center.
Edit 2: When I start the app without the status bar on top everything behaves as planned. With the status bar I couldn't get the views to act as I wanted. It looks as if the UINavigationController keeps resizing the content view by subtracting the 20 pixels of the status bar. I don't know.
I created a simple UINavigationController-based application. The root view in this navigation controller is a UITableView. At a certain time I want to slide in a 80 pixel high view from the bottom. The whole view on the top (the one that is controlled by the UINavigationController) should resize and get 80 pixel smaller to make room for the new bottom view.
This is basically the code I use to repositioning the views:
-(void)showTeaser {
float adHeight = 80;
[adView setFrame:CGRectMake(0.0,self.navigationController.view.bounds.size.height, 320.0, 80.0)];
[[[UIApplication sharedApplication] keyWindow] addSubview:adView];
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.5];
[adView setAlpha:1.0];
[adView setFrame:CGRectMake(0.0,self.navigationController.view.bounds.size.height-adHeight, 320.0, 80.0)];
[self.navigationController.view setFrame:CGRectMake(0.0,0.0, 320.0, self.navigationController.view.bounds.size.height-adHeight)];
[self.view setFrame:CGRectMake(0.0, 0, 320.0, self.view.bounds.size.height-adHeight)];
[UIView commitAnimations]; }
I lowered the Navigationbar's alpha, set the UITableviewController's view to red. The new view is purple.
This is what happens. First screenshot initial state. Everything is looking normal. Second screenshot shows state after changing the frames. The view of the UITableviewController is always pushed 20 pixel under the Navigationbar. Also, if I try to add more views to the keywindow, they always end up 20 pixel higher than I expect. It almost looks like the keywindow (minus the navigation bar) is pushed up 20 pixel.
Edit 1: No matter to what size I resize the view, it's always 20 pixel.
Do I make a mistake by adding views to the keywindow at all? Shouldn't I do this?
alt text http://www.hans-schneider.de/iphone-seo/1.png alt text http://www.hans-schneider.de/iphone-seo/2.png
To solve this, I made the view of the UINavigationController a subview of a UIView, and manually set the bounds of the view for the `UINavigationController'.
//outerView is a UIView defined in the interface
outerView = [[UIView alloc] initWithFrame:CGRectMake( 0.0, 0.0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height);];
//mainNavigationController is a UINavigationController defined in the interface
//rootViewController is a UIViewController (or inherited class) defined in the interface and instanced before this code
mainNavigationController = [[UINavigationController alloc] initWithRootViewController:rootViewController];
//set the frame for the view of the navigation controller - 20 is due to the status bar
mainNavigationController.view.frame = CGRectMake( 0.0, 20.0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height - 20);
Then later, when I go to resize, I resize the parent 'UIView' rather than the 'UINavigationController' view.
//change the outer view's frame to resize everything
//adHeight is a float defined earlier
outerView.frame = CGRectMake( 0.0, 20.0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height - 20 - adHeight);
This works well in an animation sequence.
Edit 2011-05-12: I updated the outerView frame to fill the screen. This must be set to allow for touch events.
Have you tried using the transform property of your tableview instead of manually changing it's frame? It may work out better, since the frame depends on the origin, and you only want to change it's size.