i have set my navigationbar background with this code in my App delegate:
#implementation UINavigationBar (UINavigationBarCategory)
- (void)drawRect:(CGRect)rect {
UIImage *backgroundImage = [UIImage imageNamed: #"Nav-rightpane.png"];
CGContextDrawImage(UIGraphicsGetCurrentContext(),
CGRectMake(0, 0, self.frame.size.width, self.frame.size.height),
backgroundImage.CGImage);
}
#end
This works perfect but now i must change the background to the default at UIPopoverController. Any idea for this?
Thanks
Do not use this solution. Simply delete your UINavigationBar Category and implement your own UINavigationController.
#interface XNavigationController : UINavigationController{}
and change override viewDidLoad method in implementation
- (void)viewDidLoad {
[super viewDidLoad];
UIImage *img = [UIImage imageNamed: #"Nav-rightpane.png"];
UIImageView *imageView = [[UIImageView alloc] initWithImage:img];
[imageView setFrame:self.navigationBar.bounds];
[imageView setContentMode:UIViewContentModeScaleToFill];
[self.navigationBar addSubview:imageView];
[imageView release];
}
so use this navigationController when you want to change the background of navigation bar.
The correct solution pre-iOS5, as recommended by Apple engineers is to not use categories and to not use method swizzling. This is confirmed to break on iOS 5 which is very close to being released.
Instead, you should create a subclass of UINavigationBar, override the drawRect: method, and then use Interface Builder to define your navigation controller. Interface Builder will let you change the classname for the navigation bar to your custom subclass:
If you want to avoid Interface Builder, you can create a temporary XIB and load an instance of the navigation controller from it, then use NSKeyedArchiver to archive it to a file. Then use xxd -i <filename> on the command line to generate C-code for the raw bytes of the navigation controller. Then paste that into a source file and unarchive it when you want an instance.
It seems like a lot of work, but the WWDC videos and engineers on the forums have repeatedly stated that this is the only safe and reliable way to do what you want.
In iOS 5 this all becomes a whole lot easier.
Related
I'm building an app with storyboard. Now I want on every screen in my app the same topBar/navigationbar. I want to set an image on this navigationbar. After some searching, I found this code.
#implementation UINavigationBar (CustomImage)
- (void)drawRect:(CGRect)rect
{
UIImage *image = [UIImage imageNamed: #"topbar.jpg"];
[image drawInRect:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
}
#end
I've put it right above my implemantation in my appdelagate.M file. But it is not working.
Also does anybody knows if this is working in IOS 5.1 as well?
Kind regards.
Look at UIAppearance. It allows you to change attributes across entire UI classes. For example, I have this in an app:
UIImage *img = [UIImage imageNamed: #"NavBarBackground.png"];
[[UINavigationBar appearance] setBackgroundImage:img forBarMetrics:UIBarMetricsDefault];
UIAppearance was introduced in iOS 5. The "old" way to do this was to override -drawRect: using a category for the class in question (e.g. UINavigationBar), as you did. ("Swizzling" -- exchanging your method for an SDK-provided method -- not required, by the way.)
I found, when iOS5 came out, that the drawRect trick didn't work, so I had to use UIAppearance conditionally by enclosing the above code in an if statement like so:
if ([[UINavigationBar class] respondsToSelector:#selector(appearance)]) {
...
}
Why conditionally? We continued to support iOS4, so iOS4 users would experience a crash if we didn't conditionalize this UIAppearance stuff. And so, we had both this and the drawRect solution in the app!
If you don't have to support iOS4, you should definitely look into UIAppearance to achieve your goals here.
I used to swizzle drawRect for this purpose, you can perform swizzling at +initialize with method_exchangeImplementations (runtime.h). Now the tricky part is to call the original drawRect, as the methods are swizzled you'll need to use the custom drawing method name to call the original one. Like if you do the drawing with, you'll have [super customDrawRect:rect]; at the end
- (void)customDrawRect:(CGRect)rect {
...
[super customDrawRect:rect];
}
#import <objc/runtime.h>
#implementation UINavigationBar (CustomImage)
+ (void)initialize {
Method originalDrawRectMethod = class_getInstanceMethod([self class], #selector(drawRect:));
Method customDrawRectMethod = class_getInstanceMethod([self class], #selector(customDrawRect:));
method_exchangeImplementations(originalDrawRectMethod, customDrawRectMethod);
}
- (void)customDrawRect:(CGRect)rect {
UIImage *image = [UIImage imageNamed: #"topbar.jpg"];
[image drawInRect:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
// original drawRect call
[self customDrawRect:rect];
}
#end
I have a UIViewController called HomeViewController and it has a model that contains an array of data. HomeViewController also has a button that when pressed will show a UITableViewController that display's the model's array of data.
My question is, what is the standard/best way to set the popover's size? I only want the popover to be tall enough to display the data, but no taller. What is common practice? I assume that I need to set contentSizeForViewInPopover at some point but I'm not sure where...In the viewDidLoad method of the popover class? In the prepareForSegue method?
Here's the code I have so far: (Note that DataPresentingViewController is my popover view controller class`)
//In HomeViewController
- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if([segue.destinationViewController isKindOfClass:[DataPresentingViewController class]])
{
DataPresentingViewController *dest = (DataPresentingViewController *) segue.destinationViewController;
dest.mySavedData = self.myModel.mySavedData;
}
}
I know that I could just set the contentSizeForViewInPopover here in the prepareForSegue method; however, this seems like something the popover class should deal with.
As of iOS7 contentSizeForViewInPopover is deprecated. You should use UIViewController's preferredContentSize property instead.
Hope this helps!
set contentSizeForViewInPopover in ViewDidLoad method in HomeViewController
contentSizeForViewInPopover is deprecated in iOS 7
The property: popoverContentSize of UIPopoverController represents the size of the content view that is managed by the view controller in the contentViewController property of UIPopoverController.
Reference
The advantage is that it is available in iOS 3.2 and later, so you don't need to check device version everytime, just replace contentSizeForViewInPopover method with your UIPopOverController object instance.
Have you tried:
setPopoverContentSize:<#(CGSize)#> animated:<#(BOOL)#>
in your segue logic block. Useful if you want the popover size to be variable based upon a data set, or view placement or whatever.
// self.contentSizeForViewInPopover = CGSizeMake(320.0, 600.0); //Deprecated in ios7
self.preferredContentSize = CGSizeMake(320.0, 600.0); //used instead
A little known trick to sizing your UIPopoverViewController to match the height of your UITableView is to use the tableView's rectForSection method to give you the height. Use the height in your viewController's contentSizeForViewInPopover like this:
- (CGSize)contentSizeForViewInPopover {
// Currently no way to obtain the width dynamically before viewWillAppear.
CGFloat width = 200.0;
CGRect rect = [self.tableView rectForSection:[self.tableView numberOfSections] - 1];
CGFloat height = CGRectGetMaxY(rect);
return (CGSize){width, height};
}
Try the following code:
- (IBAction)setData:(id)sender
{
UIViewController *popoverContent=[[UIViewController alloc] init];
UITableView *tableView=[[UITableView alloc] initWithFrame:CGRectMake(265, 680, 0, 0) style:UITableViewStylePlain];
UIView *popoverView=[[UIView alloc] initWithFrame:CGRectMake(0, 0, 200, 300)];
popoverView.backgroundColor=[UIColor whiteColor];
popoverContent.view=popoverView;
popoverContent.contentSizeForViewInPopover=CGSizeMake(200, 420);//setting the size
popoverContent.view=tableView;
tableView.delegate=self;
tableView.dataSource=self;
self.popoverController=[[UIPopoverController alloc] initWithContentViewController:popoverContent];
[self.popoverController presentPopoverFromRect:CGRectMake(400, 675, 0, 0) inView:self.view permittedArrowDirections:UIPopoverArrowDirectionDown animated:YES];
}
It looks like you want to do it programmatically, but in the storyboard, before you hook up a view controller to a segue, under the attributes inspector there is a "popover, use explicit size option." Maybe you can set the size that would work the best for your App first and not worry about the size with using code. Hope this helps, let us know how it goes.
I'm having a lot of trouble finding a tutorial for implementing multiple views in Xcode 4.2 without storyboard, this is for a class so I can't use storyboard yet. I'm just trying to have a 2nd view with a UIPicker come up when a button is clicked in the main view, I just can't find one for this version of Xcode and it's different enough from the older versions to confuse me.
Any help appreciated if someone can give me a quick description of what I need to do this or a newer tutorial I'd appreciate it :)
I think you should read the UIView Programming Guide to get a good handle on how UIViews work exactly. I find nibs/storyboard are really great at confusing new iOS developers.
In essence, a UIViewController has 1 view which you set in the viewDidLoad or loadView method by using the [self setView:someUIView]. You add more stuff to the screen by adding UIViews as a subview of the viewcontroller's "Main" view. For example
-(void)loadView {
// Create a view for you view controller
UIView *mainView = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
[self setView:mainView];
// Now we have a view taking up the whole screen and we can add stuff to it
// Let's try a button, a UIButton is a subview of UIView
UIButton *newButton = [[UIButton buttonWithType:UIButtonTypeRoundedRect];
// We need to set a frame for any view we add so that we specify where it should
// be located and how big it should be!
[newButton setFrame:CGRectMake(x,y,width,height)];
// Now let's add it to our view controller's view
[self.view addSubview:newButton];
// You can do the same with any UIView subclasses you make!
MyView *myView = [[MyView alloc] init];
[myView setFrame:CGRectMake(x,y,width,height)];
[self.view addSubview:myView];
}
Now here we have our viewController who'se view is just a plain UIView which in turn has 2 subviews; newButton and myView. Since we created the MyView class, maybe it contains subviews as well! Let's take a look at what a UIView subclass could look like:
// Here is the init method for our UIView subclass
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Let's add a button to our view
UIButton *newButton2 = [[UIButton buttonWithType:UIButtonTypeRoundedRect];
// Of course, the frame is in reference to this view
[newButton2 setFrame:CGRectMake(x,y,width,height)];
// We add just using self NOT self.view because here, we are the view!
[self addSubview:newButton2];
}
return self;
}
So in this example we would have a view controller who'se view now contains 2 button! But the view structure is a tree:
mainView
/ \
newButton myView
\
newButton2
Let me know if you have any other questions!
Matt
I wonder how to add UI elements programatically to existing nib files.
If I create a view programatically in loadView method and I add code like the following, the label displays correctly.
self.view = [[UIView alloc] initWithFrame:[UIScreen mainScreen].applicationFrame];
UILabel *lbl = [[UILabel alloc] initWithFrame:CGRectMake(0,2,150,100)];
[self.view addView:lbl];
But how to add the label to an existing nib file?
As Paul.s pointed out, you need to perform your custom code in viewDidLoad method.
From Apple documentation.
This method is called after the view controller has loaded its
associated views into memory. This method is called regardless of
whether the views were stored in a nib file or created
programmatically in the loadView method. This method is most commonly
used to perform additional initialization steps on views that are
loaded from nib files.
So, in your controller you could do this:
- (void)viewDidLoad
{
[super viewDidLoad];
// your other views here
// call addSubview method on self.view
}
Why do you do this? Because here you are sure that view has loaded in memory and outlets has been set correctly.
On the contrary, about loadView method
If you override this method in order to create your views manually,
you should do so and assign the root view of your hierarchy to the
view property. (The views you create should be unique instances and
should not be shared with any other view controller object.) Your
custom implementation of this method should not call super.
An example could be:
- (void)loadView
{
CGRect applicationFrame = [[UIScreen mainScreen] applicationFrame];
UIView *contentView = [[UIView alloc] initWithFrame:applicationFrame];
contentView.backgroundColor = [UIColor whiteColor];
self.view = contentView;
// call addSubview method on self.view
}
I suggest you to read View Controller Programming Guide for iOS.
Hope it helps.
AFAIK, to modify nib file programmatically is not possible.
You can add view into viewDidLoad of UIViewController method.
Inside viewDidLoad
UILabel *lbl = [[UILabel alloc] initWithFrame:CGRectMake(0,2,150,100)];
[self.view addSubView:lbl];
I'm running xcode-4.2 and the project is based for ios5 using storyboards.
I've created a single view application , using the template provided by Apple.
In the storyboard I the removed the viewcontroller created for me and added a UITabBarController.
Next I added a new class MyTabBarController which is a subclass of UITabBarController.
Now I want to show a splashscreen before the TabBar appears. So I can do some loading and calculation in the background.
I thought AppDelegate.m would be a good place for this. Since that's the place where my rootview get's loaded not ? Or should a show the splashscreen from the rootviewcontroller which is MyTabBarController in my case ?
So I created a xib file. I'm surprised you can add .xib files to ios5 storyboard projects. The xib file is called SplashView.xib it has a single view with an image on it.
Code in AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
_splashScreen = [[UIViewController alloc] initWithNibName:#"SplashView" bundle:nil];
//_splashScreen is defined as:#property (strong, nonatomic) UIViewController *splashScreen;
[_window.rootViewController presentModalViewController:_splashScreen animated:NO];
[self performSelector:#selector(hideSplash) withObject:nil afterDelay:2];
return YES;
}
The problem is nothing happens. Even if I change the value from 2 to 200. The application starts up as if there is no splashscreen.
As you might have noticed I'm still struggling with the design of objective-c and iphone application. I hope a decent answer to my question will bring some clarity to the subject.
Thanks in advance!
Splash screens are built into iOS apps. All you need to do is create a file called Default.png and Default#2x.png (for retina displays) and it will work as a splash screen for when the app launches.
You can also set what these images will be in your apps info.plist.
I've dealt with a few clients who wanted to use an animated splash.
Though I'm totally against this, following Apple's HIG,
those clients just don't understand...
Anyway, since - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions; has to return boolean,
it's very important not to halt it for anything.
Also, since launch time is measured by iOS, if it's taking too long, the app will be terminated by iOS!
For this reason, I often use - (void)applicationDidBecomeActive:(UIApplication *)application;
with some kind of flag to indicate if it happened at launch or at returning from background mode.
Or, you should use a NSTimer or - (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay;
so, didFinishLaunchingWithOptions can return without being blocked for processing your animated splash.
This delayed performSelector should be implemented not only for hiding action (like the way you intended it), but also for starting the animation.
If you are using storyboard, you can just add the splash UIImageView to your window.rootViewController.view like this:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
UIImage *splashImage = [UIImage autoAdjustImageNamed:#"Default.png"];
UIImageView *splashImageView = [[UIImageView alloc] initWithImage:splashImage];
[self.window.rootViewController.view addSubview:splashImageView];
[self.window.rootViewController.view bringSubviewToFront:splashImageView];
[UIView animateWithDuration:1.5f
delay:2.0f
options:UIViewAnimationOptionCurveEaseInOut
animations:^{
splashImageView.alpha = .0f;
CGFloat x = -60.0f;
CGFloat y = -120.0f;
splashImageView.frame = CGRectMake(x,
y,
splashImageView.frame.size.width-2*x,
splashImageView.frame.size.height-2*y);
} completion:^(BOOL finished){
if (finished) {
[splashImageView removeFromSuperview];
}
}];
return YES;
}
I think the reason why directly just add the UIImageView to window is because iOS will bring the rootViewController.view to front when the default splash will hide. And this will overlap the animation. This means the animation does happen but it's behind the rootViewController.
I just add an identical image to the launch image to my first view controller and then fade it (or whatever animation you require) - this avoids pausing the app load in the AppDelegate.
You need to ensure that the image has the same size and origin as your launch image e.g. to set the image to display on my first view controller which is a tableViewController:
UIImageView *imageView = [[UIImageView alloc] initWithFrame:self.tableView.bounds];
imageView.image = [UIImage imageNamed:#"[imagename]"];
[self.tableView addSubview:imageView];
[self.tableView bringSubviewToFront:imageView];
// Fade the image
[self fadeView:imageView];
-(void)fadeView:(UIView*)viewToFade
{
[UIView animateWithDuration:FADE_DURATION
animations:^ {
viewToFade.alpha = 0.0;
}
];
}