how to sort products by price In App Purchases? - iphone

First,I got some products from IAP
-(void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
{
[productDetailsList addObjectsFromArray: response.products];
[productDisplayTableView reloadData];
}
How to put them in a uitableview sort by product price? thank you.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *GenericTableIdentifier = #"GenericTableIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier: GenericTableIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle
reuseIdentifier:GenericTableIdentifier] autorelease];
}
SKProduct *thisProduct = [productDetailsList objectAtIndex:row];
NSUInteger row = [indexPath row];
[button setTitle:localizedMoneyString forState:UIControlStateNormal];
[cell.contentView addSubview:button];
return cell;
}

NSArray *products = [response.products sortedArrayUsingComparator:^(id a, id b) {
NSDecimalNumber *first = [(SKProduct*)a price];
NSDecimalNumber *second = [(SKProduct*)b price];
return [first compare:second];
}];

Swift 2.1.1
public func productsRequest(request: SKProductsRequest, didReceiveResponse response: SKProductsResponse) {
let unsortedProducts = response.products
let products = unsortedProducts.sort{($0.price.compare($1.price) == NSComparisonResult.OrderedAscending)}
for p in products {
print("Found product: \(p.productIdentifier) \(p.localizedTitle) \(p.price.floatValue)")
} ...

Swift 3, without the need for self variables. Worked for me like a charm to sort by price from low to high:
let validProducts = response.products
var productsArray = [SKProduct]()
for i in 0 ..< validProducts.count {
let product = validProducts[i]
productsArray.append(product)
}
productsArray.sort{(Double(truncating: $0?.price) < Double(truncating: $1?.price))}
EDIT: Updated for Swift 5.

What you want to do is sort the NSArray prior to trying to read it. There are many options for sorting NSArray, most of them involve creating your own sorting method. You do something like:
[productDetailList sortedArrayUsingFunction:intSort context:NULL];
That will use your comparator method that you specify. The comparison method is used to compare two elements at a time and should return NSOrderedAscending if the first element is smaller than the second, NSOrderedDescending if the first element is larger than the second, and NSOrderedSame if the elements are equal. Each time the comparison function is called, it’s passed context as its third argument. This allows the comparison to be based on some outside parameter, such as whether character sorting is case-sensitive or case-insensitive, but this doesn't matter in your case. The function you have to implement would look like this:
NSInteger intSort(id num1, id num2, void *context)
For more information of the above method take a look at the documentation, it give you an example.
So you can sort the array like this or you always have the choice of sorting the array every time you add something. So every time you add an object you do the sorting yourself and make sure to put it in the right location to keep the array always sorted.
Depending on what you want, I would say keeping the array constantly sorted on insertion time is the best option, so you don't have to waste time while building the view by sorting the array. To do it like this would be simple, every time you enter something into the array, you iterate through the array until you find an object with a price larger than the one you want to enter, then you insert the object at the location before that entry.

In swift
var validProducts = response.products
for var i = 0; i < validProducts.count; i++
{
self.product = validProducts[i] as? SKProduct
self.productsArray.append(product!)
println(product!.localizedTitle)
println(product!.localizedDescription)
println(product!.price)
}
self.productsArray.sort{($0.price < $1.price)}

self.products = products!.sorted(by: { (item1, item2) -> Bool in
return item1.price.doubleValue < item2.price.doubleValue
})

Related

Search for strings in large array takes a long time

I'm implementing a search field that filters a UITableView according to the text the user enters.
The TableView is built from an array that holds NSStrings (the data to display and search) and may contain 6000+ items.
When the user starts the search, I'm implementing the -(void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText method.
My code works, however, when the data array is large, it is very slow and creating a really bad user experience (my iPhone 4s get stuck for a good few seconds).
The way I'm implementing the search (in the method mentioned above) is this:
NSMutableArray *discardedItems = [[NSMutableArray alloc] init]; // Items to be removed
searchResultsArray = [[NSMutableArray alloc] initWithArray:containerArray]; // The array that holds all the data
// Search for matching results
for (int i=0; i<[searchResultsArray count]; i++) {
NSString *data = [[containerArray objectAtIndex:i] lowercaseString];
NSRange r = [data rangeOfString:searchText];
if (r.location == NSNotFound) {
// Mark the items to be removed
[discardedItems addObject:[searchResultsArray objectAtIndex:i]];
}
}
// update the display array
[searchResultsArray removeObjectsInArray:discardedItems];
[myTableView reloadData];
I did not think that looping over an array with a few thousand items would cause any issue...
Any suggestion will be appreciated!
UPDATE
I've just realized that what takes most of the time is this:
[searchResultsArray removeObjectsInArray:discardedItems];
Try fast enumeration way, my snippet:
- (void)searchBar:(UISearchBar*)searchBar textDidChange:(NSString*)text
{
if(text.length == 0)
{
self.isFiltered = NO;
}
else
{
self.isFiltered = YES;
self.searchArray = [NSMutableArray arrayWithCapacity:self.places.count];
for (PTGPlace* place in self.places)
{
NSRange nameRange = [place.name rangeOfString:text options:NSCaseInsensitiveSearch];
if(nameRange.location != NSNotFound)
{
[self.searchArray addObject:place];
}
}
}
[self.tableView reloadData];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if(self.isFiltered)
return self.searchArray.count;
else
return self.places.count;
}
In cellForRowAtIndexPath:
PTGPlace *place = nil;
if(self.isFiltered)
place = [self.searchArray objectAtIndex:indexPath.row];
else
place = [self.places objectAtIndex:indexPath.row];
// Configure the cell...
cell.textLabel.text = place.name;
cell.detailTextLabel.text = [place subtitle];
Try this:
for the first three positions, create 26 index sets, each representing the array index of items with that letter (just lower case). That is, say a entry at idx=100 starts with "formula". The index set representing "f" in the first position will contain the index "100". The index set for the second character 'o' will contain the index 100, and the index set for the third character 'r' will contain 100.
When the user types the character 'f', you immediately have the index set of all array items starting with 'f' (and can create a subset of your major array quickly). When an 'o' is typed next, you can find the intersection of indexes in the first match with the second match. Ditto for the third. Now make a subarray of the major array that had the first three indexes match - you can just use the index sets for this.
Using this drastically reduced array, you can now do brute force matching as you were doing originally.

Compare a sort list item with a string for a UItableview title

I sorted a list in a table view, but i don't want to show the item of the list as the title of the row. I need to compare the first item of the sorted list with a string, and if it is the same, I want to show an other string as the title.
Just as an example, to explain it better:
if (first item of the sorted list == some string) {
cell.textlabel.text = #"name";
}
Is it possible to do something like this?
Is your sorted array stored in an NSArray? If so, you can use:
if ([[sortedArray objectAtIndex:0] isEqualToString:#"Some String"]) {
cell.textlabel.text = #"name";
}
If I have interpreted the question correctly, then yes it is. If the first item was a cell, which it would be presumably, then you could do;
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
UITableViewCell *firstCell = [yourTableView cellForRowAtIndePath:indexPath];
NSString *titleOfFirstCell = firstCell.textLabel.text;
Then you could compare it to check what it should be changed to - I'm going to call this string 'updatedTitle'.
titleOfFirstCell.textLabel.text = updatedTitle;
[firstCell setNeedsDisplay];
Hope this helps,
Jonathan

Getting row number in scrollViewDidEndDecelerating

when i use
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
NSArray *visibleCells = [my_table visibleCells];
i want to know the number of the row (in my_table) that is visible (with visibleCells).
For example, if i do
[visibleCells count];
i know that there are 5 rows visible, but i need to know the real number (for example: number 3, 4, 5, 6, 7 in the table view).
Is it possible?
You can call indexPathForCell: on the table view with the first and last objects of the array to get the two index paths.
solved!
using this
UITableViewCell *currentCell = [visibleCells objectAtIndex:i];
NSIndexPath *indexPath = [tabella_gallery indexPathForCell:currentCell];
works fine!
Here is a method I use
func loadImagesForVisibleRows() {
// Get the visible cells indexPaths
let indexes: Array = tableView.indexPathsForVisibleRows()!
// Loop through them to determine whether visible or not
for index in indexes {
let row = index.row
// Do what you need to do
}
}

Changing number of different UITableViewCells with particular order like Calendar app

Sorry for the long title. In essence, I want to accomplish the same thing that the Calendar app does for Event details.
The first cell shows the title and date of the event. The second shows an alert, if there is one, otherwise Notes, if there are any, or nothing else if none of these fields is present.
The way I am doing it now is a really long if condition in cellForRowAtIndexPath:
if(indexPath.row == 0) {
TitleCell *titlecell = [[TitleCell alloc] init];
// config cell to do title here, always
return titlecell;
} else if (indexPath.row == 1 && foo) {
FooCell *foocell = [[FooCell alloc] init];
// config cell to show foo, if it exists
return foocell;
} else if (indexPath.row == 1 && bar) {
BarCell *barcell = [[BarCell alloc] init];
// foo doesn't exist, but bar, so show bar in cell 1
return barcell;
} // etc etc
That's really ugly, and since I create the cells in the if and return, the static analyzer tell me that each one of those is a potential leak. There is no else, since I need to cover all scenarios anyway, and that also gives a warning about the method potentially not returning anything.
Is there a better way that makes this cleaner and doesn't give me warnings?
Thanks!
Christoph
The warning are because you are leaking memory, you have to autorelease the cell: TitleCell *titlecell = [[[TitleCell alloc] init] autorelease];. Also there is a chance of not having a return statement because you don't have else in your if block.
Here is another way of doing it:
// Call this beforehand
- (void)loadTable {
// Since we are not using objects, we need to use a non-retaining array
// A better way of doing this would be with delegates or NSInvocations
array = (NSMutableArray*)CFArrayCreateMutable(NULL, 0, NULL);
[array addObject:(id)#selector(buildTitleCell)];
if (foo)
[array addObject:(id)#selector(buildFooCell)];
if (bar)
[array addObject:(id)#selector(buildBarCell)];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.row < array.count) {
SEL selector = (SEL)[array objectAtIndex:indexPath.row];
return [self performSelector:selector];
}
return nil;
}
- (UITableViewCell*)buildTitleCell {
TitleCell *titlecell = [[[TitleCell alloc] init] autorelease];
// config cell to do title here, always
return titlecell;
}
...
EDIT: fixed as per #Christoph's comment
Clang is right when it says that you're leaking memory - The UITableView retains the UITableViewCells that you give it, so you should be autoreleasing them in cellForRowAtIndexPath.
Instead of using if statments, I think you should be using a switch.

iPhone contacts app styled indexed table view implementation

My Requirement:
I have this straight forward requirement of listing names of people in alphabetical order in a Indexed table view with index titles being the starting letter of alphabets (additionally a search icon at the top and # to display misc values which start with a number and other special characters).
What I have done so far:
1. I am using core data for storage and "last_name" is modelled as a String property in the Contacts entity
2.I am using a NSFetchedResultsController to display the sorted indexed table view.
Issues accomplishing my requirement:
1. First up, I couldn't get the section index titles to be the first letter of alphabets. Dave's suggestion in the following post, helped me achieve the same: NSFetchedResultsController with sections created by first letter of a string
The only issue I encountered with Dave' suggestion is that I couldn't get the misc named grouped under "#" index.
What I have tried:
1. I tried adding a custom compare method to NSString (category) to check how the comparison and section is made but that custom method doesn't get called when specified in the NSSortDescriptor selector.
Here is some code:
#interface NSString (SortString)
-(NSComparisonResult) customCompare: (NSString*) aStirng;
#end
#implementation NSString (SortString)
-(NSComparisonResult) customCompare:(NSString *)aString
{
NSLog(#"Custom compare called to compare : %# and %#",self,aString);
return [self caseInsensitiveCompare:aString];
}
#end
Code to fetch data:
NSArray *sortDescriptors = [NSArray arrayWithObject:[[[NSSortDescriptor alloc] initWithKey:#"last_name"
ascending:YES selector:#selector(customCompare:)] autorelease]];
[fetchRequest setSortDescriptors:sortDescriptors];
fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:managedObjectContext sectionNameKeyPath:#"lastNameInitial" cacheName:#"MyCache"];
Can you let me know what I am missing and how the requirement can be accomplished ?
This is a really inefficient first-pass at this problem, which I am going to rewrite eventually. But hopefully this will help you.
The idea of this is to "guarantee" getting a real table section index back when tapping a "standard" section index view. A standard section index view should have a magnifying lens icon for search, a hash mark (#) for non-alphabetical sections, and letters A through Z for alphabetical sections.
This standard view is presented regardless of how many real sections there are, or what they are made of.
Ultimately, this code maps section view indices to real-existing alphabetic section name paths in the fetched results controller, or to real-existing non-alphabetic (numerical) sections, or to the search field in the table header.
The user will only occasionally recreate the section index mapping array (_idxArray) on each touch of the section index, but recreating the array on each touch is obviously inefficient and could be tweaked to cache pre-calculated results.
There are a lot of places to start tightening this up: I could make the sectionIndexTitleLetters static string all uppercase from the start, for example. It's fast enough on a 3GS phone, though, so I haven't revisited this recently.
In the header:
static NSString *sectionIndexTitleLetters = #"abcdefghijklmnopqrstuvwxyz";
In the implementation of the table view data source:
- (NSArray *) sectionIndexTitlesForTableView:(UITableView *)tv {
if (tv != searchDisplayController.searchResultsTableView) {
NSMutableArray *_indexArray = [NSMutableArray arrayWithCapacity:([sectionIndexTitleLetters length]+2)];
[_indexArray addObject:#"{search}"];
[_indexArray addObject:#"#"];
for (unsigned int _charIdx = 0; _charIdx < [sectionIndexTitleLetters length]; _charIdx++) {
char _indexChar[2] = { toupper([sectionIndexTitleLetters characterAtIndex:_charIdx]), '\0'};
[_indexArray addObject:[NSString stringWithCString:_indexChar encoding:NSUTF8StringEncoding]];
}
return _indexArray;
}
return nil;
}
- (NSInteger) tableView:(UITableView *)tv sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index {
if (tv != searchDisplayController.searchResultsTableView) {
if (index == 0) {
//
// This is the search bar "section"
//
[currentTableView scrollRectToVisible:[[currentTableView tableHeaderView] bounds] animated:YES];
return -1;
}
else if (index == 1) {
//
// This is the "#" section, which covers non-alphabetic section headers (e.g. digits 0-9)
//
return 0;
}
else {
//
// This is a bit more involved because the section index array may contain indices that do not exist in the
// fetched results controller's sections->name info.
//
// What we are doing here is building a "fake-index" array that will return a real section index regardless of
// whether the section index title being touched exists or not.
//
// The fake array will be of length of the section index title array, and each index will contain an unsigned
// integer from 1 to {numOfRealSections}.
//
// The value this array returns will be "nearest" to the real section that is in the fetched results controller.
//
NSUInteger _alphabeticIndex = index-2;
unsigned int _idxArray[26];
for (unsigned int _initIdx = 0; _initIdx < [sectionIndexTitleLetters length]; _initIdx++) {
_idxArray[_initIdx] = [[fetchedResultsController sections] count] - 1;
}
unsigned int _previousChunkIdx = 0;
NSNumberFormatter *_numberFormatter = [[NSNumberFormatter alloc] init];
NSLocale *_enUSLocale = [[NSLocale alloc] initWithLocaleIdentifier: #"en_US"];
[_numberFormatter setLocale:_enUSLocale];
[_enUSLocale release];
for (unsigned int _sectionIdx = 0; _sectionIdx < [[fetchedResultsController sections] count]; _sectionIdx++) {
NSString *_sectionTitle = [[[fetchedResultsController sections] objectAtIndex:_sectionIdx] name];
if (![_numberFormatter numberFromString:_sectionTitle]) {
// what's the index of the _sectionTitle across sectionIndexTitleLetters?
for (unsigned int _titleCharIdx = 0; _titleCharIdx < [sectionIndexTitleLetters length]; _titleCharIdx++) {
NSString *_titleCharStr = [[sectionIndexTitleLetters substringWithRange:NSMakeRange(_titleCharIdx, 1)] uppercaseString];
if ([_titleCharStr isEqualToString:_sectionTitle]) {
// put a chunk of _sectionIdx into _idxArray
unsigned int _currentChunkIdx;
for (_currentChunkIdx = _previousChunkIdx; _currentChunkIdx < _titleCharIdx; _currentChunkIdx++) {
_idxArray[_currentChunkIdx] = _sectionIdx - 1;
}
_previousChunkIdx = _currentChunkIdx;
break;
}
}
}
}
[_numberFormatter release];
return (NSInteger)_idxArray[_alphabeticIndex];
}
}
return 0;
}
I might be naive, but I don't understand why these solutions are so baroque. I did this:
In my model, I added a method:
-(NSString *)lastInitial {
return [self.lastname substringToIndex:1];
}
And in my tablecontroller I set the fetchedresultscontroller to use that method:
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:#"lastInitial" cacheName:#"Master"];
Seems to work - is there a reason that this is a bad idea? Or am I benefitting from new features in ios5 or something?