Load XIB from Custom Bundle via Static Library - iphone

I want to load a XIB from a CFBundle, via some code in a Static Library.
Example:
MyViewController * foo = [[Static Library] instance] getMyViewController];
So I have a MyViewController .xib in a CFBundle I manually create, in it's Resources dir.
But if I try to load it via [[MyViewController alloc] initWithNibName:#"MyViewController" bundle:MyBundlePointer] in getMyViewController I get NSInternalInconsistencyException.
Any idea? I am more or less stumped on this one. Not sure it is even possible to do this.

I'm currently having exactly the same issue with Monotouch and that's why I found your question.
I get the very same exception as you do. This makes me assume that I'm not facing a Monotouch issue but a basic (mis)behavior of XIBs/Cocoatouch.
So the answer is: no, it is not possible. Very sad though.
René

this may not be your specific issue, but it is potentially an issue:
CFBundle is not toll-free-bridged with NSBundle. since you used the term CFBundle in your description, i am wondering if you're passing a CFBundle, rather than an NSBundle.
these are two distinct types and you'll need to explicitly create an NSBundle instance because you can't just cast a CFBundleRef as a NSBundle as you can with CF/NS-String, Array, Data, etc.

Related

Sharing strings and variables throughout ios app

I'm making an app for a final project for class and I need to share strings, integers and floats throughout different views in my application. I have to create an app that a waiter/waitress would use on the job. I need different views for different types of items to order (beverages, appetizers, entrées, etc.) and need to keep the items chosen, the price and the quantity of each accessible to different views to keep a running total available in each view (one view for each type of item) and finally for a view that displays an itemized list of what was ordered plus the total.
From what I understand I wouldn't want to use a tab bar application layout because typically users don't expect information to be the same between different tabbed views, so I was thinking of using a segmented controller for this. To get the strings, integers and floats shared between views could declare them in the AppDelegate? I've read that I could use singletons but we haven't covered that in my class so I think that may just add more complexity than I need. If I were to declare them in the AppDelegate would I have to synthesize them in the AppDelegateViewController.m to make them available throughout? I can't imagine I'd have to synthesize them for each different ViewController. Or would NSUserDefaults be perfect for this situation?
If I declared an instance variable in the AppDelegate would that essentially make it a global variable? I know that this is against encapsulation practices but that won't make a big difference with this app, it's not going to be in the App Store and the overhead shouldn't make a big difference considering this is going to be a relatively small app.
Thanks in advance everyone. Good luck with your finals if you still have them approaching!
Edit
I guess I should say that this is going to be a janky, terrible app off the bat, we didn't cover a lot of the more advanced topics such as the MVC paradigm for iOS so I'm pretty limited with what I can do compared to what we're supposed to do. Stupid class, I regret signing up for it when I really could have gone about this myself and gotten a better understanding of Objective-C, which we were taught nothing about, and a better understanding of the iOS framework.
Basically, if I declare the variables in the AppDelegate, even though it's a faux pas, would that work to access the strings and such from the different views? Or would the NSUserDefaults be a better solution in this case? I'm leaning towards NSUserDefaults and declaring them in the projects ViewController.
A typical Objective C MVC solution is to put all your related shared strings and variables into a Model object (create a new class for this object) as properties. Then share that Model object with all the Controllers that need to access those variables or strings.
Using the AppDelegate as a model object is "over-sharing", and clutters up the app delegate with non-delegate related stuff. A slightly cleaner solution is to have the app delegate hold a getter to a Model object that is widely used throughout the app. A solution that can cause code reuse problems, and is otherwise considered politically-incorrect, is to assign the model object, and/or the shared variables themselves, to C global variables (be careful with your retains). (But this usually generates the fastest and smallest amount of code, if you care about such things.)
As hotpaw2 said, using a distinct model class is the best way to go.
But, as this is a small app (while you are learning the basics) implementing these few variables in the app delegate wouldn't hurt and is perhaps a little easier.
If the app grows then you'll probably want to move the model out of the app delegate and into it's own class.
An even easier and simpler method would be to store this small amount of data in nsuserdefaults:
save to nsuserdefaults:
[[NSUserDefaults standardUserDefaults] setObject:howManyDrinks forKey:#"howManyDrinks"];
[[NSUserDefaults standardUserDefaults] synchronize];
retrieve from nsuserdefaults:
NSString *howManyDrinks = [[NSUserDefaults standardUserDefaults] objectForKey:#"howManyDrinks"];
You can utilize the facility of Delegation pattern using Protocols in Objective C.
You could also store these values in a plist, which can then be accessed and edited from anywhere within the app. It is easier to implement than a singleton. And since you can set the plist up visually in XCode, it's relatively easy for beginners. I even created a wrapper for my plist to make editing the data simple. Here's a tutorial for plists.
I'm not an expert in this yet, but I've been using a singleton, with an object inside. As far as I have experienced, the values inside that object is held. To create a singleton, you'd do something like this:
Create a normal Object, and add this to the header file:
+ (id)sharedManager;
Then in the .m file, add this:
+ (id)sharedManager {
static SharedInfo *sharedMyManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedMyManager = [[self alloc] init];
});
return sharedMyManager;
}
- (id)init {
if (self = [super init]) {
self.myStuff = [[NSMutableDictionary alloc]init];
//This is where you initialise your variables. The example above is for a
//property that I have declared in the .h file
}
return self;
}
And then, throughout your app you'd use this:
[[[SharedInfo sharedManager] myStuff] addObject: SomethingTocreate];
Make sure you include the SharedInfo.h in the viewcontroller that you want to use it in.
I hope this helped.

iOS: Using device modifiers for loading xib files?

You can use a device modifier (i.e., ~ipad) to provide a device-specific key in Info.plist, and to specify a device-specific launch image (Default.png for iPhone, and Default~ipad.png for iPad, for example). Those two things are specifically mentioned in Apple Docs, but they don't say that this will work for any other kinds of files.
I've discovered (quite by accident) that this works for loading .xib files via initWithNibName:bundle:. So for example, I can have MyView.xib and MyView~ipad.xib, and this code:
MyViewController *viewController = [[MyViewController alloc]
initWithNibName:#"MyView" bundle:nil];
... will totally load MyView~ipad.xib on an iPad, and MyView.xib on other devices.
So, 1) Is this documented somewhere? I sure couldn't find it any any Apple docs. It's sure handier than checking UI_USER_INTERFACE_IDIOM() and hardcoding two different nib names everywhere, but I kinda don't trust it if it isn't documented.
And, 2) Does anyone know what version of iOS this started working in? I've only tried it in 4.2, and it works there. Device modifiers in general (even for the documented things listed above) are 4.0 minimum.
I had this same problem. The answer didn't make sense at first, but the good news is that it's easy to do! :)
Just name your iPad xibs without any modifier and your iPhone xibs with ~iphone modifier and it'll select them correctly.
So, with MyViewController, you'll have MyViewController.xib for the iPad and MyViewController~iphone.xib for the iPhone. Then you can just init your view controller with simple alloc/init.
[[MyViewController alloc] init] and it'll grab the right xib.
So, when I create a new view controller in XCode, I always choose the box to format it for ipad, because the xib it will create will be named MyViewController.xib and you want that one to be the iPad sized xib. Then I create a second xib, formatted for iPhone and name it with the ~iphone modifier.
The documentation is a little contradictory at times, but this page talks about how resources with an identifier will default to iPad.
ImageSoundResources
Check the section about using high res images. I know we're talking xibs and not images, but it does work. My last 6 apps have all used this idiom.
Actually, it is explicitly defined in the docs, but as a footnote.
CocoaNibs
In the note at the bottom of "Loading NIB files using NSBundle":
Note: If you are developing a
Universal application for iOS, you can
use the device-specific naming
conventions to load the correct nib
file for the underlying device
automatically. For more information
about how to name your nib files, see
“iOS Supports Device-Specific
Resources.”
Which links to Cocoa Conceptual LoadingResources
However, yes, this is a 4.0+ only feature.
I hate to be that guy and answer my own question, but I think the answer is:
1) Nope, not explicitly documented in any Apple documentation, and
2) 4.0 and higher (this based on my own testing)
All you really save is a couple lines of code checking for UI_USER_INTERACE_IDIOM(). Still, I'll take it. Less code is less code.
The appropriate technique to use in iOS 3.2 and later is the UI_USER_INTERFACE_IDIOM() function. I typically use a ternary operator to init the UIViewController with the appropriate XIB.
UIViewController* controller = [[UIViewController alloc]
initWithNibName:UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad ?
#"YourViewController-iPad" : #"YourViewController" andBundle:nil];

iOS media picker does not show up

I am currently working on an audio application on iPhone. It is based on apple's SpeakHere sample code with a user-defined input file from iPod library.
Here is the event raised by the button:
- (IBAction) btn_PickSong_Clicked:(id)sender{
[self showMediaPicker];
//code importing tracks from library
}
And in showMediaPicker method:
//Yup the program does reach this method but the picker does not show up
- (void)showMediaPicker {
MPMediaPickerController* mediaPicker = [[[MPMediaPickerController alloc] initWithMediaTypes:MPMediaTypeMusic] autorelease];
mediaPicker.delegate = self;
[self presentModalViewController:mediaPicker animated:YES];
}
The problems are:
The library import feature works fine in a separate program, but the media picker does not show up anymore when I put the code into SpeakHereController.mm.
Also if I place the showMediaPicker method in another class and call it, it does not work either.
Something I find it might be relevant:
The original code is in an obj-C file (xxx.m), and now it's transferred into an obj-C++ file (xxx.mm).
I have also modified the base class of SpeakHereController.h from NSObject to UIViewController<MPMediaPickerControllerDelegate> otherwise it will throw a warning that the base class does not contain the required delegate. But in the interface builder it is still displayed as an object (Please refer to SpeakHere sample code).
It seems that it's illegal to convert the built-in xxxViewController.m file to obj-C++ file (.mm extension). In this case a lot of errors will show up if I attempt to do so. Is it true? If so, how to include C++ code in a pure obj-C file?
=============
So how can I make the media picker show up in this case? Any insight will be appreciated.
Thank you very much!
Cheers,
Manca
In order for
[self presentModalViewController:mediaPicker animated:YES];
to work, self needs to be a viewcontroller. I'm worried that you have just changed the base class to avoid compiler errors as this suggests you are not actually instantiating 'self' correctly.
So how are you initialising the SpeakHereController? As a view controller, this would normally be via the designated initialiser, which for a UIViewController is of course initWithNibName:bundle:
You may find the documentation for UIViewController helpful.
With regards to the C++ issue. Although you can mix objective-c and c++ in the way you suggest, I would recommend that you encapsulate your c++ code in it's own class rather than sprinkling it around your viewcontroller code. That will make it more maintainable for the future.

Can you swap the NIB file for a UIViewController that's already on-screen?

For instance:
Create a new UIVC using initWithNibName, using "nib-v1"
Display it, e.g. using [(UINavigationController) nav pushViewController: myVC]
Change the NIB that myVC is using to "nib-v2"
So far as I can see, this is the "correct" approach to app-design for a lot of apps, when paging through information where you need two slightly different UI screens for the info being displayed.
For instance, most of your pages are text, but some of them have an image too (think of an RSS reader, where some RSS entries have text + image, some are text only).
I've dealt with this previously by having one NIB file with a second, invisible, named UIView instance that I layered over the top of the first one, and switched on/off depending on on context, using the "hidden" flag.
But this is clearly wrong, and wastes memory.
However, I cannot see an obvious way to "reload" the view from the NIB file. I'm guessing I want to somehow reproduce the magic that initWithNibName does?
I suspect this is possible, but I'm sure that if you do it "the wrong way" then the app will simply crash horribly.
You can always perform
[[NSBundle mainBundle] loadNibNamed:#"FileName" owner:viewController options:nil]];
but this will undoubtedly really mess things up, if you're not sure what you're doing, especially if view is connected in both of those nibs
You should redesign your view controller hierarchy to swap between two different controllers that load from two different nib files.
Alternately, you can have the controller manage swapping views that it loads from different files that are unrelated to it's nibName. In that case, you can load them in the above manner. And you will want to have their outlets (to, for example, subviewOne and subviewTwo) connected in different nibs.
You should check out the UINib class to see if it does what you want. It will allow you to load a nib file and keep it in memory.
But just to clarify: Do you want to modify the nib file itself? Or do you want to modify the contents of the nib file when it has been loaded into memory?
Off the top of my head, the first would be quite difficult (you can't modify the original file, since it is part of application bundle...maybe you copy it to Documents folder and write your own coder/decoder?) The second is easier, but I am not sure what the reason would be? Why not just modify the viewController/view after it has been loaded (in awakeFromNib, for example) and, if you want those changes to persist, save those changes to file afterwards.
In short, I don't know exactly what you would like to do, but the chances seem high to me there might be a better way to do it.
I agree with Rob, but if you really want to mess with swapping nib's (which is bad as it can easily lead to dangling pointers and the like), you could maybe load the view from the new nib with NSBundle's - (NSArray *)loadNibNamed:(NSString *)name owner:(id)owner options:(NSDictionary *)options method and do the view swapping yourself.
You should rather use different view controllers for different types of content. If they only differ slightly, you can still consider creating one base class and subclassing the different variations.
You shouldn't change what NIB file a UIViewController uses. Attaching the NIB to the UIViewController should be a one-time event. The hidden views are fine; they're certainly not clearly wrong. You can also programmatically add view elements after loading. If you're doing a lot of that, you can skip the NIB entirely and programmatically build the view in -loadView. Any of these are fine, but don't switch the NIB after initialization. I don't even recommend having multiple NIBs that you choose between for a given UIViewController class; it's just too confusing. Generally each NIB should map to a dedicated UIViewController class with a very similar (or identical) name.
In a related note, I recommend moving the name of the NIB into the UIViewController, as described in an earlier posting.
I came looking for an answer to the same problem, and ended up solving it like this:
UIViewController* ctrler = [[UIViewController alloc] initWithNibName:#"NewControllerNIB" bundle:nil];
// Replace previous controller with the new one
UINavigationController* nav = self.navigationController;
[nav popViewControllerAnimated:NO];
[nav pushViewController:ctrler animated:NO];
I'm not sure if it's possible the current controller gets deallocated before the call to push the new controller is executed, but so far it seems to work (until I just redesign the app with a better approach)

Best Application Delegate Practice

I have been making a few apps here and there, and I know my way around. What has always confused me is accessing global attributes, and where the best place to set them are. I have a few questions about accessing things and how to access them.
Do you have to include your application delegates header file into any other other file you want to access it in? Say I have a view controller, that I would like to use, do I need to include the .h inside my view controller's .h? Or can I set the:
#class AppDelegate;
Can you only access the delegate by typing out:
[UIApplication sharedApplication].delegate
EACH and every time? Is that something I just have to get used to? Or could I set the following in my implementation in each .h:
AppDelegate *delegate;
And inside the init function, put the singleton instance to that variable?
Sorry if this was off structure, but I think it's a logical question people have a problem encountering and solving.
Maybe you need to reconsider how you are using the App Delegate? It sounds to me like perhaps you are not making a very good class design.
Regardless, here's a way to make it easy. Don't put this in init just use it when you need it.
MyAppDelegate *delegate = (MyAppDelegate *)[[UIApplication sharedApplication] delegate];
Naturally, replace MyAppDelegate with the actual class name of your app delegate.
Another possibility is to add the code to use a properly casted app delegate reference as a #define in the app delegate header file, so after including it you can do something like:
MYAPPDELEGATE.customProperty = blah;
However I tend to favor just writing out the line that John presented, as use of #defines confuses code completion which I find more annoying than just typing the line.
As also mentioned, if you have a ton of references to the app delegate you may want to restructure to keep some of those references closer to home.