• Stars
    star
    645
  • Rank 69,781 (Top 2 %)
  • Language
    Objective-C
  • License
    MIT License
  • Created almost 10 years ago
  • Updated about 9 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

The intuitive UITableViewController.

demo Logo by T.E.D. Andrick

License Build Status Badge w/ Version

A UITableViewController subclass designed with efficiency and maintenance in mind, and tries its best to handle just about everything for you.

No more if-statements and switches in every table view dataSource and delegate method. No more splitting up all the logic for each row and section into a dozen different methods, making addition, removal, or minor alterations to format and layout a headache. We feel your pain, we know the struggle. Moving one section of cells below another, moving cells between sections, adding new cells to existing sections, etc., with a decently-complex table view can be a nightmare, but with Organic it's as simple as changing the order of objects in an array.

Organic allows you to define the sections and which cells they contain all-at-once, and takes care of the rest of the housekeeping for you.

Ignoring the custom cells, which are created in their respective classes, creating the below table view with Organic took less than 20 lines of code:

demo

Installation

Available via CocoaPods

pod ‘Organic’

Get Started

OrganicViewControllers are comprised of sections that can either be static and pre-generated, or can contain reusable cells that can be dequeued and customized for better performance and efficiency. In the above example, showing a GitHub profile, the first section with 3 cells is pre-generated, whereas the repositories section is built using reusable cells.

Building Pre-Generated Cells (not for reuse)

To do this, you first need to subclass or create instances of OrganicCell. These cells encapsulate two major properties, height and actionBlock.

The height property, as the name indicates, defines the height of the cell. When you layout your cell and all of its subViews, you can set the height based on the contents and the cell itself will be responsible for reporting the proper size in heightForRowAtIndexPath:. If this is never set, UITableViewAutomaticDimension is the fallback.

The actionBlock property defines the action that will be performed when the cell is selected and didSelectRowAtIndexPath: is called. Setting the actionBlock property on the cell when you create it allows you to keep the behavior of the cell when selected paired with the creation of the cell, so if you ever want to modify this cell and the action associated with it, you don't need to go searching through the code to find where it is defined. If this property is never set, nothing happens when the cell is selected.

OrganicCell *myCell = [OrganicCell cellWithStyle:UITableViewCellStyleDefault height:44 actionBlock:^{
	// Do whatever you want here when the cell is selected.
}];

Building Sections with Pre-Generated Cells

The OrganicSection object encapsulates all of the properties that define a table view section. Whatever you set the headerTitle and/or footerTitle properties as will be returned in the titleForHeaderInSection: and titleForFooterInSection: methods respectively.

If you want to define a UIView as the viewForHeaderInSection: or viewForFooterInSection:, set the headerView and/or footerView properties on the object. Whenever you're defining either a header or footer view, be sure to also set the accompanying headerHeight and footerHeight properties so when heightForHeaderInSection and heightForFooterInSectionare called the table view will make it fit accordingly.

Lastly, every section will contain an array of OrganicCell objects that you create that will be returned in the defined order inside cellForRowAtIndexPath:.

Various convenience class methods have been created with the different combinations of titles and views to make your life a little easier. Sections can contain any combination of titles and views that you want as long as they don't use both a title and a view for the header or footer. If this were to occur, the views would take precedent.

+ (instancetype)sectionWithCells:(NSArray *)cells;
+ (instancetype)sectionWithHeaderTitle:(NSString *)headerTitle cells:(NSArray *)cells;
+ (instancetype)sectionWithHeaderTitle:(NSString *)headerTitle footerTitle:(NSString *)footerTitle cells:(NSArray *)cells;
+ (instancetype)sectionWithHeaderTitle:(NSString *)headerTitle footerView:(UIView *)footerView footerHeight:(CGFloat)footerHeight cells:(NSArray *)cells;
+ (instancetype)sectionWithHeaderView:(UIView *)headerView headerHeight:(CGFloat)headerHeight cells:(NSArray *)cells;
+ (instancetype)sectionWithHeaderView:(UIView *)headerView headerHeight:(CGFloat)headerHeight footerTitle:(NSString *)footerTitle cells:(NSArray *)cells;
+ (instancetype)sectionWithHeaderView:(UIView *)headerView headerHeight:(CGFloat)headerHeight footerView:(UIView *)footerView footerHeight:(CGFloat)footerHeight cells:(NSArray *)cells;
+ (instancetype)sectionWithFooterTitle:(NSString *)footerTitle cells:(NSArray *)cells;
+ (instancetype)sectionWithFooterView:(UIView *)footerView footerHeight:(CGFloat)footerHeight cells:(NSArray *)cells;

