Nullable in objective c - objective-c

Is there a way to make nullable struct in objective C like in C# you can use Nullable<T>?
I need a CGPoint to be null when there is no applicable value. I cannot allocate a random invalid value for this like (-5000, -5000) because all values are valid for this.

What if you define a CGPoint using CGPointMake(NAN, NAN) similar to CGRectNull? Surely with NAN's for coordinates, it's not still a valid point.

CGPoint is a struct and that has some different rules in objective-c than you might think. You should consider reading about structs in objective-c.
The way this is done most of the time is to wrap the struct in an object because that object can be set to null. NSValue will wrap a CGPoint.
NSValue * v = [NSValue valueWithPoint:CGPointMake(1,9)];
NSVAlue * vNull = [NSValue valueWithPointer:nil];
if([v objCType] == #encode(CGPoint)) printf("v is an CGPoint");

CGPoint is a enum, not an object. You can use CGPointZero, or you can wrap all of your points inside of NSValue, which are objects and can be nil.

There is also nothing stopping you creating your own struct based on CGPoint, similar to how C# 2 works.
struct NilableCGPoint { bool isNil; CGPoint point; }
Examples of use:
// No value (nil)
NilableCGPoint myNilablePoint.point = CGPointZero;
myPoint.isNil = YES;
// Value of (0,0)
NilableCGPoint myNilablePoint.point = CGPointZero;
myPoint.isNil = NO;
// Value of (100, 50)
NilableCGPoint myNilablePoint.point = CGPointMake(100, 50);
myPoint.isNil = NO;

Related

How do I check if a CGPoint has been initialised?

I would like to initially set a CGPoint property to a particular point (middle of screen). Other methods may subsequently wish to change this property. My thoughts were to initialise it if empty in the getter, but I get the message invalid argument type 'struct CGPoint' to unary expression. I also tried using if property == nil or 0 but no joy.
Any thoughts?
-(CGPoint)graphOrigin
{
// initialise to centre of screen if has not been set
if(!_graphOrigin) // this expression is causing the problem
{
CGPoint origin = CGPointMake(self.bounds.origin.x + self.bounds.size.width / 2, self.bounds.origin.y + self.bounds.size.height / 2);
_graphOrigin = origin;
}
return _graphOrigin;
}
A CGPoint is a struct, so you can't set it to nil or NULL (it's not a pointer). In a sense, there's really no "uninitialized" state. Perhaps you could use {0.0, 0.0} to designate an unset CGPoint, but that's also a valid coordinate. Or you could use negative x and y values to flag an "uninitialized" point, since negative values can't be valid drawing points, but that's a bit of a hack, too.
Probably your best bet is to do one of two things:
Store the property as a pointer to a CGPoint. This value can be set to NULL when uninitialized. Of course, you have to worry about mallocing and freeing the value.
Store the CGPoint alongside a BOOL called pointInitialized or somesuch, initially set to NO, but set to YES once the point has been initialized. You can even wrap that up in a struct:
struct {
CGPoint point;
BOOL initialized;
} pointData;
An easier way would be to initialize _graphOrigin to CGRectZero and change your if statement for this:
if (!CGPointEqualToPoint(_graphOrigin, CGPointZero)) {
}
CGPoint does not have an uninitialized state. However, if you consider the point (0, 0) as uninitialized, you could use
if (_graphOrigin.x == 0 && _graphOrigin.y == 0)
{
...
This works because when an Objective-C instance is initialized, all its ivar are cleared to bits of zero, which in the CGFloat representation is 0.0.
(Note: The == is fine here even if the operands are CGFloat because we want to compare with the an exact bit pattern (ignoring the issue of -0))
Since CGPointZero (0,0) and any other value you give a point may exist in your context
you may want to initialize an NSValue with your point using:
NSValue *pointVal = [NSValue valueWithCGPoint:point];
You could do this based on some condition and then later test the NSValue for nil.
NSValue can also be added to an array which would allow you to have an array of points should you need.
To get the point later simply use:
CGPoint point = [pointVal CGPointValue];
static CGPoint kInvalidPoint = {.x = NSIntegerMax, .y = NSIntegerMax};
#implementation MyClass
- init()
{
self = [super init];
if (self) {
_oldPoint = kInvalidPoint;
}
return self;
}
- (void)foo
{
if (CGPointEqualToPoint(self.oldPoint, kInvalidPoint)) {
// Invalid point.
return;
}
}
#end
Create two CGPoint properties, that way they are both "uninitialized". Set one of them and use the second one to check whether or not they are equal.
#interface ClassName ()
#property (nonatomic) CGPoint point1;
#property (nonatomic) CGPoint point2;
#end
#implementation ClassName
self.point1 = CGPointMake(69.0f, 180.0f); //arbitrary numbers
//if not equal, then if statement proceeds
if (!CGPointEqualToPoint(self.point1, self.point2) {
//your code here
}
#end
Idk if you'd consider this way hackish though. And I know your question was already answered, but I had kinda the same dilemma till I thought of this.

Incompatible type 'double'

I'm assigning a CGFloat animatedDistance and I'm getting this error.
Here I'm assigning value to animatedDistance
static const CGFloat KEYBOARD_ANIMATION_DURATION = 0.3;
static const CGFloat LANDSCAPE_KEYBOARD_HEIGHT = 140;
heightFraction is CGFloat as well.
if (orientation == UIInterfaceOrientationPortrait ||
orientation == UIInterfaceOrientationPortraitUpsideDown)
{
animatedDistance = floor(PORTRAIT_KEYBOARD_HEIGHT * heightFraction);
}
else
{
animatedDistance = floor(LANDSCAPE_KEYBOARD_HEIGHT * heightFraction);
}
What type should be animatedDistace? Can someone help me?
floor returns a double. On some platforms, CGFloat is a float. animatedDistance should be typed as a double (you can cast it to a CGFloat if needed).
Use this to get a vector... CGpoint vector = ccpSub(cgpoint 1, cgpoint 2);
And if you want double/float values then do this:
CGpoint.location
for whatever you are trying to find the coordinates of, then assign a float to CGPoint.location.y and another float to CGPoint.location.x
You need cocos2d for this by the way. I think.
Sounds to me like you declared animatedDistance as holding some kind of pointer, such as NSNumber *, or a structure, such as CGSize. Either way, you can't assign a CGFloat there.
If animatedDistance holds an NSNumber object, create one around the value. Back when you asked this question, the way to do this was [NSNumber numberWithDouble:floor(…)]. Now, you can just use #(floor(…)).
If animatedDistance holds a CGSize or other structure, you're going to have to decide for yourself how to meaningfully convert from the single number you have to the kind of structure you want.

Pass struct to performSelector:withObject:afterDelay:

I have a struct Position, like this:
typedef struct Position { int x, y; } Position;
How may I pass it in NSObject performSelector:withObject:afterDelay:? Like this:
Position pos;
pos.x = pos.y = 1;
[self performSelector:#selector(foo:)
withObject:pos // ERROR
afterDelay:5.0f];
EDIT: changed code to fix typo
Uhm.. use a CGPoint and something like
[self performSelector:#selector(foo:)
withObject:[NSValue valueWithCGPoint:CGPointMake(pos.x, pos.y)]
afterDelay:5.0f];
And read it again as
NSValue v;
CGPoint point = [v CGPointValue];
or leave the Position class away completely, CGPoint does the same
You could wrap your custom type using NSValue class. The error is that you didn't provide an object reference to the method.
Try using NSValue's +(NSValue *)valueWithBytes:(const void *)value objCType:(const char *)type; class method. On the other side you can retrieve the value using -(void)getValue:(void *)buffer;.
preformSelector:withObject: accepts only objects as parameters, hence you'll have to implement your foo: method to accept an object. There are two ways to pass the struct as an object:
create a struct-like object or
wrap it into NSValue and unwrap it in the method.
Full answer, based on user756245's (which doesn't tell you how to use it, not a great deal of help). Also, Apple suggests you use a slightly different method these days, IIRC:
typedef myStruct { int a; } myStruct;
myStruct aLocalPointer = ... assign to it
[self performSelector:#selector(myOtherMethod:) withObject:[NSValue value:&aLocalPointer withObjCType:#encode(myStruct)] afterDelay:0.25];
This is most likely asking for trouble, but you can pass CGPoint as id by bridging it in this way:
withObject:(__bridge id)((void *)(&point))
This will lead to a crash if point goes out of scope and your selector tries to read it, though.

setting and using obj-c CGPoint, CGRect, and others

I have three questions surrounding what I think is the topic of structs in obj-c
1) Why is it that I often (or always) cannot take a member var that is a CGPoint or a CGRect and set the values one by one? I find I have to do:
CGPoint point;
point.x = someValue;
point.y = someOtherValue;
obj.myPoint = point;
instead of simply obj.myPoint.x = someValue etc.
2) Is this behavior that is consistent across all structs in obj-c?
3) Is there an easy way to add two CGPoints? It seems like there should already be, but I couldn't find one. I thought it'd be cumbersome if I'd have to use a temporary CGPoint to accumulate values between two CGPoints before setting the dest var to the temp var (because of not being able to just do pointA.x += pointB.x (same for y).
1) From #sb in an answer to Cocoa Objective-c Property C structure assign fails
That won't accomplish anything, because [t member] returns a struct, which is an "r-value", ie. a value that's only valid for use on the right-hand side of an assignment. It has a value, but it's meaningless to try to change that value.
Basically you just have to live with the fact that you can't set the fields of struct directly when returned from a function.
2) Yes
3) Unfortunately I don't think there is a built-in convenience method for adding two CGPoint. If you find your self doing this frequently you can make your own:
CGPoint CGPointAdd(CGPoint p1, CGPoint p2)
{
return CGPointMake(p1.x + p2.x, p1.y + p2.y);
}
and then use it like:
obj.pointA = CGPointAdd(obj.pointA, pointB);
not as elegant as obj.pointA.X += ... but sometimes life isn't fair.
This is perfectly normal. The 'obj' owns that var and has getters and setters, you can not modify parts of that variable.
The best thing to do is copy the struct and modify whatever you need.
Also note you can use the CGPointMake(x, y) function (and the same for all CG structs), which is much easier.
To update this is easiest:
CGPoint point = obj.myPoint;
point.x += 10.0f;
obj.myPoint = point;
Obj-C 2.0 hides the getters and setters which looks like this:
CGPoint point = [obj myPoint];
point.x += 10.0f;
[obj setMyPoint:point];

