Get the name of the current iPhone xib being viewed - iphone

I have 3 xib files connected to the same class to reduce duplicate coding. However, there are different stuff that I want done when the new nib file loads (i.e. in the ViewDidLoad method). I've thought about doing an if statement to compare the name of the xib currently being displayed to a string. I have been trying to figure out how to do that for most of the day but I haven't had any luck. Here's some pseudo code if that confused you:
if (currentXibInDisplay == #"XibFileName1")
// Do This...
else if (currentXibInDisplay == #"XibFileName2")
// Do This...
There is a way to do that, right? It seems pretty simple but I am pretty stumped right now. Thanks for any info you can provide.

I tried to use it with
self.view.nibName;
But it won't work, instead try this
self.nibName;

You can use the nibname
if you say: NSString *name = self.view.nibname you can proove it with
`[name isEqual:#"XIBName"]`

The view controller that you're loading has the initWithNibNamed: method, you can override there and store which XIB is going to be loaded.

ViewController subclasses have a nibName string property you can check against:
[self.nibName isEqualTo:#"XIBFileName"]

Related

What is the meaning of the console message: "snarfed from ivar layout..."?

I have a console message that appears to be triggered by apparently unrelated events.
The message states:
snarfed from ivar layout: [propertyName] = [constantString]
Where [propertyName] is the name of a property to which I set the value of a string constant [constantString].
What causes this message and what does it means?
I also ran into this issue recently. I was able to fix my specific issue, but I don't think that is exactly what the questioners are running into, since my issue was only being exposed in VoiceOver mode. As such, I'll offer thoughts on what I think is generally occurring and then I'll speak to my specific issue.
As for the general issue, I think that the Apple Framework is deciding to look through all of the ivars of a particular class in order to extract some information that it wants, but that is not provided by other parts of the UI element. This seems a little bizarre to me, but that is what I encountered.
So, to continue with the general case, and in answer to the initial question. If you're like me, then your property name is probably the same as your ivar. Try explicitly defining a getter method for that property. Then, set a breakpoint within that getter if you will be returning a non-nil value. Look at the stacktrace and that should tell you which piece of the apple frameworks is deciding to loop through your ivar layout in order to get the information it wants. (If you're not using the the same name for your property and ivar, then just define a property and getter with the ivar name and do the same thing with the breakpoint.)
My specific case was for a Custom Table Cell (like one of the commenters). In that cell,I had a property that was the same name as its ivar. I also had an explicitly defined getter for that property. I also referenced that custom table cell from the Nib file. So, it looked something like this:
class CustomTableViewCell:UITableViewCell
{
NSString *s ;
}
#property(nonatomic,retain) NSString *s ;
and in the implementation:
#synthesize s ;
-(NSString *)s
{
if( !s )
return self.reuseIdentifer ;
return s ;
}
I put a breakpoint in the return self.reuseIdentifier line,and that showed me a stacktrace from the Accessibility functions. The stacktrace showed my method being called by an Apple internal method that was looping through all of my ivars looking for something to use as the accessibilityLabel for my table cell. The name of the selector is '_accessibilityRetrieveTableViewIvarsText'.
To make matter worse, in my case, this was not just a debugger issue, it was messing up my Accessibility interface by using the wrong thing as the accessibilityLabel.
I came up with 3 fixes for my specific problem:
1) I added a value for the accessibilityLabel for the table cell inside the Nib. This satisfied the Apple framework to the point where it did not go searching through my ivars. This was not the solution I went with, however, because I did not want a static accessibility label.
2) I subclassed my CustomTableViewCell with an empty implementation and interface, and I used that as my Table cell class inside the Nib. This solved the problem because the Apple Framework looped through that class's ivars, of which there were none, and there weren't any values to 'snarf'. I did not use that solution either, but it might be the best one because it keeps Apple's frameworks from inspecting my ivars.
3) The solution I decided on was to make my ivar private and to define the property with a different name. That seems to be the standard way that a lot of folks use properties. This is what it looks like:
class CustomTableViewCell:UITableViewCell
{
#private
NSString *_s ;
}
#property(nonatomic,retain) NSString *s ;
and in the implementation:
#synthesize s = _s ;
-(NSString *)s
{
if( !_s )
return self.reuseIdentifer ;
return _s ;
}
This fixed the problem because nil is returned when Apple inspects the ivar, and, thus, nothing is 'snarfed'. I'm still not sure whether this or #2 is more appropriate.
"snarfed from ivar" basically autofills your accessibilityLabel. If you do that yourself, the message goes away, as there is no more need for sneeking into your UITableViewCell.
For future reference. The message is logged by the accessibility framework, which apparently looks through an UIView ivars for strings.
If you have a custom subclass you can define the custom attributes as specified in the following link:
Accessibility Programming Guide
Alternatively you can make the view subclass not accessible:
- (BOOL)isAccessibilityElement
{
return NO;
}
However, note:
If your application contains a custom individual view with which users need to interact, you must make the view accessible.

