How to see how many files in draggingPasteboard? - xcode

I have a drag operation that only allows to drag a single file, and I want to capture this on "draggingEntered" like so:
- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender {
if ([[sender draggingPasteboard] count]] == 1) {
return NSDragOperationCopy;
}
else {
return NSDragOperationNone;
}
}
But count is not valid method or property, but I can't figure out what to replace it with, so which is the best way to see how many items there are on the draggingPasteboard? Should I get the array of filenames on the draggingPasteboard using something like propertyListForType: NSFilenamsPboardType, and then get the index of that, or is there a more clever way to do this?

If You want to use count You need to use pasteboardItems which is items array who response to count.
It can be done like this:
- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender {
if([[[sender draggingPasteboard] pasteboardItems] count] == 1) {
return NSDragOperationCopy;
}
else {
return NSDragOperationNone;
}
}

Related

Swift: How do I create a sequence of 'if' statements?

I want to know how to write a sequence in an if statement, like I would as a sequence of actions? For example:
If I have var touchedBool = Bool(), how do I create an if statement that says:
if touchedBool == true FOLLOWED BY touchedBool == false THEN
{
// This happens.
}
I believe there should be a better way to accomplish what you really want, but here is the thing I believe you are looking for:
var touchedBool = false {
didSet {
if oldValue && !touchedBool {
print("Boom") // Do your stuff here
}
}
}
var someBool = true
if someBool {
// Do something
} else {
// Do Something
}
For your example, a simple if-else would be fine:
var anyBool: Bool = true
if anyBool == true {
//do something
} else {
//do something else
}
This would work fine for bools, but gets cumbersome for something with multiple options other than true of false. For this, I would recommend a switch statement;
var anyInt = 1
switch anyInt {
case 1:
//do something
case 2:
//do something
case 3:
//do something
case 4:
//do something
default:
//do something else
}
Switch statements are a lot more flexible as well

How to populate Enum from the values retrieved from Database

Looking at the example here at Message Controller for Pizza Example, if I want to populate Size or Kind based on some user input and make a call to the database, how would I do that?
So far as I know, there is not an easy way to populate the Enum at runtime.
It looks like this hasn't been implemented yet. I took a look inside https://github.com/Microsoft/BotBuilder/blob/master/CSharp/Library/FormFlow/FormBuilder.cs and found this:
internal static void TypePaths(Type type, string path, List<string> paths)
{
if (type.IsClass)
{
if (type == typeof(string))
{
paths.Add(path);
}
else if (type.IsIEnumerable())
{
var elt = type.GetGenericElementType();
if (elt.IsEnum)
{
paths.Add(path);
}
else
{
// TODO: What to do about enumerations of things other than enums?
}
}
else
{
FieldPaths(type, path, paths);
}
}
else if (type.IsEnum)
{
paths.Add(path);
}
else if (type == typeof(bool))
{
paths.Add(path);
}
else if (type.IsIntegral())
{
paths.Add(path);
}
else if (type.IsDouble())
{
paths.Add(path);
}
else if (type.IsNullable() && type.IsValueType)
{
paths.Add(path);
}
else if (type == typeof(DateTime))
{
paths.Add(path);
}
}
Notice the TODO about enumerations other than enums.
Outside of the FormBuilder we can use PromptDialog.Choice which takes an IEnumerable<> of your options.
It is possible to chain dialogs together, so you may have to split your FormDialog into two with the PromptDialog in-between.
Alternatively take a fork of BotBuilder and implement the TODO!

Searching NSMenuItem inside NSMenu recursively

