Source List in Cocoa

Basic Cocoa Source List

In this post we will implement a simple source list similar to iTunes, Mail etc. Bindings has NOT been used in this approach

The final output
SourceList2

Image 2: (The Group 1 is hidden using the Group Show/Hide button similar to disclosure triangle functionality)
SourceList1

Steps

  • Step 1: Create a New Cocoa Project
  • Step 2: Modify the window of the MainMenu.xib and add a vertical split view
  • Step 3: Add an Source List (a view based outline view) to the left section of the split view.
  • Step 4: Set the AppDelegate as the Delegate and DataSource of the OutlineView /Source List View

Window

 

Source List Item Declaration

  • Step 5: Add a new file called as SourceListItem (subclass of NSObject). This class will represent each item in the source list.
  • Step 6: Implement the SourceListItem class as given below sample code
//
//  KSSourceListItem.h
//  SimpleSourceList
//
//  Created by Debasis Das on 3/30/14.
//

#import <Foundation/Foundation.h>
@interface KSSourceListItem : NSObject
{
    NSString *title;
    NSString *identifier; //This is required to differentiate if a Item is header/Group By object or a data item
    NSImage  *icon;       //This is required as an image placeholder for image representation for each item
    NSArray  *children; 
}

@property (nonatomic, retain)       NSString *title;
@property (nonatomic, retain)       NSString *identifier;
@property (nonatomic, retain)       NSImage  *icon;
@property (nonatomic, retain)       NSArray  *children;

//Convenience methods
+ (id)itemWithTitle:(NSString*)aTitle identifier:(NSString*)anIdentifier;
+ (id)itemWithTitle:(NSString*)aTitle identifier:(NSString*)anIdentifier icon:(NSImage*)anIcon;

- (BOOL)hasChildren;
- (BOOL)hasIcon;
@end

Source List Item Implementation

//
//  KSSourceListItem.m
//  SimpleSourceList
//
//  Created by Debasis Das on 3/30/14.
//
#import "KSSourceListItem.h"
@implementation KSSourceListItem

@synthesize title;
@synthesize identifier;
@synthesize icon;
@synthesize children;

+ (id)itemWithTitle:(NSString*)aTitle identifier:(NSString*)anIdentifier{
//convenience method for creating a Source List Item without an icon. Ideal for Group headers
	KSSourceListItem *item = [KSSourceListItem itemWithTitle:aTitle identifier:anIdentifier icon:nil];
	return item;
}

+ (id)itemWithTitle:(NSString*)aTitle identifier:(NSString*)anIdentifier icon:(NSImage*)anIcon
{
	KSSourceListItem *item = [[KSSourceListItem alloc] init];
	[item setTitle:aTitle];
	[item setIdentifier:anIdentifier];
	[item setIcon:anIcon];
	return item;
}

- (void)dealloc{
    [title release];
    [identifier release];
    [icon release];
    [children release];
    [super dealloc];
}

- (BOOL)hasChildren{
	return [children count]>0;
}

- (BOOL)hasIcon{
	return icon!=nil;
}
@end

App Delegate Declaration

//
//  KSAppDelegate.h
//  SimpleSourceList
//
//  Created by Debasis Das on 3/30/14.
//

#import <Cocoa/Cocoa.h>
@interface KSAppDelegate : NSObject 
{
    IBOutlet NSView *sourcePlaceHolderView;
    IBOutlet NSView *containerView;
    IBOutlet NSOutlineView *sourceListOutlineView;
}
@property (assign) IBOutlet NSWindow *window;
@property (atomic, retain) NSMutableArray *sourceListItems;

@end

App Delegate Implementation

//
//  KSAppDelegate.m
//  SimpleSourceList
//
//  Created by Debasis Das on 3/30/14.
//

#import "KSAppDelegate.h"
#import "KSSourceListItem.h"
@implementation KSAppDelegate
@synthesize sourceListItems;

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    [self performSelector:@selector(expandSourceList) withObject:nil afterDelay:0.0];
}

