In my app, I have a navigation bar, a tab bar, a search bar and I always show the keypad.
However, with the different screen height with the iphone 5, I need to adjust the height of my table.
I can't anchor the table to the top of the keypad as this is shown a run time.
So I'm trying to do this dynamically, it looks like I'm missing the search bar, just too short, hmmm.
Perhaps there's an easier more obvious way?
EDIT: My code so far.
-(void)keyboardWillShow:(NSNotification*)aNotification {
int th = self.view.frame.size.height;
NSDictionary *info = [aNotification userInfo];
CGSize kbSize = [[info objectForKey:UIKeyboardFrameEndUserInfoKey]
CGRectValue].size;
int tableTop;
int tableHeight;
tableTop = table.frame.origin.y;
tableHeight = (th - tableTop) - kbSize.height;
[table setFrame:[General setTableBoundsByHeight:tableHeight:table]];
}
+ (CGRect)setTableBoundsByHeight:(int)lHeight:(UITableView*)tbl {
CGRect tableFrame = tbl.frame;
return CGRectMake(tableFrame.origin.x,
tableFrame.origin.y,
tableFrame.size.width,
lHeight);
}
I am guessing your "tableView" is part of your "self.view". If it is, it is in a different coordinate system.
The keyboard is added to the window, and tableView is on your view hierarchy. You will need to use the functions described here under the heading: Converting Between View Coordinate Systems
EDIT: If you read the documentation on UIKeyboardFrameEndUserInfoKey, you will see it mentions the rectangle is in screen coordinates, while your UITableView is almost certainly in some other views coordinate system. In the same documentation they mentioned the function to convert between the two.
I'm writing an app that uses UITabBar for parts of the navigation. I'm also using UIScrollView for presenting more information than what the screen can typically handle. Because of this, I'm needing to set the scroll view to take into account the height of the UITabBar so that all of the information is displayed.
Is there a way to calculate the height of the UITabBar?
If the view controller has an ancestor that is a tab bar controller, you can retrieve the height from that tab bar.
CGFloat tabBarHeight = self.tabBarController.tabBar.frame.size.height;
It is 320 x 49.
If you want to test, open Interface Builder, add a UITabBar, go into the ruler, you will see it
UITabBar is inherited from UIVIew so you can use the frame.size.height to get the height
In Swift:
let height = self.tabBarController?.tabBar.frame.height ?? 49.0
Relying on the actual height of the tab-bar, and using the magic number as a fallback.
Swift 3+
let tabBarHeight = tabBarController?.tabBar.frame.size.height
print(tabBarHeight ?? "not defined")
It should print 49.0 (Type CGFloat)
I was looking to do something similar with centering a label in the VISIBLE portion of a ViewController's view. This ViewController belonged to a UITabBarController.
Here's the code I used to center my label:
UILabel *roomLabel = [[UILabel alloc] init];
CGRect frame = [[self view] bounds];
float tabBarHeight = [[[super tabBarController] tabBar] frame].size.height;
frame.size.height -= tabBarHeight;
[roomLabel setFrame:frame];
[[self view] addSubview:roomLabel];
[roomLabel release];
Notice that I used [[self view] bounds] not [[self view] frame] because the latter includes the 20 pixel top bar as the Y offset (which throws off the vertical centering).
Hope this helps someone!
By the way: I'm using iOS 4.3 and XCode 4 and the "hard-code" value for the TabBar's height is still 49 for me!
I know this isn't ideal, but I really didn't want to have a magic number constant anywhere. What I did was create a throwaway UITabBarController, and get the height from there.
I did this also because [UITabBar initWithFrame:] works as desired, but doing a [bar setFrame:] doesn't. I needed the frame to be correct at creation.
UITabBarController *dtbc = [[[UITabBarController alloc] init] autorelease];
CGRect tabRect = [[[self navigationController] view] frame];
tabRect.origin.y = tabRect.size.height - [[dtbc tabBar] frame].size.height;
tabRect.size.height = [[dtbc tabBar] frame].size.height;
tabBar_ = [[UITabBar alloc] initWithFrame:tabRect];
What I like about this is that it will correctly place the tab bar at the bottom of the parent regardless of the parents size.
This should work in most cases on any instance of UIViewController:
bottomLayoutGuide.length
In swift 4 and 5. self.tabBarController?.getHeight()
extension UITabBarController{
func getHeight()->CGFloat{
return self.tabBar.frame.size.height
}
func getWidth()->CGFloat{
return self.tabBar.frame.size.width
}
}
Others can also try to get the height using the intrinsicContentSize property of the tab bar.
let tabBarHeight = self.tabBarController.tabBar.intrinsicContentSize.height
This is how I got it to work in swift 4.1
let tabBarHeight = CGFloat((self.tabBarController?.tabBar.frame.size.height)!)
Swift 5
if let tabBarController = tabBarController {
let tabBarSafeAreaHeight = tabBarController.tabBar.frame.size.height -
tabBarController.tabBar.safeAreaInsets.bottom
}
This calculates the height of the UITabBar taking into account the safeAreaInsets (UIEdgeInsets)
At the time of writing this equals 49 on iPhone portrait
let screenHeight = UIApplication.shared.statusBarFrame.height +
self.navigationController!.navigationBar.frame.height + (tabBarController?.tabBar.frame.size.height)!
This works perfectly, based it of a few ppls answer here
SWIFT 5 UPDATE :
AS this thread is old, I am posting here the update from another thread: https://stackoverflow.com/a/25550871/14559220. To sum things up,
in portrait and regular landscape, the height is still 49 points. In
compact landscape, the height is now 32 points.
On iPhone X, the height is 83 points in portrait and 53 points in
landscape.
I want to have a UIView take over the whole screen to display an image from time to time, but most of the time I'm using a UISplitViewController for the main app functionality. Is there a recommended/best practice way to do this? Right now, I have a pointer to the main window and am just adding the UIView as a sub view and bringing it to front, but it won't display in the proper orientation. I was just wondering if there is a better way/something simple I'm missing.
Thanks.
I struggled with this same issue for some time and have come up with a clunky workaround. Before my solution, some more background on the obstacles:
In iPhone OS, the root view's coordinates are always in the literal fixed coordinates of the physical screen. "Up" is always device up. Subviews have to get coordinates which have been converted to a given orientation so that "Up" is what we expect to be: gravitational up.
Placing a UIView at the top of the view hierarchy (above the UIScrollView) would therefore require that you make these conversions yourself during orientation events. You get fullscreen, but you lose the benefit of the scrollView managing orientation coordinates for your subviews.
Keeping your view within the scrollView, there is no easy method as in Mac OS to simply collapse a pane of a splitView. Reviewing the header file reveals some private instance methods used to manage width.
So, I think that what you can do is maintain two "orientation-aware" view controllers (one splitView, one fullscreen), and switch them out and move your view between them when you toggle fullscreen. I did not go this route as it is obviously a touchy situation memory- and view-hierarchy- wise.
What I do is switch between an "almost fullscreen" width for the main view and regular split width. This works great except that the splitView's rounded corners are actually hard-coded images that get draw regardless of the splitView's dimensions. You will see these tiny black round corners 100% of the time. Here is the effect:
- (IBAction)toggleFullscreen:(id)sender; {
id appDelegate = [[UIApplication sharedApplication] delegate];
UISplitViewController *split = [appDelegate splitViewController];
//get master and detail view controller
UIViewController *master = [split.viewControllers objectAtIndex:0];
UIViewController *detail = [split.viewControllers objectAtIndex:1];
//In landscape permit fullscreen
if(self.interfaceOrientation == UIInterfaceOrientationLandscapeLeft ||
self.interfaceOrientation == UIInterfaceOrientationLandscapeRight) {
CGRect f = detail.view.frame;
if ( f.origin.x == 0 ) { //exiting fullscreen
[sender setImage:[UIImage imageNamed:#"SlideLeft.png"]];
//adjust detail view
f.size.width = 703;
f.origin.x = 321;
[detail.view setFrame:f];
//adjust master view
f = master.view.frame;
f.size.width = 320;
f.origin.x = 0;
[master.view setFrame:f];
} else { //entering fullscreen
[sender setImage:[UIImage imageNamed:#"SlideRight.png"]];
//adjust detail view
f.size.width = 1024;
f.origin.x = 0;
[detail.view setFrame:f];
//adjust master view
f = master.view.frame;
f.size.width = 1;
f.origin.x = -1;
[master.view setFrame:f];
}
}
}
I got lots of this code from here and other sources, but as far as I know this implementation is unique. Probably because it is imperfect.
I ended up using a modal view controller which I present from the split view controller. I then specify the allowed orientations in -shouldAutorotateToInterfaceOrientation. I also hide the status bar. This works for my needs.
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.
I have a UITableView with an Index on the side; I want to add a UISearchBar to it, but the index overlaps with the "x" to clear the search. I've noticed in the Contacts application, the textfield within the UISearchBar is resized to accommodate this, but I can't work out how to do this in my own app.
I have tried the following in my viewDidLoad, but it does not seem to work.
UITextField * textField = (UITextField *)[[self.search subviews] objectAtIndex:0];
CGRect r = textField.frame;
[textField setFrame:CGRectMake(r.origin.x, r.origin.y, r.size.height, r.size.width-30)];
Any ideas?
it's much easier than all these suggestions. In interface builder, instead of putting the Search Bar as the header of your Table View, you can put a View instead. Then, put a Navigation Bar inside this View. Grab the left resizing handle of the Navigation Bar and pull it to the right until the N B is only 25 pixels wide. Clear out the Title in the N B (double click to select it, then delete). Then, add a Search Bar into the same View. Move its right resizing handle to the left, adjust so that it abuts the N B. That's it.
You can enable a cancel button if you want too and it also won't overlap the index (remains within the search bar).
Apparently a Table View can only have 1 subview in its header, that's why you need to put the View first, then the N B and Search Bar inside it.
UPDATE: see Beginning iPhone Development from Apress, p. 241 of SDK 3 edition. You just disable the index while searching.
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
if (isSearching) {
return nil;
}
return keys;
}
Also they talk about adding a magnifying glass to the top of the index.
Great book all around.
Why not just make the actual UISearchBar smaller horizontally, and place an (empty) UINavigationBar to the right of it? They will render the exact same background.
Better than hacking the internals of Apple's objects that could change.
Also, when animating the UISearchBar's width, you'll notice that the inner text field is not animated along with it. You can fix this by calling UISearchBar's "layoutSubviews" within your animation block after changing its frame. (that's where it determines the size of the inner text field)
Ok, I've come up with a solution.
Create a subclass of UISearchBar
Include this code in the drawRect: method.
UITextView * textField = [self.subviews objectAtIndex:0];
textField.frame = CGRectMake(5, 6, (310 - kRightSideMargin), 31);
[super drawRect:rect];
Note: kRightSideMargin is a constant I set in my header file; I have it set to 25.
Thanks for the suggestions from everyone else.
As Padraig pointed out all you have to do is subclass out the searchBar. Create your UISearchBar subclass, and add the following code into the layoutSubviews method:
- (void)layoutSubviews
{
UITextField *searchField;
for(int i = 0; i < [self.subviews count]; i++)
{
if([[self.subviews objectAtIndex:i] isKindOfClass:[UITextField class]])
{
searchField = [self.subviews objectAtIndex:i];
}
}
if(!(searchField == nil))
{
searchField.frame = CGRectMake(4, 5, 285, 30);
}
}
This loops through all the subviews and checks them against type UITextField. That way if it ever moves in its line up of subviews this will still grab it. I found 285 to just wide enough not to overlap with the index of my tableView.
As of iOS 6, the navigation bar solution didn't work well for me because of slightly different looks now between the UISearchBar and UINavigationBar. So, I switched to something similar to Padraig's approach by subclassing the UISearchBar.
#interface SearchBarWithPad : UISearchBar
#end
#implementation SearchBarWithPad
- (void) layoutSubviews {
[super layoutSubviews];
NSInteger pad = 50;
for (UIView *view in self.subviews) {
if ([view isKindOfClass: [UITextField class]])
view.frame = CGRectMake (view.frame.origin.x, view.frame.origin.y, view.frame.size.width - pad, view.frame.size.height);
}
}
#end
Edit: Ah, I haven't tried it, but I think you might be able to set a navigation bar's clipToBounds = YES to turn off it's new shadow, thereby creating a consistent look again between the two controls.
I am using ViewDeck and want to show a UISearchbar inside the leftController.
Now the problem is if I open the left side which contains the navigation, the right bit overlaps my search field.
I got rid of this by over writing UISearchBar, the textfield will always have the same width, but in one case there is the ViewDeck overlapping and in the other case I hide the ViewDeck-bit and then the cancel button will take up the space:
Subclassing UISearchBar
#import "ViewDeckSearchBar.h"
#define kViewDeckPadding 55
#interface ViewDeckSearchBar()
#property (readonly) UITextField *textField;
#end
#implementation ViewDeckSearchBar
static CGRect initialTextFieldFrame;
- (void) layoutSubviews {
[super layoutSubviews];
// Store the initial frame for the the text field
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
initialTextFieldFrame = self.textField.frame;
});
[self updateTextFieldFrame];
}
-(void)updateTextFieldFrame{
int width = initialTextFieldFrame.size.width - (kViewDeckPadding + 6);
CGRect newFrame = CGRectMake (self.textField.frame.origin.x,
self.textField.frame.origin.y,
width,
self.textField.frame.size.height);
self.textField.frame = newFrame;
}
-(UITextField *)textField{
for (UIView *view in self.subviews) {
if ([view isKindOfClass: [UITextField class]]){
return (UITextField *)view;
}
}
return nil;
}
#end
ViewController class
In my Navigation class I need to overwrite these two UISearchbarDelegate methods in order to go to fullscreen with the search results:
- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar{
[self.viewDeckController setLeftSize:0];
// I am also using scopes, which works fine (they fade out when not searching)
self.searchBar.scopeButtonTitles = #[#"Food",
#"Beverages",
#"Misc"];
}
-(void)searchBarTextDidEndEditing:(UISearchBar *)searchBar{
self.viewDeckController.leftSize = 55;
}
Result
ViewDeck showing to the right:
(source: minus.com)
Search in Fullscreen (The button and the scope buttons are animated in).
(source: minus.com)
searchBar.layoutMargins = UIEdgeInsetsMake(0, 0, 0, rightPad);
My old solution of changing the UITextField frame stopped working in iOS 13. Putting a UINavigationBar to the right of the UISearchBar never worked well for me as they had different looks at top and bottom.
Sorry to drag this all up again.
I wanted the UISearchBar to be shorter, and I'm using a UISearchBarController, but without actually wanting the index. This is because I have an overlay to the right:
To do this, I fake a sectionIndex with one blank item, then hide it. Here's how I do that:
- (void)hideTableIndex {
for (UIView *view in [tableView subviews]) {
if ([view isKindOfClass:NSClassFromString(#"UITableViewIndex")]) {
view.hidden = YES;
}
}
}
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)aTableView {
if (aTableView == self.searchDisplayController.searchResultsTableView) {
return nil;
} else {
[self performSelector:#selector(hideTableIndex) withObject:nil afterDelay:0];
return [NSArray arrayWithObjects:#"", nil];
}
}
- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index {
return 0;
}
This shortens the the UISearchBar and hides the index so it can't be tapped (a small section would otherwise hand to the left of the overlay that when tapped would scroll the UITableView to the top). Like this:
Best of all, when you use the search, you still get the full width bar:
Just put a UIView and put the search bar inside that UIView. UIView must be of same size as UISearchBar.
this worked for me.
The text field used in UISearchBar is a subclass of UITextField called UISearchBarTextField.
AFAIK, there's no way to resize a UISearchBarTextField using the public API, and the private API doesn't reveal much either.
Maybe you can take a look at UISearchBarTextField's subviews, if it has any.
UPDATE: It doesn't.
UPDATE 2: I think you should take a look at UITextField's rightView property. The below code, although it doesn't work, seems like a good starting point:
UIView *emptyView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 25, 25)];
[textField setRightView:emptyView];
[textField setRightViewMode:UITextFieldViewModeAlways];
[emptyView release];
Sorry for Necroposting, but I found another way to make a little space on the right of the textfield.
I was having the problem, that I had an indexed tableview with a searchbar as the first row. Now the index and the searchbar (made in IB, btw.) were overlapping. It tried almost everything with no success. It seems that the width and height properties of the textifield don't respond... So I came up with this:
searchBar.showsCancelButton = YES;
UIView *cButton = [searchBar.subviews objectAtIndex:2];
cButton.hidden = YES;
I still can't adjust the size of the space, but this does it for now... although... pretty weird solution...
Everyone has provided ways to modify the UI. I have discovered how to obtain identical results. You must provide the following two implementations:
Use UISearchDisplayController
More importantly, make sure you initialize it with:
- (id)initWithSearchBar:(UISearchBar *)searchBar contentsController:(UIViewController *)viewController
Failure to set a valid UISearchBar (or passing nil) will prevent the adjustment of the UITextField for the index.
You must return a valid array of titles by implementing:
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView;
If you return nil, the index will not be displayed, and the UITextField will not be properly adjusted.
I've submitted a bug report to Apple, suggesting that it seems logical that only #2 should be required, not #1. I have found nothing in the Human Interface Guideline (iPhone HIG) requiring use of the UISearchDisplayController.
The key is to use the "Search Bar and Search Display Controller" and not the "Search Bar" when using Interface Builder.
It kind of looks as though Apple resize the view (note that the index is animated to the right, off screen), making it bigger than the screen.
I would imagine that you'd need to implement the searchBarTextDidBeginEditing: method of the UISearchBarDelegate to trigger this at the appropriate point. This does, however, feel a bit hacky do maybe there's a better way of doing it.
Another appraoch (though tedious) would be to resize the search bar and fill the 'gap' with a navigation bar. Works for me.
What I've come up with isn't too much better. Basically, I make an empty view with the frame that I want to use for the search bar. Then I create a UIToolbar to go behind the search bar. Be sure to set its frame to the same frame as the UIView, except that the Y value has to be -1; otherwise, you'll get two borders drawn at the top. Next create your UISearchBar, but set the frame's width to 30 (or whatever makes sense for your app) less than the UIView. Add them as subviews and set your UIView as the tableHeaderView.
I followed Mike's advice by making a UIView, then putting a Navigation Bar and UISearch Bar inside it. Only problem is first time the search bar is shown its background is the same as a Navigation Bar normally?
Interestingly, if I activate the search, then click cancel the background of this 'fixed'!?
I'm using SDK 3.0, so I removed the UISearchBar item made when I dragged a UISearchDisplayController in to my NIB, then made the view as described above and wired it up to the file owner and the searchBar outlet in the search display controller.
It work fine!!!
[searchBar setContentInset:UIEdgeInsetsMake(5, 0, 5, 35)];