How to do searching in NSTableView with NSSearchField? - macos

I have implemented a application in which I use NSTableview with the help of its data source and delegates I have not used NSArrayController nor I want to use it. My question is how can I bind NSSearchField with my NSTableView in this situation? I had seen a lot of answer using NSArrayController.
I do not want to convert implementation to NSArrayController as things are working good with NSMutableArray.

TableView is a display control and is not for filtering.
You should add 2 NSArray properties;
1) #property(nonatomic, strong) NSArray *allItems;
2) #property(nonatomic, strong) NSArray *filteredItems;
#import "ViewController.h"
#interface ViewController()<NSSearchFieldDelegate, NSTableViewDelegate, NSTableViewDataSource>
// Your NSSearchField
#property (weak) IBOutlet NSSearchField *searchField;
// Your NSTableView
#property (weak) IBOutlet NSTableView *tableView;
// In this array you will store all items
#property(nonatomic, strong) NSArray *allItems;
// In this array you will store only filtered items
#property(nonatomic, strong) NSArray *filteredItems;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.searchField.delegate = self;// You can set delegate from XIB/Storyboard
self.tableView.delegate = self;// You can set delegate from XIB/Storyboard
self.tableView.dataSource = self;// You can set dataSource from XIB/Storyboard
self.allItems = #[#"Test1", #"Demo filter", #"Test 2", #"Abracadabra"];
[self applyFilterWithString:#""];
}
- (void)controlTextDidChange:(NSNotification *)obj{
if (obj.object == self.searchField) {
[self applyFilterWithString:self.searchField.stringValue];
}
}
-(void)applyFilterWithString:(NSString*)filter {
if (filter.length>0) {
NSPredicate *filterPredicate = [NSPredicate predicateWithFormat:#"self CONTAINS[cd] %#", filter];
self.filteredItems = [self.allItems filteredArrayUsingPredicate:filterPredicate];
}
else {
self.filteredItems = self.allItems.copy;
}
[self.tableView reloadData];
}
#pragma mark - ***** NSTableViewDataSource, NSTableViewDelegate *****
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
return self.filteredItems.count;
}
// for the "Cell Based" TableView
- (nullable id)tableView:(NSTableView *)tableView objectValueForTableColumn:(nullable NSTableColumn *)tableColumn row:(NSInteger)row {
NSString *item = self.filteredItems[row];
return item;
}
#end

Related

Setting dataSource for NSTableView across scenes in a storyboard

I am using XCode 6 on OS X 10.10 and have a storyboard containing a window with a split view controller, as shown in the following image.
The split view controller (highlighted in the image) is an instance of MyViewController, which has the following code:
MyViewController.h
#import <Cocoa/Cocoa.h>
#interface MyViewController : NSSplitViewController <NSTableViewDataSource>
#end
MyViewController.m
#import "MyViewController.h"
#implementation MyViewController
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
return 7;
}
- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
return [NSString stringWithFormat:#"%ld", (long)row];
}
#end
I would like to make the view controller the dataSource of the NSTableView in my storyboard, however I am unable to connect them. Is there a reason for this?
In your NSSplitViewController-subclass viewDidLoad-method set the data source programmatically. You need to implement the child view controller class as well (with the tableView outlet connected to the control).
MySplitViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
for (NSSplitViewItem *item in self.splitViewItems)
{
NSViewController *controller = item.viewController;
if ([controller isKindOfClass:[MyChildController class]])
{
MyChildController *myController = (MyChildController *)controller;
myController.tableView.dataSource = self;
[myController.tableView reloadData];
}
}
}
But to tell you the truth I don't like this approach. It's more better when the table view's data source methods are in the native view controller class.
Another way to do that. MyChildController.h file:
#class MyChildViewController;
#protocol MyChildControllerDelegate <NSObject>
- (void)childController:(MyChildViewController *)controller didSelectRowAtIndex:(NSUInteger)index;
#end
#interface MyChildViewController : NSViewController <NSTableViewDataSource, NSTableViewDelegate>
#property (nonatomic, weak) id<MyChildControllerDelegate> delegate;
#property (nonatomic, retain) NSArray *items;
#property (nonatomic, weak) IBOutlet NSTableView *tableView;
#end
Don't forget to implement all the table view dataSource and delegate methods you need. MySplitViewController.m file:
- (void)viewDidLoad
{
[super viewDidLoad];
for (NSSplitViewItem *item in self.splitViewItems)
{
NSViewController *controller = item.viewController;
if ([controller isKindOfClass:[MyChildController class]])
{
MyChildController *myController = (MyChildController *)controller;
myController.delegate = self;
[myController setItems:_items];
}
}
}

