Can I use two xibs with one viewcontroller - re: porting to iPhone 5 - uiviewcontroller

I just submitted my first app to the app store (yay it was just approved!). I now want to update it to work with (look nicer on) the larger iPhone 5 screen. I don't intend to change anything other than to change the layout a bit for the larger screen.
NOTE: I don't want to have my current xib stretched.
Is it possible to create two xib files (ie: copy my current xib file for the main screen) and hook them both into the view controller and have it so that when the app launches, the app detects if there is an iPhone 5 screen or an earlier screen. Then, depending on which device it is, show the user a different screen.
I intend for underlying app to remain the same. All I want is to present a slightly different (taller) screen for iPhone 5 users with a few buttons/items moved around for the new layout. I otherwise won't be adding or removing anything from the interface.
This SO question/answer shows how to switch between an iPhone or iPad view. So to does this one. Both are helpful but I don't know how to modify this for the circumstance where the user is using an iPhone 5 with a larger screen or an iPhone 4S and below. Also, they assume two view controllers. I only want ONE view controller since absolutely NOTHING in the view controller logic changes - only the placement of the objects on the screen change and that is all done in the XIB.
I should think the answer should be that the view controller iteslf assesses what device it is running on then presents the appropriate xib? Yes? No?
If so, how would I go about this?