- (IBAction)expandSourceList
{
    //If Expand Children is set to NO, All the Groups will be displayed as a collapsed view
    [sourceListOutlineView expandItem:nil expandChildren:YES];
}

-(void)awakeFromNib
{
   //In this method we have created 3 groups and have added Items to each group.
   //If the List of Items is static it can also be read from a Plist File. 
   self.sourceListItems = [[NSMutableArray alloc] init];

    KSSourceListItem *groupOne = [KSSourceListItem itemWithTitle:@"GROUP 1" identifier:@"headerOne"];
    KSSourceListItem *groupTwo = [KSSourceListItem itemWithTitle:@"GROUP 2" identifier:@"headerOne"];
    KSSourceListItem *groupThree = [KSSourceListItem itemWithTitle:@"GROUP 3" identifier:@"headerOne"];

    KSSourceListItem *kItemOne      = [KSSourceListItem itemWithTitle:@"Item One" identifier:@"itemOne"];                   [kItemOne setIcon:[NSImage imageNamed:@"time.png"]];
    KSSourceListItem *kItemTwo      = [KSSourceListItem itemWithTitle:@"Item Two" identifier:@"item"];                      [kItemTwo setIcon:[NSImage imageNamed:@"time.png"]];
    KSSourceListItem *kItemThree    = [KSSourceListItem itemWithTitle:@"Item Three" identifier:@"item"];                    [kItemThree setIcon:[NSImage imageNamed:@"time.png"]];
    KSSourceListItem *kItemFour     = [KSSourceListItem itemWithTitle:@"Item Four" identifier:@"item"];                     [kItemFour setIcon:[NSImage imageNamed:@"time.png"]];
    KSSourceListItem *kItemFive     = [KSSourceListItem itemWithTitle:@"Item Five" identifier:@"item"];                     [kItemFive setIcon:[NSImage imageNamed:@"time.png"]];
    KSSourceListItem *kItemSix      = [KSSourceListItem itemWithTitle:@"Item Six" identifier:@"item"];                      [kItemSix setIcon:[NSImage imageNamed:@"time.png"]];
    KSSourceListItem *kItemSeven    = [KSSourceListItem itemWithTitle:@"Item Seven" identifier:@"item"];                    [kItemSeven setIcon:[NSImage imageNamed:@"time.png"]];
    KSSourceListItem *kItemEight    = [KSSourceListItem itemWithTitle:@"Item Eight" identifier:@"item"];                    [kItemEight setIcon:[NSImage imageNamed:@"time.png"]];
    KSSourceListItem *kItemNine     = [KSSourceListItem itemWithTitle:@"Item Nine" identifier:@"item"];                     [kItemNine setIcon:[NSImage imageNamed:@"time.png"]];
    KSSourceListItem *kItemTen      = [KSSourceListItem itemWithTitle:@"Item Ten" identifier:@"item"];                      [kItemTen setIcon:[NSImage imageNamed:@"time.png"]];

	[groupOne setChildren:[NSArray arrayWithObjects:
                                kItemOne,
                                kItemTwo,
                                kItemThree,
                                kItemFour,
                                kItemFive,
                                kItemSix,
                                kItemSeven,
                                kItemEight,
                                kItemNine,
                                kItemTen,
                                nil]];

    KSSourceListItem *pAreaOne      = [KSSourceListItem itemWithTitle:@"G2 Item One" identifier:@"item"];           [pAreaOne setIcon:[NSImage imageNamed:@"time.png"]];
    KSSourceListItem *pAreaTwo      = [KSSourceListItem itemWithTitle:@"G2 Item Two" identifier:@"item"];           [pAreaTwo setIcon:[NSImage imageNamed:@"time.png"]];
    KSSourceListItem *pAreaThree    = [KSSourceListItem itemWithTitle:@"G2 Item Three" identifier:@"item"];         [pAreaThree setIcon:[NSImage imageNamed:@"time.png"]];
    KSSourceListItem *pAreaFour     = [KSSourceListItem itemWithTitle:@"G2 Item Four" identifier:@"item"];          [pAreaFour setIcon:[NSImage imageNamed:@"time.png"]];
    KSSourceListItem *pAreaFive     = [KSSourceListItem itemWithTitle:@"G2 Item Five" identifier:@"item"];          [pAreaFive setIcon:[NSImage imageNamed:@"time.png"]];

    [groupTwo setChildren:[NSArray arrayWithObjects:
                                pAreaOne,
                                pAreaTwo,
                                pAreaThree,
                                pAreaFour,
                                pAreaFive,
                                nil]];

	[self.sourceListItems addObject:groupOne];
	[self.sourceListItems addObject:groupTwo];
        [self.sourceListItems addObject:groupThree];
}

