iPhone SDK, replace XIB/NIB file onViewDidAppear or anytime during runtime? - ios

I have an iPhone/iPad app screen which contains two possible layouts that I have defined in two interface builder files (xib/nib). When the app starts, I look in the userdefaults and choose which one to load up.
However if the user chooses the settings button and loads the in app settings menu and changes the layout, then hits the navigation button to go back to the previous screen, how would I change the user interface/xib file to be the new choice without restarting the app?
Thank you.

You can force the XIB to load using:
[[NSBundle mainBundle] loadNibNamed:#"XIBName" owner:self options:nil];
Edit
Try the following:
Add the following in an utility class which you can use anywhere.
+ (id)loadNibNamed:(NSString *)nibName ofClass:(Class)objClass {
if (nibName && objClass) {
NSArray *objects = [[NSBundle mainBundle] loadNibNamed:nibName
owner:nil
options:nil];
for (id currentObject in objects ){
if ([currentObject isKindOfClass:objClass])
return currentObject;
}
}
return nil;
}
Then make the call like this:
MyClass *myClassInstance = [Utility loadNibNamed:#"the_nib_name"
ofClass:[MyClass class]];
See this also: Single View controller with multiple nibs?

MyUIViewController *controller;
if (IS_IPAD){
controller = [[MyUIViewController alloc] initWithNibName:#"MyUIViewController_iPad" bundle:nil];
} else {
controller = [[MyUIViewController alloc] initWithNibName:#"MyUIViewController_iPhone" bundle:nil];
}
And you can for example push from a navigationController
[self.navigationController pushViewController:controller animated:YES];

Related

UIView that loaded from nib files , can i set the owner to the view it self?

the most common method to init is
UIView *rootView = [[[NSBundle mainBundle] loadNibNamed:#"MyRootView" owner:self options:nil] objectAtIndex:0];
and in my case , self is refer to a view controller , but i have to set the owner to the view itself ,because there is many outlet between .m and .xib , how to do with this situations?
You should use a init method in your view.m class like:
- (id)initWithNibNamed:(NSString *)nibName{
NSArray *nibContents = [[NSBundle mainBundle] loadNibNamed:nibName owner:nil options:nil];
self = [nibContents lastObject];
if(self){
//do your code here
}
return self;
}
and call this method from viewcontroller. Connect view to view xib.
thanks stack overflow , there is already a perfect solution for this kind of problem .
https://github.com/PaulSolt/CompositeXib
the key is to call the loadNibNamed in the custom view 's implementation ,not in the controller that create the view.

Best way to access subviews of views instantiated from XIB?

I'm instantiating multiple clone views from a XIB, like this:
UIView *view = [[NSBundle mainBundle] loadNibNamed:#"MyNib" owner:self options:nil][0];
Then I need to access a subview (say, change a label) of every single of those views.
Connecting an element with IBOutlet wont work here (as the reference would be rewrite but the most recent view instantiated).
Here is my best shot at this so far:
for (UIView *subview in myView.subviews) {
if ([subview.restorationIdentifier isEqualToString:#"myTargetElement"]) {
// do something with the view
break;
}
}
So I'm basically iterating though subviews to find my element by restorationIdentifier. I wonder if there is a way to get a direct reference without iteration?
You can use IBOutlets. They need to be made to the custom view subclass though, not to the view controller where you add the view. Something like this works fine,
#import "ViewController.h"
#import "RDView.h"
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
RDView *view = [[NSBundle mainBundle] loadNibNamed:#"RDView" owner:self options:nil][0];
[self.view addSubview:view];
view.topLabel.text = #"Hello";
view.bottomLabel.text = #"Goodbye";
}
What's wrong with reference by outlet? I think it'll work just as fine.
It just matters on how you do the dot referencing.
Example:
UIView *view = [[NSBundle mainBundle] loadNibNamed:#"MyNib" owner:self options:nil][0];
//...
UIView *view_1 = [[NSBundle mainBundle] loadNibNamed:#"MyNib" owner:self options:nil][0];
//...
[view.someLabel setText:#"1"];
[view_1.someLabel setText:#"2"];
Anyways... alternatively, you can give the subviews a specific tag and access them via the -viewWithTag: method.
Example:
Say a UILabel in this 'MyNib' of yours has a tag 100, then you can reference it via:
[view viewWithTag:100];
//like so:
//UILabel *lblTemp = [view viewWithTag:100];
//[lblTemp setText:#"NewText"];

Delegate reference disappears on modal Popup

This is convoluted, so I will do my best to give as much info as possible. My main UIViewController opens a modal popup in the form of an Info screen.
here is the call from MainViewController
infoPopup = [ModalPopup modalPopupWithDelegate:self];
[infoPopup presentInView:self.view.window];
[self.view addSubview:infoPopup];
and here is the receiving method in ModalPopup
+ (id)modalPopupWithDelegate:(id <ModalPopupDelegate>)dlg {
ModalPopup *info = [[ModalPopup alloc] init];
info.delegate = dlg;
return info;
}
In ModalPopup I create a protocol with an optional method of "modalPopupFinished" and make MainViewController the delegate.
in ModalPopup I've add a UIScrollView and insert 5 UIViews into the scrollview.
I created the views all in the same XIB file
NSString *infoXib;
if (IS_IPAD)
infoXib = #"info_iPad";
else
infoXib = #"info_iPhone";
NSArray *views;
views = [[NSBundle mainBundle] loadNibNamed:infoXib owner:self options:nil];
UIView *v1 = [views objectAtIndex:0];
views = [[NSBundle mainBundle] loadNibNamed:infoXib owner:self options:nil];
UIView *v2 = [views objectAtIndex:1];
views = [[NSBundle mainBundle] loadNibNamed:infoXib owner:self options:nil];
UIView *v3 = [views objectAtIndex:2];
views = [[NSBundle mainBundle] loadNibNamed:infoXib owner:self options:nil];
UIView *v4 = [views objectAtIndex:3];
views = [[NSBundle mainBundle] loadNibNamed:infoXib owner:self options:nil];
UIView *v5 = [views objectAtIndex:4];
NSArray *pages = [NSArray arrayWithObjects:v1, v2, v3, v4, v5, nil];
[self setPagesInArray:pages];
- (void)setPagesInArray:(NSArray *)pages {
if (pages) {
int numberOfPages = pages.count;
[pageScroll setContentSize:CGSizeMake(pageScroll.frame.size.width * numberOfPages, pageHeight)];
pageControl.numberOfPages = numberOfPages;
NSUInteger i = 0;
while (i < numberOfPages) {
UIView *page = [pages objectAtIndex:i];
page.frame = CGRectMake(pageWidth * i, 0, pageWidth, pageHeight);
[pageScroll addSubview:page];
i++;
}
}
}
the Views load fine in the scrollview and I can scroll through them all as expected.
One of the views in the XIB as some buttons and I've made the view a member of the Custom Class ModalPopup. I've wired the buttons to some IBActions in ModalPopup, and they fire as expected.
In ModalPopup I create a close button that fires the delegates "modalPopupFinished" event on MainViewController.
- (void)finishCloseAnimation {
if ([_delegate respondsToSelector:#selector(modalPopupFinished)])
[_delegate modalPopupFinished];
[self removeFromSuperview];
}
No Problems everything works great...except
when I press one of the buttons from the View and goto fire a delegate method, it has lost its brain and can't remember the delegate
- (IBAction)facebook:(id)sender {
if ([_delegate respondsToSelector:#selector(sendLikeFacebook)])
[_delegate sendLikeFacebook];
}
In fact when I get into the Facebook method on ModalPopup everything is nil.
The method that I am trying to fire in MainViewController opens
[self presentViewController:composeController animated:YES completion:nil];
thus I want to open it from the main page.
It sounds to me like you have 2 instances of ModalPopup. Since you say that you wired your buttons to ModalPopup in IB, this leads me to believe you are creating an instance of ModalPopup in the xib which is distinct from the ModalPopup that you have created programmatically. The fact that the ModalPopup that is called when the button is pressed is "completely empty" is further evidence.
If this is the case, instead of programmatically creating the ModalPopup, you should get the instance from the xib, just like you get the views. I would also suspect that you already added the views to the ModalPopup in the xib which means you do not have to manually add them as well.

iOS Controllers for universal (iPhone/iPad) app

I am just working on iOS app and I want to make it universal for both iPhones and iPads. This is done and works without any problems:
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
self.viewController_iPhone = [[ViewController_iPhone alloc] initWithNibName:#"ViewController_iPhone" bundle:nil];
} else {
self.viewController_iPad = [[ViewController_iPad alloc] initWithNibName:#"ViewController_iPad" bundle:nil];
}
if (self.viewController_iPhone == nil)
self.window.rootViewController = self.viewController_iPad;
else
self.window.rootViewController = self.viewController_iPhone;
There is a view for each controller (ViewController_iPad.xib, ViewController_iPhone.xib). It doesn't matter which view is loaded in my problem. In a view there is a subview added (UIScrolView). And in this ScrollView there are two views from xib:
NSArray *nibContents = [[NSBundle mainBundle] loadNibNamed:#"SubView1" owner:self options:nil];
UIView *view = [nibContents objectAtIndex:0];
view.frame = CGRectMake(2, 0, scrollView.frame.size.width - 2, scrollView.frame.size.height);
[scrollView addSubview:view];
nibContents = [[NSBundle mainBundle] loadNibNamed:#"SubView2" owner:self options:nil];
view = [nibContents objectAtIndex:0];
view.frame = CGRectMake(scrollView.frame.size.width + 2 , 0, scrollView.frame.size.width - 4, scrollView.frame.size.height);
[scrollView addSubview:view];
(This code is in iPad/iPhone controller). Still everything is OK. But I don't know how to set owners (in IB) of these subviews that are shown in ScrollView. These subviews are in ScrollView which is in a main view so I want to set owners of these subviews as iPad/iPhone controller. But as a owner can be only one class. Can you tell me how to set owners if I have two main controllers and I don't know which one will be loaded in runtime. Thank you.
EDIT: I have another question: I have ViewController_iPhone. It has a View property and this property is assigned to the "root" view in the main view in ViewController_iPhone (.xib). Can I assign this view property also to subview view? Because I got EXC_BAD_ACCESS error if I assign view property of ViewController_iPhone to a "root" view of subview in IB.
Looks like you need to use a class cluster. This will abstract the iPhone/iPad instantiation, so you don't explicitly need to instantiate one of the two.
You can read a bit about class clustering in the Apple documentation:
http://developer.apple.com/library/ios/#documentation/general/Conceptual/DevPedia-CocoaCore/ClassCluster.html
It boils down to creating a master view controller which will handle the allocation of iPhone or iPad subclasses based on the current device.
You should override the ViewController alloc class method:
+ (id)alloc {
NSString *classString = NSStringFromClass([self class]);
NSString *append = nil;
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
append = #"_iPhone";
} else {
append = #"_iPad";
}
NSString *subClassString = [classString stringByAppendingString:append];
id subClass = NSClassFromString(subClassString);
id object;
if (subClass && ![self isKindOfClass:[subClass class]]) {
object = [subClass alloc];
} else {
object = [super alloc];
}
return object;
}
This way you can just allocate the ViewController class and at runtime the correct class definition will be used to instantiate your view controller.
This will allow you to use the ViewController class as the owner in IB provided that you create an abstraction of iPhone and iPad interfaces and define it in their super class.

Loading UIView from a nib file without guesswork

Ok, here's another question.
I am creating a UIView called ProgressView that is a semi-transparent view with an activity indicator and a progress bar.
I want to be able to use this view throughout different view controllers in my app, when required.
I know of 3 different ways of doing this (but I am only interested in one):
1) Create the entire view programatically, instantiate and configure as required. No worries I get that one.
2) Create the UIView in interface builder, add the required objects and load it using a method like the below. Problem with this is that we are basically guessing that the view is the objectAtIndex:0 because nowhere in the documentation I found a reference to the order of the elements returned from the [[NSBundle mainBundle] loadNibName: function.
NSArray *nibContents = [[NSBundle mainBundle] loadNibNamed:#"yournib"
owner:self
options:nil];
UIView *myView = [nibContents objectAtIndex:0];
myView.frame = CGRectMake(0,0,300,400); //or whatever coordinates you need
[scrollview addSubview:myView];
3) Subclass UIViewController and let it manage the view as per normal. In this case I would never be actually pushing the view controller onto the stack, but only its main view:
ProgressViewController *vc = [[ProgressViewController alloc] initWithNibName:#"ProgressView" bundle:nil];
[vc.view setCenter:CGPointMake(self.view.center.x, self.view.center.y)];
[self.view addSubview:vc.view];
[vc release];
As far as I can tell, #3 is the the correct way of doing this (apart from programatically) but I am not entirely sure if it is safe to release the ProgressView's view controller whilst another controller's view is retaining its main view (gut feel says it is going to leak?)?
What do I do in terms of memory management in this case, where and when should I release the ProgressView's view controller?
Thanks in advance for your thoughts.
Cheers,
Rog
I think that your solution #3 adds unnecessary complexity by introducing a UIViewController instance just as a container for your ProgressView so that you can setup nib bindings. While I do think that it is nice to be able to work with an IBOutlet bound property rather than iterating through the nib contents you can do so without introducing a UIViewController whose behavior you neither need nor want. This should avoid your confusion around how and when to release the view controller and what, if any, side effects it might have on the responder chain or other behaviors of the loaded view.
Instead please reconsider using NSBundle and taking advantage of the power of that owner argument.
#interface ProgressViewContainer : NSObject {
}
#property (nonatomic, retain) IBOutlet ProgressView *progressView;
#end
#implementation ProgressViewContainer
#synthesize progressView = progressView;
- (void) dealloc {
[progressView release];
[super dealloc];
}
#end
#interface ProgressView : UIView {
}
+ (ProgressView *) newProgressView;
#end
#implementation ProgressView
+ (ProgressView *) newProgressView {
ProgressViewContainer *container = [[ProgressViewContainer alloc] init];
[[NSBundle mainBundle] loadNibNamed:#"ProgressView" owner:container options:nil];
ProgressView *progressView = [container.progressView retain];
[container release];
return progressView;
}
#end
Create a nib named "ProgressView" containing a ProgressView and set it's File's Owner class to ProgressViewContainer. Now you can create ProgressViews loaded from your nib.
ProgressView *progressView = [ProgressView newProgressView];
[scrollView addSubview:progressView];
[progressView release];
If you have multiple configurations of your progress view then maybe you'll want to implement a -initWithNibNamed: method on ProgressView instead of +newProgressView so you can specify which nib to use to create each ProgressView instance.
I vote for option #2. The return value from -[NSBundle loadNibNamed] is an array of the top-level objects. So as long as you have just one top level object in your nib, then the index 0 will be correct. The other views are subviews and not top level objects.
Another option of course is to do something like create a superclass for all of your view controllers that includes an outlet called something like 'progressView' and then connect your view to that outlet on file's owner in the nib. Seems like overkill for this, though.
I also prefer alternative #2. If the "0" is bothering you, you could:
Create a subclass of UIView called ProgressView
Create a nib-file called ProgressView.xib describing your progress view.
Select the topmost view in your nib, and set its Class to ProgressView in interface builder
then do
NSArray *nibContents = [[NSBundle mainBundle] loadNibNamed:#"ProgressView" owner:self options:nil];
ProgressView *progressView = nil;
for (UIView *view in nibContents) {
if ([view isKindOfClass:[ProgressView class]]) {
progressView = (ProgressView *) view;
break;
}
}
if (progressView != nil) {
//Use progressView here
}
I ended up adding a category to UIView for this:
#import "UIViewNibLoading.h"
#implementation UIView (UIViewNibLoading)
+ (id) loadNibNamed:(NSString *) nibName {
return [UIView loadNibNamed:nibName fromBundle:[NSBundle mainBundle] retainingObjectWithTag:1];
}
+ (id) loadNibNamed:(NSString *) nibName fromBundle:(NSBundle *) bundle retainingObjectWithTag:(NSUInteger) tag {
NSArray * nib = [bundle loadNibNamed:nibName owner:nil options:nil];
if(!nib) return nil;
UIView * target = nil;
for(UIView * view in nib) {
if(view.tag == tag) {
target = [view retain];
break;
}
}
if(target && [target respondsToSelector:#selector(viewDidLoad)]) {
[target performSelector:#selector(viewDidLoad)];
}
return [target autorelease];
}
#end
explanation here: http://gngrwzrd.com/blog-view-controller-less-view-loading-ios-mac.html

Resources