[Revised with Complete Answer on : Oct 7, 2012]
After significant research I found the answer, partly on this SO page (which shows how to detect which iPhone version your app is running on) and partly this SO page (showing how to have two xib's share the same 'File's Owner'. The final piece of the puzzle (loading separate xib's via the loadNibNamed: method) I found in chapter 10 of The Big Nerd Ranch's excellent iOS Programming text. Here's how:
Create a second xib (File, New..., File, select 'User Interface', select 'Empty' and save it. This creates the new xib. In the example below, my classic xib (for 3.5" iPhones) was named TipMainViewController.xib. I saved the new iPhone 5 xib with the name 'TipMainViewController-5.xib'
Make that new xib's 'File's Owner' the same ViewController as your existing xib. To do this, in the new xib file, select 'File's Owners'. Then in the 'Identity Inspector' select the existing View Controller as the Custom Class. In my case I selected 'TipMainViewController'.
Drag a new UIView onto the new xib's empty canvas. In the new UIView's attribute inspector set the 'Size' attribute to 'Retina 4 Full Screen'
Select all the contents in the existing 'Classic' 3.5" xib - eg: all your controls, buttons, selectors, labels etc. Copy them and paste them into the new iPhone 5 xib. Resize/move etc. them to optimize for the iPhone's 4" display.
Make all the connections to/from File's Owner as you did when you created your original xib.
Finally, in the 'viewDidLoad' method of your 'single' ViewController, insert the following logic (using your nib/xib names of course):
- (void)loadView
{
[super viewDidLoad];
if(UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
{
CGSize result = [[UIScreen mainScreen] bounds].size;
if(result.height == 480)
{
// iPhone Classic
[[NSBundle mainBundle] loadNibNamed:#"TipMainViewController" owner:self options:nil];
}
if(result.height == 568)
{
// iPhone 5
[[NSBundle mainBundle] loadNibNamed:#"TipMainViewController-5" owner:self options:nil];
}
}
}

Here is a simple, working code sample for your view controller that shows how to load myXib-5.xib on the iPhone 5 and myXib.xib on iPhones/iPods predating the iPhone 5:
- (void)loadView
{
if([[UIScreen mainScreen] bounds].size.height == 568)
{
// iPhone 5
self.view = [[NSBundle mainBundle] loadNibNamed:#"myXib-5" owner:self options:nil][0];
}
else
{
self.view = [[NSBundle mainBundle] loadNibNamed:#"myXib" owner:self options:nil][0];
}
}
It assumes that you are only targeting the iPhone and not the iPad, to keep it simple.
The XIB's file owner's class property should also be set to the view controller that contains loadView.

Code in answer was helpful, but I needed something that worked better for universal apps (iphone/ipad).
In case someone else needs the same thing, here's something to get you started.
Say you built a universal app using the nib/xib naming standards for ios for view controllers that have xibs with the same name:
The two built-in defaults for autoloading xibs when providing no name is passed to initWithNibName:
ExampleViewController.xib [iphone default when nib named empty for Retina 3.5 Full Screen for classic layouts iphone 4/4s etc...]
ExampleViewController~ipad.xib [ipad/ipad mini default when nib named empty]
Now say you need custom xibs for the iphone 5/5s in IB using Retina 4 Full Screen option, i.e., you don't want the 3.5 xibs displaying for any 568h devices.
Here's the custom naming convention using a category approach:
ExampleViewController-568h.xib [iphone non default/custom naming convention when nib name empty for Retina 4 Full Screen (568h)]
Instead of overriding the built-in naming defaults, use a category to help set the right xib for the controller.
https://gist.github.com/scottvrosenthal/4945884
ExampleViewController.m
#import "UIViewController+AppCategories.h"
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
nibNameOrNil = [UIViewController nibNamedForDevice:#"ExampleViewController"];
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Do any additional customization
}
return self;
}
UIViewController+AppCategories.h
#import <UIKit/UIKit.h>
#interface UIViewController (AppCategories)
+ (NSString*)nibNamedForDevice:(NSString*)name;
#end
UIViewController+AppCategories.m
// ExampleViewController.xib [iphone default when nib named empty for Retina 3.5 Full Screen]
// ExampleViewController-568h.xib [iphone custom naming convention when nib named empty for Retina 4 Full Screen (568h)]
// ExampleViewController~ipad.xib [ipad/ipad mini default when nib named empty]
#import "UIViewController+AppCategories.h"
#implementation UIViewController (AppCategories)
+ (NSString*)nibNamedForDevice:(NSString*)name
{
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
{
if ([UIScreen mainScreen].bounds.size.height == 568)
{
//Check if there's a path extension or not
if (name.pathExtension.length) {
name = [name stringByReplacingOccurrencesOfString: [NSString stringWithFormat:#".%#", name.pathExtension] withString: [NSString stringWithFormat:#"-568h.%#", name.pathExtension ]
];
} else {
name = [name stringByAppendingString:#"-568h"];
}
// if 568h nib is found
NSString *nibExists = [[NSBundle mainBundle] pathForResource:name ofType:#"nib"];
if (nibExists) {
return name;
}
}
}
// just default to ios universal app naming convention for xibs
return Nil;
}
#end

Related

Stacking two ContainerViews?

I have an UIViewController with its TableView view.
For the TableView header, I need to show 2 separate views, based on some flags.
I have 2 ContainerViews, each with its own embedding, two separate UIViewControllers. I was trying to show/hide the ContainerViews's view, based on the above mentioned flags.
The problem is that, the embedded views are not showing up like I expect them to. Here is my code:
//main `UIViewController` code; simplified
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"header_1"]) {
if(_shouldShowH1){
self.headerViewController = (HeaderViewController *)segue.destinationViewController;
[self.view bringSubviewToFront:self.headerViewController.view];
}
} else if ([segue.identifier isEqualToString:#"header_2"]){
if(_shouldShowH2){
self.headerViewController2 = (HeaderViewController2 *)segue.destinationViewController;
[self.view bringSubviewToFront:self.headerViewController2.view];
}
}
}
My problem is that, even if I call bringSubviewToFront:, I don't see the actual View.
Any suggestions?
I actually solved my issue following this post http://sandmoose.com/post/35714028270/storyboards-with-custom-container-view-controllers with one minor modification: I don't need to swap the views, so I removed the functionality.
I decide beforehand which segue needs to be performed, so the decision branching is also removed.

How to get object referece of UIPickerView when it create through html select tag

I have a UIWebview contains a html "select" tag, which is shown as a on the screen.
When I click the dropdown, the UIWebview brings up a UIWebSelectSinglePicker View automatically, which is shown as .
I want to change the picker view background color and text color. How can I achieve this goal?
I tried to listen on UIKeyboardWillShowNotification event, but at that moment, this view has not been created.
Thanks in advance for any helps.
I managed to resolve the issue myself.
If someone also want to change the UIPickView on the fly, please take a look:
First, add a listener on UIKeyboardWillShowNotification event.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(_pickerViewWillBeShown:) name:UIKeyboardWillShowNotification object:nil];
Second, when notification fired, call change background color method after delay. <-- This is very important, if call method without delay, the pickview does not exist at that moment.
- (void)_pickerViewWillBeShown:(NSNotification*)aNotification {
[self performSelector:#selector(_resetPickerViewBackgroundAfterDelay) withObject:nil afterDelay:0];
}
Third, go through the UIApplication windows and find out pickerView. And you can change what ever you want for pickerView.
-(void)_resetPickerViewBackgroundAfterDelay
{
UIPickerView *pickerView = nil;
for (UIWindow *uiWindow in [[UIApplication sharedApplication] windows]) {
for (UIView *uiView in [uiWindow subviews]) {
pickerView = [self _findPickerView:uiView];
}
}
if (pickerView){
[pickerView setBackgroundColor:UIColorFromRGB(0x00FF00)];
}
}
(UIPickerView *) _findPickerView:(UIView *)uiView {
if ([uiView isKindOfClass:[UIPickerView class]] ){
return (UIPickerView*) uiView;
}
if ([uiView subviews].count > 0) {
for (UIView *subview in [uiView subviews]){
UIPickerView* view = [self _findPickerView:subview];
if (view)
return view;
}
}
return nil;
}
Hope it will help.
I believe I've come up with an alternate solution to this problem. There are certain circumstances with the other solution proposed where the label colours appear incorrect (using the system default instead of the overridden colour). This happens while scrolling the list of items.
In order to prevent this from happening, we can make use of method swizzling to fix the label colours at their source (rather than patching them after they're already created).
The UIWebSelectSinglePicker is shown (as you've stated) which implements the UIPickerViewDelegate protocol. This protocol takes care of providing the NSAttributedString instances which are shown in the picker view via the - (NSAttributedString *)pickerView:(UIPickerView *)pickerView attributedTitleForRow:(NSInteger)row forComponent:(NSInteger)component method. By swizzling the implementation with our own, we can override what the labels look like.
To do this, I defined a category on UIPickerView:
#implementation UIPickerView (LabelColourOverride)
- (NSAttributedString *)overridePickerView:(UIPickerView *)pickerView
attributedTitleForRow:(NSInteger)row
forComponent:(NSInteger)component
{
// Get the original title
NSMutableAttributedString* title =
(NSMutableAttributedString*)[self overridePickerView:pickerView
attributedTitleForRow:row
forComponent:component];
// Modify any attributes you like. The following changes the text colour.
[title setAttributes:#{NSForegroundColorAttributeName : [UIColor redColor]}
range:NSMakeRange(0, title.length)];
// You can also conveniently change the background of the picker as well.
// Multiple calls to set backgroundColor doesn't seem to slow the use of
// the picker, but you could just as easily do a check before setting the
// colour to see if it's needed.
pickerView.backgroundColor = [UIColor yellowColor];
return title;
}
#end
Then using method swizzling (see this answer for reference) we swap the implementations:
[Swizzle swizzleClass:NSClassFromString(#"UIWebSelectSinglePicker")
method:#selector(pickerView:attributedTitleForRow:forComponent:)
forClass:[UIPickerView class]
method:#selector(overridePickerView:attributedTitleForRow:forComponent:)];
This is the Swizzle implementation I developed based off the link above.
#implementation Swizzle
+ (void)swizzleClass:(Class)originalClass
method:(SEL)originalSelector
forClass:(Class)overrideClass
method:(SEL)overrideSelector
{
Method originalMethod = class_getInstanceMethod(originalClass, originalSelector);
Method overrideMethod = class_getInstanceMethod(overrideClass, overrideSelector);
if (class_addMethod(originalClass,
originalSelector,
method_getImplementation(overrideMethod),
method_getTypeEncoding(overrideMethod))) {
class_replaceMethod(originalClass,
overrideSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
}
else {
method_exchangeImplementations(originalMethod, overrideMethod);
}
}
#end
The result of this is that when a label is requested, our override function is called, which calls the original function, which conveniently happens to return us a mutable NSAttributedString that we can modify in anyway we want. We could completely replace the return value if we wanted to and just keep the text. Find the list of attributes you can change here.
This solution allows you to globally change all the Picker views in the app with a single call removing the need to register notifications for every view controller where this code is needed (or defining a base class to do the same).

Slide view controller menu and status bar issue with IOS7

I have a slide view controller setup.
When viewing the app in IOS7 the status bar is shown and translucent so it is shown with the content.
Is there something I should be doing to offset the content below the status bar for this specific View Controller in my storyboard?
Awarded answer to #Idan for the suggestion but as this is a table view controller had to accomplish differently:
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:YES];
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(#"7")) {
self.tableView.frame = CGRectMake(0, 20, self.tableView.frame.size.width, self.tableView.frame.size.height-20);
}
}
I've solved it by introducing setting the table header view as a 20 point height view.
This code in viewDidLoad
UIView *headerView = [[UIView alloc] initWithFrame:CGRectMake(0.f, 0.f, self.tableView.frame.size.width, 20.f)];
headerView.backgroundColor = [UIColor whiteColor];
self.tableView.tableHeaderView = headerView;
Two different methods (depands on what you are trying to do):
Add this value to plist: "View controller-based status bar appearance" and set it to "NO". then you can code whatever you want (setStatusBarHidden etc.)
If you just want to move the view when it's iOS7 (status bar is above), in interface builder -> attribute inspector -> set delta y to -20 (so it would be below status bar).

how to find current loaded viewcontroller?

I have an tabBarController app with 2 tabBarItems.
each viewControllers contains tableView.
On didSelectRowAtIndexPath i am loading the detailview with this lines of code:
detailViewController = [[DetailViewController alloc] initWithNibName:#"DetailViewController_iPad" bundle:[NSBundle mainBundle]];
detailViewController.selectedDetail = [selectedDetail valueForKey:#"cardText"];
detailViewController.selectedCardTitle2 = [selectedCardTitle valueForKey:#"cardTitle"];
detailViewController.selectedRow2 = [self.tableViewInbox indexPathForSelectedRow];
detailViewController.detailCardsArray = allCards;
detailViewController.detailAllFetchedCards = allFetchedCards;
detailViewController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[inboxViewController presentModalViewController:detailViewController animated:YES];
Problem is, when detailView is loaded(is the actual shown viewController) and i change to the other tabBarItem, the detailView DOES NOT DISMISS. That means, that i cant load the detailView again, if didSelectRowAtIndexPath is called.
In my AppDelegate i have the method
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController {
In this method i will check up, if the detailView is the actual shown viewController.
And if it is, and the tabBarItem changes, THEN dismiss the DetailView.
Now my question is: How can I CHECK, if the detailView is loaded (current shown view) or not?
The documentation tells us that the detailView becomes a child of the presenting view. The presenting view controller will have it's modalViewController property updated to point to the presented view. Also, the modal view's parentViewController will be updated to point to the presenting view.
So, you could check these properties to see whether or not the modal view is displayed or not.

UISplitView with toolbar/tabbar at bottom of detail view

So here goes. I started with a standard out of the box splitview application for iPad. Root view left and detail view to the right. Detail view has it's toolbar at the top.
What I would like to add is a tab bar to the bottom of the detail view and have the tabs load in the details view, between the toolbar tabbar.
Here is the problem, do I add another view between them to load the tabs into, if so how do I get it resize and respect the toolbar and tabbar heights.
Clear?
Hope someone can point me in the right direction. Examples would be great, every example on the web seems to just be out of the box hello world style.
Yes the answer is really very simple. UITabBarControllers like SplitViewControllers were intended by Apple to only ever be the Root View Controller and hence you cannot nest a TabBarController in another view, but you can nest a UITabBar in a view, however.
I added the Tabbar to the details view at the bottom, a Navigation bar at the top and then a placeholder view between them. All in Interface Builder!, You will want to switch everything on with the autosize on the Placeholder view.
Next, Implement the UITabBarDelegate. For this you will need:
- (void)tabBar:(UITabBar *)tabBar didSelectItem:(UITabBarItem *)item {
from that you can use item.tag which if you give each item a unique tag in Interface Builder will let you know which tab the user clicked. I setup defined values for mine:
#define VIEW_TAB_A 0
#define VIEW_TAB_B 1
#define VIEW_TAB_C 2
Then you will then want to... well best I just let you see
- (void)tabBar:(UITabBar *)tabBar didSelectItem:(UITabBarItem *)item {
[self switchToView:item];
}
- (void) switchToView : (UITabBarItem*) item {
if( currentViewController != nil ) {
[currentViewController viewWillDisappear:NO];
[currentViewController.view removeFromSuperview];
}
switch(item.tag) {
case VIEW_TAB_A:
currentViewController = self.viewA;
break;
case SCAN_VIEW_TAB_B:
currentViewController = self.viewB;
break;
case PROMOTIONS_VIEW_TAB_C:
currentViewController = self.viewC;
break;
}
UIView *aView = currentViewController.view;
aView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
aView.frame = placeholderView.frame;
[currentViewController viewWillAppear:NO];
[self.view insertSubview:aView aboveSubview:placeholderView];
if( currentViewController != nil ) {
[currentViewController viewDidDisappear:NO];
}
[currentViewController viewDidAppear:NO];
}
Remember to alloc the views (viewA, viewB, viewC) first in you viewDidLoad and obviously release in dealloc. Also take note of the autoresizingMask!
Hope this helps others.