Converting a CGPoint to NSValue - objective-c

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));
}

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.

Nullable in 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;

.Net developer new to Objective-C. Need some critique and suggestions

I've created one of my first apps using Objective-C. Being a noob, there's a lot of stuff I want to do, but don't know how to apply it in Objective-C. Please take a look at the method below (which I created from scratch) and tell me what you would do to make it better. Obviously I've duplicated code across 2 UILabels, but I'd like to simplify that (I hate duplicating code) but I'm unaware what the best way to do it is. I just need suggestions which will help me better understand the right way to do stuff in Objective-C
timeText and dateText are of type UILabel
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
if (isRearranging)
{
NSLog(#"touchesMoved");
NSLog(#"touches=%#,event=%#",touches,event);
//TOUCH INFO
UITouch *touch = [[touches allObjects] objectAtIndex:0];
CGPoint currentLocation = [touch locationInView:touch.view];
CGPoint previousLocation = [touch previousLocationInView:touch.view];
//FRAME INFO
float timeHalfWidth = timeText.frame.size.width / 2;
float timeHalfHeight = timeText.frame.size.height / 2;
CGRect timeTextRect = CGRectMake(timeText.center.x - (timeHalfWidth), timeText.cener.y - (timeHalfHeight), timeText.frame.size.width, timeText.frame.size.height);
float dateHalfWidth = dateText.frame.size.width / 2;
float dateHalfHeight = dateText.frame.size.height / 2;
CGRect dateTextRect = CGRectMake(dateText.center.x - (dateHalfWidth), dateText.center.y - (dateHalfHeight), dateText.frame.size.width, dateText.frame.size.height);
//IF TIME TEXT
if(CGRectContainsPoint(timeTextRect,previousLocation))
{
CGPoint item = timeText.center;
CGPoint diff;
diff.x = previousLocation.x - item.x;
diff.y = previousLocation.y - item.y;
CGPoint newLoc;
newLoc.x = currentLocation.x - diff.x;
newLoc.y = currentLocation.y - diff.y;
if (newLoc.x<timeHalfWidth)
newLoc.x = timeHalfWidth;
if (newLoc.y<timeHalfHeight)
newLoc.y = timeHalfHeight;
[timeText setCenter:(newLoc)];
}
//IF DATE TEXT
if(CGRectContainsPoint(dateTextRect,previousLocation))
{
CGPoint item = dateText.center;
CGPoint diff;
diff.x = previousLocation.x - item.x;
diff.y = previousLocation.y - item.y;
CGPoint newLoc;
newLoc.x = currentLocation.x - diff.x;
newLoc.y = currentLocation.y - diff.y;
if (newLoc.x<dateHalfWidth)
newLoc.x = dateHalfWidth;
if (newLoc.y<dateHalfHeight)
newLoc.y = dateHalfHeight;
[dateText setCenter:(newLoc)];
}
}
touchMoved = YES;
}
Thanks so much for your help!
A first step, independent from the language you are working in, would be to follow DRY - most of your code is the same for both labels.
Then there is already functionality for hit-testing in the SDK, e.g. -hitTest:withEvent: or -pointInside:withEvent::
NSArray *labels = [NSArray arrayWithObjects:timeText, dateText, nil];
for (UILabel *label in labels) {
if ([label pointInside:previousLocation withEvent:nil]) {
[self relocateLabel:label];
break;
}
}
I will answer an additional question posed by asker in a comment. Quote:
What if I wanted to use mixed types in that array? i.e. a couple UILabel and a couple UIImageView. Is there a way to compare types or use generics? This is a poor guess/example for (NSObject *obj in objects) { if (label.type==UILabel) [self relocateLabel:obj]; else if (label.type==UIImageView) [self relocateImage:obj]; }
As Georg Fritzsche answered there, Objective-C messaging is dynamic. Object will be "queried" if it supports that message at runtime, and if so, it will execute the method associated with the message. Method/message name is called a "selector".
If you explicitly want to figure out object's class, you can do that as well.
if([view isKindOfClass:[UILabel class]])
{
// your code here
}
If you just want to figure out if the target object responds to a selector (that is, implements a method):
if([view respondsToSelector:#selector(relocateView:)])
{
// your code here
}
Selectors are derived from method names by omitting arguments themselves, leaving colons intact and appending everything closely. For example, if you had a message send (that is, method call): [thing moveTowardsObject:door movementType:XYZ_CRAWL], its selector would be: moveTowardsObject:movementType: and you'd get it using #selector(moveTowardsObject:movementType:).
In a loop such as what Georg posted, you typically want to just check if the target object responds to a selector, since otherwise an exception would be thrown, and Objective-C code rarely catches exceptions as part of a normal code flow (as opposed to what Python developers do).

How to pass and set a CGFloat by reference?

I want to make an method which takes an CGFloat by reference.
Could I do something like this?
- (void)doStuff:(CGFloat*)floatPointer
I guess this must look different than other object pointers which have two of those stars. Also I'm not sure if I must do something like:
- (void)doStuff:(const CGFloat*)floatPointer
And of course, no idea how to assign an CGFloat value to that floatPointer. Maybe &floatPointer = 5.0f; ?
Could someone give some examples and explain these? Would be great!
objective-c is still c, so
-(void) doStuff (CGFloat *) f
{
*f = 1.2;
}
call with
CGFloat f = 1.0;
[self doStuff:&f];
If you are passing a CGFloat by reference, then accessing it is simple:
- (void)doStuff:(CGFloat*)floatPointer {
*floatPointer = 5.0f;
}
Explanation: as you are getting a reference, you need to de-reference the pointer (with the *) to get or set the value.
if you (hate pointers and ;-) prefer objective-c++ pass by reference, the following is an alternative:
-(void) doStuffPlusPlus:(CGFloat &) f
{
f = 1.3;
}
call by
CGFloat abc = 1.0;
[self doStuffPlusPlus:abc];
and, you need to rename the source filename from ???.m to ???.mm