Move selection sequentially in NSCollectionView - objective-c

By default selection in NSCollectionView get moved by arrow keys within one row (or column).
How to make selection move sequentially, like items arranged by index?
Screenshot from developer.apple.com

I have asked same question to Apple Developer Technical Support, after checked they said "Our engineers have reviewed your request and have determined that this would be best handled as a bug report.". I submitted bug report to Apple's radar. So far no response.
I decided to implement my own solution. Subclass your NSCollectionView and override keyDown event.
Swift 4
override func keyDown(with event: NSEvent) {
if event.modifierFlags.rawValue == 10617090 {
return
}
if event.isARepeat == true && event.keyCode != 123 && event.keyCode != 124 && event.keyCode != 125 && event.keyCode != 126 {
return
}
if event.keyCode == 123 || event.keyCode == 124 || event.keyCode == 125 || event.keyCode == 126 {
for index in self.selectionIndexes {
if event.keyCode == 124 && index < YOUR_DATASOURCE_ARRAY.count - 1 {
self.deselectItems(at: [NSIndexPath(forItem: index, inSection: 0) as IndexPath])
self.selectItems(at: [NSIndexPath(forItem: index + 1, inSection: 0) as IndexPath], scrollPosition: NSCollectionView.ScrollPosition.nearestHorizontalEdge)
return
}
if event.keyCode == 123 && index > 0 {
self.deselectItems(at: [NSIndexPath(forItem: index, inSection: 0) as IndexPath])
self.selectItems(at: [NSIndexPath(forItem: index - 1, inSection: 0) as IndexPath], scrollPosition: NSCollectionView.ScrollPosition.nearestHorizontalEdge)
return
}
}
}
super.keyDown(with: event)
}
This interrupts left and right arrow key inputs and move selection to previous/next cell. Since
"Allows Multiple Selection" has to be NO. Also disable modifier keys as like CMD or Control.
If you keep pressing right arrow, when selection arrives to last cell at right hand it jump one row down and keep moving or vice versa for left arrow.
Hope it helps.

Related

'unrecognized selector' for category method on SpriteKit class [duplicate]

