Testing for existence of a property (i.e. numberOfValidItemsForDrop) - macos

My general question is if and how it is possible to test to see if an arbitrary object supports a given property. For methods, this is simple. I would send a respondsToSelector: message to the object and check the result. I cannot find a similar mechanism for properties.
My specific problem at hand is that I'm writing a custom NSView that supports dropping of image files (plural) onto it. I am building on 10.9 and deploying on 10.6. For machines running 10.7 and later, I would like to take advantage of the user feedback provided in the drag image (number and acceptable files) by enumerateDraggingItemsWithOptions:... method and the numberOfValidItemsForDrop property.
My initial thought is as follows. Test the sender (declared id < NSDraggingInfo >) provided to the draggingEntered: method. If so, use the mechanisms provided in 10.7. If not, go back to the earlier mechanisms.
if( [sender respondsToSelector:#selector(enumerateDraggingItemsWithOptions:forView:classes:searchOptions:usingBlock:) ] )
{
__block NSInteger n=0;
[sender enumerateDraggingItemsWithOptions:NSDraggingItemEnumerationClearNonenumeratedImages
forView:self
classes:types
searchOptions:options
usingBlock:^(NSDraggingItem *draggingItem, NSInteger idx, BOOL *stop) { ++n; } ];
self.numberOfValidItemsForDrop = n;
rval = (n>0 ? NSDragOperationCopy : NSDragOperationNone);
}
else
{
NSArray *itemsInDrag = [[sender draggingPasteboard] readObjectsForClasses:types options:options];
rval = [itemsInDrag count]>0 ? NSDragOperationCopy : NSDragOperationNone);
}
My concern is whether or not the assignment to the numberOfValidItemsForDrop will cause problems in the 10.6 environment. I threw together a quick test in which I assigned to a bogus property inside an if(0) condition and the compiler choked.
if(0)
{
sender.bogus=1;
}
This is not really the same thing as the property is not valid in the development environment. But, it has me concerned that the assignment to numberOfValidItemsForDrop might also have a problem at runtime on 10.6 even though it should never actually be executed.
I have thrown together a small dummy application which I have sent to my tester, but have not yet heard back and would like to keep moving on this project.
Thanks for any/all info.

You can use the Objective-C runtime function class_getProperty to test for the existence of a named property:
if(class_getProperty([self class], "numberOfValidItemsForDrop")) {
// property exists
}
See https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ObjCRuntimeRef/Reference/reference.html

To check if a property exists or not, you can use as : the property is assumed not to be readonly.
if ([yourClassObject respondsToSelector:#selector(set<yourPropertyName>:)]) {
NSLog(#"yes it exists");
}
else{
NSLog(#"no it doesn't exists");
}

Related

Drag and drop files into NSOutlineView

I'm trying to implement simple drag and drop operation into NSOutlineView Based on Apple's example - https://developer.apple.com/library/mac/samplecode/SourceView/Introduction/Intro.html
All seems to be ok, but finally when I drop some files from Finder I get error:
[<ChildNode 0x60800005a280> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key description.') was raised during a dragging session
Here is my test project: https://www.dropbox.com/s/1mgcg2dysvs292u/SimpleDrag.zip?dl=0
What I exactly need in my app: allow user to drag and drop multiple files and folder into some tree list and then display them to user. Also save all this this into some file, so it can be loaded again with all user dragged files and folders.
A final result I want to have like this:
The description property of NSObject is read-only, and is generally set by providing a getter in the implementation file:
- (NSString *)description {
return [self urlString]; // Using urlString solely for demo purposes.
}
You can't set it, either via key-value coding or by direct assignment:
self.description = [self urlString]; // Xcode error: 'Assignment to readonly property'
[self setValue:[self urlString] forKey:#"description"];
In -[ChildNode copyWithZone:] an attempt is made to do the latter of the two, and that's what causes the warning to be logged to the console.
// -------------------------------------------------------------------------------
// copyWithZone:zone
// -------------------------------------------------------------------------------
- (id)copyWithZone:(NSZone *)zone
{
id newNode = [[[self class] allocWithZone:zone] init];
// One of the keys in mutableKeys is 'description'...
// ...but it's readonly! (it's defined in the NSObject protocol)
for (NSString *key in [self mutableKeys])
{
[newNode setValue:[self valueForKey:key] forKey:key];
}
return newNode;
}
This begs the question why do you get the warning in your app, and not in the sample app? From what I can tell no ChildNode instance is ever sent a copyWithZone: message in the sample app, whereas this does happen in your app, immediately after the drop. Of course there's a second question here as well: why do Apple explicitly include the description key-path when it can't be set this way? - unfortunately I can't help you with that.
A really handy way of trying to trap errors that don't actually cause exceptions is to add an All Exceptions breakpoint. If you do this in your sample app you'll see that the app freezes at the line that's causing the problem, giving you a better chance of figuring out the issue.

CoreMIDI call back function issue with using bridge casting on void pointer

Background: I am trying to write a program that will take MIDI information from my synthesizer and do various things with it musically. I have already connected the keyboard so that I receive MIDI data. In my midiInputCallback() method, which is the method that is called when MIDI data is generated, it takes 3 parameters: a MIDIPacketList, any object from "the outside" and the input source of the MIDI info. I'm trying to use the second parameter to pass in a UIButton. For now, I am using UIButtons for the piano keys. When I play on my synth, I want it to show (on my GUI) that a certain note has been played. To show this, the button that is associated with the key on my keyboard will appear "pressed".
Problem: Just to test this process, I tried passing in a NSNumber called test.
- (void)awakeFromNib {
MIDIClientRef midiClient;
checkError(MIDIClientCreate(CFSTR("MIDI client"), NULL, NULL, &midiClient), "MIDI client creation error");
test = [NSNumber numberWithInt:4];
MIDIPortRef inputPort;
checkError(MIDIInputPortCreate(midiClient, CFSTR("Input"), midiInputCallback, &test, &inputPort), "MIDI input port error");
unsigned long sourceCount = MIDIGetNumberOfSources();
for (int i = 0; i < sourceCount; ++i) {
MIDIEndpointRef endPoint = MIDIGetSource(i);
CFStringRef endpointName = NULL;
checkError(MIDIObjectGetStringProperty(endPoint, kMIDIPropertyName, &endpointName), "String property not found");
checkError(MIDIPortConnectSource(inputPort, endPoint, NULL), "MIDI not connected");
}
}
I pass test into MIDIInputPortCreate() which sets up the callback method as midiInputCallback().
Here's what midiInputCallBack() looks likes:
static void midiInputCallback(const MIDIPacketList *list, void *procRef, void *srcRef) {
NSNumber *test = (__bridge_transfer NSNumber*)procRef;
NSLog(#"%#", test);
...
I tried regular casting at first: NSNumber test = (NSNUmber) procRef, but then XCode said to use some bridge casting instead. I've read up on this a bit and someone suggested to use __bridge_transfer instead of just __bridge. Each time I run this I get a EXC_BAD_ACCESS(code=1, address=0x43f) error for my NSLog() in midiInputCallback() and I'm not sure how to proceed. Let me know if you need more information. I'm fairly new to developing for the Mac. Thanks in advance!
Had the same problem some time ago when writing a similar app that receives MIDI input and logs it to the console.
Am assuming you are working with ARC.
Have solved the problem as follows:
First, I did the following cast (that would be in your awakeFromNib method, whereas &test would be some_object):
SomeClass *some_object = [[SomeClass alloc] init];
MIDIInputPortCreate(midiClient, CFSTR("Input"), midiInputCallback, (__bridge_retained void *)some_object, &inputPort);
Then, inside midiInputCallback, I have casted it back with:
SomeClass *some_object = (__bridge SomeClass*)procRef;
Hope I don't have any typos here, just did a quick copy and paste with some modifications for the context of this answer.
Checkout the following question and its answers, helped me a lot understanding it:
ARC and bridged cast

Update predicate on arraycontroller

I have an array controller and I have bound an entity to it, sort descriptor and predicate.
If I change the predicate format when the app runs, it works, so the binding is working.
My problem is when I want to change the predicate, f.ex. with a search term or some string that a user inputs, nothing happens, but when I add a record to the core data database, the tableview does update.
So my question is, how do I tell the array controller that the predicate has changed and it should update itself. Here is a code that runs when I enter search term, it also works, and I get all the NSLogs output correctly. Just my tableview is not updating itself.
- (IBAction)didChangeSearch:(id)sender {
if (sender == searchField) {
NSString *searchterm = [sender stringValue];
if (searchterm.length > 1) {
predicate = [NSPredicate predicateWithFormat:#"name contains [c]%#", #"m"];
NSLog(#"Putting predicate to the job : %#", searchterm);
} else {
predicate = nil;
NSLog(#"There is nolonger any predicate");
}
}
NSLog(#"I just got %#", [sender stringValue]);
}
I would like to say in the start that I am very new to bindings, have never used them until tonight, got a good feeling for them, and liked it, saves me so much code and I finally understood it (as much as 1 day can).
You should use self.predicate = ..... This will ensure that the proper KVO notifications are sent out, which will make your tableview update immediately (this assumes that "predicate" is a property and is bound to your array controller's filter predicate binding).

How to avoid copy and pasting?

I'd like to improve this method if possible: this is a small section whereby all of the textfield (eyepiece, objectivelenses etc) texts are saved. Unfortunately, having to do this lots of times for each part of my app is prone to error so I would like to improve it. I'm thinking some sort of fast enumeration with arguments for the method being the textfields etc. and I can have all the keys in a dictionary (which is already set up). Just a pointer to the right docs or, perhaps, some sort of process that has worked for you would be fantastic!
-(IBAction)saveUserEntries {
if (eyepiece.text != nil) {
eyepieceString = [[NSString alloc] initWithFormat:eyepiece.text];
[eyepiece setText:eyepieceString];
NSUserDefaults *eyepieceDefault = [NSUserDefaults standardUserDefaults];
[eyepieceDefault setObject:eyepieceString forKey:#"eyepieceKey"];
}
else {
[[NSUserDefaults standardUserDefaults] removeObjectForKey:#"eyepieceKey"];
}
if (objectiveLenses.text != nil) {
objectiveLensString = [[NSString alloc] initWithFormat:objectiveLenses.text];
[objectiveLenses setText:objectiveLensString];
NSUserDefaults *objectiveDefault = [NSUserDefaults standardUserDefaults];
[objectiveDefault setObject:objectiveLensString forKey:#"objectiveKey"];
}
else {
[[NSUserDefaults standardUserDefaults] removeObjectForKey:#"objectiveKey"];
}
Thank you for taking the time to read this!
I will attempt to answer this question based on a OOP solution.
Create a method that accepts whatever type object these textboxes are as an argument, send the reference of said object to the method, and save the entry in a similar method you do know. This will avoid the "copy and paste" errors you are worried about.
You should be able to loop through every instance of said object that exists, if a cocoa application, works like similar to Java and .NET ( I really don't know ). I just know there must be a way to loop through every instance of a single object within the application domain.
If this was .NET I simply would suggest TextBox.Name and TextBox.String to make this a generic method that could be used to save the properties of any TextBox sent to it. If this doesn't anwer your question ( was a little long for a comment ) then I aplogize.

Is this the right way to add items to NSCombobox in Cocoa?

I'm Delphi programmer and very new to Cocoa.
at first I tried this :
-(void)awakeFromNib
{
int i;
NSString *mystr;
for (i=1;i<=24;i++)
{
[comboHour addItemWithObjectValue:i];
}
}
But it didn't work. Then I tried to search on Google but no luck.
After experimenting about 30 min, I come with this:
-(void)awakeFromNib
{
int i;
NSString *mystr;
for (i=1;i<=24;i++)
{
mystr = [[NSString alloc]initWithFormat:#"%d",i];
[comboHour addItemWithObjectValue:mystr];
//[mystr dealloc];
}
}
My questions are:
Is this the right way to do that ?
Do I always need to alloc new
NSString to change its value from
integer ?
When I uncomment [mystr dealloc],
why it won't run ?
Does it cause memory leak to alloc
without dealloc ?
Where can I find basic tutorial like
this on internet ?
Thanks in advance
Do I always need to alloc new NSString to change its value from integer ?
Generally yes; however, there are more convenient ways to create strings (and many other types of objects) than using alloc and init (see autorelease pools below)
You can pass any Objective-C object type to addItemWithObjectValue:, including NSString and NSNumber objects. Both classes have a number of convenient class methods you can use to create new instances, for example:
for (int i = 0; i < 24; ++i)
{
[comboHour addItemWithObjectValue:[NSNumber numberWithInt:i]];
}
When I uncomment [mystr dealloc], why it won't run ?
Never call dealloc. Use release instead.
Cocoa objects are reference counted, like COM objects in Delphi. Like COM, you call release when you're finished with an object. When an object has no more references it is automatically deallocated.
Unlike COM, Cocoa has "autorelease pools", which allows you to, for example, create a new NSString instance without having to worry about calling release on it.
For example: [NSString stringWithFormat:#"%d", 123] creates an "autoreleased" string instance. You don't have to release it when you're done. This is true of all methods that return an object, except new and init methods.
Does it cause memory leak to alloc without dealloc ?
Yes, unless you're using garbage collection.
Where can I find basic tutorial like this on internet ?
See Practical Memory Management
The correct way is:
-(void)awakeFromNib
{
int i;
for (i=1;i<=24;i++)
{
NSString *mystr = [[NSString alloc]initWithFormat:#"%d",i];
[comboHour addItemWithObjectValue:mystr];
[mystr release];
}
}
You can use NSNumber instead of NSString, which might be preferable depending on your context.
You do need to create a new object everytime, because addItemWithObjectValue: is expecting an object rather than a primitive.
You can create a new object (e.g. `NSString), via two methods:
Using alloc/init, like how you did it initially. Such initializations require the release of the object once it isn't required anymore in the allocation scope, using release rather than dealloc.
Using stringWithFormat: factory methods that use auto release pool to release themselves "automatically". The code would look like:
-(void)awakeFromNib
{
int i;
for (i=1; i <= 24; i++) {
NSString *s = [NSString stringWithFormat:#"%d", i];
[comboHour addItemWithObjectValue:s];
}
}
However, it is recommended not to use such construction within loops.
For memory issues, check out the Memory Management Programming Guide for Cocoa
Based on the code you posted and your stated experience level, I recommend going through Apple's Currency Converter tutorial if you haven't already. It's the standard Cocoa tutorial every beginner should read. Fundamentals like interacting with IBOutlets are covered.

Resources