Converting a CGPoint to NSValue

In CABasicAnimation.fromValue I want to convert a CGPoint to a "class" so I used NSValue valueWithPoint but in device mode or simulator one is not working...
need to use NSMakePoint or CGPointMake if in device or simulator.
There is a UIKit addition to NSValue that defines a function
+ (NSValue *)valueWithCGPoint:(CGPoint)point
See iPhone doc
#ashcatch 's answer is very helpful, but consider that those methods from addition copy values, when native NSValue methods store pointers! Here is my code checking it:
CGPoint point = CGPointMake(2, 4);
NSValue *val = [NSValue valueWithCGPoint:point];
point.x = 10;
CGPoint newPoint = [val CGPointValue];
here newPoint.x = 2; point.x = 10
CGPoint point = CGPointMake(2, 4);
NSValue *val = [NSValue valueWithPointer:&point];
point.x = 10;
CGPoint *newPoint = [val pointerValue];
here newPoint.x = 10; point.x = 10
In Swift, you can change a value like this:
var pointValueare = CGPointMake(30,30)
NSValue(CGPoint: pointValueare)
&(cgpoint) -> get a reference (address) to cgpoint
(NSPoint *)&(cgpoint) -> casts that reference to an NSPoint pointer
*(NSPoint )(cgpoint) -> dereferences that NSPoint pointer to return an NSPoint to make the return type happy
In Swift the static method is change to an initialiser method:
var pointValue = CGPointMake(10,10)
NSValue(CGPoint: pointValue)
If you are using a recent-ish version of Xcode (post 2015), you can adopt the modern Objective-C syntax for this. You just need to wrap your CGPoint in #():
CGPoint primitivePoint = CGPointMake(4, 6);
NSValue *wrappedPoint = #(primitivePoint);
Under the hood the compiler will call +[NSValue valueWithCGPoint:] for you.
https://developer.apple.com/library/archive/releasenotes/ObjectiveC/ModernizationObjC/AdoptingModernObjective-C/AdoptingModernObjective-C.html
Don't think of it as "converting" your point-- NSValue is a wrapper class that holds primitive structs like NSPoint. Anyway, here's the function you need. It's part of Cocoa, but not Cocoa Touch. You can add the entire function to your project, or just do the same conversion wherever you need it.
NSPoint NSPointFromCGPoint(CGPoint cgpoint) {
return (*(NSPoint *)&(cgpoint));
}