I'm using an NSOutlineView object to represent a file structure and am finding that it will not correctly indent any children which are expandable, though it will indent children that aren't.
Here's a picture to show what I mean:
In this example, "AnotherFolder" is a child of "Folder2" yet it does not indent in line with the other indented files. Curiously enough, the child "AnotherFile.java" of "AnotherFolder" does indent correctly (2 levels in).
I have tried setting properties such as "indentationFollowsCells" to no avail. This seems as though it should be very simple but I can't solve it.
Thanks!
Edit: Some extra information upon request:
I am using the NSOutlineViewDataSource protocol for the implementation, here is the code related to that:
- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
return item;
}
- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item {
NSMutableDictionary* dict;
if(item == nil) {
dict = fileTree;
} else {
dict = [((MyFile*) item) children];
}
NSArray* keys = [dict allKeys];
NSArray* sorted = [keys sortedArrayUsingSelector:#selector(compare:)];
NSString* key = [sorted objectAtIndex:index];
return [dict objectForKey:key];
}
- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
return [[item children] count] > 0;
}
- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
if(item == nil) {
return [fileTree count];
}
return [[item children] count];
}
Try changing your outline view from a Source outline view to a normal one.
I ran into this right now, and found it a bit strange that nine years after this post, the problem still persists.
This behaviour is baked into the Source style: the first row of the standard content is aligned with the header cell rather than indented, so everything is shifted over by one level.
If you use header cells, you want this behaviour, and everything is fine. If you don't want to use header cells, not using a SourceList is your only option.
Related
I have a two level UIPickerView which means if user selects the first component, the second component will update its data.
The data should be look like this:
self.type = #[#"fruit", #"airlines"];
self.data = #[#[#"Apple", #"Orange"], #[#"Delta", #"United", #"American"]];
datasource and delegate method:
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView {
return 2;
}
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component {
if(component == 0){
return self.data.count;
}else if(component == 1){
NSInteger row1 = [pickerView selectedRowInComponent:0];
return [self.data[row1] count];
}
}
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component{
if(component == 0){
[pickerView reloadComponent:1];
}
NSInteger row1 = [pickerView selectedRowInComponent:0];
NSInteger row2 = [pickerView selectedRowInComponent:1];
// here may crash, some time
NSLog(#"data: %#", self.data[row1][row2]);
}
- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component{
if(component == 0){
return self.type[row];
}else if(component == 1){
NSInteger row1 = [pickerView selectedRowInComponent:0];
// here may also crash
return self.data[row1][row];
}
}
There are several places which may cause crashes(NSRangeException) in the above code as commented. However, they rarely happened. I haven't gotten this crash yet, but my users had according to Crashlytics report.
I add some codes to validate row1 and row2 before accessing self.data[row1][row2] and send to my server if fail. I found that the value of [pickerView selectedRowInComponent:0] may be incorrect some time. For example, when user changes the first component from "fruit" to "airlines" and select some item in second component, the value of selectedRowInComponent:0 may still be 0(index of "fruit").
I guess this is caused by race condition but how can I solve this?
It will crash if a component represents an array with three members (airlines) AND the selected row in that component is the last member and then you flip the component over to represent the two member array (fruit). The pickerview has retained this variable (selectedrowincomponent:x) and now wants to represent member #2 on a two member array (has members #0-1 only) -> NSRangeException. You need to alter the selectedRow in that component before you flip it to a different array with reload ..
I personally don't like using one picker to do two things, it's messy IMHO just write a different class to be delegate/dataSource for each, one can be a subclass of the other, then lazy load em as needed
I have two entities, Note and Tag, that have a many-to-many relationship.
I want to be able to tap on a Note and have a UITableView that is populated with all the Tags that exist, and all the Tags that have a relationship with the Note that was tapped have a UITableViewCellAccessoryCheckmark on the right.
What I've tried doing is fetching two arrays, the array with all the tags and the array with the related tags. Then I did something like this:
for (int i = 0; i < [self.tagArray count]; i++) {
Tag *new = [self.tagArray objectAtIndex:i];
if ([self.all containsObject: new])
{
new.isIn = TRUE;
}
else {
new.isIn = FALSE;
}
}
isIn is a property of Tag, initialized like this:
In Tag.h:
#property BOOL isIn;
In Tag.m:
#synthesize isIn;
I have a feeling this is the wrong approach though. Can anyone think of a better solution?
As per my comment, if you are displaying all of the tags anyway, you don't need to precheck to see if the tag should have a checkmark or not.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// not going to go into basic cell generation logic here
Tag *currentTag = [self.tagArray objectAtIndex:indexPath.row];
if([self.all containsObject: currentTag])
{
// checkmark
}
else
{
// no checkmark
}
return cell;
}
In my cocoa app i have 2 NSTableViews. The AppDelegate is the datasource for both of them. I want to change the data of the second TableView depending on the selected row of the first TableView. For that i use an NSDictionary. I add an array for every entry in the data array of Table1.
- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView{
if([table1 selectedRow] != -1 && [[aTableView identifier] isEqualToString:#"table2"]){
return [[dict objectForKey:[arrForTable1 objectAtIndex:[table1 selectedRow]]] count];
}
if([[aTableView identifier] isEqualToString:#"table1"]) return [arrForTable1 count];
return 0;
}
- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex{
if([table1 selectedRow] != -1 && [[aTableView identifier] isEqualToString:#"table2"]){
return [[dict objectForKey:[arrForTable1 objectAtIndex:[table1 selectedRow]]] objectAtIndex:rowIndex];
}
if([[aTableView identifier] isEqualToString:#"table1"]){
return [arrForTable1 objectAtIndex:rowIndex];
}
return nil;
}
This code crashes when in table2 a row is selected and after that a switch in the table1 to a row with an empty array.
How can i fix it? I tried to [table2 deselectAll:self] in the - selectionShouldChangeInTableView: method but it doesn't work. It works to trigger the deselectAll: method with an button and that was ok.
Sounds like you might need to call [table2 reloadData] when your selection in table1 changes.
I am trying to create a UITableView with two different sections. I know I can group them on an attribute of my managed object. For instance if I'd like to group them per name I'd do:
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:_context
sectionNameKeyPath:#"name"
cacheName:#"uploadProperties"];
And I return the number of secionts like:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return [[_fetchedResultsController sections] count];
}
The problem though is that I do not want to group it per attribute (such as the name). I want to group them for specific values, namely one part that has pud_id = 0 and another section that has pud_id > 0.
How can I achieve this? Is it possible to use kind of a where clause? Or can I create a property on my managed object and use this in the sectionNameKeyPath such as:
- (BOOL) hasPudZero {
if (self.pud_id == 0)
return YES;
return NO;
}
??
Thanks for your input!
Perhaps you could create a transient attribute of type NSString that returns one of two strings?
- (NSString *)sectionIdentifier
{
[self willAccessValueForKey:#"sectionIdentifier"];
NSString *tmp = [self primitiveValueForKey:#"sectionIdentifier"];
[self didAccessValueForKey:#"sectionIdentifier"];
if (!tmp) {
if (self.pud_id == 0) {
tmp = #"SectionOne";
} else {
tmp = #"SectionTwo";
}
[self setPrimitiveValue:tmp forKey:#"sectionIdentifier"];
}
return tmp;
}
and then use sectionNameKeyPath:#"sectionIdentifier" when creating your NSFetchedResultsController. Make sure you set the primitive value of sectionIdentifier back to nil if the value of put_id changes.
I have a Mac OS X application that uses an NSOutlineView with two columns: key and value, where you can edit the value column. I either have a NSString or a NSDictionary in a row. The code for the value of the cells is like this:
- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
if ([[[tableColumn headerCell] stringValue] isEqualToString:#"Key"]) {
id parentItem = [outlineView parentForItem:item] ? [outlineView parentForItem:item] : root;
return [[parentItem allKeysForObject:item] objectAtIndex:0];
} else {
if ([item isKindOfClass:[NSString class]]) {
return item;
} else if ([item isKindOfClass:[NSDictionary class]]) {
return #"";
} else {
return nil;
}
}
}
It's working as it should, except for when to value fields has the same string value. It always just takes the first element with that value to show as the key, so the same key value will appear for all value values that are the same. Anybody know how to fix this problem?
It looks like you're showing a tree of dictionaries, whose objects are either strings or dictionaries.
The first problem is that every item object must uniquely identify a row. Neither the key nor the value has this property. (The key would if this were a flat table view, but this is an outline view, and two dictionaries—one a descendant, sibling, or cousin of the other—can have the same key.) Instead, you should make a model object for each key-value pair.
Second, the dictionary-value rows should be group items. There's a delegate method you can implement for this.