Building Sections That Support Cell Reuse

OrganicSections now support cell reuse. To create such a section, you need to call the below convenience initializer and provide a few parameters:

+ (instancetype)sectionSupportingReuseWithTitle:(NSString *)title cellCount:(NSInteger)cellCount cellHeight:(CGFloat)cellHeight cellForRowBlock:(CellForRowBlock)cellForRowBlock actionBlock:(CellActionBlock)actionBlock;
  • title will be the title of the table view section. Leave this nil if you don't want a header title.
  • cellCount is just what it sounds like, the number of cells this row will have. You should use whatever you would normally use for your dataSource, such as the count of an NSArray.
  • cellHeight is what you would normally return in heightForRowAtIndexPath:. Currently, Organic only supports a single height for all cells in a section supporting reuse. I am looking into ways to get around the fact that heightForRowAtIndexPath: is called before cellForRowAtIndexPath:, which causes headaches-galore for people who want to let their cells determine their height once they are created.
  • cellForRowBlock is a block that is called when the parent OrganicViewController's cellForRowAtIndexPath: is called. You will be provided a reference to the table view, and the row index, and should implement this the same way you would normally implement cell reuse.
  • actionBlock is a block that is called when the cell is selected. You are provided the row index for this, and can define the action you want to occur when a cell at that index is tapped.

Putting It All Together

First, subclass OrganicViewController. You can choose when/where to define your sections. viewDidLoad or viewWillAppear: are two decent candidates. Create your cells, insert them into sections, insert the sections in an array in the order you want, and set them as the sections property. There is no need to reloadData after doing this, setting the sections will take care of that automatically.

Here is a simple example of a table view with three section, two that are pre-built, and one that supports reuse, and could easily be scaled to whatever degree you desire. All of the heights, actions, titles, etc., are kept logically in the same place, where they belong.

- (void)viewDidLoad {
	[super viewDidLoad];
	
	__weak typeof(self) weakSelf = self;
	OrganicCell *helloWorldCell = [OrganicCell cellWithStyle:UITableViewCellStyleDefault height:40 actionBlock:^{
		[weakSelf doA];
	}];
	helloWorldCell.textLabel.text = @"Say Hello";
    
	OrganicCell *goodbyeWorldCell = [OrganicCell cellWithStyle:UITableViewCellStyleDefault height:55 actionBlock:^{
		[weakSelf doB];
	}];
	goodbyeWorldCell.textLabel.text = @"Say Goodbye";
    
	OrganicSection *firstStaticSection = [OrganicSection sectionWithHeaderTitle:@"Welcome" cells:@[helloWorldCell, goodbyeWorldCell]];
    
    OrganicCell *randomCell = [OrganicCell cellWithStyle:UITableViewCellStyleSubtitle height:44 actionBlock:^{
		[weakSelf doC];
	}];
	randomCell.textLabel.text = @"Knock knock...";
	randomCell.detailTextLabel.text = @"Who's there?";
    
	OrganicSection *secondStaticSection = [OrganicSection sectionWithCells:@[randomCell]];
    
	NSArray *demoDataSource = @[@"One", @"Two", @"Three"];
	OrganicSection *sectionWithReuse = [OrganicSection sectionSupportingReuseWithTitle:@"Section with Reuse" cellCount:demoDataSource.count cellHeight:55 cellForRowBlock:^UITableViewCell *(UITableView *tableView, NSInteger row) {
		static NSString *cellReuseID = @"CellReuseID";
        
		UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellReuseID];
        
       if (!cell) {
			cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellReuseID];
		}
        
		cell.textLabel.text = demoDataSource[row];
        
       return cell;
        
	} actionBlock:^(NSInteger row) {
		[weakSelf doDForRow:row];
	}];
    
	self.sections = @[firstStaticSection, secondStaticSection, sectionWithReuse];
}

Step 4. Compare

