CoreData How to filter NSFetchedResultsController with Search and ARC - objective-c

I found this perfect answer to search through an NSFetchedResultsController : https://stackoverflow.com/a/4481896/1486928
EDIT : project showing the issue : http://cl.ly/2x0C0N0E4240
It seems really great except it wasn't written to use with ARC, I tried to just remove all the "retain" "release" "autorelease".
It still works, well mostly, the thing is, when I enter a char in the searchbar it shows the filtered table as expected but it only takes 1 char (if you add more it doesn't do anything) and after that every other "search" will show the results of the first search that only took 1 char.
I've been at it for 2 days putting NSlog anywhere to see when every methods are called but still couldn't find how to make it work :(
Thanks !
Edit : here is .m http://pastebin.com/9U4TfbA6
Edit : here is .h http://pastebin.com/S9aaNRFE
Also if it can help the search works when I comment this :
- (NSFetchedResultsController *)fetchedResultsController {
if (fetchedResultsController_! = nil)
{
return fetchedResultsController_;
}
...
}
And this :
- (NSFetchedResultsController *)searchFetchedResultsController {
if (searchFetchedResultsController_ != nil)
{
return searchFetchedResultsController_;
}
...
}
But it mess up other things :/

I guess that you are messing up with the search display controller delegate methods,
and especially you need to check this method
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString;
Because this method will reload your table view for every character which you type in your search field as the name itself suggests shouldReloadTableForSearchString
Edit:
Well you need to implement 2 delegate methods of UISearchBar because all your UISearchDisplayController delegate methods are same and those 2 methods are
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText;
This tells the delegate that the user changed the search text.
- (BOOL)searchBar:(UISearchBar *)searchBar shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
This is used if the text in a specified range should be replaced with given text.
and please note these methods are called several times, i mean for the every character added and deleted in the search bar and because you are setting the searchFetchedResultsController to nil every time the search text changes
just comment out this part, it will work well
/*if (searchFetchedResultsController_ != nil)
{
NSLog(#"Returned !nil searchController");
return searchFetchedResultsController_;
}*/

Related

Mac OSX: What is the best pagination method to load more data for NSTableView?

I am trying to find a method to allow me to load more data in NSTableView so the user can see all of his data. However, I am only use to iOS development and not Mac OSX development. iOS has a way where you can pull down on the UITableView to load more data. Is there such a method for Mac OSX development or is there a better alternative? I heard something about using "pages" to achieve this, but I don't know if it is the best way. I google this, but keeps giving me iOS results.
P.S. I will appreciate it if your provided visual and code examples so I can better understand.
Edit: I see this piece of code on the internet. I guess I can implement something like this to load few data at a time. Fetch data can grab the next 100 data for each page.
#interface ViewController : NSObject {
NSTableView *tableView;
NSMutableArray *mDataSource;
NSInteger mPageNumber;
NSInteger mTotalPage;
}
-(IBAction)nextPage : (id)sender;//for next page
-(IBAction)prevPage : (id)sender;// for prev. page
-(void)fetchData;
#property (assign) IBOutlet NSTableView *tableView;
#end
#import "ViewController.h"
#implementation ViewController
#synthesize tableView;
- (id) init
{
self = [super init];
if (self != nil) {
mDataSource = [[NSMutableArray alloc] init];
mPageNumber=0;
mTotalPage =2;// total numer of pages
[self fetchData];
}
return self;
}
-(IBAction)nextPage : (id)sender;
{
++mPageNumber;
if (mTotalPage<=mPageNumber) {
mPageNumber=0;// point to first page
}
[self fetchData];
}
-(IBAction)prevPage : (id)sender;
{
--mPageNumber;
[self fetchData];
}
-(void)fetchData;
{
[tableView setDataSource:nil];
NSString *lDataSourcePath = [[NSString alloc] initWithFormat:#"/page%d",mPageNumber];
NSArray *lDataSource = [[NSArray alloc] initWithContentsOfFile:lDataSourcePath];
[mDataSource setArray:lDataSource];
[lDataSource release];
[lDataSourcePath release];
[tableView setDataSource:self];
[tableView reloadData];
}
-(void)dealloc
{
[mDataSource release];
[super dealloc];
}
#pragma mark Data Source
- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView
{
return [mDataSource count];
}
- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
{
return [mDataSource objectAtIndex:rowIndex];
}
#end
Others have downvoted and voted to close this question because it is (truly) a bit too vague. It also seems like you're asking about two different things: 1) How to paginate your data source so the table only shows a page of data at a time and, 2) How to handle this in the user interface.
In order to answer your pagination question, you'll need to provide a lot more information about the nature of the data, why you feel the need to paginate it, where it's coming from (ie, how does it get into your data source), etc.
In order to answer your UI question, we'll need to know more about what your data represents and how the user is expected to use it, because there are several different approaches.
For example, the "Pulling top-down to refresh" may just reload the data, not append to it (this isn't really for "pagination", just "updated info"). However, this could be used as a way to load the "previous page" worth of data. Pull hard enough and it moves to the previous page (and the users find themselves at the bottom of the previous page). Get to the end and pull hard enough and the next page is loaded (users find themselves at the top of the scroll list of the next page). There are dozens of open source examples out there (search Github) that show you how to do a pull-to-refresh on the Mac. This can be pretty easily adapted to this purpose.
Another example would be to add a "load next/prev x" entry to the top and bottom of your data source, where needed. Obviously the first page wouldn't have the "load previous x" and the last page wouldn't have the "load next x" entries, since you're at the beginning/end. If your "page size" is 100 rows and you have 100+ rows of total data, then your first page would hold 101 total rows (but the last one is a button that says "load 100 more..."). Clicking that last row behaves like the previous example I gave. If you're in the "middle" (you can load 100 prev and 100 next), your table is given 102 rows (first and last are nav controls); if at the end, back to 101, but the nav button is the first row in your table.
Another example might be to eliminate this altogether and have a page control in the toolbar, maybe a segmented control with first/prev/position,next,last buttons like so: [ |< < (101-200) > >| ] ... the position segment (that shows the page's range) could be clickable and allow you to specify page size and/or jump directly to a page by typing it in.
The above examples illustrate why your question is far too vague to answer. I encourage you to start a new question for your UI portion (with very specific details about the nature of the data and what your users will do with it, etc.). Start a separate question, if necessary, for how to build a custom data source that allows for data pagination. Again, be very specific about the nature of the data, where it comes from, etc., as I mentioned above. Without this information, you'll only be able to get vague answers (though more specific answers to vague questions means an answerer made some assumptions and may end up confusing you further). So always be specific.
I hope this helps.

How/should I use nested for loops inside of if statements?

I was hoping to get some help from someone who is better than me with using Objective C and Xcode.
I am using an example project and trying to figure out exactly what it's asking me to do and how to do it. It is specifically asking me:
STEP 1: Use the if statement below to determine if defaults contains a bool value of "TRUE" for a key called "registered"
This snippet is what I have so far for this problem:
if (self.defaults == YES)
{
[self performSelector:#selector(goToLogin)];
}
The "for" and "if" are both bold in the directions, leading me to believe I need to use both.
Edit: This is more of the surrounding code because of the comments suggesting I didn't upload all of the necessary code.
#import "RegistrationViewController.h"
#interface RegistrationViewController ()
#end
#implementation RegistrationViewController
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self.navigationController setNavigationBarHidden:TRUE];
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.textFields = [NSArray arrayWithObjects:_emailTextField,_userNameTextField,_passwordTextField,_confirmPasswordText Field, nil];
defaults = [NSUserDefaults standardUserDefaults];
//STEP 1: Use the if statement below to determine if defaults contains a bool value of "TRUE" for a key called "registered"
if (self.defaults == YES)
{
[self performSelector:#selector(goToLogin)];
}
}
I am really new to this and just don't know where to go from here! any help would be very appreciated!
Thanks in advance,
FMM92
It is difficult to tell the precise situation here, but this is how it sounds to me:
defaults is almost certainly meant to be an instance of NSUserDefaults. The bolded "for" in your instructions is not suggesting a for-loop; it's giving you a hint at the method you need to use. Take a look at the NSUserDefaults reference there and see if you can find a method that might be useful for looking up a BOOL value for a named key.

Want to make UITextView react to return key

I'm trying to implement a program in Xcode that's somewhat like a command line. I basically have a UITextView that can take multiple lines of text. Right now, I have a button that will make the necessary changes after the user is done entering commands, but I want to be able to have a method that gets called after the user hits the return key in the UITextView, so basically it makes changes after each command. Is it possible to do this?
The BOOL method mentioned above is a wrong answer... for one the person is checking the text from the TextView the moment before it is updated so they are viewing the old text... Also the methods are out of date.
This usage will work immediately once the return key is pressed (The current "answer" will not work until after the return key has been pressed and then ANOTHER key is pressed):
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
if ([text isEqualToString:#"\n"]) {
NSLog(#"Return pressed");
} else {
NSLog(#"Other pressed");
}
return YES;
}
Don't forget to add the UITextViewDelegate to your .h file's protocols.
#interface ViewController : UIViewController <UITextViewDelegate> {
and set yourTextView.delegate = self; in .m file!
/*
note: This will also get called if a user copy-pastes just a line-break...
unlikely but possible. If you need to ignore pasted line-breaks for some
reason see here: http://stackoverflow.com/a/15933860/2057171 ...
Now for an unrelated-tip: If you want to accept pasted line breaks however
I suggest you add an "or" to the conditional statement and make it also
check if "text" isEqualToString #"\r" which is another form of line-break
not found on the iOS keyboard but it can, however, be found on a website
and copy-pasted into your textView. If you want to accept pasted text
with a line-break at the end you will need to change the
"isEqualToString" code above to say "hasSuffix", this will check for
any string %# with a "\n" at the end. (and again for "\r") but make
sure you don't call your "next" method until after `return YES;`
has been called and the text view has been updated, otherwise
you will get only the text that was there before the copy paste
since this is "shouldChangeTextInRange" method, not
"didChangeTextInRange", if you do this I suggest stripping the
"\n" or "\r" from the end of your final string after the copy-paste
was made and applied and the text was updated.
*/
If you set a delegate to the UITextView which implements
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
Then you can check if the last character is "\n", and get the text entered since the last command by doing
NSArray* components = [textView.text componentsSeparatedByString:#"\n"];
if ([components count] > 0) {
NSString* commandText = [components lastObject];
// and optionally clear the text view and hide the keyboard...
textView.text = #"";
[textView resignFirstResponder];
}
Note, I haven't tested this, just an idea:
- (void)textViewDidChange:(UITextView *)textView
{
if ([[textView.text substringWithRange:NSMakeRange(textView.text.length - 1, 1)] isEqualToString:#"\n"])
{
[textView resignFirstResponder];
[self methodYouWantToCall];
}
}
You can do this by setting up a delegate for the UITextView. See UITextViewDelegate Protocol Reference for details on what can be done.

Drag and drop between nstableviews in nscollectionview sets window controller property to nil?

In the main window of my application, I have a collection view such that each collection view item contains an NSTableView. The window controller for this window has an NSString * property projecttitle. This property is bound to an NSTextField in the window for which I have overridden the default return key behavior so that the user can hit Return and write a carriage return into the text field. Now, after changing the string in the text field and THEN dragging an item between the table views for two different collection view items, the projecttitle property becomes nil. I sort of feel like maybe I just have way too much going on here and that this bug will be impossible to track down, but maybe someone has seen something even remotely similar to this behavior elsewhere?
EDIT: Putting a breakpoint on the projecttitle property doesn't seem to yield anything useful. The program execution does not break at all upon dragging and dropping, but the property will indeed be nil after this.
EDIT 2: After more digging around, it appears the behavior is related to the NSFormatter object. It happens not only when dragging and dropping, but apparently any time the nstextfield attempts to resign as responder. This behavior stops when I disconnect the formatter object in IB. Here's the string validation code for the formatter which forces the string to be less than or equal to 4 lines long and with each line being no longer than 32 characters. This seems to work fine when actually typing the in the text field, but apparently, not after.
-(BOOL)isPartialStringValid:(NSString **)partialStringPtr
proposedSelectedRange:(NSRangePointer)proposedSelRangePtr
originalString:(NSString *)origString
originalSelectedRange:(NSRange)origSelRange
errorDescription:(NSString **)error {
NSArray * lines = [*partialStringPtr componentsSeparatedByString:#"\n"];
if ( [lines count] > 4 ) {
return NO;
}
for (NSString * line in lines) {
if ( [line length] > self.maxlength ) {
return NO;
}
}
return YES;
}
Okay, solved. Turned out it was the -getObjectValue:forString:errorDescription: method. I had it implemented as
-(BOOL)getObjectValue:(id *)obj forString:(NSString *)string errorDescription:(NSString **)error {
*obj = string;
return YES;
}
Changing *obj = string to *obj = [NSString stringWithString:string] fixed everything right up.

Why are my Cocoa bindings broken?

I have a window with an NSTextField (in Snow Leopard), which I have binded to an NSString function in my WindowController class. This string will combine information about my table view's selection and count, provided by my array controller. It gets an initial value, "0 0", but doesn't ever update, when the selection or count changes. The binding looks like this (File's Owner is MyWindowController):
I implemented + (NSSet *)keyPathsForValuesAffecting<key> (below), but the binding never updates, even when the array controller's total count and selection change.
(Additional troubleshooting performed) I had originally been using the Display Pattern Value binding of the NSTextField, but I needed more complicated logic than that binding afforded. I then started listening to the selection changed/changing events of the TableView that displays the array controller's contents and changing the Display Pattern Value bindings dynamically, but that felt like a hack, and overly complicated.
I'm sure there's something I'm missing, but I can't tell what. Does anyone have any ideas? I've read through Apple's key-value-observing documentation, and this seems to be all that's necessary. I've checked, and my keyPathsForValuesAffectingMyString is getting called, but myString only gets called once. I've distilled my code below (updated x3).
Update 1/21
I'm still plugging away trying to figure this out. When I addObserver to self for the arrayController key paths, the notifications do fire as expected, so my key paths and the key value observing mechanism is fine. When I call [self didChangeValueForKey:#"myString"]; within my observeValueForKeyPath method for the same keys, the binding still doesn't update, leading me to believe it's a bindings problem rather than a KVO problem. I'm going to be reading up on the bindings mechanism more...
#interface MyWindowController : NSWindowController {
IBOutlet NSArrayController *arrayController;
}
- (NSArrayController *)arrayController;
- (NSString *)myString;
#end
#implementation MyWindowController
+ (NSSet *)keyPathsForValuesAffectingMyString {
return [NSSet setWithObjects:
#"arrayController.arrangedObjects",
#"arrayController.selection",
nil];
}
- (NSArrayController *)arrayController {
return arrayController;
}
- (NSString *)myString {
// Just as an example; I have more complicated logic going on in my real code
return [NSString stringWithFormat:#"%#, %#",
[arrayController valueForKeyPath:#"arrangedObjects.#count"],
[arrayController valueForKeyPath:#"selection.#count"]];
}
#end
I’ve verified this exact same bug. Someone on Cocoabuilder had a guess as to why the bug happens:
http://www.cocoabuilder.com/archive/cocoa/284396-why-doesn-nsarraycontroller-selection-et-al-fire-keypathsforvaluesaffectingkey.html#284400
I can’t speak as to whether this explanation is true, but I certainly can’t get +keyPathsForValues… to work with NSArrayControllers.
I've got a workaround, but I'm not happy about it, since it shouldn't be necessary, and I would still prefer to get the bindings working properly. I won't accept this answer, and will delete it if someone posts an actual fix. </disclaimer>
#interface MyWindowController : NSWindowController {
IBOutlet NSArrayController *arrayController;
IBOutlet NSTextField *fieldThatShouldBeBinded;
}
- (NSString *)myString;
#end
#implementation MyWindowController
- (void)awakeFromNib {
[arrayController addObserver:self
forKeyPath:#"selection"
options:0
context:NULL];
[arrayController addObserver:self
forKeyPath:#"arrangedObjects"
options:0
context:NULL];
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
if( object == arrayController )
[fieldThatShouldBeBinded setStringValue:[self myString]];
}
- (NSString *)myString {
return [NSString stringWithFormat:#"%#, %#",
[arrayController valueForKeyPath:#"arrangedObjects.#count"],
[arrayController valueForKeyPath:#"selection.#count"]];
}
#end
Make sure that the arrayController outlet is connected in Interface Builder. I'm guessing that it's nil.
Don't use the #count keyword. Bindings and KVO on array controllers will get updated when the content changes. If that doesn't work, then there is a problem somewhere else.
Another option is to use the display pattern bindings instead of a composite property. Bind Display Pattern Value1 to arrayController.arrangedObjects.#count and Display Pattern Value2 to arrayController.selection.#count, and set the pattern to "%{value1}#, %{value2}#"
I met the same problem and found another way (but it is still workaround).
You have to declare dynamic workaround property. In implementation section, just return new empty object for it. Now, you can KVO this workaround property.
#property(nonatomic,retain) NSArray *workaround;
#dynamic workaround;
- (NSArray *)workaround { return [NSArray array]; } // new *every* time
- (void)setWorkaround:(NSArray *)unused { }
+ (NSSet *)keyPathsForValuesAffectingMyString { return [NSSet setWithObject:#"workaround"]; }
To get this work, you still need to manually bind self.workaround to arrayController.selectedObjects (or whatever):
- (void)awakeFromNib // or similar place
{
[super awakeFromNib];
[self bind:#"workaround" toObject:arrayController withKeyPath:#"selectedObjects" options:nil];
}
Manual binding works as expected, workaround is updated with what you have bound it to. But KVO tests whether property value is really changed (and stops propagating if it is the same). If you return new self.workaround value every time, it works.
Warning: never call -[setWorkaround:] by yourself — this will effectively flush the other side of binding (arrayController.selectedObjects in this case).
This method has some benefits: you avoid centralized observeValueForKeyPath:... and your logic is in the right place. And it scales well, just add workaround2, 3, and so on for similar cases.