UISearchBar Issue on Table Loaded by SQLite Query - objective-c

First of all, I'm new to this so excuse my any simplest question.
I'm trying to view data and filter it which is very common.
So I use different tutorials. First, I loaded data from my SQLite database, then I used custom cell tutorials and customized my cells. But I got stuck in UISearchBar.
The tutorial I followed uses an NSArray to fill the table which is declared in code-behind. Since my SQLite methods gets the data from database to an Array, I thought if I copy this array to the array in filtering methods, that would work.But it didn't.
Here is some code with explanations:
-(void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText {
if ([searchText length] == 0) {
[displayItems removeAllObjects];
[displayItems addObjectsFromArray:allItems];
} else {
[displayItems removeAllObjects];
for (NSString *string in allItems) {
NSRange r = [string rangeOfString:searchText options:NSCaseInsensitiveSearch];
if (r.location != NSNotFound) {
[displayItems addObject:string];
}
}
}
[tableViewScreen reloadData];
}
Code above is for filtering and I tried to copy the array that I used to fill the table to allItems array like this:
MyProjectAppDelegate *appDelegate = (MyProjectAppDelegate *)[[UIApplication sharedApplication] delegate];
allItems = appDelegate.mySqliteArray;
Or like this:
allItems = [[NSArray alloc] initWithArray:appDelegate.mySqliteArray];
But none of them did work.
I'd like to point my problem again,I have a method that gets the data into an array in AppDelegate class, and in my TableView class, I want to copy that array to another.P.S. mySqliteArray is mutable array and allItems array is not.And also, my cells are created by custom cell class, and there are 2 labels in each row.

Since I was defining an array that has objects created by my defined class, I later realized that I was mistaken to carry objects but not strings.So I updated my code like this and succeeded to create the needed array:
for (MyClass *object in appDelegate.mySqliteArray) {
[allItems addObject:[NSString stringWithFormat:#"%#", object.objectName]];
}

Related

NSPopupButton Bindings with Value Transformer

I don't know if what I see with a popup button populated by bindings with a value transformer is the way it's supposed to be or not -- the unusual thing I'm seeing (at least with respect to what I've seen with value transformers and table views) is that the "value" parameter in the transformedValue: method is the whole array bound to the array controller, not the individual strings in the array. When I've done this with table views, the transformer is called once for each displayed row in the table, and the "value" parameter is whatever object is bound to that row and column, not the whole array that serves as the content array for the array controller.
I have a very simple app to test this. In the app delegate there is this:
+(void)initialize {
RDTransformer *transformer = [[RDTransformer alloc] init];
[NSValueTransformer setValueTransformer:transformer forName:#"testTransformer"];
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
self.theData = #[#{#"name":#"William", #"age":#"24"},#{#"name":#"Thomas", #"age":#"23"},#{#"name":#"Alexander", #"age":#"64"},#{#"name":#"James", #"age":#"47"}];
}
In the RDTransformer class is this:
+ (Class)transformedValueClass {
return [NSString class];
}
+(BOOL)allowsReverseTransformation {
return NO;
}
-(id)transformedValue:(id)value {
NSLog(#"%#",value);
return value;
}
In IB, I added an NSPopupButton to the window and an array controller to the objects list. The content array of the controller is bound to App Delegate.theData, and the Content Values of the popup button is bound to Array Controller.arrangedObjects.name with the value transformer, testTransformer.
When I run the program, the log from the transformedValue: method is this:
2012-09-19 20:31:39.975 PopupBindingWithTransformer[793:303] (
)
2012-09-19 20:31:40.019 PopupBindingWithTransformer[793:303] (
William,
Thomas,
Alexander,
James
)
This doesn't seem to be other people's experience from what I can see on SO. Is there something I'm doing wrong with either the bindings or the value transformer?
Unfortunately, this is how NSPopUpButton works. The problem is not limited to that control. If you try binding an NSArrayController.contentArray to another NSArrayControllers.arrangedObject.someProperty you will get the same problem. Here is a simple workaround that I use in all my value transformers, which makes them work with both tables and popups:
You can modify your value transformer in the following way:
-(id)transformedArrayValue:(NSArray*)array
{
NSMutableArray *result = [NSMutableArray array];
for (id value in array)
[result addObject:[self transformedValue:value]];
return result;
}
-(id)transformedValue:(id)value
{
if ([value isKindOfClass:[NSArray class]])
return [self transformedArrayValue:value];
// Do your normal-case transform...
return [value lowercaseString];
}
It's not perfect but it's easy to replicate. I actually put the transformedArrayValue: in a class category so that I don't need to copy it everywhere.

Outlets not working after showing "old" view again

I have several UIViews in my Storyboard and, of course, I can switch between them using a segue. Initially this works just fine:
notenKurse is a NSMutableArray, and kurse1Outlets is an outlet collection with my UITextFields.
int counter = 0;
for (UITextField *tf in kurse1Outlets) {
NSMutableString *t = [NSMutableString stringWithFormat:#"%#", [notenKurse objectAtIndex:counter]];
NSLog(#"Object at index %i is %#", counter, [notenKurse objectAtIndex:counter]);
if ([t isEqualToString:#"42"]) {
[t setString:#""];
}
[tf setText:t];
NSLog(#"UITextField in slot %i should now display %#", counter, t);
counter++;
}
All of my UITextFields are displaying the value stored in the array. But if I go to another view (let's assume I have a Button for it ;) ) Change something, and then go back to the original UIView the above code still gets executed, and there are different values in the array (this is supposed to be). I can see that in the log. But the stupid UITextField just doesn't display anything. Neither what was in there before, nor the new text. But why? The log clearly shows that t is what it's supposed to be, so the error must be in writing it into the textfield, and therefore I guess it's an outlet issue...
There is no guarantee of the order of your outlet collection. It's treated very much like an NSDictionary as opposed to an NSArray - where order is guaranteed. Iterating over this sort of collection will yield different results for different devices/people/phase of the moon.
When I use a collection like this I tend to set the 'tag' and then reorder the outlet collection when viewDidLoad by sorting off of the tag.
self.calendarDayImageViews = [_calendarDayImageViews sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
if ([(UIView *)obj1 tag] < [(UIView *)obj2 tag]) {
return NSOrderedAscending;
}
else if([(UIView *)obj1 tag] > [(UIView *)obj2 tag]){
return NSOrderedDescending;
}
else{
return NSOrderedSame;
}
}];
You can just output the tf,by
NSLog(#"%#",tf);
To check if the tf is null
Ok, i found it. I forgot to release some stuff, and so my UITextFields did get set before the array was sorted. My mistake!

Issue with NSMutableArray visibility / retain

Alright so I am a little new to the NSMutableArray class and I think I am missing something obvious. I have an object pass a NSMutable Array to my window controller like so in my.m:
summaryWindow = [[SummaryWindowController alloc] init];
[summaryWindow setGlobalStatusArray:globalStatusArray];
I have the receiver method in the summaryWindow object as so:
-(void)setGlobalStatusArray:(NSMutableArray *)myArray
{
if ([myArray count] >0) {
if (globalStatusArray) {
[globalStatusArray release];
}
globalStatusArray = [[NSMutableArray alloc] initWithArray:myArray];
NSLog(#"Summary Window Init with new array: %#",globalStatusArray);
I see the NSLog no problem, and in that same object (summaryWindow) I have the following method:
- (NSMutableArray *)getGlobalStatusArray
{
return globalStatusArray;
}
Now I have globalStatusArray declared in my .h file as
NSMutableArray *globalStatusArray;
So shouldn't This be retained because I am using: initWithArray?
When I try to access this value in an another IBAction method:
- (IBAction)refreshButtonClicked:(id)sender
{
NSLog(#"The user has clicked the update button");
[ aBuffer addObjectsFromArray: globalStatusArray];
NSLog(#"Buffer is currently:%#",aBuffer);
[tableView reloadData];
}
The NSMutable array is null
2011-08-18 10:40:35.599 App Name[65677:1307] The user has clicked the update button
2011-08-18 10:40:35.600 App Name[65677:1307] Buffer is currently:(
)
I have tried using my own method to get the value i.e. [ self getGlobalStatusArray] to but I am missing something huge. FYI aBuffer is also declared in my .h ,
As albertamg noted, that looks like an empty array rather than nil, and a released object doesn't magically become nil under normal circumstances anyway.
This smells strongly of two different objects. Try logging self in your methods and see if one instance is getting the array and another is interacting with the UI.
This code isn't doing anything useful:
if ([myArray count] >0) {
if (globalStatusArray) {
[globalStatusArray release];
}
globalStatusArray = [[NSMutableArray alloc] initWithArray:myArray];
If the count of the old array is zero, it's leaking the actual array object. If the count is not zero, then it's releasing it properly. Just do the release and don't bother counting.
Are you sure there's actually something in myArray?
joe

UISearchBar Search table row with text, subtext and image

I've noticed that in order to do a search of a table, a copy of that data must be inserted to a search array.
E.g.
//Initialize the array.
listOfItems = [[NSMutableArray alloc] init];
NSArray *countriesToLiveInArray = [NSArray arrayWithObjects:#"Iceland", #"Greenland", #"Switzerland", #"Norway", #"New Zealand", #"Greece", #"Rome", #"Ireland", nil];
NSDictionary *countriesToLiveInDict = [NSDictionary dictionaryWithObject:countriesToLiveInArray forKey:#"Countries"];
NSArray *countriesLivedInArray = [NSArray arrayWithObjects:#"India", #"U.S.A", nil];
NSDictionary *countriesLivedInDict = [NSDictionary dictionaryWithObject:countriesLivedInArray forKey:#"Countries"];
[listOfItems addObject:countriesToLiveInDict];
[listOfItems addObject:countriesLivedInDict];
//Initialize the copy array.
copyListOfItems = [[NSMutableArray alloc] init];
So what is searched is the objects that are stored in the copied array.
My Question is, how do I search Cell rows with text, subtext and image in that particular cell.
(1)
There isn't really any such thing as searching a table. What happens when the user enters text in a UISearchBar is totally up to you - you can make that operation mean anything you like. All you have to do is function as the delegate-and-data-source for the results table and form the results table in response to the standard Three Big Questions that form the basis for any table ("how many sections have you? how many rows in this section? what's the cell for this row?") in any way you like. The results table does often look like a reduced version of the original table, but this is not at all required! It can be any table you want it to be.
(2)
Don't confuse Model with View. The table is just a view. Your data is Model. It is the Model, your data that is the basis of the original table, that you are going to be searching. So when the user types in your UISearchBar and you start searching, you want to form a new Model that will be the basis of the results table. How you form it is completely up to you. Typically you'll want to filter the original model so that the only stuff left in your results model is stuff that counts as a valid result. You could do this by walking the whole original model, putting everything that matches the search criterial into the new model. Or, if the original model is an array, you could use one of the filteredArray methods to help you. The most flexible way is to form a predicate with a block, as in this example from my book:
NSPredicate* p = [NSPredicate predicateWithBlock:
^BOOL(id obj, NSDictionary *d) {
NSString* s = obj;
NSStringCompareOptions options = NSCaseInsensitiveSearch;
return ([s rangeOfString:sbc.searchBar.text
options:options].location != NSNotFound);
}];
self.filteredStates = [states filteredArrayUsingPredicate:p];
In that example, s (one item of the array) is a string each time, and I'm looking to see whether the user's search term occurs in that string. But if you had a dictionary or other structure holding both a title and a subtitle and info about an image, you could examine that dictionary in any way you like. It's just a matter of returning YES or NO according to whether this array item passes the test based on the search term, on whatever definition you attach to the notion of passing the test.
(3)
The big question remaining is when to form the results model. I usually start by making the results model identical to the original model in response to searchDisplayControllerWillBeginSearch, because otherwise the results table will say No Results while the user is typing. (That is probably why you think the first thing to do is copy the original model.) Then, I can either do the actual filtering in response to searchBarSearchButtonClicked (the user is done typing and has tapped Search), or if the model is small enough, I can filter it afresh after every letter the user types, in response to searchBar:textDidChange (the user has typed a letter in the search bar).
There are a few steps involved. Note that the code below is just an example that I'm typing in by hand now, so it probably won't compile, it's just to give you an idea.
1) Ensure that you have an array containing all the cell values.
2) Create a copy of that array, and use that copy as the data source when returning cells in your table delegate methods.
3) Set yourself up as delegate for the UISearchBar, and respond to its events:
- (void)searchBarButtonClicked(UISearchBar *)searchBar {
[self doSearch:searchBar.text];
}
- (void)searchBar(UISearchBar *)searchBar textDidChange:(NSString *)searchTerm {
if (searchTerm.length == 0) {
[self resetSearch];
[table reloadData];
}
else
[self doSearch:searchTerm];
}
- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar {
searchBar.text = #"";
[self resetSearch];
[table reloadData];
[searchBar resignFirstResponder];
}
4) Create the other methods
The resetSearch method just needs to copy your full data array to the data source array used by your table delegates:
- (void)resetSearch {
self.tableSourceArray = [self.dataSourceArray copy]; // Or write a deep copy if you want to.
}
Whereas when searching, we need to filter the datasource array. You may be able to create something more efficient - this is just an example.
- (void)doSearch:(NSString *)searchTerm {
NSMutableArray *filtered = [[NSMutableArray alloc] init];
for (NSString *item in self.self.dataSourceArray) {
if ([item rangeOfString:searchTerm options:NSCaseInsensitiveSearch].location != NSNotFound])
[filtered addObject:[item copy]];
}
self.tableSourceArray = filtered;
}
And that should be it!
Tim

Editing one object in an NSMutableArray also changes another object in the NSMutableArray

I had a navigation application that was working normally. In the table view, the last item is called "add item", and if the user pressed it, it would create a new object and pass it to another view where the user could enter the details for that object. When the user returned to the previous screen, the new object would show in the array which was displayed in the table.
I changed it so that the "add item" field is always the first field in the table, not the last. I made the appropriate changes so that the array would display correctly on the table. However, now I am noticing strange behavior.
If I edit the first object in the array, the 7th object also changes to be the same as this object. If I edit the second object in the array, the fourth and sixth object also change to be the same. If I edit the third item in the array, the fifth object changes to be the same.
What could be happening?
In the viewDidLoad: method I initialize the object like this:
PersonDetails *personDetails = [[PersonDetails alloc] init];
This is the method that gets executed when a user selects a row on the table
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// Navigation logic may go here. Create and push another view controller.
updatePersonArray = YES;
arrayIndex = indexPath.row-1;
editPerson = [[EditClassController alloc] initWithNibName:#"EditPerson" bundle:nil];
editPerson.title = #"Edit Person";
if (arrayIndex != -1) {
personDetails = [classArray objectAtIndex:arrayIndex];
}
else {
personDetails = [[PersonDetails alloc] init];
}
editPerson.personDetails = personDetails;
[self.navigationController pushViewController:editPerson animated:YES];
[editPerson release];
}
This is what the viewWillAppear looks like. It will update the table after an object has been edited.
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
if ([personDetails isEmpty]) {
updatePersonArray = NO;
}
if (updatePersonArray) {
if (arrayIndex == -1) {
NSLog(#"adding new object to array");
[personArray addObject:personDetails];
}
else {
NSLog(#"replacing object at index %d", arrayIndex);
[personArray replaceObjectAtIndex:arrayIndex withObject:personDetails];
}
[self saveArrayToDisk];
[self.tableView reloadData];
updatePersonArray = NO;
}
else {
//load the array from disk
NSLog(#"loading array from disk");
NSData *theData = [[NSUserDefaults standardUserDefaults] objectForKey:#"personArray"];
if (theData != nil) {
NSLog(#"found something");
personArray = [[NSMutableArray alloc] initWithArray:[NSKeyedUnarchiver unarchiveObjectWithData:theData]];
}
else {
personArray = [[NSMutableArray alloc] init];
}
}
}
Edit: I solve the problem by implementing NSCopy for the person object and then making a copy of the object from the array instead of directly pointing to the object in the array. Anyone know why this solved the problem?
Edit: I solve the problem by
implementing NSCopy for the person
object and then making a copy of the
object from the array instead of
directly pointing to the object in the
array. Anyone know why this solved the
problem?
The original problem was pretty much guaranteed to be an issue of having the same PersonDetails in the array multiple times. If you were to do something like:
for (id p in myArray) NSLog("%p", p);
I'd bet that some of the addresses would be the same, indicating same object in array multiple times.
Which is why copying the object "fixed" the problem. You are hiding the dodgy logic that led to the above situation by making a copy on every insertion.
this part here looks a bit dodgy with regard to memory ownership:
if (arrayIndex != -1) {
// here you get back an autorelease object - which you haven't retained
personDetails = [classArray objectAtIndex:arrayIndex];
}
else {
// here you create an object with retainCount =1
personDetails = [[PersonDetails alloc] init];
}
// depending on your property attribute this may or may not work as you expect
editPerson.personDetails = personDetails;
i.e. #property(??) personDetails