How do I "connect" a table view to a view controller

Alright, I know this is a vague conceptual question, but I really need help here. Thanks in advance if you decide to take the time to read this. I would never even consider writing this much except this is such a great forum with so many helpful people I thought this would be the best place to ask.
This is all related to the question here (you don't have to look at it - I explain everything below): Pass parameter when initializing table
I've been working for days on the same problem, but I'm realizing there must be something big I'm missing. I've googled and googled and I even bought (and about 50% read) two Obj-C books a few days ago, but I'm still grasping at something that seems like it should be incredibly easy. I'm obviously pretty new to OOP, but I have mediocre skills in HTML, perl, sql, python as well as some of the ancient stuff like pascal and basic. I'm a n00b, but not a moron (well, actually this experience is changing my mind a bit).
Anyway, my end goal here is to simply create an app with 8 "normal" buttons on the first view (level 1), each doing basically does the same thing - which is to show a simple table view (level 2) with data in cells that can be clicked to continue to drill down to details (level 3). Very basic and straightforward concept. The only difference between the 8 possible level 2's is the data that will be shown. I've already constructed sql queries that work just as I want for each button.
So, here's where I stand: I have a perfectly working app that does everything from level 2 down exactly as I expect - the queries work, the tables are beautiful - so that's great.
Also, I have another nav-based app that launches on "level 1" and shows me 8 buttons (I hide the nav bar on level 1). If I click any of the buttons on level 1, the level 2 view (which is a nav bar + a table) slides into view exactly like I want. The problem is the table is just blank. No matter what I do, I can't get the level 2 in the second app to show me the data, even though I can show all of that data in the first app perfectly. For the life of me, I can't figure out how to "link" level 1 with level 2.
Hopefully you can understand this gap I'm trying to bridge. Since there are 8 possibilities for level 2 (with only very slight differences in sql queries on the same sql table), I initially tried coming up with a way of "passing" an integer to the level 2 view (in the first app) and then selecting the sql query based on what was passed (see the link above for that fiasco). Once I got that working, I planned to figure out how to make the buttons do the "passing" later. However, after about 16 hours screwing with that, I just gave up and decided to make 8 different table view controllers, all with nearly identical code except the query. That way, if I could just get a SINGLE button on level 1 to simply push to just ONE of the level 2's with NO parameters I would be a horrible but successful programmer.
Unfortunately, even that hasn't worked out for me. I have tried every possible control-drag and window/view/table combination I can think of in Interface Builder, but no matter what I try, the data never loads into the table view, even though it works great in my first app. I have gone through every line of code - they are the same except something has to "call" or "launch" the level 2 part and I'm just not getting it.
So, I'm going to break with convention/expectations here and not post any code in my question. I just want to know - how can this possibly be so difficult?? I am very analytically minded and I catch on quickly, but I have to say I have never been so humbled by a technical challenge in my life.
Can anyone explain to me, at a conceptual level, what I need to be doing here or what I'm missing? Even if you give me a link to something to read I would appreciate it very much. I have watched tens of hours of tutorials on youtube, but I'm always up for more.
Of course I'm willing to share my code, but there is so much of it and I'm so new at this I really don't know where the relevant parts are. Plus, I actually want to learn how all of this works so I can help others. If there is such a thing as PM on here I'll email it to you if you're willing to take a look. Once I get it working, I will post the code here. I have to believe there are other people looking for the same kind of thing as I am. However, more importantly, I just want to know, from a high level, what is the correct way to approach my problem? If you look at my link you can see what I've been trying (which was to pass an integer to the method that populates the table), but as I said, I basically gave up on that because I wasn't getting anywhere. People are trying to help me, but I'm an idiot.
Thanks for bearing with my agonizingly long message. If you made it this far and have some suggestions for me I'm all ears. I'll be honest, though - if you tell me I should just scrap the whole thing and use core data I'll cry. I really don't think I have the time to figure out a whole different way of managing data. As I said, I'm pretty happy with the database and the query parts of my app - it's just managing the freaking views and passing data between them that is killing me!
Any help is appreciated - thank you all so much.
If I understand your question correctly, you are asking how to initialize a view controller and pass in some data to alter its behavior. The key concept here to understand is how objects are initialized in Objective-C. One of the most common questions that developers who are new to iOS have is:
How can I pass data between my views?
Yes, eight different links there. (Okay, that eighth link is a little bit off topic, but it's close enough.) There are several ways of doing this and I'll go through them briefly. I'll also describe custom initializers, which are a relevant point as well.
Let's pretend we were building a catalog application which shows a bunch of products in various categories. Imagine that our app opens to a list of products, much like the Apple Store App. Say that when the user taps on a product we want to show a product page.
You can set properties on the "next" view controller. - Simply, we can create a UIViewController subclass and set the productID property (which we made up). Let's call our new UIViewController a ProductPageViewController. Here's how this would look:
- (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath
//Create a ProductPageViewController
ProductPageViewController *ppvc = [[ProductPageViewController alloc] initWithNibName:#"ProductPageViewController" bundle:nil];
//set the property on our ProductPageViewController
[ppvc setProductID:42];
//We would usually present the PPVC here.
//After presenting, remember to release the view controller
}
In the first line, we create the product view controller. We call alloc, then init. (The functions are wrapped - that is, we call init directly on the result of the alloc method.)
Then, we set a property of our view. The view can now be configured in viewWillAppear and all is well.
You can share the data through a persistent store. - This method works a little differently. The view controllers don't communicate at all, except for the first one presenting a second one. Whenever a value in the first view changes (that you want to persist), you write it to Core Data or NSUserDefaults. Then, the new view reads the value as it needs it.
In your first view controller:
//A method to store the data
- (void)storeData:(id)pageID{
[[NSUserDefaults setObject:pageID forKey:#"pageID"];
}
- (void)showNewPPVC{
ProductPageViewController *ppvc = [[ProductPageViewController alloc] initWithNibName:#"ProductPageViewController" bundle:nil];
//Show and then release the PPVC
}
You can use custom initializers. - This is probably the most intuitive way to do it, once you understand the concept, because this is the only one where data is actually "passed". (As opposed to method 2 where no data is directly shared and method 1 where data is passed as a property.)
Notice that in the earlier examples, I used the initWithNibName:Bundle method. You might also notice that UITableViewControllers use a different initializer, initWithStyle:. Those two initializers take in some information for the new object so that it knows how to load. Let's look at the first one first:
- (id)initWithNibName:(NSString *)nibNameOrNil Bundle:(NSBundle *)bundleNameOrNil;
The first argument tells the view controller which nib file to load up. I'm going to ignore the second argument for now, since I've never seen anything passed in except nil. Moving right along to the second example:
- (id)initWithStyle:(UITableViewStyle)style;
You can pass in one of two UITableViewStyle values here. This is one way to define the style of a table view (the other way being to modify a nib file directly).
Let's extend this concept a bit to our example. I'm now going to show you how to make your own custom initializer. Let's initialize our ProductPageViewController instance:
- (id) initWithProductID:(int)productID;
That's simple enough. Now, we need to implement the method and actually do something with the product ID. We'll start with the barebones code here, required to "mimic" the functionality of the default initializer.
- (id) initWithProductID:(int)productID{
self = [super init];
return self;
}
This method will return an initialized copy of our ProductPageViewController, however, it won't load up our UI from a NIB yet, or if this were a UITableViewController, it wouldn't set the UITableViewStyle. Let's work with a NIB first and then I'll show how to work a UITableViewController. So...
- (id) initWithProductID:(int)productID{
self = [super initWithNibName:#"ProductPageViewController" Bundle:nil];
return self;
}
Now. we have an initialized ProductPageViewController, loaded from a NIB, but it doesn't do anything yet. Notice how we don't expose the NibName and Bundle arguments, but we just pass them in ourselves. If you want, you could theoretically expose those too. Now, let's take that productID and do something with it.
- (id) initWithProductID:(int)productID{
self = [super initWithNibName:#"ProductPageViewController" Bundle:nil];
if(self){
self.prodID = productID;
}
return self;
}
With our latest changes, our "PPVC" now knows about the productID. It can query the database as you want and do things with the results. You can then run different queries based on this productID.
Two More Quick Tips:
Perhaps you want to pass in several arguments. Of course you can simply add them to them method signature - (id) initWithProductID:(int)productID andCategoryID(int)categoryID, but what happens if you have five, six, or fifty six (yea, that's a lot) arguments? I'd advise passing in a collection or array of arguments.
To use custom initializers with UITableView, you pass in a UITableViewStyle instead of a NIB name. Here's what it might look like:
- (id) initWithProductID:(int)productID{
self = [super initWithStyle:UITableViewStyleGrouped];
if(self){
self.prodID = productID;
}
return self;
}
When making your subsections, I'd suggest a combination of persistent data and custom initializers. I also advise taking a peek at the viewDidLoad and viewWillAppear methods.

What UIView is returned when using -viewWithTag: when several views have same .tag?

Say I have 4 UIViews, made in IB, all with the tag property = 2
When I get a view with:
UIView *thisView = (UIView*)[self.view viewWithTag:2];
What is the criterion for retrieving that UIView since several have the same .tag value?
Is it:
random
the first one created
the view with the lowest index in it's superview
something else
Its weird but the view that is added first will be returned if you try to get the views among the views with same tag. You can check it in this way too.
NSLog(#"%#",[[self.view viewWithTag:custTag] class]);
Hope this helps.
If you use Interface Builder, it depends which order you use. In my case, UIActivityIndicator will be my result, and not UIWebView or UIButton with the same tag.
if I had to guess, I would assume that it would be almost-random. as in, you'll probably get some amount of consistency, but every so often it'll be something completely different (heh).
I'd say use different tags for them?

Count method of subclass of NSMutableArray crashes app

This seems to be a common problem, but I can't figure out anything from the answers I've seen so far. I have an iPhone app that uses a subclass of NSMutableArray to store objects, plus some additional properties. The subclass is skhCustomArray. The subclass initializes fine, with no objects in the skhCustomArray, and I assign it to the the property of my view controller, which is a pointer to an skhCustomArray.
prescriptionListVC* newPrescList = [[prescriptionListVC alloc] initWithNibName:#"PrescriptionList" bundle:nil];
newPrescList.curPersonPrescriptions = [personDetails objectAtIndex:0];
That works fine. Yet when I push my view managed by my view controller onto the navigation controller stack, the count method in the numberOfRowsInSection method crashes the app, see below.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
return [curPersonPrescriptions count];
}
What could be causing this? How can a valid custom array, with no objects, not return a valid count? Where am I going wrong? Thanks.
Subclass of NSArray? You're aware that NSArray is a class cluster, and is therefore somewhat difficult to subclass, right? In fact, it's so fraught with danger that the NSArray documentation has a whole section dedicated to what you need to do in order to subclass it.
I'll bet that that's the source of your woes.
You almost certainly don't need to subclass NSMutableArray in this situation. Instead, make a new class which has an array as a property, along with the extra properties you desire.
When you subclass NSMutableArray, you need to implement some mandatory methods like count, addObject:, insertObjectAtIndex etc. This is what we call as class cluster.
If you want to add some more feature/behavior to already implemented object then you can write a "category" instead of "subclassing" it.
If you want to subclass it, then you have to implement all those methods which your are going to use so better write a category of NSMutableArray and extend the feature what you want and use the NSMutableArray object only. This will solve your problem and also this is the easy and almost right way to add new behavior to already existing class.

How to use NSLocalizedString in IB [iPhone SDK]?

When I assign text programmatically, I can use NSLocalizedString(....), but if I am writing the text directly in IB (say to a UILabel) how do I localize it?
One solution is to create multiple localized nib files. This probably works best if your localization needs are very straightforward and you're simply going hand your resources over to expert software localizers at the end of development.
If you're localizing while you're developing and the UI is changing rapidly, duplicate nib files can be a big pain, since every UI tweek must be duplicated in each local version of the nib. To avoid this, you'll need to write some code in your view controllers to handle setting the localized strings, typically in the view controller's -viewDidLoad method. Make sure that every control with localized text is an IBOutlet and wired them up to your view controller in IB. Then your view controller's -viewDidLoad will look something like this:
- (void)viewDidLoad {
[super viewDidLoad];
hello.text = NSLocalizedString(#"Hello", #"Hello label");
world.text = NSLocalizedString(#"world", #"world label");
// ... etc
}
You do this type of view setup in -viewDidLoad since the objects in your nib aren't fully created until after your -init method runs. -viewDidLoad runs after -init but before your view becomes visible.
This is a large and complex topic. A good starting place is Introduction to Internationalization Programming Topics and there is also Preparing Your Nib Files for Localization
You can create multiple versions of a .nib file for each locale. There are also tools to let you edit the strings in them easily. Apple has pretty good documentation on this.
I also was searching for a solution, not necessarily for iPhone, but XCode/IB in general. All references did not deal with the fact that you might need a key internally to indicate a state and want to display a localized string for the user in a label or text cell corresponding to that key. I did not found in first step a standard approach how to store e.g. a key value for a key in shared user defaults and display the localized string for that key value in a label.
I found a solution wich does not need many coding and is compliant with the bindings in ib.
First you provide a file Localizable.strings e.g. with a line containing
"MyKeyValue" = "Localized display label";
Now you can localize the key value with: NSLocalizedString(aKeyValue,nil).
In the label you did not find any value transformer dealing with NSLocalized String.
So I created a class KeyToLocalizedStringTransformer to transform a key value into a localized string:
#interface KeyToLocalizedStringTransformer : NSValueTransformer {}
#implementation KeyToLocalizedStringTransformer
+ (Class)transformedValueClass
{
return [NSString class];
}
+ (BOOL)allowsReverseTransformation
{
return NO;
}
- (id)transformedValue:(id)aValue
{
NSString *NLString = [NSString stringWithString:NSLocalizedString(aValue,nil)];
return NLString;
}
Last step for preparing is to register the transformer e.g. in +initialize:
NSValueTransformer *transformer = [[KeyToLocalizedStringTransformer alloc] init];
[NSValueTransformer setValueTransformer:transformer forName:#"KeyToLocalizedStringTransformer"];
Now you can use a value transformer in the bindings for the text field or cell (Simply type in the name if you do see only the NSUnArchiveFromData and so on...)
Sorry no image here from IB 'cause I am new here and "have no reputation": you have to imagine the binding to the shared user defaults controller, Kontroller key: values, Model Key Path: MyStateKey and a value transformer as described.
As a result you dont have to do anything in the nl duplicated nib with the label, simply translate the string in the Localizable.strings.
from iOS 7 & Xcode 5 on, you should avoid using NSLocalizedString where possible. The preferred method is called 'base localisation', and works through the Storyboard. It will save you a lot of work. If you google for 'base localisation' you'll find enough tutorials to get you going