Should I create .h .m and xib files together ? Is it recommended ?
I added a TextAudioViewController.xib later and associated it to TextAudioViewController class.
I added each outlets.
I have no error in ViewDidLoad. But the view is still empty after tapping into TabBar.
I wonder if it is because I didn't create all files together. A kind of missing link between xib and class files ?
With another class, I created .h .m and xib together and the view was successfully loaded after tapping into TabBar...
EDITED
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
if (self.tabBarController == nil)
{
NSArray* languages = [NSLocale preferredLanguages];
NSString* currentLang = [languages objectAtIndex:0];
NSString* path = #"";
if ([currentLang isEqual: #"en"] || [currentLang isEqual: #"fr"] || [currentLang isEqual: #"es"])
{
path = [[NSBundle mainBundle] pathForResource:currentLang ofType:#"lproj"];
}else{
path = [[NSBundle mainBundle] pathForResource:#"en" ofType:#"lproj"];
}
selectedBundle = [NSBundle bundleWithPath:path];
[self startApplication];
}
return YES;
}
It is not necessary that you should create all the files together. After creating the xib file follow these steps.
1.Select the filesowner and change the class name in to TextAudioViewController, you can find the class name under filesinspector.
2.Connect your view outlet to the filesowner view.
Now it shoiuld be working fine.
It is not necessary that you should use initWithNibName:. You can use this only if your class name and xib name are different.
Related
I have a class, FooView, that is a subclass of UIView, and whose view is loaded from a nib, something like:
+ (instancetype)viewFromNib
{
NSArray *xib = [[NSBundle mainBundle] loadNibNamed:#"FooView" owner:self options:nil];
return [xib objectAtIndex:0];
}
The nib itself has its Custom Class set to FooView in the Identity Inspector.
This is instantiated as:
FooView *view = [FooView viewFromNib];
This behaves as you'd expect. However, when FooView is itself subclassed as FooSubclassView, and instantiated as:
FooSubclassView *view = [FooSubclassView viewFromNib];
view is still of type FooView, not FooSubclassView.
Swizzling the class with object_setClass doesn't fix the fact that the underlying object is an instance of FooView, and thus methods called on the subclass instance will be those of the superclass (FooView), not FooSubclassView.
How can I correct this so that subclasses are of the correct type, without having to create a new nib for every subclass, or having to reimplement viewFromNib in every subclass?
Swizzling is not (ever) the answer.
The problem is in your NIB; it is archived with object[0] being an instance of FooView, not FooSubclassView. If you want to load the same NIB with a different view subclass as object[0], you need to move the instance out of the NIB's archive.
Probably the easiest thing to do, since your class is already loading the NIB, is make an instance of FooView or FooSubclassView the File's Owner.
This question has a decent explanation of the File's Owner pattern. Note that you are pretty much already there in that your class is what is loading the XIB/NIB anyway.
And here is the official docs on File's Owner.
I'm not sure you are onto the best solution, but I think this is what you are looking for.
+ (instancetype)viewFromNib
{
NSString *className = NSStringFromClass([self class]);
NSArray *xib = [[NSBundle mainBundle] loadNibNamed:className owner:self options:nil];
return [xib objectAtIndex:0];
}
That is as long as you can be sure that the NIB has the same name as the class.
After realizing that I mistook one of the requirements, I say I'll have to agree with #bbum.
- (id)init
{
// NOTE: If you don't know the size, you can work this out after you load the nib.
self = [super initWithFrame:GCRectMake(0, 0, 320, 480)];
if (self) {
// Load the nib using the instance as the owner.
[[NSBundle mainBundle] loadNibNamed:#"FooView" owner:self options:nil];
}
return self;
}
+ (instancetype)viewFromNib
{
return [[self alloc] init];
}
I'm trying to write a unit test that setup the view controller, I've tried two ways to get the view init, the first way is to use the bundle to load nib content and filter out the one I'm looking for, as follow:
MyViewController *controller = nil;
....
NSArray* nibContents = [[NSBundle mainBundle] loadNibNamed:#"MyView" owner:self options:nil];
NSEnumerator *nibEnumerator = [nibContents objectEnumerator];
NSObject* nibItem = nil;
while ( (nibItem = [nibEnumerator nextObject]) != nil) {
if ([nibItem isKindOfClass:[MyViewController class]]) {
controller = (MyViewController*) nibItem;
break;
} else {
NSLog(#"nibItem class is %#", [nibItem class]);
NSLog(#"nibItem is %#", nibItem);
}
}
After these code finished, the controller still be nil, I've insert some logs to inspect the nib class(the %# place holder), and it turns out is the same as MyViewController (at least both classes description did), and I'm so sure these code works very well in the debug/release target, but it's not worked while I run the unit tests.
So is that means the classes is different although their classes description are the same?
The second way I've tried is use the initWithNibNamed:owner:options method, just simply specify the xib file name, but Xcode reply that the nib loaded but view outlet not set, the situation just as the questions describe I found, but I have double check that things have been setup correctly:
In Interface Buildedr, specify the custom class name
Add xib file to the list of copy build resources section in my test target
Link the tableview outlet to the interface file (though I can not drag the view outlet to my class, but it's auto pointed to the tableView in the class)
till now the only way I could get the test pass is manually to set the view controller's view and table view.
MyViewController *controller;
NSArray* nibContents = [[NSBundle mainBundle] loadNibNamed:#"MyView" owner:self options:nil];
controller=(MyViewController *)[nibContents objectAtIndex:0];
replace your existing code with this one , Hope this will help you.
I want to create an iPhone application with English and Arabic language. I checked the Internationalization document for language switcher, however to take that into effect I have to manually go and change the iPhone setting. I don't want to do that. So what I am planning is on home screen I will have two button as English and Arabic. If user click Arabic, I will have arabic text and if user select English, app will be in english.
Any idea/ suggestion how to get this done?
Note: I don't want to manually go and change the language.
Edit 1
As per #Jano, I have done below.
Created new project. Added Arabic in Localization. Now I have two storyboard and two InfoPlist.strings file.
Added Localization.h and .m file as shown in answer.
Directory structure is MyProject-ar.lproj & MyProject-en.lproj
Content of Plist are "myButton01" = "Back"; & "myButton01" = "ظهر";
First View Controller have two button as English and Arabic. Called action on those button.
- (IBAction)pressedEnglish:(id)sender {
[Localization sharedInstance].fallbackLanguage = #"ar";
[Localization sharedInstance].preferredLanguage = #"en";
NSLog(#"pressed english");
}
- (IBAction)pressedArabic:(id)sender {
[Localization sharedInstance].fallbackLanguage = #"en";
[Localization sharedInstance].preferredLanguage = #"ar";
NSLog(#"pressed arabic");
}
In second view controller, I added one button and gave name as myButton. Now in viewDidLoad, I have
[self.myButton setTitle:localize(#"myButton01") forState:UIControlStateNormal];
I hope this should be working, however when I run the project, I see button as myButton01
Any reason why this is happening?
Edit 2
I got Edit 1 problem. I renamed InfoPlist.strings to Localizable.strings and it worked. But but but, I am still getting Arabic text irrespective of whatever button I press.
When finding reason, I found that it was because of below statement that we have in Localization.m
static Localization *shared = nil;
dispatch_once(&pred, ^{
shared = [[Localization alloc] init];
shared.fallbackLanguage = #"en";
shared.preferredLanguage = #"ar";
The problem is at last two lines. As we have set Arabic as preferredLanguage, I am always seeing the arabic text.
What changes will I need to do so that I can have it as changeable as per button pressed.
You want to set the language of the app from the app UI ignoring the user preference on the device. This is unusual, but here you go...
First write all your language strings on a directory structure like this:
i18n/en.lproj/Localizable.strings
i18n/ar.lproj/Localizable.strings
Create an additional directory with the corresponding two letter code for each additional language supported.
If the files are recognized as i18n resources, they will be presented like this:
Files will have a key=value with the following format:
"button.back" = "ظهر";
In your code, replace any localizable string with the key. Example:
[self.stateBtn setTitle:localize(#"button.back") forState:UIControlStateNormal];
Usually you would use NSLocalizedString(#"key",#"fallback") but since you want to ignore iPhone settings, I wrote a localize(#"key") macro above that will have the following implementation:
Localization.h
#ifndef localize
#define localize(key) [[Localization sharedInstance] localizedStringForKey:key]
#endif
#interface Localization : NSObject
#property (nonatomic, retain) NSBundle* fallbackBundle;
#property (nonatomic, retain) NSBundle* preferredBundle;
#property (nonatomic, copy) NSString* fallbackLanguage;
#property (nonatomic, copy) NSString* preferredLanguage;
-(NSString*) localizedStringForKey:(NSString*)key;
-(NSString*) pathForFilename:(NSString*)filename type:(NSString*)type;
+(Localization*)sharedInstance;
#end
Localization.m
#import "Localization.h"
#implementation Localization
+(Localization *)sharedInstance
{
static dispatch_once_t pred;
static Localization *shared = nil;
dispatch_once(&pred, ^{
shared = [[Localization alloc] init];
[shared setPreferred:#"en" fallback:#"ar"];
});
return shared;
}
-(void) setPreferred:(NSString*)preferred fallback:(NSString*)fallback
{
self.fallbackLanguage = fallback;
self.preferredLanguage = preferred;
NSString *bundlePath = [[NSBundle mainBundle] pathForResource:#"Localizable" ofType:#"strings" inDirectory:nil forLocalization:self.fallbackLanguage];
self.fallbackBundle = [[NSBundle alloc] initWithPath:[bundlePath stringByDeletingLastPathComponent]];
bundlePath = [[NSBundle mainBundle] pathForResource:#"Localizable" ofType:#"strings" inDirectory:nil forLocalization:self.preferredLanguage];
self.preferredBundle = [[NSBundle alloc] initWithPath:[bundlePath stringByDeletingLastPathComponent]];
}
-(NSString*) pathForFilename:(NSString*)filename type:(NSString*)type
{
NSString *path = [self.preferredBundle pathForResource:filename ofType:type inDirectory:nil forLocalization:self.preferredLanguage];
if (!path) path = [self.fallbackBundle pathForResource:filename ofType:type inDirectory:nil forLocalization:self.fallbackLanguage];
if (!path) NSLog(#"Missing file: %#.%#", filename, type);
return path;
}
-(NSString*) localizedStringForKey:(NSString*)key
{
NSString* result = nil;
if (_preferredBundle!=nil) {
result = [_preferredBundle localizedStringForKey:key value:nil table:nil];
}
if (result == nil) {
result = [_fallbackBundle localizedStringForKey:key value:nil table:nil];
}
if (result == nil) {
result = key;
}
return result;
}
#end
This will use lookup the key strings in the arabic file, and if the key is missing, it will look in the arabic file. If you want it the other way, do the following from your button handlers:
[[Localization sharedInstance] setPreferred:#"ar" fallback:#"en"];
Sample project at Github.
If localisation doesn't work
If localisation doesn't work, use the plutil command line tool to verify the format of the file. It should output: Localizable.strings: OK. Example:
$ plutil -lint Localizable.strings
Localizable.strings: OK
This format is described in Internationalization Programming Topics > Localizing String Resources. You can optionally add // single-line or /* multi-line */ comments. For non latin languages it’s recommended to encode Localized.strings in UTF-16. You can convert between encodings in the inspector pane of XCode.
If it still doesn't work, check that you are copying the Localizable.strings file in the Copy files phase of your target. Note that when you add Localizable.strings files there, sometimes they appear in red, keep doing it until a file appears in black, then delete the red ones (hacky I know, blame Xcode).
I'm familiar with most of the process of creating an XIB for my own UIView subclass, but not everything is working properly for me - it's mostly to do with the IBOutlets linking up. I can get them to work in what seems like a roundabout way.
My setup is this:
I have MyClass.h and MyClass.m. They have IBOutlets for a UIView (called view) and a UILabel (called myLabel). I added the 'view' property because some examples online seemed to suggest that you need this, and it actually solved an issue where I was getting a crash because it couldn't find the view property, I guess not even in the UIView parent class.
I have an XIB file called MyClass.xib, and its File's Owner custom class is MyClass, which prefilled correctly after my .h and .m for that class existed.
My init method is where I'm having issues.
I tried to use the NSBundle mainBundle's 'loadNibNamed' method and set the owner to 'self', hoping that I'd be creating an instance of the view and it'd automatically get its outlets matched to the ones in my class (I know how to do this and I'm careful with it). I then thought I'd want to make 'self' equal to the subview at index 0 in that nib, rather than doing
self = [super init];
or anything like that.
I sense that I'm doing things wrong here, but examples online have had similar things going on in the init method, but they assign that subview 0 to the view property and add it as a child - but is that not then a total of two MyClass instances? One essentially unlinked to IBOutlets, containing the child MyClass instantiated via loadNibNamed? Or at best, is it not a MyClass instance with an extra intermediary UIView containing all the IBOutlets I originally wanted as direct children of MyClass? That poses a slight annoyance when it comes to doing things like instanceOfMyClass.frame.size.width, as it returns 0, when the child UIView that's been introduced returns the real frame size I was looking for.
Is the thing I'm doing wrong that I'm messing with loadNibNamed inside an init method? Should I be doing something more like this?
MyClass *instance = [[MyClass alloc] init];
[[NSBundle mainBundle] loadNibNamed:#"MyClass" owner:instance options:nil];
Or like this?
MyClass *instance = [[[NSBundle mainBundle] loadNibNamed:#"MyClass" owner:nil options:nil] objectAtIndex:0];
Thanks in advance for any assitance.
The second option is the correct one. The most defensive code you could do is like this:
+ (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;
}
And call like this:
MyClass *myClassInstance = [Utility loadNibNamed:#"the_nib_name"
ofClass:[MyClass class]];
// In my case, the code is in a Utility class, you should
// put it wherever it fits best
I'm assuming your MyClass is a subclass of UIView? If that's the case, then you need to make sure that the UIView of your .xib is actually of MyClass class. That is defined on the third Tab on the right-part in the interface builder, after you select the view
All you need to do is create the subview via loadNibNamed, set the frame, and add it to the subview. For example, I'm adding three subviews using my MyView class, which is a UIView subclass whose interface is defined in a NIB, MyView.xib:
So, I define initWithFrame for my UIView subclass:
- (id)initWithFrame:(CGRect)frame
{
NSLog(#"%s", __FUNCTION__);
self = [super initWithFrame:frame];
if (self)
{
NSArray *nibContents =
[[NSBundle mainBundle] loadNibNamed:#"MyView"
owner:self
options:nil];
[self addSubview:nibContents[0]];
}
return self;
}
So, for example, in my UIViewController, I can load a couple of these subclassed UIView objects like so:
for (NSInteger i = 0; i < 3; i++)
{
CGRect frame = CGRectMake(0.0, i * 100.0 + 75.0, 320.0, 100.0);
MyView *myView = [[MyView alloc] initWithFrame:frame];
[self.view addSubview:myView];
// if you want, do something with it:
// Here I'm initializing a text field and label
myView.textField.text = [NSString stringWithFormat:#"MyView textfield #%d",
i + 1];
myView.label.text = [NSString stringWithFormat:#"MyView label #%d",
i + 1];
}
I originally advised the use controllers, and I'll keep that answer below for historical reference.
Original answer:
I don't see any references to view controllers here. Usually you'd have a subclass of UIViewController, which you would then instantiate with
MyClassViewController *controller =
[[MyClassViewController alloc] initWithNibName:#"MyClass"
bundle:nil];
// then you can do stuff like
//
// [self presentViewController:controller animated:YES completion:nil];
The NIB file, MyClass.xib, could specify that the base class for the UIView, if you want, where you have all of the view related code (e.g. assuming that MyClass was a subclass of UIView).
Here's one method that I use:
Create a subclass for UIView, this will be called MyClass
Create a view xib file. Open in interface builder,
click File's Owner and in the Identity Inspector, change the class
to that of your parent view controller, e.g.
ParentViewController.
Click the view already in the list of objects and change it's
class in Identity Inspector to MyClass.
Any outlets/actions that you declare in MyClass will be
connected by click-dragging from View (not File's Owner). If you want to connect them to variables from ParentViewController then click-drag from File's Owner.
Now in your ParentViewController you need to declare an instance
variable for MyClass.
ParentViewController.h add the following:
#class MyClass
#interface ParentViewController : UIViewController {
MyClass *myClass;
}
#property (strong, nonatomic) MyClass *myClass;
Synthesize this in your implementation and add the following in your
viewDidLoad method:
- (void)viewDidLoad {
[super viewDidLoad];
[[NSBundle mainBundle] loadNibNamed:#"MyClass" owner:self options:nil];
self.myClass.frame = CGRectMake(X,Y,W,H); //put your values in.
[self.view addSubview:self.myClass];
}
I'm struggling with some basics in my storyboard-based iOS Application.
I like to load some UIView's which are builded up in NIB-Files (xib). If I load the View, the initWithCoder-method will be called.
Now I like to load "green" Views; I read, that I'm able to put some other initializations in the initWithCoder - but only "new" objects and nothing, that regarding the View itself, like self.backgroundColor in cause of memory (maybe the object isn't complete initialized at that point).
Where is the best place to add some stuff, like setBackgroundColor, setCornerRadius in the view-Class itself?
I'm doing it like this:
NSArray* nibViews = [[NSBundle mainBundle] loadNibNamed:#"MyCustomView" owner:nil options:nil];
self = [nibViews objectAtIndex: 0]; //here it would be best practice to loop through nibViews and get the first view that is member of your class.
Also , the view , in the nib file is set as a MyCustomView instead of UIView.
After these 2 lines you can set any values that you want. Be aware though , if you set the frame for super for example , it will be overwritten by the values in the xib file. So better set everything AFTER loading the nib , not before.
As far as I know , there are no callbacks for UIView loading as viewDidLoad is for UIViewController.
Hope this helps.
Cheers!
EDIT:
You can do something like this:
- (id)initWithInfo:(MyInfoClass *) selectedInfo Body:(NSString *) _body
{
if ((self = [super init]))
{
NSArray* nibViews = [[NSBundle mainBundle] loadNibNamed:#"MyCustomClass" owner:nil options:nil];
self = [nibViews objectAtIndex: 0];
[self setInfo:selectedInfo];
[self setBody:_body];
}
}
And then just use that initWithInfo: Body: method.