The following does not work
UILabel *titleLabel = (UILabel *)self.navigationController.navigationItem.titleView;
OR
UILabel *titleLabel = (UILabel *)self.navigationItem.titleView;
Both return nil.
Yes, the title in question is in the navigation bar. The objective is simple: I want the UILabel. Can that be done?
UPDATE
One of the reasons I needed the UILabel is so that I could grab its width after I fill it with text, which would essentially tell me the max width of the label (basically a device agnostic way of measuring: whether iPad, iPhone, etc).
The short answer: You can't. UINavigationBar 's internals handle the display of a UIViewController's title. The titleView property of UINavigationItem is used for other purposes.
The long answer
Look at UINavigationBar.h and get a rough understanding of how they both work together. - You'll quickly see why the titleView has nothing to do with a viewController's title.
UINavigationItem is the dataSource for a UINavigationBar.
It holds the barButtonItems, the title and the titleView for each UIViewController.
Look at the UIViewController (UINavigationControllerItem) category defined in UINavigationController.h there is a good explanation in the comments there.
The UINavigationBar on the other hand is 'simply' displaying the content of UINavigationItems.
When you push a viewController onto the stack of a navigationController, the navigationController implicitly calls UINavigationBar: pushNavigationItem:animated: on its navigationBar, sending the UINavigationItem of the viewController you are pushing.
I see you have the same misconception that i had when i started digging into UINavigationControllers:
The fact that you can write things like UILabel *titleLabel = (UILabel *)self.navigationController.navigationItem.titleView; without any warning is very misleading
UINavigationController does not have a UINavigationItem. It will never have.
This is because a navigationController will never be contained inside another navigationController. (If you try to build this containment in IB it will give you a warning i think !?) The reason you can call self.navigationController.navigationItem is that the category i mentioned above is giving EVERY UIViewController a UINavigationItem property. UINavigationController in turn IS a UIViewController and thus receives the property from the category within its own implementation. - That's very confusing!
I assume you want to grab a reference to the titleLabel (even though you can't !), because you want to set the font or the textcolor of the title. If so, you can do these things through UINavigationBar: setTitleTextAttributes:.
If you want to use a complete custom titleView, you are better off creating a UILabel and assigning it to the titleView property of the UINavigationItem
this works for me:
- (UILabel *)getNavBarTitleLabel
{
UINavigationBar *navBar = self.navigationController.navigationBar;
if (!navBar) { return nil; }
for (UIView *subview in navBar.subviews) {
for (UIView *subSubview in subview.subviews) {
if ([subSubview isMemberOfClass:[UILabel class]])
{
// consider adding more checks to verify that this UILabel is indeed the title label before returning it
// for example, if you know what the text should be you can compare the strings
return (UILabel*)subSubview;
}
}
}
return nil;
}
Related
I have created a TableView that contains UIViews, which hold a few other elements. These UIViews are created dynamically, as the data is called from a server. Inside each UIView there is a UILabel and a UIButtton. Once the button is clicked, I would like to have the corresponding label updated with some value. I was able to modify the UIButton and the view itself but unable to modify the UILabel. Here is an example of the method that is called when a UIButton is called. Right now it will change the background color of the corresponding UIView, but the label element does not work as intended. How can accomplish modifying this label element which is a subelement of the UIView?
- (void) heartPlus:(id)sender {
UIButton *button = (UIButton*) sender;
NSInteger id_num = button.tag;
UIView * view = (UIView *)[self.view.superview viewWithTag:id_num];
UILabel * label = (UILabel *)[view viewWithTag:id_num];
label.backgroundColor = [UIColor blueColor];
}
Creating the UIView along with adding the corresponding elements.
UIView * msgView = [[[UIView alloc] initWithFrame:CGRectMake(0,offSet,320,120)] init];
[msgView setTag:someID];
// Add button
UIButton * buttonUpdate = [UIButton buttonWithType:UIButtonTypeRoundedRect];
buttonUpdate.tag = someID;
[buttonUpdate addTarget:self action:#selector(heartPlus:) forControlEvents:UIControlEventTouchUpInside];
UILabel * labelHeart = [[[UILabel alloc] initWithFrame:CGRectMake(280,100,20,10)] init];
labelHeart.tag = someID;
// Add each element to the msgView
[msgView addSubview:buttonUpdate];
[msgView addSubview:labelHeart];
There is still some missing code, but as far as I can see you have the error in heartPlus method and viewWithTag does not return the correct view. Therefore the assignment of backgroundColor fails. The explanation is below:
You are first getting the parent view of view that contains UIButton and UILabel. I assume your view hierarchy looks something like this:
UITableViewCell
UIView (tag: someID)
UILabel (tag: someID)
UIButton (tag: someID)
So, if I assume there is no error in the line below and that it returns correct UITableViewCell (or whatever other view is the parent of both UILabel and UIButton):
UIView * view = (UIView *)[self.view.superview viewWithTag:id_num];
We can clearly see the problem with both UILabel and UIButton having the same tags. So if you ask the UITableViewCell for the view with tag, it will simply fail - returns nil.
There are multiple solutions to this problem, but the problem remains the same.
Do not give the UILabel the same tag as UIButton.
I assume you are a beginner with Objective-C so I would suggest you first look into some tutorials of how UIViews work. But to make it easier for you, here are few options:
Create a custom subclass of the UIView with UIView properties.
Use unique tags for UILabel and for UIButton. To find the correct id_num on click, use introspection and sender's superview, which is passed to you in a method.
You could easily loop through UIView's subviews property (which is an array) and find the UILabel manually - this works, if there is only one UILabel or you are looking for all of them.
But in either case, you need to rethink of how UITableView works. It is not a good practice to ask self.view.superview for viewWithTag and your method could already fail at this point.
I'd like to place an ADBannerView object onto my UITableView screen statically, what means that I want it to always stay above my toolbar (self.navigationController.toolbar), even when the user is scrolling the tableview. I've solved this by adding by ADBannerView as a subview to my toolbar and given it negative values for the frames origin:
[self setBannerViewSize];
[self.navigationController.toolbar addSubview:bannerView];
The only problem is: I can't click and open the iAd this way - I can see the banner but nothing happens when I tap on it.
Since I'm also using a refreshControl, the option to use a UIViewController instead of UITableViewController and add a tableView manually wouldn't work for me. Is there any other way I can get my ADBannerView statically showing in my table view controller AND still being tappable?
Thank you in advice!
Yay!! After all I succeeded in solving this (really annoying) problem by myself (and a lot of reading around)!
First, I found this really world-changing post. Basically this post handles with the topic that a UITableViewController uses self.view for its tableView property, so overriding the tableView property (or synthesizing it manually) plus giving self.view a new view (from application) and adding tableView as its subview would make it possible to reach the real superview of tableView.
But this still didn't solve my problem, although I was sure it would, because it all made sense. My bannerView appeared in the right place (and was fixed) but it still didn't do anything when clicked. But there was a second minor thing I didn't know about:
As I read in this post the superview of a subview doesn't only have to be userInteractionEnabled but also have a non-transparent backgroundColor. Because my superviews background color was set to [UIColor clearColor] it all didn't work - but setting its backGroundColor to e.g. blackColor solved the whole problem: the bannerView got finally tappable! :)
So, my code is now looking like this:
#synthesize tableView;
- (void)viewDidLoad
{
[super viewDidLoad];
if (!tableView && [self.view isKindOfClass:[UITableView class]]) {
tableView = (UITableView *)self.view;
}
self.view = [[UIView alloc] initWithFrame:[UIScreen mainScreen].applicationFrame];
self.tableView.frame = self.view.bounds;
[self.view addSubview:self.tableView];
[self resizeTableToFitBanner];
self.view.backgroundColor = [UIColor whiteColor];
[self.view addSubview:bannerView];
// some other code
}
BannerViewController in Apple's iAdSuite sample code solves this problem very elegantly:
https://developer.apple.com/library/ios/samplecode/iAdSuite/Introduction/Intro.html
I think you should use a container view, and set things up in IB. You can add a tool bar and ADBannerView to the bottom of the view of your navigation controller's root view controller. Fill the rest of the space with a container view - this will give you an embedded view controller automatically. You should delete this one and then drag in a tableViewController and control drag from the container view to the tableViewController to hook up the embed segue.
I have a Navigation Controller as the root of my app and I am using the appearance proxy to customise the look on iOS 5, but for iOS 4 I was hoping to use a category to override drawRect:, this was fine, except that all the Navigation bars were affected as you would expect from a category.
I don't want to tamper with the "system" popups, such as the Mail composer, or the SMS composer, I want their bars to stay blue and system-like.
I Tried to create my own UINavigationController with it's xib and change the class of the NavigationBar to my custom sub lass of UINavigationBar. But the results are not taking affect at all on screen.
I am aware of the following post but couldn't get any solutions to run as expected.
How to subclass UINavigationBar for a UINavigationController programmatically?
My first attempt which does work but uses an undocumented setNavigationBar: method:
_myNavigationController = [[UINavigationController alloc] initWithRootViewController:someVC];
if([[BT_NavigationController class] respondsToSelector:#selector(appearance)]){
// some iOS 5 magic in here !
[_myNavigationController.navigationBar setBackgroundImage:[UIImage imageNamed:#"topbar.png"];
}else{
// Probably looking at app store refusal
CustomBar * bar = [[CustomBar alloc] init];
// [_myNavigationController setNavigationBar: bar];
[bar release];
}
[parentView presentModalViewController:_myNavigationController animated:YES];
To avoid that I created a UINavigationController, which I also had a xib for, reassigning the class of the navigation bar to my custom class, but placing breakpoints in drawRect: method, I can see that this isn't being called.
Why would that be, it seems that my code is not loading the nib, and therefore not realising the nab bar should be my custom class and not the UINavigationBar.
Any tips would be helpful, thanks.
If your modification is just about adding an image, you can simply insert it from the view controllers you want to customize:
UIImageView *bgImageView = [[[UIImageView alloc] initWithImage:[UIImage imageNamed:#"topbar.png"]] autorelease];
[self.navigationController.navigationBar addSubview:bgImageView];
You have to tweak a little based on what other elements you have in your UINavigationBar for your UIImageView to be at the lowest index of the subviews but still above the background. Worked for us.
Good luck.
In order to customize the aspect of the navigationBar of my UINavigationController, I've been told to subclass the UINavigationBar class.
I need to change the height of the bar, placing an image instead of the title.. etc. I can't do it just using UINavigationBar properties.
Now, my issue is, how to assign to the UINavigationController, my CustomNavigationBar instance ? I can't use UINavigationController.navigationBar because it is read-only.
The only way seems to load a xib, but subclassing UINavigationController is not reccomended by Apple, so I'm a bit confused.
Depending exactly on how custom you want the nav bar, the following may help you:
1) An image in the bar, replacing the title text. (self is a UIViewController)
UIImageView *titleImg = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"image.png"]];
self.navigationItem.titleView = titleImg;
[titleImg release];
2) A totally custom nav bar
Subview UIView (CORRECTION: UINavigationController) with a nib, using (for example, of iPhone portrait orientation) a imageview as the background (could have a gradient) and then custom buttons on the left and right side, all hooked up to your relevant responder functions. As long as you add this nav bar view as a subview to each view controller etc each time a new one is pushed to the stack, you would have overriden Apple's standard nav bar. (remember to set theirs to hidden in the app delegate)
Edit:
NavigationBar.h
#interface NavigationBar: UIViewController
{
IBOutlet UIImageView* navigationBarImgView; // remember to set these 4 IBOutlets as properties with (nonatomic, retain) and synthesize them.
IBOutlet UILabel* controllerHeader;
IBOutlet UIButton* rightButton;
IBOutlet UIButton* leftButton;
eController controller;
int screenWidth;
}
NavigationBar.m
-(id)initNavigationBar: (NSString*)name bundle:(NSBundle*)bundle: (eController)controllerName: (int)inScreenWidth
{
[super initWithNibName:name bundle:bundle];
controller = controllerName;
screenWidth = isScreenWidth;
return self;
}
You can see my init here only passes in a few things - an enum which was defined in my app delegate which contains the names of all possible view controllers. We interpret this enum in my custom Navigation Bar class and switch between all the possible controllers, and then setup their buttons and titles and the actions that get fired off using that information. The screen width is important - although you may not need this if you create the bar in IB, set the size of it to fill up the allocated space for both orientations.
Keep me posted :)
This is really causing me fits. I see a lot of info on putting a UISearchBar in the top row of a UITableView -- but I am putting the UISearchBar into the Toolbar at the top of my screen (on the iPad). I cannot find ANYTHING regarding how to handle UISearchBar and UISearchDisplayController using a UIPopoverController on the iPad. Any more info about the UISearchDisplayController using a UIPopoverController would be greatly appreciated. Please help with this as I am at my wit's end.
Using IB, I put a toolbar on the IUView on the iPad. I added the following: Search Bar (not Search Bar and Search Display) to the toolbar. I set the options to be as follows: Show Cancel Button, Show Scope Bar, Scope Button Titles are: "Title1" and "Title2" (with Title2's radio button selected). Opaque, Clear Context and Auto Resize are checked. I hooked up the delegate of Search Bar to the "File's Owner" and linked it to IBOutlet theSearchBar.
In my viewWillAppear I have the following:
//Just in case:
[theSearchBar setScopeButtonTitles:[NSArray arrayWithObjects:#"Near Me",#"Everywhere",nil]];
//Just in case (again):
[theSearchBar setShowsScopeBar:YES];
//doesn't seem to do anything:
//[theSearchBar sizeToFit];
searchDisplayController = [[UISearchDisplayController alloc] initWithSearchBar:theSearchBar contentsController:self];
[self setSearchDisplayController:searchDisplayController];
[searchDisplayController setDelegate:self];
[searchDisplayController setSearchResultsDataSource:self];
//again--does not seem to do anything..but people have suggested it:
[theSearchBar sizeToFit];
Okay, so far, I thought, so good. So, I made the File's Owner .m file to be a delegate for: UISearchBarDelegate, UISearchDisplayDelegate.
My issue: I have yet to implement the delegates necessary to do the search but still... shouldn't I be seeing the scopeBar next to the search field when I click into the search field? Just so you know I DO see the log of the characters I type, so the delegate is working.
Here is a routine I used to check to see if IB really put the Scope Bar (UISegementedControl) in the searchbar:
for (UIView *v in theSearchBar.subviews)
{
if ([v isMemberOfClass:[UISegmentedControl class]])
{
// You've got the segmented control!
UISegmentedControl *scope = (UISegmentedControl *)v;
// Do your thing here...
NSLog(#"Found Scope: '%#'\n",scope);
NSLog(#"Scope Segments: '%d'\n",[v numberOfSegments]);
}
}
This shows:
[30013:207] Found Scope: '<UISegmentedControl: 0x68a06b0; frame = (0 0; 200 44); opaque = NO; layer = <CALayer: 0x68a0600>>'
[30013:207] Scope Segments: '2'
So, I know the 2 segments are there. I also know they are not showing up...
What am i doing wrong?
Why doesn't the Scope Bar appear? A results UIPopoverController appears with the title "Results" and "No results found" (of course) when i type the first character in my search...but no scope bar. (not that i expect anything other than "No Results Found".
I am wondering where the scope bar is supposed to appear...in the titleView of the UIPopover? In the toolbar to the right of the search area? Where?
[self.searchDisplayController searchBar] setScopeButtonTitles:[NSArray arrayWithObjects:#"Near Me",#"Everywhere",nil]];
This Might Help.
UISearchBar does not display it's scope when embedded in a UIToolbar, this is true even in iOS 7.
You must build your own UISegment and add it into the table view of the search results or build your own view to hold the UISearchBar that would display scope.