The method itemWithTitle locates a menu item within a NSMenu. However it looks only inside the first level. I cannot find a ready-to-use method that will do the same job recursively by searching inside all the nested submenus. Or, somehow equivalently, a function that swipes NSmenu's recursively.
It looks quite incredible to me that such a thing would not exist. Maybe there is some function not directly related to NSMenu that can come in handy?
Ok, since I really need to do this clumsy workaround for a number of specific reasons (beyond the scope of this post, and quite uninteresting anyway) I ended up coding it myself.
Here is the snippet:
NSMenuItem * mitem;
while (mitem) { // loop over all menu items contained in any submenu, subsubmenu, etc.
// do something with mitem ....
mitem = next_menu_item(mitem);
}
which is powered by the functions:
NSMenuItem * goto_submenu(NSMenuItem * mitem){
NSMenu * submen = [mitem submenu];
if (submen && [[submen title] isNotEqualTo:#""] && [submen numberOfItems])
return goto_submenu([submen itemAtIndex:0]);
return mitem;
};
NSMenuItem * next_menu_item(NSMenuItem * mitem){
NSMenu * menu = [mitem menu];
if ([menu indexOfItem:mitem]==([menu numberOfItems]-1)) //if is last item in submenu go to parent item
return [mitem parentItem];
return goto_submenu([menu itemAtIndex:([menu indexOfItem:mitem]+1)]);
};
#implementation NSMenu (UT)
- (NSMenuItem *)itemWithTitle:(NSString *)title recursive:(BOOL)recursive {
if (recursive) {
for (NSMenuItem *item in self.itemArray) {
if ([item.title isEqualToString:title]) {
return item;
} else if (item.submenu) {
NSMenuItem *result = [item.submenu itemWithTitle:title recursive:recursive];
if (result) {
return result;
}
}
}
return nil;
} else {
return [self itemWithTitle:title];
}
}
- (NSMenuItem *)itemWithTag:(NSInteger)tag recursive:(BOOL)recursive {
if (recursive) {
for (NSMenuItem *item in self.itemArray) {
if (item.tag == tag) {
return item;
} else if (item.submenu) {
NSMenuItem *result = [item.submenu itemWithTag:tag recursive:recursive];
if (result) {
return result;
}
}
}
return nil;
} else {
return [self itemWithTag:tag];
}
}
#end

How to Limit the Number of Tokens in a NSTokenField?

I have a NSTokenField Where the tokens are created upon hitting enter. I would like to limit the number of tokens in this field. Say for example, User should be allowed to enter only 2 tokens one after the other. Later, neither user should be allowed to set the Token nor user should be allowed to search further. In short, User should be blocked after 2 tokens.
Could any one please help me in achieving this???
Thanks in advance :)
The solution is divided in 2 parts:
-(NSArray *)tokenField:(NSTokenField *)tokenField shouldAddObjects:(NSArray *)tokens atIndex:(NSUInteger)index
{
//limit the tokens
if(self.tokensLimit)
{
NSArray * tokensArray = [_tokenField objectValue];
if([tokensArray count] > 0)
{
if([tokens isEqualToArray:tokensArray])
{
return tokens;
}
else if([tokensArray count]>=self.tokensLimit)
{
return #[];
}
else if([tokens count]>0)
{
tokens = [tokens subarrayWithRange:NSMakeRange(0, MIN([tokens
count], self.tokensLimit))];
}
else
return #[];
}
else
{
tokens = [tokens subarrayWithRange:NSMakeRange(0, MIN([tokens count], self.tokensLimit))];
}
}
return tokens;
}
where tokensLimit is an int > 0
the delegate covers all the cases like tokens added by copy/paste, completion list, drag&drop, manually written etc..
this other delegate cover the case where the user write a string and hit "TAB"
- (BOOL)control:(NSControl *)control isValidObject:(id)object
{
if(self.tokensLimit)
{
NSArray * tokensArray = [_tokenField objectValue];
tokensArray = [tokensArray subarrayWithRange:NSMakeRange(0, MIN([tokensArray count], self.tokensLimit))];
[_tokenField setObjectValue:tokensArray];
}
return YES;
}
If you save the tokens in a db, you can count the number of rows of the particular users id, and add an if statement to limit it to 2.
Behold:
var maximumTokens: Int = 2
func tokenField(_ tokenField: NSTokenField, shouldAdd tokens: [Any], at index: Int) -> [Any] {
var count = 0
if let textView = tokenField.currentEditor() as? NSTextView {
for scalar in textView.string.unicodeScalars {
if scalar.value == unichar(NSAttachmentCharacter) {
count += 1
}
}
}
return tokens.filter({ _ in
count += 1
return count <= maximimTokens
})
}
I've tested it and it works when you are typing tags or even copying & pasting them in.

Problems implementing the NSBrowserDelegate protocol

I'm able to get my NSBrowser instance to display the correct data in the first column. When I select one of the options, however, the next column simply displays the same set of options. I have read the docs, looked at all of Apple's relevant sample code, and just about everything I could find on the internet but I simply can't figure out the correct way to implement the required methods. The data I'm supplying to the browser is an array of dictionaries. Each dictionary in turn contains a "children" key that is another array of dictionaries. And those dictionaries have their own "children" key that are also arrays of dictionaries, etc. Using JSON for descriptive purposes (objects are dictionaries, arrays are arrays), it looks like this:
data = [
{
name: 'David',
children:[
{
name: 'Sarah',
children: {...}
},
{
name: 'Kevin',
children: {...}
}
]
},
{
name: 'Mary',
children:[
{
name: 'Greg',
children: {...}
},
{
name: 'Jane',
children: {...}
}
]
}
]
So the first column should show "David" and "Mary". If "David" is selected, the next column should show "Sarah" and "Kevin", and so on.
My current implementation relies on a custom method I created that is supposed to translate the browser's index path into the corresponding NSArray level from the provided data. This method looks like:
- (NSArray *)getSelectionInBrowser:(NSBrowser *)browser
{
NSArray *selection = browserData;
NSIndexPath *path = [browser selectionIndexPath];
for (NSUInteger i = 0; i < path.length; i++) {
selection = [[selection objectAtIndex:i] objectForKey:#"children"];
}
return selection;
}
My implementation of the required NSBrowserDelegate protocol methods looks like:
- (NSInteger)browser:(NSBrowser *)sender numberOfRowsInColumn:(NSInteger)column
{
return [[self getSelectionInBrowser:sender] count];
}
- (NSInteger)browser:(NSBrowser *)browser numberOfChildrenOfItem:(id)item {
return [[self getSelectionInBrowser:browser] count];
}
- (id)browser:(NSBrowser *)browser child:(NSInteger)index ofItem:(id)item {
return [self getSelectionInBrowser:browser];
}
- (BOOL)browser:(NSBrowser *)browser isLeafItem:(id)item {
return ![item isKindOfClass:[NSMutableArray class]];
}
- (id)browser:(NSBrowser *)browser objectValueForItem:(id)item {
return nil;
}
- (void)browser:(NSBrowser *)browser willDisplayCell:(NSBrowserCell *)cell atRow:(NSInteger)row column:(NSInteger)column {
NSArray *selection = [self getSelectionInBrowser:browser];
cell.title = [[selection objectAtIndex:row] objectForKey:#"name"];
}
The first column of the NSBrowser is populated with the correct names. However, as soon as I make a selection the program crashes with the error -[__NSArrayM objectAtIndex:]: index 4 beyond bounds [0 .. 0]. After doing some debugging, the line of code it crashes on is the objectAtIndex: call in my custom getSelectionInBrowser:.
That doesn't fully surprise me because even before the crash I figured I was doing something wrong by relying on that custom method to retrieve the current selection. I imagine this work should be done within the delegate methods themselves and, when implemented correctly, the current selection should be accessible in the item variable that is provided in many of those methods. However, I couldn't get that to work. The item variable always seemed to be simply the root data object rather than reflecting the most "drilled-down" selection.
So how do I correct my implementation?
Solved it! Here is my final working code. No need for that custom getSelection... method, and a couple of the delegate methods I had were unnecessary (only used of you are NOT going with the "item-based API").
- (NSInteger)browser:(NSBrowser *)browser numberOfChildrenOfItem:(id)item {
if (item) {
return [[item objectForKey:#"children"] count];
}
return [browserData count];
}
- (id)browser:(NSBrowser *)browser child:(NSInteger)index ofItem:(id)item {
if (item) {
return [[item objectForKey:#"children"] objectAtIndex:index];
}
return [browserData objectAtIndex:index];
}
- (BOOL)browser:(NSBrowser *)browser isLeafItem:(id)item {
return [item objectForKey:#"children"] == nil;
}
- (id)browser:(NSBrowser *)browser objectValueForItem:(id)item {
return [item objectForKey:#"name"];
}
The first method is how you tell the NSBrowser the number of rows there should be. The second method is where you determine what data should be represented in a given row (index). In both cases, you must first check to see if item actually exists. If it doesn't, that's because you are at the root of the data (first column in the NSBrowser). Only when a row (or item!) in the NSBrowser gets selected will the item variable hold anything. The final method should return the string you wish to show in the given row.
Hopefully this helps people in the future.

Resources