#pragma mark OUTLINE VIEW DELEGATE & DATASOURCE
- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item 
{
	if(item==nil) 
        {
		return [self.sourceListItems count];
	}
	else {
		return [[item children] count];
	}
}

- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
{
    return [item hasChildren];
}

- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item
{
	if(item==nil) 
        {
		return [self.sourceListItems objectAtIndex:index];
	}
	else 
        {
		return [[item children] objectAtIndex:index];
	}
}

- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item
{
    return [item title];
}

- (void)outlineView:(NSOutlineView *)outlineView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn byItem:(id)item
{
    // This method needs to be implemented if the SourceList is editable. e.g Changing the name of a Playlist in iTunes
     //[item setTitle:object];
}

- (BOOL)outlineView:(NSOutlineView *)outlineView shouldEditTableColumn:(NSTableColumn *)tableColumn item:(id)item 
{
    //Making the Source List Items Non Editable
    return NO;
}

- (NSCell *)outlineView:(NSOutlineView *)outlineView dataCellForTableColumn:(NSTableColumn *)tableColumn item:(id)item
{
	NSInteger row = [outlineView rowForItem:item];
	return [tableColumn dataCellForRow:row];
}

- (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item
{
    if ([[item identifier] isEqualToString:@"headerOne"])
    {
        return YES;
    }
    else 
    {
        return NO;
    }
    return NO;

}

- (NSView *)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item {
// Different Source List Items can have completely different UI based on the Item Type. In this sample we have only two types of views (Header and Data Cell). One can have multiple types of data cells.
// If there is a need to have more than one type of Data Cells. It can be done in this method
    NSTableCellView *view = nil;
    if ([[item identifier] isEqualToString:@"headerOne"] || [[item identifier] isEqualToString:@"headerTwo"])
    {
        view = [outlineView makeViewWithIdentifier:@"HeaderCell" owner:self];
    }
    else {
        view = [outlineView makeViewWithIdentifier:@"DataCell" owner:self];
        [[view imageView] setImage:[item icon]];
    }
    [[view textField] setStringValue:[item title]];
    return view;
}

- (BOOL)outlineView:(NSOutlineView *)outlineView shouldSelectItem:(id)item
{
    //Here we are restricting users for selecting the Header/ Groups. Only the Data Cell Items can be selected. The group headers can only be shown or hidden. 
    if ([outlineView parentForItem:item])
    {
        return YES;
    }
    return NO;
}

- (void)outlineViewSelectionDidChange:(NSNotification *)notification
{
    NSIndexSet *selectedIndexes = [sourceListOutlineView selectedRowIndexes];
	if([selectedIndexes count]>1)
    {
        //This is required only when multi-select is enabled in the SourceList/ Outline View and we are allowing users to do an action on multiple items
    }
    else {
	   //Add code here for triggering an action on change of SourceList selection.
           //e.g: Loading the list of songs on changing the playlist selection
	}
}

@end

Also read Source List in Swift

Posted in Cocoa, Objective C Tagged with: , , ,
5 comments on “Source List in Cocoa
  1. Ash-Bash says:

    I Have a Question How do you Load Plist data into one of the groups?

    • Debasis Das says:

      In the above sample–> In the awakeFromNib method

      depending on whether the root of the PList is a dictionary or an array.

      Step 1: Load the plist to a variable
      NSDictionary *dictionary = [NSDictionary dictionaryWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@”ScreenA” ofType:@”plist”]];

      Step 2: Create a Group Level

      Step 3: Then iterate through the dictionary allKeys and create KSSourceListItem inside the for loop and finally append to the group level.

      If you have a sample Plist you can share the same with me and I can show you the sample code

      • Ash-Bash says:

        The Structure of my Plist is a Array of Dictionaries with Keys like “title”, “image”, “website_url” (website_url is for the other Part of the app) Heres the sample plist code :-

        title
        BBC
        image
        BBCtile
        website_url
        http://www.bbc.co.uk/

        title
        ABC
        image
        abc
        website_url
        http://www.abc.com/

        • Ash-Bash says:

          Can i Ask a Quick Question about A Group Item in NSOutlineView Source List using Plist Data? Can it be Possible to add new Plist Data to the Current List and Reload the NSOutlineView Source List. Because i saw the comments in the code (at awakeFromNib part) that the plist data will be static or read it as static.

          • Ash-Bash says:

            I think I’ve solved my own problem with the Plist when adding new Plist data and loads / updates the NSOutLine :- I still keep the code for all Groups in awakeFromNib but Copy the Plist Group and Pasted it in the
            – (void)applicationDidUpdate:(NSNotification *)notification
            with some additional code :-

            – (void)applicationDidUpdate:(NSNotification *)notification{

            //Loads and Checks for the Plist File.
            NSFileManager *fileManager = [NSFileManager defaultManager];
            NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
            NSString *documentDirectory = [paths objectAtIndex:0];
            NSString *plistPath = [documentDirectory stringByAppendingPathComponent:@”WebsiteList.plist”];

            if (![fileManager fileExistsAtPath:plistPath]) {
            NSString * defaultPath = [[NSBundle mainBundle] pathForResource:@”WebsiteList” ofType:@”plist”];
            [fileManager copyItemAtPath:defaultPath toPath:plistPath error:NULL];

            NSLog(@”No File At Doc”);
            }
            if([fileManager fileExistsAtPath:plistPath]){

            }

            NSMutableArray *dataRoot = [NSMutableArray arrayWithContentsOfFile:plistPath];

            NSUInteger numItems = [dataRoot count];

            self.icon = [NSMutableArray arrayWithCapacity:numItems];
            self.title = [NSMutableArray arrayWithCapacity:numItems];
            self.url = [NSMutableArray arrayWithCapacity:numItems];

            self.newsfeedobjects =[NSMutableArray arrayWithCapacity:numItems];

            for (NSDictionary *itemDicData in dataRoot) {
            [self.icon addObject:[itemDicData objectForKey:@”image”]];
            [self.title addObject:[itemDicData objectForKey:@”title”]];
            [self.url addObject:[itemDicData objectForKey:@”website_url”]];

            //Gets the Plist data and process it into a NSSideBarItem
            plistItem = [NSSideBarItem itemWithTitle:[itemDicData objectForKey:@”title”] identifier:@”item”];
            [plistItem setIcon:[NSImage imageNamed:[itemDicData objectForKey:@”image”]]];

            //Adds all the NSSideBarItem Objects into a NSMutableArray.
            [self.plistarrayobjects addObject: plistItem];

            }

            //Adds / Set the plistarrayobjects as children for the PlistGroup
            [PlistGroup setChildren:self.plistarrayobjects];

            //Reloads Only the PlistGroup
            [self.sidebar reloadItem:PlistGroup reloadChildren:YES];

            }

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

Recent Posts


Hit Counter provided by technology news