NSRuleEditor: Criteria for new row - cocoa

When I hit the "+" button on a row in NSRuleEditor, a new row is created. How can I take influence on the criteria used for that row.
It seems NSRuleEditor defaults to selecting the first criterion sequentially from the list of possible values. I would much rather have the new row match the row where the "+" was clicked.

I was able to fake it by subclassing a private method:
- (void)_addOptionFromSlice:(id)slice ofRowType:(unsigned int)type
{
int rowIndex = [(NSRuleEditorViewSlice*)slice rowIndex];
NSArray *criteriaForRow = [self criteriaForRow:rowIndex];
NSArray *displayValuesForRow = [self displayValuesForRow:rowIndex];
self.template = [NSArray arrayWithObjects:criteriaForRow, displayValuesForRow, nil];
[super _addOptionFromSlice:slice ofRowType:type];
}
- (void)insertRowAtIndex:(NSInteger)rowIndex withType:(NSRuleEditorRowType)rowType asSubrowOfRow:(NSInteger)parentRow animate:(BOOL)shouldAnimate
{
[super insertRowAtIndex:rowIndex withType:rowType asSubrowOfRow:parentRow animate:shouldAnimate];
NSArray *template = self.template;
if (template != nil) {
[self setCriteria:[template objectAtIndex:0] andDisplayValues:[template objectAtIndex:1] forRowAtIndex:rowIndex];
}
}

Related

How To Provide Search-As-You-Type Filtering With UICollectionView?

I have a UICollectionView added to a ViewController. The UICollectionView displays a grid of items with peoples names on each cell.
I would like to add search as you type functionality that will filter the UICollectionView as the user types in their name to a searchbar or UITextField on the ViewController so that it doesn't scroll with the content in the UICollectionView.
Even though a few places I have read that UICollectionView is similar to UITableView I don't see that implementing this filter/search functionality is the same in UICollectionView as it is with UITableView and need some help with it.
Does anyone have a good example of doing this search/filter as you type functionality with UICollectionView?
I solved it by doing the following (hope it helps somebody else):
I populate my UICollectionView from CoreData into an NSArray:
self.allCVData = [context executeFetchRequest:fetchRequest error:&error];
then add my NSArray to an NSMutableArray so that I can use that for filtering on the UICollection:
self.filteredCVData = [[NSMutableArray alloc] initWithArray:allCVData];
Added a UITextField and called it searchField.
Added a selector in viewDidLoad:
[mytextfield addTarget:self action:#selector(textDidChange:) forControlEvents:UIControlEventEditingChanged];
Added a method to accept changes in the UITextField:
-(void) textDidChange:(id)sender
{
UITextField* searchField = (UITextField *) sender;
if(searchField.text.length == 0)
{
self.isFiltered = FALSE;
[self.filteredCVData removeAllObjects];
[self.filteredCVData addObjectsFromArray:self.allCVData];
}
else
{
self.isFiltered = true;
[self.filteredCVData removeAllObjects];
self.filteredCVData = [[NSMutableArray alloc] init];
LogInfo(#"Before Looping in allCVData Array.");
for (OLPerson* person in allCVData)
{
NSRange firstnameRange = [person.firstname rangeOfString:searchField.text options:NSCaseInsensitiveSearch];
NSRange surnameRange = [person.surname rangeOfString:searchField.text options:NSCaseInsensitiveSearch];
if(firstnameRange.location != NSNotFound || surnameRange.location != NSNotFound)
{
[self.filteredCVData addObject:person];
}
}
}
[self.collectionView reloadData];
}

Retrieving the data from the table in iphone

I have a table wherein every row contains a textfield.After i entertext in all the rows which are text fields i want to retrieve the data into an array.Can any one tell me the idea of retrieving the data from every row which are textfields?
I cannot believe you. Bad grammar and no research.
To get an array of all text fields, I would do:
NSArray *subviews = self.view.subviews;
NSMutableArray *textFelds = [[NSMutableArray alloc]init];
NSMutableArray *indexPaths = [[NSMutableArray alloc]init];
NSObject *obj;
for (obj in subviews) {
if ([obj class] == [UITextField class]) {
[textFields addObject:obj];
CGPoint location = obj.center;
    NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:location];
[indexPaths addObject:indexPath];
}
}
It first gets all of the subviews of the main view, then determines if they are text fields. If they are textFields, it adds that to an array and also gets the indexPath.
I hope I have answered your question
You are having the details like, which row has text fields, which are not.
You can get this feature by using two different king of cells
static NSString *CellIdentifierNormal = #"Cell";
static NSString *CellIdentifierTF = #"Cell";
UITableViewCell *cell;
if(indexPath.row %2 == 0){ //Example
cell = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifierNormal];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifierNormal] autorelease];
}
} else {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifierTF] autorelease];
TF *tf = [[TF alloc] init];
tf.tag = 10;
}
In Text field row case, should not use the dequeueReusableCellWithIdentifier it re uses the existing cells, so that the text fields data may corrupt [This will happen when your data rows size greater than the table size - height]
Whenever you want collect the data from TF's
Just run loop with number of rows in table, and check same condition as you are using in creating cell.
Access the cell, and TF from cell by using tag number [10] from TF get the text

