I've been looking at the Stanford University iphone videos on iTunes U. And saw the teacher trying to do something similar to this code but he realised and said it didn't work though I didn't get why:
- (IBAction)flashPressed{
if (window.backgroundColor == [UIColor magentaColor]){
window.backgroundColor = [UIColor redColor];
}else {
window.backgroundColor = [UIColor magentaColor];
}
}
Objective-C, windows based application. Not sure what else you need to know.
The reason it doesn't work is that UIView's backgroundColor is a copy property. It's declared like this:
#property(nonatomic, copy) UIColor *backgroundColor;
That means that when the color object that you get from [UIColor redColor] is set as the backgroundColor, the whole object is copied, and the copy retained by the UIView will be on a different memory address than the one retained by the UIColor class object.
== checks if the pointers are the same, which means that it will succeed only if the two pointers point to the very same object. This is what you want to do sometimes. For example:
if ([aView superView] == self)
[aView removeFromSuperview];
Here, you want to be sure that aView's super view is actually this very object, not just one that is "the same" according to some criteria.
But when you compare two strings, you are (almost always) interested in whether they contain the same characters, and it doesn't matter whether they are on different memory addresses. Thus you use:
if ([aString isEqualToString:anotherString]) // faster than isEqual:
And in our example with the colors, it's the same: we want to know whether the two objects both represent a red color, not whether the two pointers point to the exact same object.
If the backgroundColor property was declared as retain you could have used ==, and it would have worked until UIColor for some reason reallocated its redColor object. That's not likely to happen, but it's to underscore that the object represents a unique thing that objects like strings and colors are usually copied rather than ´retained´. There can be only one color red, and there can be only one string that containing the characters "Hello world!". So it comes down to a metaphysical argument in the end.
To check if two UIColors are equal, use the isEqual: message instead of the == operator.
if ([window.backgroundColor isEqual:[UIColor redColor]]) {
NSLog(#"Yup, it's red");
} else {
NSLog(#"OMG, it's not red!");
}
// result --> Yup, it's red
That's a general pattern for comparing objects rather than using == like you do for primitives like ints or floats. NSString works the same way.
Too much information section:
The pattern for objects that have a defined order is to give them a compare: method that returns a NSSortDescriptor.
Related
I have two arrays that include 5 pictures of a background with a solid color. This is the code used to put the images into the arrays:
self.colorArray = #[
[UIImage imageNamed:#"orange_square"],
[UIImage imageNamed:#"purple_square"],
[UIImage imageNamed:#"red_square"],
[UIImage imageNamed:#"blue_square"],
[UIImage imageNamed:#"green_square"],
];
self.iconColorArray = #[
[UIImage imageNamed:#"orange_icon"],
[UIImage imageNamed:#"purple_icon"],
[UIImage imageNamed:#"red_icon"],
[UIImage imageNamed:#"blue_icon"],
[UIImage imageNamed:#"green_icon"],
];
As you can see the images are respectively put in order in terms of the color of their background. (I use different images for the arrays because iconColorArray is being used for a smaller UIImageView)
The app randomly changes the images of the two UIImageViews.
What I want is a if-else statement to compare the two arrays to see if the same objectAtIndex is being used. Exp: If orange_square is used for one UIImageView and orange_icon is being used for the other, the condition in the if-else statement will return true.
Basically to answer my question, just tell me how I would get the index of an object being used by a UIImageView in a array.
Edit:
Using Matt's advice, I changed the code to:
NSUInteger d = [self.colorArray indexOfObject:self.squareOne.image];
NSUInteger e = [self.iconColorArray indexOfObject:self.icon.image];
Now, I can compare them.
The way you get the index of an object is with (wait for it) indexOfObject:.
The problem is that the use of this method presupposes that this object can be compared for equality. I don't know whether UIImage does; I rather doubt it, but you can try.
Another possibility is indexOfObjectIdenticalTo:; this might work if the image is not copied by the image view but is an actual reference to one and the same image as the image in the array.
Having said all that, which would I do? None of them. I wouldn't even keep arrays of images; it's terribly wasteful of memory, and your app is likely to crash before it gets off the ground. What I would do is keep an array of the names of the images; strings are tiny, images are huge. And in order to make the comparison, I would subclass UIImageView to have a name property so that I could assign the name as well as the image, and now the problem is trivial.
Or you could even display the images through a view controller of their own, and give the view controller the name property. As a matter of fact I happen to have an example of doing that:
https://github.com/mattneub/Programming-iOS-Book-Examples/blob/master/bk2ch06p311pageController/ch19p626pageController/Pep.swift
And in fact if you explore the rest of that project you will see that I the proceed to do exactly what you are asking to do: to learn what pep boy is being displayed, I ask the Pep object for its boy string and I look up its index in a list of Pep boys:
https://github.com/mattneub/Programming-iOS-Book-Examples/blob/master/bk2ch06p311pageController/ch19p626pageController/AppDelegate.swift
It's in Swift so I use find instead of indexOfObject: but it amounts to the same thing.
You could create a third object as a dictionary, where the image square names names are the keys are the keys. And then you colorArray and iconColorArray could derive from this dictionary.
self.imageMap = { #"orange_square" : #"orange_icon",
#"purple_square" : #"purple_icon" }
- (BOOL)squareUsed:(UIImage *)square isSameAsIcon:(UIImage *)iconUsed {
for (NSString *key in self.imageMap) {
if ([[UIImage imageNamed:key] isEqual:square]) {
if ([UIImage imageNamed:self.imageMap[key]] isEqual:iconUsed]) {
return YES;
}
// We matched the square and the icon didn't match.
return NO;
}
}
// We never matched the square. Assert?
return NO;
}
I have an object with two properties - type and name - which I want to show in its description. The out-of-the-box description looks like this:
<SGBMessage: 0x7663bb0>
If I override description, like so:
return [NSString stringWithFormat:#"<%#: %x type:%# name%#>",
[self class], (int)self, self.type, self.name];
Then I can get a nice description like this:
<SGBMessage: 0x7663bb0 type:loadScreen name:mainScreen>
So far, so good. But Apple's objects have dynamic descriptions; if I look at a view's description I get this:
<UIView: 0x767bcb0; frame = (0 0; 0 0); layer = <CALayer: 0x767bd50>>
But if I set hidden to true, I get this:
<UIView: 0x767bcb0; frame = (0 0; 0 0); hidden = YES;
layer = <CALayer: 0x767bd50>>
Now, I don't believe for a second that they've got a massive set of if statements in the description methods of all of their objects; it seems much more likely that there's some method in some category somewhere on NSObject that can be overridden to specify which properties show up in the description. Does anyone know what's really going on, and if so, is it something I can take advantage of?
Mine tend to follow this pattern:
- (NSString *) description {
NSMutableDictionary *descriptionDict = [[NSMutableDictionary alloc]init];
if (account) [descriptionDict setObject:account forKey:#"account"];
if (date) [descriptionDict setObject:date forKey:#"date"];
if (contentString) [descriptionDict setObject:contentString forKey:#"contentString"];
return [descriptionDict description];
}
You could use a similar approach to build an NSMutableArray, and then iterate through the array, adding what's in there to the string.
For more complex apps, if you have custom classes that inherit from other classes, you can also make a separate method that returns descriptionDict, and then in the subclass call NSMutableDictionary *descriptionDict = [super descriptionDict] and continue adding / removing elements to it.
NOTE: The reason I use if statements on each line is that if one object happens to be nil, an exception is thrown. This will cause "no objective c description available" to print when you try to po your object.
But to answer your question, there's no secret way to make certain properties appear in the description. You just have to build a string yourself, by whatever means you decide is appropriate.
I have an NSComboBox in my mainmenunib file.
I have created an outlet of combobox "cb" and made a connection of it with my delegate
I also connected delegate and datasource with my delegate.
-(void)applicationDidFinishLaunching:(NSNotification *)aNotification
{ arr=[NSMutableArray arrayWithObjects:#"a",#"b",#"c",#"d",#"e",#"f", nil];
[cb reloadData];
}
-(NSInteger)numberOfItemsInComboBox:(NSComboBox *)aComboBox{
return arr.count;
}
-(id)comboBox:(NSComboBox *)aComboBox objectValueForItemAtIndex:(NSInteger)loc{
return [arr objectAtIndex:loc];
}
But when I run the application data is not coming in combobox.
Please help me out as i am new to cocoa programming.
Thanks in advance.
Your approach seems reasonable on the face of it, though using a mutable object as an instance variable is often ill-advised (for reasons wholly unrelated to your issue here, and which we needn't get into at this stage).
There are two things that jump out as possible issues:
1) Are you using ARC? If not, arr is going to disappear from under you because -arrayWithObjects returns an autoreleased object and you have nothing retaining it. If you are using ARC (the default for new project on Lion, I believe), this doesn't apply to you. Plus I would expect you would crash, not just get no data.
2) More likely, you forgot to -setUsesDataSource:YES, which is the flag that tells NSComboBox whether to look at its data source or to use the internal contents approach that #JustinBoo supplied. I believe this defaults to NO, which would cause your exact problem. I don't have Interface Builder in front of me at the moment, but IIRC there is a "uses data source" checkbox that you can check to enable this attribute.
You can add objects using -addItemWithObjectValue to Your NSComboBox like this:
arr = [NSMutableArray arrayWithObjects:#"a",#"b",#"c",#"d",#"e",#"f", nil];
for (int i = 0; i < [arr count]; ++i)
{
[cb addItemWithObjectValue:[arr objectAtIndex:i]];
}
You can see NSComboBox Reference for more information.
I'm kind of new with objective c and I'm trying to pass an argument by reference but is behaving like it were a value. Do you know why this doesn't work?
This is the function:
- (void) checkRedColorText:(UILabel *)labelToChange {
NSComparisonResult startLaterThanEnd = [startDate compare:endDate];
if (startLaterThanEnd == NSOrderedDescending){
labelToChange.textColor = [UIColor redColor];
}
else{
labelToChange.textColor = [UIColor blackColor];
}
}
And this is the call:
UILabel *startHourLabel; // This is properly initialized in other part of the code
[self checkRedColorText:startHourLabel];
Thanks for your help
Objective-C only support passing parameters by value. The problem here has probably been fixed already (Since this question is more than a year old) but I need to clarify some things regarding arguments and Objective-C.
Objective-C is a strict superset of C which means that everything C does, Obj-C does it too.
By having a quick look at Wikipedia, you can see that Function parameters are always passed by value
Objective-C is no different. What's happening here is that whenever we are passing an object to a function (In this case a UILabel *), we pass the value contained at the pointer's address.
Whatever you do, it will always be the value of what you are passing. If you want to pass the value of the reference you would have to pass it a **object (Like often seen when passing NSError).
This is the same thing with scalars, they are passed by value, hence you can modify the value of the variable you received in your method and that won't change the value of the original variable that you passed to the function.
Here's an example to ease the understanding:
- (void)parentFunction {
int i = 0;
[self modifyValueOfPassedArgument:i];
//i == 0 still!
}
- (void)modifyValueOfPassedArgument:(NSInteger)j {
//j == 0! but j is a copied variable. It is _NOT_ i
j = 23;
//j now == 23, but this hasn't changed the value of i.
}
If you wanted to be able to modify i, you would have to pass the value of the reference by doing the following:
- (void)parentFunction {
int i = 0; //Stack allocated. Kept it that way for sake of simplicity
[self modifyValueOfPassedReference:&i];
//i == 23!
}
- (void)modifyValueOfPassedReference:(NSInteger *)j {
//j == 0, and this points to i! We can modify i from here.
*j = 23;
//j now == 23, and i also == 23!
}
Objective-C, like Java, only has pass-by-value. Like Java, objects are always accessed through pointers. "objects" are never values directly, hence you never assign or pass an object. You are passing an object pointer by value. But that does not seem to be the issue -- you are trying to modify the object pointed to by the pointer, which is perfectly allowed and has nothing to do with pass-by-value vs. pass-by-reference. I don't see any problem with your code.
In objective-c, there is no way to pass objects by value (unless you explicitly copy it, but that's another story). Poke around your code -- are you sure checkRedColorText: is called? What about [startDate compare:endDate], does it ever not equal NSOrderedDescending? Is labelToChange nil?
Did you edit out code between this line
UILabel *startHourLabel;
and this line?
[self checkRedColorText:startHourLabel];
If not, the problem is that you're re-declaring your startHourLabel variable, so you're losing any sort of initialization that was there previously. You should be getting a compiler error here.
Here are the possibilities for why this doesn't work:
the label you pass in to checkRedColorText is not the one you think it is.
the comparison result is always coming out the same way.
... actually, there is no 3.
You claim you initialised startHourLabel elsewhere, but, if it is a label from a nib file, you should not be initialising it at all. It should be declared as an IBOutlet and connected to the label in the nib with interface builder.
If it is not a label in the nib i.e. you are deliberately creating it programmatically, you need to check the address of the label you initialise and check the address of the label passed in to checkRedColorText. Either NSLog its address at initialisation and in checkRedColorText or inspect it with the debugger.
For example I want to store this in an ivar:
CGFloat color[4] = {red, green, blue, 1.0f};
so would I put this in my header?
CGFloat color[];
How would I assign values to that guy later? I mean I can't change it, right?
Instance variables are zeroed out on allocation so you can't use initialisers with them.
You need something like this:
// MyObject.h
#interface MyObject
{
CGFloat color[4];
}
#end
// MyObject.m
#implementation MyObject
-(id) init
{
self = [super init];
if (self != nil)
{
color[0] = red;
color[1] = green;
color[2] = blue;
color[3] = alpha;
}
return self;
}
You'd need to put the size in so that enough space is reserved.
CGFloat color[4];
Or use a pointer to the array, but that's more work and hardly superior for representing something as well-known as a color.
You are better off using a NSColor object if you can.
However, to your original question one of my first questions is where do you want to create this array. When you say put it in a header do you mean as a member of a class or as a global array, you certainly can do both however there are some serious gotchas with putting globals in headers. If you need that follow up and I can explain it better.
If it is in a class then you can just declare it like any other member field. If you say
CGFloat color[4];
then the space for the array is allocated in your object itself. You can also just use a
CGFloat *color;
or its moral equivalent to refer to an array that is stored outside of the object. You do need to manage that storage appropriately however.
This matters in the case you hinted at where you use a constant array object and cannot later change it. That can happen but is rare, since it cannot happen with the first approach, you don't see it in the wild very often.
There is a whole dissertation on the corner cases in here, I am sure it is not helping to go into it. Just use CGFloat color[4] in your object and it won't matter, by the time you see things they will be mutable and you can just use them the way you expect.