Compilation fails due to prototype cell

I have a TableViewController subclass with one prototype cell designed in storyboard. Then I am passing the references to the class header etc and for some reason it fails to compile it. But the connections seem fine. I provide you the code and pictures of the build errors and the storyboard. I am using Xcode 4.3.3. Any help is really appreciated.
favTable.h
#import <UIKit/UIKit.h>
#interface favTable : UITableViewController <NSFetchedResultsControllerDelegate>
{
NSFetchedResultsController *fetchedResultsController;
NSManagedObjectContext *managedObjectContext;
NSArray *favArr;
NSMutableArray *favName;
NSMutableArray *favScore;
}
#property (nonatomic, retain) NSArray *favArr;
#property (nonatomic, retain) NSManagedObjectContext *managedObjectContext;
#property (nonatomic, strong) NSMutableArray *favName;
#property (nonatomic, strong) NSMutableArray *favScore;
#property (nonatomic, retain) NSFetchedResultsController *fetchedResultsController;
#property (strong, nonatomic) IBOutlet UITableViewCell *celldes;
#property (strong, nonatomic) IBOutlet UIImageView *cellimage;
#property (strong, nonatomic) IBOutlet UILabel *cellname;
#property (strong, nonatomic) IBOutlet UILabel *cellmanu;
#property (strong, nonatomic) IBOutlet UILabel *cellscore;
#end
favTable.m
#import "favTable.h"
#import "ecoAppDelegate.h"
#interface favTable ()
#end
#implementation favTable
#synthesize favArr;
#synthesize managedObjectContext;
#synthesize fetchedResultsController;
#synthesize favName;
#synthesize favScore;
#synthesize celldes;
#synthesize cellimage;
#synthesize cellname;
#synthesize cellmanu;
#synthesize cellscore;
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.title = #"Favorites";
self.navigationController.navigationBar.translucent = NO;
// passing the array of addedtofavorites to the total one with all favorites
self.managedObjectContext = ((ecoAppDelegate *) [UIApplication sharedApplication].delegate).managedObjectContext;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"FavoritesInfo" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
fetchRequest.resultType = NSDictionaryResultType;
[fetchRequest setPropertiesToFetch:[NSArray arrayWithObjects:#"name", nil]];
NSError *error=nil;
self.favArr=[[self.managedObjectContext executeFetchRequest:fetchRequest error:&error]mutableCopy];
if (error!=nil) {
NSLog(#" fetchError=%#,details=%#",error,error.userInfo);
}
self.favName = [[self.favArr valueForKey:#"name"] mutableCopy];
self.favScore = [[self.favArr valueForKey:#"score"] mutableCopy];
}
- (void)viewDidUnload
{
[self setCelldes:nil];
[self setCellimage:nil];
[self setCellname:nil];
[self setCellmanu:nil];
[self setCellscore:nil];
[super viewDidUnload];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
#warning Potentially incomplete method implementation.
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
#warning Incomplete method implementation.
// Return the number of rows in the section.
return [favName count];;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
// Configure the cell...
UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
cellname.text = #"Test";
return cell;
}
#end
I moved manually the IB outlets into a separate custom cell class and then linked the references to the storyboard (maybe this is fixed in newer versions)
Then applied that cell style in the prototype cell and simply changed the content of the UI items of my cell using the identifier (no tag was needed)

how to set the property of the ViewController before presenting it as presentModalViewController?

I present the view controller like this:
FBViewController *fbViewController =[[FBViewController alloc] initWithNibName:#"FBViewController" bundle:nil];
fbViewController.label.text=#"hello"; // I set the value of the property label which is the outlet
[self presentModalViewController:fbViewController animated:YES];
FBViewController.h:
#import <UIKit/UIKit.h>
#interface FBViewController : UIViewController
#property (weak, nonatomic) IBOutlet UILabel *label;
#end
FBViewController.m:
...
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(#"%#", self.label.text); // Here instead of "hello" i get the value which was in nib file.
}
...
The question is how to set the value of the label?
You can pass the NSString retrieve text to assign label modalview controller:
FBViewController *fbViewController =[[FBViewController alloc] initWithNibName:#"FBViewController" bundle:nil];
fbViewController.labeltext=#"Your Text";
[self presentModalViewController:fbViewController animated:YES];
FBViewController.h
#interface FBViewController : UIViewController {
NSString *labeltext;
}
#property (nonatomic, retain) NSString *labeltext;
and use view to load method in FBViewController.m
- (void)viewDidLoad {
label1.text=labeltext;
}

NSTableView: no text in new rows

I wrote a method to add new rows to NSTableView. It adds new rows, but there're empty, without text.
Here's code:
AppDelegate.h
#import <Cocoa/Cocoa.h>
#interface AppDelegate : NSObject <NSApplicationDelegate>
{
IBOutlet NSButton *addDreamButton;
IBOutlet NSButton *removeDreamButton;
IBOutlet NSTextField *dreamField;
IBOutlet NSTableView *dreamTable;
IBOutlet NSMutableArray *dreamlist;
IBOutlet NSTextField *testf;
}
- (IBAction)addDream:(id)sender;
- (IBAction)deleteDream:(id)sender;
#property (assign) IBOutlet NSWindow *window;
#end
AppDelegate.m
#import "AppDelegate.h"
#implementation AppDelegate
#synthesize window = _window;
-(int) numberOfRowsInTableView: (NSTableView *) tableView
{
return [dreamlist count];
}
-(void) dreamTable: (NSTableView *) tableview
setObjectValue:(id)anObject
objectValueForTableColumn: (NSTableColumn *) tablecolumn
row:(int) rowIndex
{
[dreamlist replaceObjectAtIndex:rowIndex withObject:anObject];
}
- (IBAction)addDream:(id)sender
{
NSString* thedream = dreamField.stringValue;
[dreamlist addObject: thedream];
[dreamTable reloadData];
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
dreamlist = [[NSMutableArray alloc] init];
}
#end
AppDelegate is bound as datasource with TableView.
So when I press "add" button empty rows appears in table (textfield where I get text is not empty; I debugged and saw that MutableArray contains right strings).
Your table view data source needs to implement this method:
- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
{
return [dreamlist objectAtIndex:rowIndex];
}

Cocoa class not displaying data in NSWindow

I have one class that controls one window, and another class that controls a different window in the same xib, however, the second window never displays what it should.
In the first class I alloc and init the second class, then pass some information to it. In the second class it displays that data in the table view.
Yes, in the .xib I have all the connections set up correctly, I've quadruple checked. Also the code is correct, same with the connections, I've quadruple checked.
Edit: and yes, there's data in the arrays, and the classes are NSObjects.
Edit2: I kinda found the problem. For some reason, the array is filled with contents, but it's returning 0 as a count.
Edit 9000:
Here's the code:
Answer.h
#import <Cocoa/Cocoa.h>
#interface MSAnswerView : NSObject {
IBOutlet NSWindow *window;
NSArray *User;
NSArray *Vote;
NSArray *Text;
IBOutlet NSTableView *view;
IBOutlet NSTableColumn *voteCount;
IBOutlet NSTableColumn *saidUser;
IBOutlet NSTextView *body;
}
-(void)setUpWithVoteCount:(NSArray *)array User:(NSArray *)user Text:(NSArray *)text;
#property (nonatomic, retain) NSWindow *window;
#property (nonatomic, retain) NSTableView *view;
#property (nonatomic, retain) NSTableColumn *voteCount;
#property (nonatomic, retain) NSTableColumn *saidUser;
#property (nonatomic, retain) NSTextView *body;
#end
.m
#import "MSAnswerView.h"
#implementation MSAnswerView
#synthesize view;
#synthesize voteCount;
#synthesize saidUser;
#synthesize body;
#synthesize window;
-(void)awakeFromNib
{
[view setTarget:self];
[view setDoubleAction:#selector(bodydata)];
[view reloadData];
}
-(void)setUpWithVoteCount:(NSArray *)array User:(NSArray *)user Text:(NSArray *)text
{
Vote = array;
User = user;
Text = text;
if (window.isVisible = YES) {
[view reloadData];
[view setNeedsDisplay];
}
}
-(int)numberOfRowsInTableView:(NSTableView *)aTable
{
return [User count];;
}
-(id)tableView:(NSTableView *)aTable objectValueForTableColumn:(NSTableColumn *)aCol row:(int)aRow
{
if (aCol == voteCount)
{
return [Vote objectAtIndex:aRow];
}
else if (aCol == saidUser)
{
return [User objectAtIndex:aRow];
}
else
{
return nil;
}
}
-(void)bodydata
{
int index = [view selectedRow];
[body setString:[Text objectAtIndex:index]];
}
#end
The problems in your code are numerous.
For one thing, this comparison in -setUpWithVoteCount:User:Text: is incorrect:
window.isVisible = YES
That should be the comparison operator, == not the assignment operator =.
Secondly, you are naming your ivars and methods incorrectly. Instance variables (in fact, variables of any type) should start with a lower-case letter. This is to distinguish them from class names. Check out the Apple coding guidelines.
I'd also suggest that a name like text is a bad name for a variable that stores a collection like an NSArray. Instead, you should name it something like textItems so it's clear that the variable represents a collection and not a single string.
Also, the class itself is poorly named. You have called it MSAnswerView but it's not a view, it's some type of window controller. At the very least call it MSAnswerWindowController. Better still would be to make it a subclass of NSWindowController and make it File's Owner in its own nib. This is the standard pattern for window controllers.
Your method -setUpWithVoteCount:User:Text: should really be an initializer:
- initWithVoteCount:user:text:
That way it's clear what it's for and that it should be called once at object creation time.
The main problem, however, is that you're not retaining the values that you pass in to your setup method. That means that if no other object retains a reference to them, they will go away at some indeterminate point in the future. If you access them at a later time, you will crash or at the very least receive bad data, which is what's occurring.
Of course, you must also add a -dealloc method in this case to ensure you release the objects when you're finished with them.
Putting all those suggestions together, your class should really look something like this:
MSAnswerWindowController.h:
#import <Cocoa/Cocoa.h>
//subclass of NSWindowController
#interface MSAnswerWindowController : NSWindowController <NSTableViewDataSource>
{
//renamed ivars
NSArray *users;
NSArray *voteCounts;
NSArray *textItems;
IBOutlet NSTableView *view;
IBOutlet NSTableColumn *voteCount;
IBOutlet NSTableColumn *saidUser;
IBOutlet NSTextView *body;
}
//this is now an init method
- (id)initWithVoteCounts:(NSArray *)someVoteCounts users:(NSArray *)someUsers textItems:(NSArray *)items;
//accessors for the ivars
#property (nonatomic, copy) NSArray* users;
#property (nonatomic, copy) NSArray* voteCounts;
#property (nonatomic, copy) NSArray* textItems;
#property (nonatomic, retain) NSWindow *window;
#property (nonatomic, retain) NSTableView *view;
#property (nonatomic, retain) NSTableColumn *voteCount;
#property (nonatomic, retain) NSTableColumn *saidUser;
#property (nonatomic, retain) NSTextView *body;
#end
MSAnswerWindowController.m:
#import "MSAnswerWindowController.h"
#implementation MSAnswerWindowController
//implement the init method
- (id)initWithVoteCounts:(NSArray*)someVoteCounts users:(NSArray*)someUsers textItems:(NSArray*)items
{
//this is an NSWindowController, so tell super to load the nib
self = [super initWithWindowNibName:#"MSAnswerWindow"];
if(self)
{
//copy all the arrays that are passed in
//this means we hold a strong reference to them
users = [someUsers copy];
voteCounts = [someVoteCounts copy];
textItems = [items copy];
}
return self;
}
//make sure we deallocate the object when done
- (void)dealloc
{
self.users = nil;
self.voteCounts = nil;
self.textItems = nil;
[super dealloc];
}
//this is called when the window first loads
//we do initial window setup here
- (void)windowDidLoad
{
[view setTarget:self];
[view setDataSource:self];
[view setDoubleAction:#selector(bodydata)];
}
//this is called when the view controller is asked to show its window
//we load the table here
- (IBAction)showWindow:(id)sender
{
[super showWindow:sender];
[view reloadData];
}
- (NSInteger)numberOfRowsInTableView:(NSTableView*)aTable
{
return [users count];
}
- (id)tableView:(NSTableView*)aTable objectValueForTableColumn:(NSTableColumn*)aCol row:(NSInteger)aRow
{
if (aCol == voteCount)
{
return [voteCounts objectAtIndex:aRow];
}
else if (aCol == saidUser)
{
return [users objectAtIndex:aRow];
}
return nil;
}
- (void)bodydata
{
NSInteger index = [view selectedRow];
[body setString:[textItems objectAtIndex:index]];
}
#synthesize users;
#synthesize voteCounts;
#synthesize textItems;
#synthesize view;
#synthesize voteCount;
#synthesize saidUser;
#synthesize body;
#end

Resources