I'm looking for a way to populate a table view from one single document, namely I want to load a .po file.
I would like each line of my table view to load one line of text from the PO file.
Ideally, I would like to have one line in the first column, and the corresponding translation in the second column (to get a clear view of the contents).
I have not worked much with table views yet so please forgive my ignorance!
I have done my research but I find the apple documentation confusing and very unclear -- and didn't find much online...
Thanks in advance for any help!
bbum is correct, you don't push data to your table, you provide it and the table displays it. Friday I did a quick mock-up on putting a text file displayed line by line, so maybe some of the code can help some.
Get a table view connected with an outlet to it's data source, then you can do something like this:
// Class variable in your table delegate object
NSArray* lineList;
IBOutlet NSTableView* table;
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
{
return [lineList count];
}
- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
return [lineList objectAtIndex:row];
}
// Be sure to use the proper encoding for your text file
// do something like this to load your text file.
- (void) loadData:(NSString*)ourPath
{
NSError* err = nil;
NSString* fullFileText = [NSString stringWithContentsOfFile:ourPath encoding:NSMacOSRomanStringEncoding error:&err];
if (err)
NSLog(#"Err: %#, %d", [err localizedDescription], [err code]);
if (fullFileText)
{
lineList = [[fullFileText componentsSeparatedByString:#"\n"] retain];
[table reloadData];
}
}
In your case you may want to hold an array of dictionaries, using a different key for both versions of your text. That way you can have two columns. The NSTableColumn will tell you which column you will be drawing into when tableView:objectValueForTableColumn: gets called. The other option you have here is making a custom cell that has two fields in it but that's probably overkill for what you're asking.
Note also that there are a number of other optional delegate calls you can add for more flexibility of how you show your data.
Additionally for more dynamic complex tasks I've found that bindings are better. They can be confusing if you're not comfortable with them though. For simple tables it's often just as easy to go this route. Good luck!
You don't push data to a table view, it pulls data from you. This can be done with either bindings or by implementing the table view data sour (which are a little bit different, but mostly the same, between the NS* and UI* platforms).
The NSTableView and UITableView documentation both have links to examples and programming guides. Read those and if you still don't get it, ask a specific question.
Related
I'm seeking to implement a UITableView that has sections representing the recent history and future queue of a media player. It seems to me that a Queue-type structure would be most applicable for this, as this represents the nature of the operation, but the requirement that the future queue part be editable poses some challenges.
I think that a linked list would be the best option here to store the media representations, as opposed to a vector type structure where all of the elements are stored contiguously. It seems to me that, in the case of moving or removing an object within the queue and adding object at the end, a linked list is more efficient than a vector, as simply assigning a few pointers different values seems more lightweight than moving entire chunks of memory. The internal implementation details of NSMutableArray seem quite obscure but I'm assuming it's a vector type.
However, I've never really seen a true linked-list implementation in Objective-C. Furthermore, the structure of UITableViewDataSource, requiring cellForRowAtIndexPath: to be called with a specific row rather than simply iterating through the list, exposes the weakness of linked list implementations, as seeking a specific index can be expensive. This would be irrelevant if cellForRowAtIndexPath: is only called in order, but it seems reckless to ignore the indexPath parameter in favor of just iterating through the list.
How are these structures typically implemented? Is iterating through a link list as proposed a bad idea?
Since Objective-C doesn't have an explicitly defined "linked list" object type, the closest alternative would be NSMutableArray.
You should never assume the order in which
- (UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath *)indexPath;
will be called, and if you implement the UITableView correctly, it should only be called for the index paths of cells right before they appear on screen (thus the order it's called would alter based on the direction the user is scrolling in).
I recommend that you create a 2-dimensional NSMutableArray with the 1st representing the sections in the table and the 2nd representing the rows in each section. You would then initialize the array using something like:
self.sectionsArray = [NSMutableArray array];
[self.sectionsArray addObject:[NSMutableArray array]]; // history rows
[self.sectionsArray addObject:[NSMutableArray array]]; // queued rows
Which would allow you to easily retrieve the stored items using something along the lines of:
- (UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSUInteger section = indexPath.section;
NSUInteger row = indexPath.row;
id fetchedObject = self.sectionsArray[section][row];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"CellReuseID"];
// perform setup here based on the fetchedObject
return cell;
}
I've listed some NSMutableArray methods you may find helpful below:
- (void)addObject:(ObjectType)anObject;
- (void)insertObject:(ObjectType)anObject atIndex:(NSUInteger)index;
- (void)removeObject:(ObjectType)anObject atIndex:(NSUInteger)index;
- (void)replaceObjectAtIndex:(NSUInteger)index withObject:(ObjectType)anObject;
- (void)removeLastObject;
I do not understand the Objective-C in the following method taken from Apple's Master Detail Template:
- (void)insertNewObject:(id)sender
{
if (!_objects)
{
_objects = [[NSMutableArray alloc] init];
}
[_objects insertObject:[NSDate date] atIndex:0];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0
inSection:0];
[self.tableView insertRowsAtIndexPaths: #[indexPath]
withRowAnimation: UITableViewRowAnimationAutomatic];
}
The code is in the MasterViewController.m file. The points of confusion for me are:
- what does #[indexPath] mean? Is this a syntax for making something an array. I have only run into the '#' as the character denoting a string. Clarification: I understand what an NSIndexPath is and how it functions. I am not familiar with the syntax for putting an object in brackets preceded by the # character. Does this make something an array? What if I had several NSIndexPaths? Can I load all of them into an array in this manner #[indexPath1, indexPath2, indexPath3] Does this work with anything derived from NSObject? Is the result always an array? Where is this documented in the language - what is this language feature called (so I can look it up)?
The method insertRowsAtIndexPaths:withRowAnimation seems to specify the locations to put something into the table but not exactly what to put. How does the method know what object to use as the data source when inserting items into the table. The delegate and data source relationships are specified in the xib so it is apparent that the MasterViewController will handle the association of data with the table, but there does not seem to be any relationship with the NSMutable *_objects; array and the table specified anywhere.
Thanks for the help in explaining this as it is apparent I am missing some pretty basic stuff.
Is this a syntax for making something an array
Yes. In modern Objective-C, as understood by the latest clang compiler, there are some more # directives for easily creating hard-coded objects:
#[object1, object2]
creates an NSArray;
#{#"key1": value1, #"key2": value2}
creates an NSDictionary,
#"hello world"
creates an NSString, as usually, and
#1, #2, #YES, #3.1415927
create NSNumber instances, respectively.
How does the method know what object to use as the data source when inserting items into the table.
It uses the data source currently set on the table view it is called on.
Does it make sense logically to have two data sources populating a single NSTableView? Or would a pop up button (with ability to choose from which data source to read)be more reasonable?
If I go with the single NSTableView option, would I be better off if I input all the data into a single data source (say an NSDictionary) and then populating the table? The only problem that I see with this idea is what happens when a selection of the table is done.. How would I decipher which of the original location was selected.
Another idea/potential problem that just popped into my head ... if I take the data from the data source and populate the table directly, how would give the count of the table in the relevant NSTableView 'count' method.. Would adding the count of data store 1 and data store 2 do it?
Sorry if it's a bit jumbled up.. Thanks for any input!
You can definitely use multiple data sources for the data to be displayed in the table view, but they must all be funnelled through a single controller object that is assigned as the table view's datasource and which implements the NSTableViewDatasource protocol.
You will have to write some code in your controller object so that you keep track of the multiple source arrays that make up and supply the table view with the appropriate values for the number of items and the content of each item.
This is a very simplistic example:
- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView
{
return [array1 count] + [array2 count];
}
- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
{
if(rowIndex >= [array1 count])
{
return [array2 objectAtIndex:rowIndex - [array1 count]];
}
else
{
return [array1 objectAtIndex:rowIndex];
}
}
As for selection, well, your controller knows where it's getting its data from, so when the user selects a particular row it should be trivial to translate that row index to a matching object in one of your backing stores.
Depending on your application logic, it can make sense. The easiest way would be to set up a different source for every table section. Then, you could use the section number as a selector to your data base.
I think the biggest problem I'm having with understanding programming is understanding what a particular method does. For example
- (BOOL)tableView:(NSTableView *)aTableView shouldEditTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
When I look at the above method I know that it returns a boolean and that the method receives the following.
NSTableView
NSTableColumn
NSInteger
However, I don't understand what I need to provide to use the method correctly.
Do I just return a boolean?
It comes down to a saying: code tells you how, comments tell you why.
If you are writing a method, you need to know why you want the method and document it accordingly, a.k.a. comments. If you are overriding a method, then you would hope the producer of the method would document about what the method does.
If you're diving into iPhone development without a bit of programming background, you should at least read up the Introduction to The Objective-C Programming Language. If you want to find out what a particular method does in your code, you can always right click the method name and choose "Find Text in Documentation" to read more about it.
This method will be called by the table (or something) on your delegate (the class you are implementing this method in) when the table needs to know if a certain row and column can be edited. You just need to return YES or NO to indicate if you want to let it be edited.
This is an example implementation:
-(BOOL)tableView:(NSTableView *)aTableView shouldEditTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex {
return NO;
}
This will mean the table can never be edited.
A more complex implementation like this would let only the first row be editable:
-(BOOL)tableView:(NSTableView *)aTableView shouldEditTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex {
if (rowIndex == 0)
return YES;
else
return NO;
//This can be shortened to:
//return rowIndex == 0;
}
The name of the method tableView:shouldEditTableColumn:row: should give you a clue that it is about editing rows and columns in a table view. You could probably guess that, since it is returning a boolean it's called to determine with a table view should edit the supplied row of the supplied column. However, that would just be a guess, so at this point (if I hadn't been using that method only yesterday and thus know exactly what it is for) I would start googling for it (or using Xcode search if I had access to Xcode).
A useful tip for Googling Cocoa documentation is to search only on site:developer.apple.com so we'll try site:developer.apple.com tableView:shouldEditTableColumn:row:. The top hit in this case is NSTableViewDelegate which is the one we want.
I have a problem with sorting NSTableColumn contents. In my NSTableView there are three columns: File, Size, Path. The contents are stored in NSMutableArray. Each object in this array is a NSDictionary containing three keys: file, size and path - value for each is a NSString.
In Interface Builder, in each Table Column's attributes I can choose sorting options:
Selector: IB entered "compare:" which I think is ok, because I compare NSStrings.
Sort Key - and that's the problem I think - I don't know what to enter here.
Any clues? If you've got questions about my code, please ask.
So I found the complete solution.
First, go to Interface Builder. Select column that you want to sort. Go to the column's inspector and choose the first button to see the "Table Column Attributes". Set appropriate values (literally, no " or ' or # are needed):
Sort key: file
where 'file' is the key of dictionary that contents is shown in your column.
Selector: compare:
standard sort function.
Now, save all the changes here and jump to Xcode, to the class in which is the model, source of the data shown in NSTableView. You should already know that you need two methods there:
-(NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
-(id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn
*)tableColumn row:(NSInteger)row
these two are needed to conform the NSTableDataSource informal protocol. You can read about it at the MacDev Center.
Now all you have to do is to add a new method:
-(void)tableView:(NSTableView *)tableView sortDescriptorsDidChange: (NSArray *)oldDescriptors
it can contain a very simple code that will do the thing:
-(void)tableView:(NSTableView *)tableView sortDescriptorsDidChange: (NSArray *)oldDescriptors
{
NSArray *newDescriptors = [tableView sortDescriptors];
[results sortUsingDescriptors:newDescriptors];
//"results" is my NSMutableArray which is set to be the data source for the NSTableView object.
[tableView reloadData];
}
And that's all. It's working in my app, hope it will work in your case :D Everything should work automatically. I've just saved all files and Built the app. And it worked. :)
Site that helped me:
CocoaDev: SortingNSTableView
Key is the key that you use in dictionary to retrieve the value.
In your case you have three keys: file, size and path. Select the one on which you want to sort. The key is used to retrieve value from each record to be used for sorting.
If your keys are #"file", #"size", #"path" and you want to sort on file then try to put value.file into the Sort Key field in IB.
Use keys that you use when inserting values into your NSDictionary.