We just created an extremely simple table view controller with only a few lines of code and no logic needed. Compare this with a similar implementation using a generic UITableViewController:

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
	return 3;
}

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
	if (section == 0) {
		return @"Welcome";
	}
	
	else if (section == 2) {
		return @"Section with Reuse";
	}
	
	else {
		return nil;
	}
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    if (section == 0) {
        return 2;
    }
    
    else if (section == 1) {
        return 1;
    }
    
    else {
        return demoDataSource.count;
    }
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
	if (indexPath.section == 0) {
		if (indexPath.row == 0) {
			return 40;
		}
        
       else {
			return 55;
		}
	}
    
	else if (indexPath.section == 1) {
		return 44;
	}
    
	else {
		return 55;
	}
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
	if (indexPath.section == 0) {
		if (indexPath.row == 0) {
			UITableViewCell *helloWorldCell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
			helloWorldCell.textLabel.text = @"Say Hello";
			return helloWorldCell;
		}
        
		else {
			UITableViewCell *goodbyeWorldCell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
			goodbyeWorldCell.textLabel.text = @"Say Goodbye";
			return goodbyeWorldCell;
		}
	}
	else if (indexPath.section == 1) {
		UITableViewCell *boringCell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:nil];
		boringCell.textLabel.text = @"Knock knock..";
		boringCell.detailTextLabel.text = @"Who's there?";		return boringCell;
	}
    
	else {
		static NSString *cellReuseID = @"CellReuseID";
        
		UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellReuseID];
        
		if (!cell) {
			cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellReuseID];
		}
        
		cell.textLabel.text = demoDataSource[indexPath.row];
        
		return cell;
	}
}
    
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
	[tableView deselectRowAtIndexPath:indexPath animated:YES];
    
	if (indexPath.section == 0) {
		if (indexPath.row == 0) {
			[self doA];
		}
        
		else {
			[self doB];
		}
	}
    
	else if (indexPath.section == 1) {
		[self doC];
	}
    
	else {
		[self doDForRow:indexPath.row];
	}
}

Now consider what you'd have to do if you wanted to switch around the order of the cells, or add another cell into the section in between, or add another section and move one cell to the other.

Using Organic reduced the code in the above example, which was one of the simplest table view controllers possible, by roughly 70%. It will make the initial implementation drastically quicker, and will make maintanence a breeze, or your money back.

Community

Questions, comments, issues, and pull requests welcomed!!

License

This project is made available under the MIT license. See LICENSE.txt for details.

More Repositories

1

Onboard

An iOS framework to easily create a beautiful and engaging onboarding experience with only a few lines of code.
Objective-C
6,458
star
2

Neon

A powerful Swift programmatic UI layout framework.
Swift
4,580
star
3

Facade

Programmatic view layout for the rest of us.
Objective-C
688
star
4

MAThemeKit

Create an iOS app color theme using a single line of code.
Objective-C
552
star
5

MAFormViewController

Quick and easy iOS forms.
Objective-C
289
star
6

xkcd-Open-Source

A free and open source xkcd comic reader for iOS.
Objective-C
256
star
7

Follower

Track trip distance, speed, altitude, and duration like a boss.
Objective-C
197
star
8

Wethr

Wethr provides developers the ability to add location-based current weather conditions to their views as simply as adding any UIView.
Objective-C
170
star
9

Evolve

An Evolution Simulation Engine written in Objective-C.
Objective-C
73
star
10

MALoggingViewController

MALoggingViewController is a real-time pseudo-console you can embed in your application, perfect for testing and debugging in the real world. Whether you are determining the reliability of network traffic while driving through areas with poor service, testing push notifications on ad-hoc builds while not connected to Xcode, or working out those pesky Core Location bugs, there's no need to carry around half of your development environment with you. No more driving around town with the Xcode console open, or having to handle logging to files and emailing them later to figure out what the heck happened - you can see all the data on your device, anywhere, in real time.
Objective-C
53
star
11

MAPageViewController

MAPageViewController is a simple wrapper around the most common boiler-plate UIPageViewController setup.
Swift
51
star
12

MAActionCell

Objective-C
34
star
13

MATextFieldCell

MATextFieldCell is a drop-in subclass of UITableViewCell, written in Swift, used for drastically streamlining UITableView-based form creation.
Swift
22
star
14

Essentials

A curated list of things I wish I knew about Objective-C, Xcode, and Cocoa Touch when I started programming iOS apps.
Objective-C
15
star
15

MADial

MADial and MATimerDial are UIViews that can quickly and easily be created to add slick circular sliders or minute/second timers to your views.
Objective-C
10
star
16

MontyHall

A swift implementation of the famous Monty Hall problem, attempting to explain the counter-intuitive nature of the puzzle.
Swift
4
star
17

Swift-State-Machine

A swift implementation of the state machine design pattern - with a demo implementation.
Swift
1
star