How to change UITableview's array content from a dictionary input? - iphone

I have many different arrays with dictionaries of hotels in a data model class. For example, the array madridHotels would hold dictionaries of hotels in Madrid. To list these hotels in a tableview, I use a method called madridHotelsCount in my dataModel class:
-(int)madridHotelsCount {
return self.madridHotels.count;
}
In a tableview's numberOfRowsInSection method, I would put
- (NSInteger)tableView:(UITableView*)theTableView numberOfRowsInSection:(NSInteger)section {
return [self.dataModel madridHotelsCount];
}
in order to list the hotels in Madrid. This works just fine. But since I have about 20 cities, I have a feeling that having 20 different VC's and XIB's to make city-based tableviews is plain stupid and wasteful.
In my "Select City" tableview, each city has a key called cityString with the appropriate string meant for the "Hotels in City X" tableview. In Madrid's case, for example, the string would be madridHotelsCount. If I'm not mistaken there is a way to use this information when pushing the "Hotels in City X" tableview controller.
What I can't figure out is how to change the string madridHotelsCount in the tableview method with, for example, barcelonaHotelsCount depending on a key in a "Select City" tableview so that I can only use one VC and XIB.
Hope this makes sense...

You need to change it to something like this so you pass the hotel to each view.
-(int)hotelCountForCity:(NSString *)cityName
I would rewrite it so it was more generic as you shouldn't be repeating yourself like that.
although its not the best way, but if all that code is already there, you can..
NSString city = #"madrid";
SEL selector = selectorFromString([NSString stringWithFormat:#"%#HotelsCount", city]);
int count = 0;
if([self respondsToSelector:selector])
{
int *count = [self performSelector:selector];
}

Change the data structure in dataModel to use
#property (nonatomic, retain) NSDictionary *citiesHotels;
The cities are the keys and the values are the arrays of hotels.
An example might be like
NSArray *madrid = [[NSArray alloc] initWithObjects:#"madridHotelA", #"madridHotelB", nil];
NSArray *barcelona = [[NSArray alloc] initWithObjects:#"barcelonaHotelA", #"barcelonaHotelB", nil];
NSDictionary *citiesHotels = [[NSDictionary alloc] initWithObjectsAndKeys:madrid, #"Madrid", barcelona, #"barcelona", nil];
[madrid release]; madrid = nil;
[barcelona release]; barcelona = nil;
self.citiesHotels = citiesHotels;
[citiesHotels release]; citiesHotels= nil;
Add a method to the model like this
- (NSInteger)hotelCountForCity:(NSString *)city
{
int count = 0;
NSArray *hotels = [self.citiesHotels valueForKey:city];
if (hotels) {
count = [hotels count];
}
return count;
}
To get the hotel you would need something like this in your model
- (Hotel *)hotelAtIndex:(NSInteger)index forCity:(NSString *)city
{
NSArray * hotels = [self.citiesHotels valueForKey:city];
return [hotels objectAtIndex:index];
}
These are called like this
- (NSInteger)tableView:(UITableView*)theTableView numberOfRowsInSection:(NSInteger)section
{
return [self.dataModel hotelCountForCity:theSelectedCityName];
}
And in `cellForRowAtIndexPath you do
Hotel *hotel = [self.dataModel hotelAtIndex:indexPath.row forCity:theSelectedCity];

Related

iPhone Table View: Making Sections, UILocalizedIndexedCollation selector

I'm having trouble making the sections in a UITableView. I've looked at the documentation for UILocalizedIndexedCollation as well as this sample code project:
https://developer.apple.com/library/ios/#samplecode/TableViewSuite/Listings/3_SimpleIndexedTableView_Classes_RootViewController_m.html#//apple_ref/doc/uid/DTS40007318-3_SimpleIndexedTableView_Classes_RootViewController_m-DontLinkElementID_18
What I have below is basically a straight copy/paste from the sample project. However, the sample project uses a custom object (TimeZoneWrapper.h) and then places the object in the correct section based on the object's instance variable (TimeZoneWrapper.localeName). However, I'm not using custom objects. I'm using just a bunch of regular NSStrings. So my question is what method on NSString should I pass to the #selector() to compare and place the string in the correct section array?
Currently, I'm calling NSString's copy method as a temporary hack to get things working (which it does), but I'm not sure if this is correct. A little explanation would be much appreciated!
- (void)configureSections {
// Get the current collation and keep a reference to it.
self.collation = [UILocalizedIndexedCollation currentCollation];
NSInteger index, sectionTitlesCount = [[collation sectionTitles] count]; // sectionTitles are A, B, C, etc.
NSMutableArray *newSectionsArray = [[NSMutableArray alloc] initWithCapacity:sectionTitlesCount];
// Set up the sections array: elements are mutable arrays that will contain the locations for that section.
for (index = 0; index < sectionTitlesCount; index++) {
NSMutableArray *array = [[NSMutableArray alloc] init];
[newSectionsArray addObject:array];
}
// Segregate the loctions into the appropriate arrays.
for (NSString *location in locationList) {
// Ask the collation which section number the location belongs in, based on its locale name.
NSInteger sectionNumber = [collation sectionForObject:location collationStringSelector:#selector(/* what do I put here? */)];
// Get the array for the section.
NSMutableArray *sectionLocations = [newSectionsArray objectAtIndex:sectionNumber];
// Add the location to the section.
[sectionLocations addObject:location];
}
// Now that all the data's in place, each section array needs to be sorted.
for (index = 0; index < sectionTitlesCount; index++) {
NSMutableArray *locationsArrayForSection = [newSectionsArray objectAtIndex:index];
// If the table view or its contents were editable, you would make a mutable copy here.
NSArray *sortedLocationsArrayForSection = [collation sortedArrayFromArray:locationsArrayForSection collationStringSelector:#selector(/* what do I put here */)];
// Replace the existing array with the sorted array.
[newSectionsArray replaceObjectAtIndex:index withObject:sortedLocationsArrayForSection];
}
self.sectionsArray = newSectionsArray;
}
Thanks in advance!
You should use #selector(self).
Using #selector(copy) will cause memory leaks in your project

Sorting an NSMutableArray containing matching "pairs" of values

I'd prefer not to change the way anArray is designed because it is also used elsewhere as a dataSource for a UITableView. The odd number values are "user names", and the even number values are their matching "dates". (The "pairs" must remain together.)
How would I sort "by user name"?
How would I sort "by date"?
Should I be using sortUsingFunction or sortUsingSelector?
-(void) test
{
NSMutableArray *anArray = [NSMutableArray arrayWithObjects:#"Zeke", #"01-Jan-2010", #"Bob", #"02-Jan-2010", #"Fred", #"03-Jan-2010", #"Susan", #"04-Jan-2010", #"Kim", #"05-Jan-2010", #"Debbie", #"06-Jan-2010", nil];
[anArray sortUsingFunction:SortByDate context:(void *)context];
}
NSComparisonResult SortByDate(NSMutableArray *a1, NSMutableArray *a2, void *context)
{
// What goes here to keep the pair-values "together"?
}
Why aren'e you using a NSDictionary? You can have user names as keys and the dates as corresponding values. Then if you want to sort on user names, just use [dictObj allkeys] to get an array containing just the keys. Sort them as you prefer and then when displaying display the sorted array of keys and fetch the corresponding value for the key and display along side. Similarly, you can get all the values using [dictObj allValues] sort them and display them along with keys.
I'd prefer not to change the way anArray is designed because it is also used elsewhere as a dataSource for a UITableView
Does that mean cells alternate between displaying usernames and dates, or are you multiplying/dividing by 2?
Either way, you need to fix your model. The bit where you go "oops, sorting doesn't work" should be a big hint that you haven't chosen a good representation of your data. Adding a workaround will only lead to more tears later.
I'd suggest something like this:
Create a class to represent your (username,date) tuple.
Add -compareByDate: and -compareByUsername: methods.
[array sortUsingSelector:#selector(compareByDate:)]
If you want to display usernames and dates in alternating cells, then it's still easy to do, but I'd simply use a taller cell and save myself some pain.
Note: The overhead of Objective-C method calls means that you can get a significant performance increase by using sortUsingComparator: and defining appropriate functions (you can stick the function between #implmentation and #end to let them access protected/private ivars) but it's not really worth doing this unless you've determined that sorting is a bottleneck.
Something like this could work:
#class SortWrapper: NSObject
#property (copy,readwrite,atomic) NSString* name;
#property (copy,readwrite,atomic) NSDate* date;
#end
...
- (void)test
{
NSArray* names = #[#"Alice", #"Bob", #"Cameron"];
NSArray* dates = #[aliceDOB, bobDOB, cameronDOB]; // NSDate objects
// turn into wrapped objects for sorting
NSMutableArray* wrappedObjects = [NSMutableArray array];
for ( int i=0; i<names.count; i++ ) {
SortWrapper* wrapped = [[SortWrapper alloc] init];
wrapped.name = names[i];
wrapped.date = dates[i];
[wrappedObjects addObject:wrapped];
}
// to sort by date:
NSArray* sortedByDate = [wrappedObjects sortedArrayUsingComparator:^NSComparisonResult(SortWrapper* obj1, SortWrapper* obj2) {
return [obj1.date compare:obj2.date];
}];
// to sort by name:
NSArray* sortedByName = [wrappedObjects sortedArrayUsingComparator:^NSComparisonResult(SortWrapper* obj1, SortWrapper* obj2) {
return [obj1.name compare:obj2.name];
}];
}

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?

UIView as dictionary key?

I want to have a NSDictionary that maps from UIViews to something else.
However, since UIViews do not implement the NSCopying protocol, I can't use them directly as dictionary keys.
You can use an NSValue holding the pointer to the UIView and use this as key. NSValues
are copyable. but, if the view is destroyed, the NSValue will hold a
junk pointer.
Here is the actual code (based on the answer by luvieere and further suggestion by Yar):
// create dictionary
NSMutableDictionary* dict = [NSMutableDictionary new];
// set value
UIView* view = [UILabel new];
dict[[NSValue valueWithNonretainedObject:view]] = #"foo";
// get value
NSString* foo = dict[[NSValue valueWithNonretainedObject:view]];
Although this isn't really what they're intended for, you could whip up a functional dictionary-like interface using Associative References:
static char associate_key;
void setValueForUIView(UIView * view, id val){
objc_setAssociatedObject(view, &associate_key, val, OBJC_ASSOCIATION_RETAIN);
}
id valueForUIView(UIView * view){
return objc_getAssociatedObject(view, &associate_key);
}
You could even wrap this up in a class ThingWhatActsLikeADictionaryButWithKeysThatArentCopyable*; in that case you might want to retain the views that you use as keys.
Something like this (untested):
#import "ThingWhatActsLikeADictionaryButWithKeysThatArentCopyable.h"
#import <objc/runtime.h>
static char associate_key;
#implementation ThingWhatActsLikeADictionaryButWithKeysThatArentCopyable
- (void)setObject: (id)obj forKey: (id)key
{
// Remove association and release key if obj is nil but something was
// previously set
if( !obj ){
if( [self objectForKey:key] ){
objc_setAssociatedObject(key, &associate_key, nil, OBJC_ASSOCIATION_RETAIN);
[key release];
}
return;
}
[key retain];
// retain/release for obj is handled by associated objects functions
objc_setAssociatedObject(key, &associate_key, obj, OBJC_ASSOCIATION_RETAIN);
}
- (id)objectForKey: (id)key
{
return objc_getAssociatedObject(key, &associate_key);
}
#end
*The name may need some work.
Provided you don't need to support before iOS 6, NSMapTable (suggested by neilsbot) works well because it can provide an enumerator over the keys in the collection. That's handy for code common to all of the text fields, like setting the delegate or bi-directionally syncing the text values with an NSUserDefaults instance.
in viewDidLoad
self.userDefFromTextField = [NSMapTable weakToStrongObjectsMapTable];
[self.userDefFromTextField setObject:#"fooUserDefKey" forKey:self.textFieldFoo];
[self.userDefFromTextField setObject:#"barUserDefKey" forKey:self.textFieldBar];
// skipped for clarity: more text fields
NSEnumerator *textFieldEnumerator = [self.userDefFromTextField keyEnumerator];
UITextField *textField;
while (textField = [textFieldEnumerator nextObject]) {
textField.delegate = self;
}
in viewWillAppear:
NSEnumerator *keyEnumerator = [self.userDefFromTextField keyEnumerator];
UITextField *textField;
while (textField = [keyEnumerator nextObject]) {
textField.text = [self.userDefaults stringForKey:[self.textFields objectForKey:textField]];
}
in textField:shouldChangeCharactersInRange:replacementString:
NSString *resultingText = [textField.text stringByReplacingCharactersInRange:range withString:string];
if(resultingText.length == 0) return YES;
NSString *preferenceKey = [self.textFields objectForKey:textField];
if(preferenceKey) [self.userDefaults setString:resultingText forKey:preferenceKey];
return YES;
And now I will go cry, because I implemented all of this before realizing that my iOS 5.1-targeted app can't use it. NSMapTable was introduced in iOS 6.
Rather than store a pointer to the view and risk the garbage issue, just give the UIView a tag and store the tag's value in the dictionary. Much safer.
I'm using a simple solution under ARC provided by Objective-C++.
MyClass.mm:
#import <map>
#implementation MyClass
{
std::map<UIView* __weak, UIColor* __strong> viewMap;
}
- (void) someMethod
{
viewMap[self.someView] = [UIColor redColor];
}
In this example I am getting stronger type checking by making all the values have to be a UIColor* which is all I needed this for. But you could also use id as the value type if you want to allow any object as the value, ex: std::map<UIView* __weak, id __strong> viewMap; Likewise for keys: id __weak, id __strong> viewMap;
You can also vary the __strong and __weak attributes as needed. In my case, the views are already retained by the view controller that I use this in, so I saw no need to take a strong pointer to them.
a simple solution when you just want UIView as key occasionally,I use it to store UILabel and UIColor
NSArray<UIView *> *views = #[viewA,viewB,viewC,viewD];
NSArray *values = #[valueA,valueB,valueC,valueD];
for(int i = 0;i < 4;i++) {
UIView *key = views[i];
id value = values[i]
//do something
}
id value = values[[views indexOfObject:key]]

Class variable type gets changed

So in my view controller, I run code to populate an NSArray of Customer (custom class) objects. This custom class has objects that are of ANOTHER custom class called Address (a customer has a billing address and a shipping address). In the view controller when a customer in the list is selected, it passes a new view controller a customer object, like so:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
InfoViewController *customerinfoViewController = [[InfoViewController alloc] initWithStyle:UITableViewStyleGrouped andCustomer:[[[customers objectAtIndex:indexPath.section] objectAtIndex:indexPath.row] retain]];
[self.navigationController pushViewController:customerinfoViewController animated:YES];
[customerinfoViewController release];
}
The first time I visit this view controller while running the application, it works fine. However, when I revisit the view controller, something interesting happens. The application crashes, with unrecognized selector sent to instance 0x00whatever. Using the mouseover debugging feature in xCode, I am finding that the first object of the customer's shipAddress variable has its type changed from NSString to NSIndexPath. This does not happen to the customer's billAddress object. Anyone have any idea what is going on here? It seems like I may be having memory management issues but I would definitely like a confirmation on this before I tear my code apart tracking down all the retains and releases....
EDIT: More information here. with the following code, I have an NSMutableArray at the class level. At each iteration of the loop, I am looping through nodes in XML (which works fine). Every time a new letter is detected as the first letter of the name, I create a new subarray and add the customer to it, thus filling my class-level NSMutableArray (customers) with subArrays of customers for each letter of the alphabet detected. My question is about the retains and releases of the cycling customer object. Clang Static says there is an over-retaining error on the customer, but when I fix it according to Clang, the loop crashes. what gives? Related code below:
DDXMLDocument *rootDoc = [[[DDXMLDocument alloc] initWithData:xmlData options:0 error:nil] autorelease];
NSArray *elems = [rootDoc nodesForXPath:#"QBXML/QBXMLMsgsRs/CustomerQueryRs/CustomerRet" error:nil];
DDXMLNode *node;
sectionTitles = [[[NSMutableArray alloc] initWithCapacity:1] retain]; // Letters for UITableView section titles
NSMutableArray *subArray;
NSString *lastchar = #"A";
NSString *testchar;
int indexCount = -1;
customers = [[[NSMutableArray alloc] initWithCapacity:[elems count]] retain];
Customer *newCust;
for (int i = 0; i < [elems count]; i++) {
node = [elems objectAtIndex:i];
newCust = [[Customer alloc] initWithCustomerRetNode:node];
testchar = [[newCust fullName] substringToIndex:1];
if (i == 0 || ![[testchar uppercaseString] isEqualToString:lastchar]) {
[sectionTitles addObject:testchar];
lastchar = testchar;
indexCount++;
subArray = [[NSMutableArray alloc] initWithCapacity:1];
[customers addObject:subArray];
[subArray release];
[[customers lastObject] addObject:[newCust retain]];
}
else {
[[customers lastObject] addObject:[newCust retain]];
}
[newCust release];
}
NOTE: this code works for the most part, but clang doesn't like it.
EDIT: Addresses in the Customer class are assigned like so (which now does not work after Clang fixes)
...
else if ([tempname isEqualToString:#"BillAddress"])
billAddress = [billAddress initWithAddressNode:tempnode];
else if ([tempname isEqualToString:#"ShipAddress"])
shipAddress = [shipAddress initWithAddressNode:tempnode];
...
It sounds like you are having a over release issue, so yes memory management, you might be overreleasing that array you are storing your objects in.Cant really tell from the snippet of code though. Youll have to go and look through the code and find the source. Also using Clang Static Analyzer might be of help to you.