I am trying to create table using TTTableViewController. I want to display an image in the section header along with some caption text, something similar to what instagram and many other application does. I tried using the sample from TTNavigatorDemo to display data from TTSectionedDatasource, (I am not sure if this is the right way to do it, please suggest some better way if you know any). It contains section name/headers as regular strings and data as TTTableViewItem. I tried implementing <UITableViewDelegate> protocol and achieved it using viewForHeaderInSection method, now the data is separated from section, Is there any better way using Three20 through which I can pass on my Header/Section view and TableItemView along in a datasource as it would allow me to implement the TTTableViewDragRefreshDelegate, which I was not able to implement when implementing the <UITableViewDelegate> protocol in my class.
My class declaration right now, looks something like
#interface ImageTable : TTTableViewController <UITableViewDelegate> { }
I would like to show section headers with image and text like:
:
I am currently using the TTNavigatorCode to populate the data in my
Table which is:
self.dataSource = [TTSectionedDataSource dataSourceWithObjects:
#"Food"
[TTTableViewItem itemWithText:#"Porridge" URL:#"tt://food/porridge"],
[TTTableTextItem itemWithText:#"Bacon & Eggs" URL:#"tt://food/baconeggs"],
[TTTableTextItem itemWithText:#"French Toast" URL:#"tt://food/frenchtoast"],
#"Drinks",
[TTTableTextItem itemWithText:#"Coffee" URL:#"tt://food/coffee"],
[TTTableTextItem itemWithText:#"Orange Juice" URL:#"tt://food/oj"],
#"Other",
[TTTableTextItem itemWithText:#"Just Desserts" URL:#"tt://menu/4"],
[TTTableTextItem itemWithText:#"Complaints" URL:#"tt://about/complaints"],
#"Other",
[TTTableTextItem itemWithText:#"Just Desserts" URL:#"tt://menu/4"],
[TTTableTextItem itemWithText:#"Complaints" URL:#"tt://about/complaints"],
nil];
I was able to make something similar by implementing the method:
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
return myCustomView;
}
But since my data is coming from the Model Class and I want my generate views and add them to datasource in a formatted way. I would like to know if there is a way using Three20 through which I specify an Array of my data for sections and another array for corresponding data under the sections?
Your help is highly appreciated
Thanks
Create Delegate FOOTableDelegate.h
#interface FOOTableDelegate : TTTableViewDragRefreshDelegate
{
}
- (UIView*) createHeaderView:(FOODataSource *)fDataSource forSectionID:(NSString *)secID;
#end
In FOOTableDelegate.m file
- (UIView*)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
if (tableView.style == UITableViewStylePlain)
{
if ([tableView.dataSource respondsToSelector:#selector(tableView:titleForHeaderInSection:)])
{
NSString* title = [tableView.dataSource tableView:tableView titleForHeaderInSection:section];
if (title.length > 0)
{
UIView* header = [_headers objectForKey:title];
if (nil != header)
{
header.alpha = 1;
}
else
{
if (nil == _headers)
{
_headers = [[NSMutableDictionary alloc] init];
}
header = [self createHeaderView:tableView.dataSource forSectionID:title];
[_headers setObject:header forKey:title];
}
return header;
}
}
}
return nil;
}
- (UIView*) createHeaderView:(FeedDataSource *)fDataSource forSectionID:(NSString *)secID
{
//do some code for header View
return View;
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
//return height for the Section Header
return height;
}
This will help you to solve your problem
Related
I am using grouped tableview for developing a contact list using database. I have to show the message "No Contacts" on tableview when there is no contact in list. how can I do it?
Share your ideas..
Thanks in Advance
supposing that you are using an array to store all the contacts then use the following delegate
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// You can also modify this condition according to a specific section
if([YOUR_ARRAY count] == 0)
{
return 1;
}
else
return [YOUR_ARRAY count];
}
Now adding data to table in following delegate
-(UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Initialise your cell
if([YOUR_ARRAY count] > 0){
// add your array data to cells
}
if([YOUR_ARRAY count] == 0){
// this means no contacts in array and therfore you have only one cell to display NO CONTACTS
}
return cell;
}
For cases like this one we used table headers.
If the table had elements in his data source, the table header was clear and had a 1px height.
If the data source had no elements, then the table header view was set as big as the table's frame and contained a message, an image or whatever you might need.
The functions (table view delegate methods, actually) we used were height for header in section and view for header in section. We verified the data source inside the viewForHeader function
You can achieve the same effect using the table footers as well
you can add UILabel
ande set the text of the label
label.text = #"No results ";
and you make a test
if ([contacts count] == 0)
{
yourTableview.hidden = YES;
yourLabel.hidden = NO;
}
else
{
yourTableview.hidden = NO;
yourLabel.hidden = YES;
}`
This is related to another question of mine which wasn't answered in a helpful way (message when a UITableView is empty).
I'm trying to show an UIImage graphic that says You haven't saved any bookmarks over an UITableView when it's empty. I have NSNotification set-up so that when bookmarks are added or deleted, a message is sent so that the UITableView can be updated.
I've been trying to do it with this code. Why won't this work?
- (void)bookmarksChanged:(NSNotification*)notification
{
[self.tableView reloadData];
UIImageView* emptyBookmarks = [[UIImageView alloc] initWithFrame:CGRectMake(75, 100, 160, 57)];
emptyBookmarks.alpha = 1;
emptyBookmarks.image = [UIImage imageNamed:#"emptyBookmark.png"];
[self.view addSubview:emptyBookmarks];
[emptyBookmarks release];
if ([self.dataModel bookmarksCount] == 0)
{
emptyBookmarks.alpha = 1;
}
else
{
emptyBookmarks.alpha = 0;
}
}
I'm probably approaching this the wrong way... But if salvageable, what am I doing wrong?
When I initially have an empty bookmarks tableview, there's no image displayed. After I add a bookmark and then delete it, the image shows. Grrh.
Another way (and IMO the correct way) to do this is to manipulate the backgroundView property on the UITableView.
While making a single cell with a custom image cell would certainly works, I think it overly complicates the logic of your UITableViewController's data source. It feels like a kludge.
According to UITableView documentation:
A table view’s background view is automatically resized to match the
size of the table view. This view is placed as a subview of the table
view behind all cells , header views, and footer views.
Assigning an opaque view to this property obscures the background color
set on the table view itself.
While you probably don't want to just set it to your UIImageView, it is very easy to make a UIView that contains the UIImageView that you want.
Well first off if you were going to do it that way, you would need to reload the tableView after updating the image or model etc. and not before.
But you are probably making things more complicated than they need to be!
Why not just check to see if the data for section 0 and indexPath.row 0 are empty and if so in cellForRowAtIndexPath display a text message accordingly.
// First make sure there is always one row returned even if the dataModel is empty.
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSInteger numRows = 0;
if ([self.dataModel lastObject]) {
// Return the number of rows in the section.
numRows = [self.dataModel count]; // etc.
}
if (numRows < 1) numRows = 1;
return numRows;
}
// Then display the data if there is some, otherwise a message if empty.
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
if ([self.dataModel lastObject]) {
// setup the cell the normal way here.
} else { // the datasource is empty - print a message
cell.textLabel.text = nil;
cell.detailTextLabel.text = NSLocalizedString(#"You haven't saved any bookmarks", #"");
cell.detailTextLabel.textColor = [UIColor colorWithRed:0/255.0 green:0/255.0 blue:0/255.0 alpha:0.7];
cell.accessoryType = UITableViewCellAccessoryNone;
}
return cell;
}
Are you sure [self.dataModel bookmarksCount] is equal to 0 ?
While I agree that you are probably going about this the wrong way,
your image is allocated and added in your bookmark changed, your notification does not trigger when there are no bookmarks initially. Hence you don't see the image. Call the bookmar changed when your table view inits or appears.
Probably the best way to achieve this is to perform a check in your numberOfRowsInSection method to return 1 if your data source is empty. Then in cellForRowAtIndexPath check if your data source is empty and if it is, create a custom cell that contains whatever you want. In heightForRowAtIndexPath you need to return your custom cell height if your datasource is empty, but only if you want the cell larger than the default. At least that is how I would approach it.
when bookmarks count is nil add one to your row method:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
int c;
c = bookmarks.count;
if(c == 0){
c = 1;
}
return c;
}
and then the same check again in your cellforrowatindexpath.
Another thing to be aware of in this situation is that if you're using core data and you're datasource is feeding off an entity, you will want to make sure your model matches. You can get some weird side-effect behavior in certain situations. This is especially true if you allow editing and core data has an empty model but you're tableview is still showing a cell.
Hello I am using dropbox api and displaying meta data from dropbox account..
I want to differentiate files and folders from loaded data..because I want to show next level if there is folder and if there is file I don't want to show next View
my code to load data
- (void)restClient:(DBRestClient*)client loadedMetadata:(DBMetadata*)metadata {
[self.metaArray release];
self.metaArray = [[NSMutableArray alloc]init ];
for (DBMetadata *child in metadata.contents) {
NSString *folderName = [[child.path pathComponents] lastObject];
[self.metaArray addObject:folderName];
}
[self.tableView reloadData];
[self.activityIndicator stopAnimating];
}
According to the Dropbox Developer Docs the metadata includes a property called is_dir which should allow you to determine whether the particular item is a directory or not.
Looking at the header of DBMetaData it is indeed exposed as a property
#property (nonatomic, readonly) BOOL isDirectory;
So you can just do a simple test like so
- (void)restClient:(DBRestClient*)client loadedMetadata:(DBMetadata*)metadata
{
if (metadata.isDirectory) {
// handle directory here
} else {
// handle file here
}
}
With regards pushing views based on whether or not an entry is a directory, you could subclass UITableViewCell and add an isDirectory property. Instead of adding just the name to self.metaArray you could add a dictionary containing both the name and the value of isDirectory. Then in your table view datasource where you populate the cells you'd set the isDirectory property of the UITableViewCell based on the same property in the appropriate dictionary from the array. Finally, in the table view delegate method
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
you can get the selected cell using the indexPath and then test the isDirectory property and based on it's value take the appropriate action.
Hope this helps.
Using the API V2 of Dropbox with the Dropbox SDK is:
DropboxClient *client = [DropboxClientsManager authorizedClient];
[[client.filesRoutes listFolder:path]
response:^(DBFILESListFolderResult *result, DBFILESListFolderError *routeError, DBRequestError *error) {
if (result) {
for (DBFILESMetadata *entry in result.entries) {
if ([entry isKindOfClass:[DBFILESFileMetadata class]]) {
DBFILESFileMetadata *fileMetadata = (DBFILESFileMetadata *)entry;
NSLog(#"File: %#", fileMetadata.name);
} else if ([entry isKindOfClass:[DBFILESFolderMetadata class]]) {
DBFILESFolderMetadata *folderMetadata = (DBFILESFolderMetadata *)entry;
NSLog(#"Folder: %#", folderMetadata.name);
}
}
}
I have a TTableView. The items in this table a mapped to an url, so that when I click on an item, another view appear with informations about this item.
All these informations are attributes of a class. So, how can I build my TTableTextItem URL in order to transmit the class containing informations to the view responsible for the display of these informations ?
Thanks in advance.
One way of doing it is to use a TTURLAction. When the user selects a row in your table, which will call your didSelectObject (of TTTableViewController) method, extract the object or set of objects you want to pass and build a TTURLAction like this:
TTURLAction *action = [[[TTURLAction actionWithURLPath:#"tt://showUser"]
applyQuery:[NSDictionary dictionaryWithObject:user forKey:#"kParameterUser"]]
applyAnimated:YES];
Then open the action:
[[TTNavigator navigator] openURLAction:action];
The controller you want to open as a result of this action should be registered in your TTURLMap and should have a constructor thus:
- (id) initWithNavigatorURL:(NSURL*)URL query:(NSDictionary*)query {
self = [super init];
if (self != nil) {
self.user = [query objectForKey:kParameterUser];
}
return self;
}
I tend to create categories on classes for objects I want to be able to open another controller and display themselves.
The big problem with directly using TTURLAction is that you can't really use them with TTTableViewItem. The only way to really do it is override -didSelectObject:atIndexPath: and build your custom TTURLAction with your desired object in the query dictionary. But this breaks the nice separation of Model and View Controller, and gets complicated once you have multiple objects to pass.
Instead of this, I've been using a small category which automatically takes the userInfo property of the table item (which I set to whatever I need), and automatically adds it as a URL parameter.
And then you use this to retrieve it in your mapped view controller.
- (id)initWithNavigatorURL:(NSURL *)URL query:(NSDictionary *)query {
if (self = [self initWithNibName:nil bundle:nil]) {
id myPassedObject = [query objectForKey:#"__userInfo__"];
// do the rest of your initlization
}
return self;
}
You can download it as a GitHub Gist here. The code is also below. We're considering merging this into the main branch at some point.
TTTableViewDelegate+URLAdditions.h
#interface TTTableViewDelegate(URLAdditions)
#end
TTTableViewDelegate+URLAdditions.m
#import "TTTableViewDelegate+URLAdditions.h"
#implementation TTTableViewDelegate(URLAdditions)
- (void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath {
id<TTTableViewDataSource> dataSource = (id<TTTableViewDataSource>)tableView.dataSource;
id object = [dataSource tableView:tableView objectForRowAtIndexPath:indexPath];
// Added section to automatically wrap up any TTTableItem userInfo objects. If it is a dictionary, it gets sent directly
// If it is not, it is put in a dictionary and sent as they __userInfo__ key
if( [object isKindOfClass:[TTTableLinkedItem class]] ) {
TTTableLinkedItem* item = object;
if( item.URL && [_controller shouldOpenURL:item.URL] ) {
// If the TTTableItem has userInfo, wrap it up and send it along to the URL
if( item.userInfo ) {
NSDictionary *userInfoDict;
// If userInfo is a dictionary, pass it along else create a dictionary
if( [item.userInfo isKindOfClass:[NSDictionary class]] ) {
userInfoDict = item.userInfo;
} else {
userInfoDict = [NSDictionary dictionaryWithObject:item.userInfo forKey:#"__userInfo__"];
}
[[TTNavigator navigator] openURLAction:[[[TTURLAction actionWithURLPath:item.URL]
applyQuery:userInfoDict]
applyAnimated:YES]];
} else {
TTOpenURL( item.URL );
}
}
if( [object isKindOfClass:[TTTableButton class]] ) {
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
else if( [object isKindOfClass:[TTTableMoreButton class]] ) {
TTTableMoreButton* moreLink = (TTTableMoreButton*)object;
moreLink.isLoading = YES;
TTTableMoreButtonCell* cell
= (TTTableMoreButtonCell*)[tableView cellForRowAtIndexPath:indexPath];
cell.animating = YES;
[tableView deselectRowAtIndexPath:indexPath animated:YES];
if( moreLink.model )
[moreLink.model load:TTURLRequestCachePolicyDefault more:YES];
else
[_controller.model load:TTURLRequestCachePolicyDefault more:YES];
}
}
[_controller didSelectObject:object atIndexPath:indexPath];
}
#end
I think the documentation on the official website does describe very clearly the navigation scheme for Three20. Your question is the very common task of any application, and Three20 provides powerful support for that.
I have little fix this awesome code :)
as posted version strip callback from TTTableButton.
Correction is:
if( [object isKindOfClass:[TTTableButton class]] ) {
if (item.delegate && item.selector) {
[item.delegate performSelector:item.selector withObject:object];
}
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
In the Contacts app's add/edit view, if you hit the 'Return' key in a field, you cycle through each of the UITextFields. These fields seem to be inside a UITableViewCell. What's a good way to do this?
When I have a series of UITextFields not inside a Table, I can invoke
[self.view viewWithTag:tag] to get a list of the views and the use that to cycle through them. But within a table, I only get one view and I'm not sure how to convert this over.
-(BOOL)textFieldShouldReturn:(UITextField *)textField {
// Find the next entry field
for (UIView *view in [self entryFields]) {
if (view.tag == (textField.tag + 1)) {
[view becomeFirstResponder];
break;
}
}
return NO;
}
/*
Returns an array of all data entry fields in the view.
Fields are ordered by tag, and only fields with tag > 0 are included.
Returned fields are guaranteed to be a subclass of UIResponder.
From: http://iphoneincubator.com/blog/tag/uitextfield
*/
- (NSArray *)entryFields {
if (!entryFields) {
self.entryFields = [[NSMutableArray alloc] init];
NSInteger tag = 1;
UIView *aView;
while (aView = [self.view viewWithTag:tag]) {
if (aView && [[aView class] isSubclassOfClass:[UIResponder class]]) {
[entryFields addObject:aView];
}
tag++;
}
}
return entryFields;
}
Just discovered this while browsing for something myself - sorry for the delay.
If you haven't resolved the issue, you could create an array of uitextfield objects in the same order presented on screen. As you create the table, set the .tag property of the text field. Within the text field delegate method - read the tag, increment by 1 (to move to the next field), and issue the becomefirstresponder call.
You could also look at the textFieldDidEndEditing delegate method.