I'm creating a game with SpriteKit, that has collision between 2 bodies. After setting up the bodies, I've implemented the didBegin(_contact:) moethod as shown below:
func didBegin(_ contact: SKPhysicsContact) {
if contact.bodyA.categoryBitMask == 0 && contact.bodyB.categoryBitMask == 1 {
gameOver()
}
}
and it worked perfectly.
Later, while inspecrting the documentation for this method, I found the following:
The two physics bodies described in the contact parameter are not passed in a guaranteed order.
So to be on the safe side, I've extended the SKPhysicsContact class with a function the swaps the categoryBitMask between both bodies, as following:
extension SKPhysicsContact {
func bodiesAreFromCategories(_ a: UInt32, and b: UInt32) -> Bool {
if self.bodyA.categoryBitMask == a && self.bodyB.categoryBitMask == b { return true }
if self.bodyA.categoryBitMask == b && self.bodyB.categoryBitMask == a { return true }
return false
}
}
The problem is that when the function gets called, the app crashes, and I get the following error:
2017-07-18 13:44:18.548 iSnake Retro[17606:735367] -[PKPhysicsContact bodiesAreFromCategories:and:]: unrecognized selector sent to instance 0x60000028b950
2017-07-18 13:44:18.563 iSnake Retro[17606:735367] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[PKPhysicsContact bodiesAreFromCategories:and:]: unrecognized selector sent to instance 0x60000028b950'
This apparently is a bug, as answered here:
https://stackoverflow.com/a/33423409/6593818
The problem is, the type of contact is PKPhysicsContact (as you've noticed), even when you explicitly tell it to be an SKPhysicsContact, and the extension is on SKPhysicsContact. You'd have to be able to make an extension to PKPhysicsContact for this to work. From this logic, we can say that no instance methods will work in SKPhysicsContact extensions at the moment. I'd say it's a bug with SpriteKit, and you should file a radar. Class methods still work since you call them on the class itself.
In the meantime, you should be able to move that method into your scene or another object and call it there successfully.
For the record, this is not a Swift-specific problem. If you make the same method in an Objective-C category on SKPhysicsContact you'll get the same crash.
You can submit a bug report to apple:
https://developer.apple.com/bug-reporting/
And report it to the community:
https://openradar.appspot.com/search?query=spritekit
However, what you really want to do with your code is to add the category masks together. And then check for the sum (2 + 4 and 4 + 2 always equals 6, regardless of bodyA and bodyB order).
This is how you get unique contacts, if you set up your masks correctly in powers of two (2, 4, 8, 16, etc)
SKPhysicsContact is a wrapper class to PKPhysicsContact, you are extending SKPhysicsContact but in reality you need to extend PKPhysicsContact (Which you can't do)
To preserve order in your contact methods, just do:
let bodyA = contact.bodyA.categoryBitMask <= self.bodyB.categoryBitMask ? contact.bodyA : contact.bodyB
let bodyB = contact.bodyA.categoryBitMask > self.bodyB.categoryBitMask ? contact.bodyA : contact.bodyB
This way when you need to check for a specific node, you know what node to hit, so
func didBegin(_ contact: SKPhysicsContact) {
if contact.bodyA.categoryBitMask == 0 && contact.bodyB.categoryBitMask == 1 {
gameOver()
}
}
Becomes
func didBegin(_ contact: SKPhysicsContact) {
let bodyA = contact.bodyA.categoryBitMask <= self.bodyB.categoryBitMask ? contact.bodyA : contact.bodyB
let bodyB = contact.bodyA.categoryBitMask > self.bodyB.categoryBitMask ? contact.bodyA : contact.bodyB
if bodyA.categoryBitMask == 0 && bodyB.categoryBitMask == 1 {
gameOver()
}
}
You can then add to your code since you now know the individual bodies.
func didBegin(_ contact: SKPhysicsContact) {
let bodyA = contact.bodyA.categoryBitMask <= self.bodyB.categoryBitMask ? contact.bodyA : contact.bodyB
let bodyB = contact.bodyA.categoryBitMask > self.bodyB.categoryBitMask ? contact.bodyA : contact.bodyB
if bodyA.categoryBitMask == 0 && bodyB.categoryBitMask == 1 {
gameOver()
//since I know bodyB is 1, let's add an emitter effect on bodyB.node
}
}
BTW, for people who see this answer, categoryBitMask 0 should not be firing any contacts, you need some kind of value in it to work. This is a bug that goes beyond the scope of the authors question, so I left it at 0 and 1 to since that is what his/her code is doing and (s)he is claiming it works.

Check for consecutive value in Map?

I'm creating a chat app and don't want to display the user avatar over and over if a user sends multiple messages consecutively.
The user messages are stored in a Map, where the newest message has the index 0.
To check if a message at an index is sent by the same person as the message before, I use the following method:
bool _sameUser () {
if (index > 0 && map != null && map[index + 1] != null && map[index + 1]['fromUser’] == map[index][‘fromUser’]) {
return true;
} else {
return false;
}
}
However, this doesn't work for the newest message or if there are more than two messages from the same user.
How I can rewrite the conditional so it works as intended?
Thanks!
UPDATE:
After try #Marcel answer and test more I find another issue maybe cause this.
map[index + 1] != null is not true even when message 2 is send. Is only true after message 3 is send.
I test with conditional in function:
if (map[index + 1] != null) {
print('!=null');
}
There's no reason your code shouldn't work for the index 0 except you test index > 0.
By removing that, it should work fine:
bool _sameUser () {
assert(index >= 0);
assert(map != null);
return map[index + 1] != null && map[index + 1]['fromUser'] == map[index]['fromUser'];
}
Because I assumed, the index should never be smaller than 0 and the map should never be null, I moved some of the condition code to assert statements.
Also, because your if-expression is a boolean, you can just return it directly.

TicTacToe is cheating

I've created a TTT game. However I'm having trouble with the AI taking spots. How do I avoid this from happening so that in the chooseCellRow:Col method it only returns a random spot that has not already been selected by either player?
- (void) chooseCellRow:(NSInteger)row Col:(NSInteger)col
{
//pick random spot for computer's turn
row = random()%2;
col = random()%2;
if (row == 0 && col == 0) {
[but00 setTitle:#"X" forState:0];
}
else if (row == 0 && col == 1) {
[but01 setTitle:#"X" forState:0];
}
else if (row == 0 && col == 2) {
[but02 setTitle:#"X" forState:0];
}
else if (row == 1 && col == 0) {
[but10 setTitle:#"X" forState:0];
}
else if (row == 1 && col == 1) {
[but11 setTitle:#"X" forState:0];
}
else if (row == 1 && col == 2) {
[but12 setTitle:#"X" forState:0];
}
else if (row == 2 && col == 0) {
[but20 setTitle:#"X" forState:0];
}
else if (row == 2 && col == 1) {
[but21 setTitle:#"X" forState:0];
}
else if (row == 2 && col == 2) {
[but22 setTitle:#"X" forState:0];
}
}
Cheating is a moral concept, one which doesn't apply to the computer since it's doing exactly what you've told it to do. However, you could prevent this by simply choosing again if the cell is already occupied, pseudo-code:
row = random() % 3;
col = random() % 3;
while cell[row][col] != empty: # Add your REAL detection code here.
row = random() % 3;
col = random() % 3;
You'll also notice that the above code stops you from cheating as well. Shame on you for only allowing the computer to choose four of the nine possibilities :-)
Applying % 2 to a number will give you 0 or 1, you need to use % 3 to allow for 2 as well.
Based on your comments that you have a cell array which contains the character occupying that cell, the code to continuse until you find a blank cell would be along the lines of:
do {
row = random() % 3;
col = random() % 3;
} while (cell[row][col] != ' ');
One easy way would be to keep a list of unused positions and choose from that list. When either player moves, remove the position they choose from the list.
Another, even simpler but less efficient way is to have it choose any position, but try again if the spot it picks is already occupied.

BST [homework] how does this method of iterative traversal work?

I'm using an example from a free book Open Data Structures (in Java) by Pat Morin. I believe i understand the concept of what is going on for tree traversal (keep going left until you can't go any more left, then right and then back up.
I'm a little flummoxed on the code below however. How exactly does it know to change branches in a structure such as:
r(oot)
|
- -
| |
a b
| |
c d
void traverse2() {
Node u = r, prev = nil, next;
while (u != nil) {
if (prev == u.parent) {
if (u.left != nil) next = u.left;
else if (u.right != nil) next = u.right;
else next = u.parent;
} else if (prev == u.left) {
if (u.right != nil) next = u.right;
else next = u.parent;
} else {
next = u.parent;
}
prev = u;
u = next;
} }
from what I can see it automatically goes to parent even though the root has none?
The root's parent is nil, so the algorithm terminates once it leaves the root by its parent (which it does after visiting the right subtree.)

Comparing multiple values at once using &&

I have this code:
if ((total == (total1 && total2 && total3)))
{
[scrollview.contentOffset = CGPointMake (0,0)];
}
here is what it's something like on button action:
if (sender.tag == 1)
{
total1 = 10;
}
if (sender.tag == 2)
{
total2 = 20;
}
if (sender.tag == 3)
{
total3 = 30;
}
I am trying to go back to the start page of the scroll view if the user clicked the three correct buttons (similar to a password key).
Does the logical operator && work well in Objective-C, and did I use it right?
if ((total == (total1 && total2 && total3)))
You cannot do that. You have to explicitly compare each separately.
if ((total == total1) && (total == total2) && (total == total3)))
But that leaves the question of how total can be equal to all the three simultaneously though.
In your code:
if ((total == (total1 && total2 && total3)))
{
[scrollview.contentOffset = CGPointMake (0,0)];
}
When the if expression is evaluated, (total1 && total2 && total3) is evaluated first. And that can be either YES or NO (true or false if you prefer), or (0 or 1).
So your code is equivalent to the following:
BOOL allVariablesAreNotZero = total1 && total2 && total3;
if (total == allVariablesAreNotZero)
{
[scrollview.contentOffset = CGPointMake (0,0)];
}
Edit after the question was better explained
Make your buttons perform the following action when pressed:
- (void)buttonClicked:(id)sender
{
UIButton *button = (UIButton *)sender;
buttonsCombination = buttonsCombination | (1 << button.tag);
}
Where buttonsCombination is an NSUInteger. Then use the following test to see if the buttons that were pressed are the correct ones (I am doing this with three buttons, but you guess the idea)
NSUInteger correctCombination = (1 << button1) | (1 << button2) | (1 << button3)
if (buttonsCombination == correctCombination) {
// The combination is correct
} else {
// The combination is incorrect
}
buttonsCombination = 0;
Finally, note that this works because there are enough bits in a NSUInteger for 30 buttons.
Here I used bitwise operators | and <<.
What your current code is essentially saying is "if total is 'true' and total1, total2, and total3 are also all nonzero or if total is zero and total1, total2, and total3 are also all zero, then do something".
The && you have there is doing a logical/boolean comparison. It treats its arguments as being either true or false, and returns true if both arguments evaluate to true and false in any other case. The == compares the value of total with the true or false value that was obtained from your && expressions. That is probably not what you want here.
It seems like probably what you want to be saying is "if total is equal to the sum of total1, total2, and total3, then do something". Assuming this is the case, you would do:
if (total == (total1 + total2 + total3)) {
[scrollview.contentOffset = CGPointMake (0,0)];
}
Trying to determine what you mean by your comment on two other answers "i tried it but it executes the codes when i started to run the app" maybe this is what you're trying to achieve:
/* all in your button handler */
switch(sender.tag)
{
case 1:
total1 = 10;
break;
case 2:
total2 = 20;
break;
case 3:
total3 = 30;
break;
default:
break; // other buttons are ignored
}
// check it latest click means the total is now correct
if((total1 + total2 + total3) == total)
{
[scrollview.contentOffset = CGPointMake (0,0)];
}
So you update any totalX's effected by the button click and then check the condition to reset the scrolling.