Respond to mouse events in text field in view-based table view

I have text fields inside a custom view inside an NSOutlineView. Editing one of these cells requires a single click, a pause, and another single click. The first single click selects the table view row, and the second single click draws the cursor in the field. Double-clicking the cell, which lets you edit in a cell-based table view, only selects the row.
The behavior I want: one click to change the selection and edit.
What do I need to override to obtain this behavior?
I've read some other posts:
The NSTextField flyweight pattern wouldn't seem to apply to view-based table views, where the cell views are all instantiated from nibs.
I tried subclassing NSTextField like this solution describes, but my overridden mouseDown method is not called. Overridden awakeFromNib and viewWillDraw (mentioned in this post) are called. Of course mouseDown is called if I put the text field somewhere outside a table view.
By comparison, a NSSegmentedControl in my cell view changes its value without first selecting the row.
Here's the working solution adapted from the accepted response:
In outline view subclass:
-(void)mouseDown:(NSEvent *)theEvent {
[super mouseDown:theEvent];
// Forward the click to the row's cell view
NSPoint selfPoint = [self convertPoint:theEvent.locationInWindow fromView:nil];
NSInteger row = [self rowAtPoint:selfPoint];
if (row>=0) [(CellViewSubclass *)[self viewAtColumn:0 row:row makeIfNecessary:NO]
mouseDownForTextFields:theEvent];
}
In table cell view subclass:
// Respond to clicks within text fields only, because other clicks will be duplicates of events passed to mouseDown
- (void)mouseDownForTextFields:(NSEvent *)theEvent {
// If shift or command are being held, we're selecting rows, so ignore
if ((NSCommandKeyMask | NSShiftKeyMask) & [theEvent modifierFlags]) return;
NSPoint selfPoint = [self convertPoint:theEvent.locationInWindow fromView:nil];
for (NSView *subview in [self subviews])
if ([subview isKindOfClass:[NSTextField class]])
if (NSPointInRect(selfPoint, [subview frame]))
[[self window] makeFirstResponder:subview];
}
Had the same problem. After much struggle, it magically worked when I selected None as against the default Regular (other option is Source List) for the Highlight option of the table view in IB!
Another option is the solution at https://stackoverflow.com/a/13579469/804616, which appears to be more specific but a little hacky compared to this.
I'll try to return the favor... Subclass NSOutlineView and override -mouseDown: like so:
- (void)mouseDown:(NSEvent *)theEvent {
[super mouseDown:theEvent];
// Only take effect for double clicks; remove to allow for single clicks
if (theEvent.clickCount < 2) {
return;
}
// Get the row on which the user clicked
NSPoint localPoint = [self convertPoint:theEvent.locationInWindow
fromView:nil];
NSInteger row = [self rowAtPoint:localPoint];
// If the user didn't click on a row, we're done
if (row < 0) {
return;
}
// Get the view clicked on
NSTableCellView *view = [self viewAtColumn:0 row:row makeIfNecessary:NO];
// If the field can be edited, pop the editor into edit mode
if (view.textField.isEditable) {
[[view window] makeFirstResponder:view.textField];
}
}
You really want to override validateProposedFirstResponder and allow a particular first responder to be made (or not) depending on your logic. The implementation in NSTableView is (sort of) like this (I'm re-writing it to be pseudo code):
- (BOOL)validateProposedFirstResponder:(NSResponder *)responder forEvent:(NSEvent *)event {
// We want to not do anything for the following conditions:
// 1. We aren't view based (sometimes people have subviews in tables when they aren't view based)
// 2. The responder to valididate is ourselves (we send this up the chain, in case we are in another tableview)
// 3. We don't have a selection highlight style; in that case, we just let things go through, since the user can't appear to select anything anyways.
if (!isViewBased || responder == self || [self selectionHighlightStyle] == NSTableViewSelectionHighlightStyleNone) {
return [super validateProposedFirstResponder:responder forEvent:event];
}
if (![responder isKindOfClass:[NSControl class]]) {
// Let any non-control become first responder whenever it wants
result = YES;
// Exclude NSTableCellView.
if ([responder isKindOfClass:[NSTableCellView class]]) {
result = NO;
}
} else if ([responder isKindOfClass:[NSButton class]]) {
// Let all buttons go through; this would be caught later on in our hit testing, but we also do it here to make it cleaner and easier to read what we want. We want buttons to track at anytime without any restrictions. They are always valid to become the first responder. Text editing isn't.
result = YES;
} else if (event == nil) {
// If we don't have any event, then we will consider it valid only if it is already the first responder
NSResponder *currentResponder = self.window.firstResponder;
if (currentResponder != nil && [currentResponder isKindOfClass:[NSView class]] && [(NSView *)currentResponder isDescendantOf:(NSView *)responder]) {
result = YES;
}
} else {
if ([event type] == NSEventTypeLeftMouseDown || [event type] == NSEventTypeRightMouseDown) {
// If it was a double click, and we have a double action, then send that to the table
if ([self doubleAction] != NULL && [event clickCount] > 1) {
[cancel the first responder delay];
}
...
The code here checks to see if the text field
cell had text hit. If it did, it attempts to edit it on a delay.
Editing is simply making that NSTextField the first responder.
...
}
I wrote the following to support the case for when you have a more complex NSTableViewCell with multiple text fields or where the text field doesn't occupy the whole cell. There a trick in here for flipping y values because when you switch between the NSOutlineView or NSTableView and it's NSTableCellViews the coordinate system gets flipped.
- (void)mouseDown:(NSEvent *)theEvent
{
[super mouseDown: theEvent];
NSPoint thePoint = [self.window.contentView convertPoint: theEvent.locationInWindow
toView: self];
NSInteger row = [self rowAtPoint: thePoint];
if (row != -1) {
NSView *view = [self viewAtColumn: 0
row: row
makeIfNecessary: NO];
thePoint = [view convertPoint: thePoint
fromView: self];
if ([view isFlipped] != [self isFlipped])
thePoint.y = RectGetHeight(view.bounds) - thePoint.y;
view = [view hitTest: thePoint];
if ([view isKindOfClass: [NSTextField class]]) {
NSTextField *textField = (NSTextField *)view;
if (textField.isEnabled && textField.window.firstResponder != textField)
dispatch_async(dispatch_get_main_queue(), ^{
[textField selectText: nil];
});
}
}
}
Just want to point out that if all that you want is editing only (i.e. in a table without selection), overriding -hitTest: seems to be simpler and a more Cocoa-like:
- (NSView *)hitTest:(NSPoint)aPoint
{
NSInteger column = [self columnAtPoint: aPoint];
NSInteger row = [self rowAtPoint: aPoint];
// Give cell view a chance to override table hit testing
if (row != -1 && column != -1) {
NSView *cell = [self viewAtColumn:column row:row makeIfNecessary:NO];
// Use cell frame, since convertPoint: doesn't always seem to work.
NSRect frame = [self frameOfCellAtColumn:column row:row];
NSView *hit = [cell hitTest: NSMakePoint(aPoint.x + frame.origin.x, aPoint.y + frame.origin.y)];
if (hit)
return hit;
}
// Default implementation
return [super hitTest: aPoint];
}
Here is a swift 4.2 version of #Dov answer:
override func mouseDown(with event: NSEvent) {
super.mouseDown(with: event)
if (event.clickCount < 2) {
return;
}
// Get the row on which the user clicked
let localPoint = self.convert(event.locationInWindow, from: nil)
let row = self.row(at: localPoint)
// If the user didn't click on a row, we're done
if (row < 0) {
return
}
DispatchQueue.main.async {[weak self] in
guard let self = self else {return}
// Get the view clicked on
if let clickedCell = self.view(atColumn: 0, row: row, makeIfNecessary: false) as? YourOutlineViewCellClass{
let pointInCell = clickedCell.convert(localPoint, from: self)
if (clickedCell.txtField.isEditable && clickedCell.txtField.hitTest(pointInCell) != nil){
clickedCell.window?.makeFirstResponder(clickedCell.txtField)
}
}
}
}

IOS: searchbar in tableview

I am creating a searchbar in a tableview but I have problems with this method delegate because I fill the table view with array inside another array...I show my code:
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
ProgramAppDelegate *appDelegate = (ProgramAppDelegate *)[[UIApplication sharedApplication] delegate];
[tableData removeAllObjects];// remove all data that belongs to previous search
if([searchText isEqualToString:#""] || searchText==nil){
[myTableView reloadData];
return;
}
NSInteger counter = 0;
for(NSString *name in appDelegate.globalArray )
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];
NSRange r = [name rangeOfString:searchText];
if(r.location != NSNotFound)
{
if(r.location== 0)//that is we are checking only the start of the names.
{
[tableData addObject:name];
}
}
counter++;
[pool release];
}
[myTableView reloadData];
}
You can see the code "for(NSString *name in appDelegate.globalArray )" it don't work because I fill the table view with elements of arrays inside this global array, I make an example
In a row of my table view there is a uitableviewcell and inside it there are four label;
I write these label with string of this globalArray but in this way:
[cell.label1 setText:[[appDelegate.globalArray objectAtIndex:indexPath.row]objectAtIndex:1]];
[cell.label2 setText:[[appDelegate.globalArray objectAtIndex:indexPath.row]objectAtIndex:2]];
[cell.label3 setText:[[appDelegate.globalArray objectAtIndex:indexPath.row]objectAtIndex:3]];
[cell.label4 setText:[[appDelegate.globalArray objectAtIndex:indexPath.row]objectAtIndex:4]];
then in delegate method for searchbar the code "for(NSString *name in appDelegate.globalArray )" don't work, How can I change my code?
** I DON'T SAY THAT I WANT TO CHECK ONLY LABEL1 FOR THE SEARCH
The problem here is that globalArray is an array of arrays. So the loop should be something like.
for(NSArray *rowArray in appDelegate.globalArray )
{
for ( NSString *name in rowArray ) {
// Do your processing here..
}
}
Using tableData
In your tableView:cellForRowAtIndexPath:, do this
[cell.label1 setText:[[tableData objectAtIndex:indexPath.row]objectAtIndex:1]];
[cell.label2 setText:[[tableData objectAtIndex:indexPath.row]objectAtIndex:2]];
[cell.label3 setText:[[tableData objectAtIndex:indexPath.row]objectAtIndex:3]];
[cell.label4 setText:[[tableData objectAtIndex:indexPath.row]objectAtIndex:4]];
In your numberOfSectionsInTableView:, return 1;
In your tableView:numberOfRowsInSection:, return [tableData count];
You should include a method that will reset the search data to the original content when the user stops searching.
- (void)resetSearchData {
// Get appDelegate first.
self.tableData = [NSArray arrayWithArray:appDelegate.globalArray];
}

How to automatically select an item inserted into and NSOutlineView & Core Data

I have an NSTreeController bound an NSArrayController bound to an entity and the tree bound to an NSOutlineView. Now, when an "add" button is clicked, I would like to add a new entity to the treeController, select the entity, and highlight it for editing. If I call [arrayController add], the insertion is asynchronous and I have no way of knowing which the new object is since the outline view does not select new rows automatically. So I am left with inserting the new Entity programatically. So addButton calls createNewGroup on a outlineViewController (see below).
Again, inserting a new entity does not seem to be a synchronous process. I can't locate it in the NSOutlineView in the next line after currentObject = [NSEntityDescription.... And I did try after reloading the data. So I am left with observing the value changes in the array controller. This sort of works, most of the time, but occasionally it does not. Is this the right approach to this sort of thing?
- (void) createNewGroup:(id)sender {
NSInteger row = [myOutlineView selectedRow];
if(row == -1) {
[groupsController addObserver:self
forKeyPath:IR_GROUPS_KEYPATH
options:NSKeyValueObservingOptionInitial
context:IR_GROUPS_CONTEXT];
currentObject = [NSEntityDescription insertNewObjectForEntityForName:#"Group"
inManagedObjectContext:appDelegate.managedObjectContext];
return;
}
if([myOutlineView levelForRow:row] != 0) return;
[subGroupsController addObserver:self
forKeyPath:IR_GROUPS_KEYPATH
options:NSKeyValueObservingOptionInitial
context:IR_SUBGROUPS_CONTEXT];
NSManagedObject *parent = [[myOutlineView itemAtRow:row] representedObject];
currentObject = [NSEntityDescription insertNewObjectForEntityForName:#"Group"
inManagedObjectContext:appDelegate.managedObjectContext];
[currentObject setValue:parent forKey:#"parent"];
}
- (void) observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
if([keyPath isEqualToString:IR_GROUPS_KEYPATH]) {
if(currentObject == nil) return;
[myOutlineView noteNumberOfRowsChanged];
NSString *ctx = (NSString *) context;
if([ctx isEqualToString:IR_GROUPS_CONTEXT]) {
NSInteger length = [myOutlineView numberOfRows];
NSInteger index;
for(index = 0; index < length; index++) {
id item = [myOutlineView itemAtRow:index];
if(currentObject == [item representedObject]) {
// We found the new object:
NSIndexSet *indices = [NSIndexSet indexSetWithIndex:index];
[myOutlineView selectRowIndexes:indices byExtendingSelection:NO];
[myOutlineView editColumn:0 row:index withEvent:nil select:YES];
currentObject = nil;
return;
}
}
//[groupsController removeObserver:self forKeyPath:nil];
} else if([ctx isEqualToString:IR_SUBGROUPS_CONTEXT]) {
NSTreeNode *parent = [myOutlineView itemAtRow:[myOutlineView selectedRow]];
[myOutlineView expandItem:parent];
NSInteger length = [myOutlineView numberOfRows];
NSInteger index;
for(index = 0; index < length; index++) {
id item = [myOutlineView itemAtRow:index];
if(currentObject == [item representedObject]) {
NSIndexSet *indices = [NSIndexSet indexSetWithIndex:index];
[myOutlineView selectRowIndexes:indices byExtendingSelection:NO];
[myOutlineView editColumn:0 row:index withEvent:nil select:YES];
currentObject = nil;
return;
}
}
}
}
}
If you use (a subclass of) NSTreeController to provide the content for you outlineView, it is very simple. You create a button, either in code or in Interface Builder, and set bind the target to insert: to add an element or remove: to delete it. In code it would look like this:
[aButton bind:NSTargetBinding
toObject:aController
withKeyPath:keyPathToTreeController
options:[NSMutableDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSConditionallySetsEnabledBindingOption,
#"insert:", NSSelectorNameBindingOption,
nil]];
Selecting the new object is handled by the treeController. Again, in code:
[aTreeController setSelectsInsertedObjects:YES];
In IB, it's a checkbox that you need to check. Oh, there's also addChild:. Let the bindings do their magic.

Resources