In a regular UITableView in edit mode you drag the cell into the position you want the cell to be in and the other cells can pop into place. I want to create a UITableView edit mode where you select a cell and it is held in the center as you scroll the tableview to move the selected item, holding the selected item in the center with the table cells moving around the center selected cell.
A valid 'bounty worthy' answer will require a minimally working example that holds a selected cell in the center of the table and can be moved by swiping the table up and down. Including the edge cases of first and last position in the table. Alternatively, you can outline the key points of what you think would work and if they lead me in the right direction, then you'll get the bounty.
Update 1
I have established a project called PickerTableView on GitHub. Working on the develop branch. Selection is working and I'm working on subclassing TableView to handle the movement of the cell on scroll. Finding a working solution before me will still earn the bounty.
Further Clarification
Based upon a comment, I'll provide some ASCII art.
The TableView
|==========|
| Next|
|==========|
| |
|----------|
| |
|----------|
| |
|----------|
| |
|----------|
| |
|==========|
Select a cell, then tap Next
|==========|
| Next|
|==========|
| |
|----------|
| |
|----------|
| X|
|----------|
| |
|----------|
| |
|==========|
Tableview Editing Mode
|=============|
| Done|
|=============|
| |
|-------------|
| |
|-------------|
| This cell is|
| Highlighted |
| and locked |
| in place |
|-------------|
| |
|-------------|
| |
|=============|
As you scroll the tableview the cells that were not selected flow around the selected cell while the selected cell stays in the middle.
Would it be acceptable if the selected cell was not really a cell but a separate UIView (that could be made to look like a cell)? If so, you could do something like this:
When a cell is tapped remove it from the table and show the cell-like UIView over the table.
Reposition cells as they scroll by responding to -scrollViewDidScroll:.
When the Done button is tapped, reinsert the item into the table and hide the cell-like UIView
To see it in action, I've created a UIViewController subclass for you to test:
PickerTableViewController.h
#import <UIKit/UIKit.h>
#interface PickerTableViewController : UIViewController
#end
#interface SelectedItemView : UIView
#property (nonatomic, readonly) UILabel *label;
#end
PickerTableViewController.m
#import "PickerTableViewController.h"
#interface PickerTableViewController () <UITableViewDataSource, UITableViewDelegate>
#property (strong, nonatomic) UIButton *button;
#property (strong, nonatomic) UITableView *tableView;
#property (strong, nonatomic) UIView *tableViewContainer;
#property (strong, nonatomic) SelectedItemView *selectedItemView;
#property (strong, nonatomic) NSMutableArray *items;
#property (nonatomic, getter = isPicking) BOOL picking;
#property (strong, nonatomic) NSNumber *selectedItem;
#end
#implementation PickerTableViewController
- (id)init
{
self = [super init];
if (self) {
// generate random cell contents
NSInteger countItems = 20;
self.items = [NSMutableArray arrayWithCapacity:countItems];
for (int i = 0; i < countItems; i++) {
[self.items addObject:#(arc4random() % 100)];
}
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
self.button.backgroundColor = [UIColor yellowColor];
[self.button setTitle:#"Done" forState:UIControlStateNormal];
[self.button addTarget:self action:#selector(stopPicking) forControlEvents:UIControlEventTouchUpInside];
self.button.enabled = self.isPicking;
[self.view addSubview:self.button];
// use a container for easy alignment of selected item view to center of table
_tableViewContainer = [[UIView alloc] init];
[self.view addSubview:_tableViewContainer];
_tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
self.tableView.delegate = self;
self.tableView.dataSource = self;
[self.tableViewContainer addSubview:self.tableView];
}
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
CGFloat const buttonHeight = 100.0f;
CGRect const buttonFrame = CGRectMake(0.0f, 0.0f, self.view.bounds.size.width, buttonHeight);
self.button.frame = buttonFrame;
CGRect tableFrame = self.view.bounds;
tableFrame.origin.y += buttonHeight;
tableFrame.size.height -= buttonHeight;
self.tableViewContainer.frame = tableFrame;
self.tableView.frame = self.tableViewContainer.bounds;
// allow table to scroll to first and last row
CGFloat selectedItemViewY = self.selectedItemView.center.y;
self.tableView.contentInset = UIEdgeInsetsMake(selectedItemViewY, 0.0f, selectedItemViewY, 0.0f);
}
#pragma mark - Custom
- (SelectedItemView *)selectedItemView
{
if (!_selectedItemView) {
CGRect frame = CGRectMake(0.0f, 0.0f, self.tableView.bounds.size.width, self.tableView.rowHeight);
_selectedItemView = [[SelectedItemView alloc] initWithFrame:frame];
_selectedItemView.center = CGPointMake(self.tableView.bounds.size.width * 0.5f, self.tableView.bounds.size.height * 0.5f);
_selectedItemView.hidden = YES;
[self.tableViewContainer addSubview:_selectedItemView];
}
return _selectedItemView;
}
- (void)startPickingForItemAtIndex:(NSInteger)index
{
if (self.isPicking) {
return;
}
self.picking = YES;
// update tableview
self.selectedItem = [self.items objectAtIndex:index];
[self.items removeObjectAtIndex:index];
[self.tableView reloadData];
[self repositionCells];
// update views
self.selectedItemView.label.text = [NSString stringWithFormat:#"%#", self.selectedItem];
self.selectedItemView.hidden = NO;
self.button.enabled = YES;
}
- (void)stopPicking
{
if (!self.isPicking) {
return;
}
self.picking = NO;
// calculate new index for item
NSSortDescriptor *sd = [NSSortDescriptor sortDescriptorWithKey:#"row" ascending:YES];
NSArray *sds = [NSArray arrayWithObject:sd];
NSArray *indexPaths = [[self.tableView indexPathsForVisibleRows] sortedArrayUsingDescriptors:sds];
NSInteger selectedItemIndex = NSNotFound;
for (NSIndexPath *indexPath in indexPaths) {
if ([self isCellAtIndexPathBelowSelectedItemView:indexPath]) {
selectedItemIndex = indexPath.row;
break;
}
}
if (selectedItemIndex == NSNotFound) {
selectedItemIndex = self.items.count;
}
// update tableview
[self.items insertObject:self.selectedItem atIndex:selectedItemIndex];
self.selectedItem = nil;
[self.tableView reloadData];
// update views
self.selectedItemView.hidden = YES;
self.button.enabled = NO;
}
- (BOOL)isCellAtIndexPathBelowSelectedItemView:(NSIndexPath *)indexPath
{
CGFloat yInTable = indexPath.row * self.tableView.rowHeight;
CGFloat yInContainer = yInTable - self.tableView.contentOffset.y;
return yInContainer > self.selectedItemView.frame.origin.y;
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.items.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
cell.textLabel.text = [NSString stringWithFormat:#"%#", [self.items objectAtIndex:indexPath.row]];
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (self.isPicking) {
[self stopPicking];
}
// scroll position seems to be confused... UITableViewScrollPositionMiddle doesn't work?
[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionTop animated:NO];
[self startPickingForItemAtIndex:indexPath.row];
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if (self.isPicking) {
[self repositionCells];
}
}
- (void)repositionCells
{
CGFloat tableOffset = self.tableView.contentOffset.y;
NSArray *indexPaths = [self.tableView indexPathsForVisibleRows];
CGFloat selectedItemViewY = self.selectedItemView.frame.origin.y;
CGFloat const bufferHeight = self.tableView.rowHeight; // adjust to liking
for (NSIndexPath *indexPath in indexPaths) {
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
CGRect cellFrame = cell.frame;
CGFloat cellYInTable = indexPath.row * self.tableView.rowHeight;
cellFrame.origin.y = cellYInTable;
CGFloat cellYInContainer = cellYInTable - tableOffset;
if (cellYInContainer <= selectedItemViewY) {
cellFrame.origin.y -= bufferHeight;
} else {
cellFrame.origin.y += bufferHeight;
}
cell.frame = cellFrame;
}
}
#end
#interface SelectedItemView ()
#property (strong, nonatomic) UILabel *label;
#end
#implementation SelectedItemView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.userInteractionEnabled = NO;
self.backgroundColor = [UIColor blueColor];
_label = [[UILabel alloc] init];
_label.backgroundColor = [UIColor clearColor];
[self addSubview:_label];
}
return self;
}
- (void)layoutSubviews
{
[super layoutSubviews];
self.label.frame = self.bounds;
}
#end
when u select a cell in edit mode. Do these:
Scroll the cell to center position.
use renderInContext to cut out an image of the moved cell.
put the image in an imageView, and add it to the tableView's superview. In accordance to center position, relative to tableView's superView.
after that u are free to scroll around the table. Just delete the selected cell beforehand, to avoid duplicacy in UI.
On pressing next. Insert a row in the desired position, with the desired data and remove the added ImageView.
Animations involved are entirely left to ur discretion :D
Cheers have fun.
Since my answer is the best at this time I will provide my own answer.
I have established a project called PickerTableView on GitHub. Working on the develop branch. This project uses Cocoapods for the dependencies. Selection is working and I've subclassed TableView to handle the movement of the cell on scroll.
Here's an example of something that does what you want (that is, after some adjustments). I think it's pretty minimal code-wise:
https://github.com/nielsbot/funny-tables
(For your use case, you would reveal centerTableView and bottomTableView when you enter edit mode.)
Related
For the sake of context I'm developing a calendar app with 3 different views. Day, Month and Year View.
In order to display the days I've decided to use a UICollectionView with a custom implementation of UICollectionViewCell (so that I'm able to customize the aspect of each cell).
On the init of my NSObject CtrlCalendar class I register 3 cell Classes like this:
//Used for the yearView
[self.grid registerClass:[CollectionViewCell class] forCellWithReuseIdentifier:#"monthCell"];
//Used for the monthView
[self.grid registerClass:[CollectionViewCell class] forC ellWithReuseIdentifier:#"dayCell"];
//Used for the dayView
[self.grid registerClass:[CollectionViewCell class] forCellWithReuseIdentifier:#"hourCell"];
Than, on collectionView:collectionView cellForItemAtIndexPath:indexPath I'm doing as follows:
CollectionViewCell *cell = nil;
if ([self.currentDisplayType isEqualToString:#"monthView"]) {
cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"dayCell" forIndexPath:indexPath];
cell.backgroundColor = [UIColor grayColor];
self.month = [self.displayingViews get:indexPath.section];
if (indexPath.row + 1 >= self.month.firstWeekDay && indexPath.row + 1 < self.month.numberOfDays + (self.month.firstWeekDay)) {
NSDateComponents *tempDay = [self.calendar components:NSCalendarUnitDay fromDate:self.month.date];
cell.date = [self.dateHelper addToDate:self.month.date days:(int)(tempDay.day + indexPath.row - self.month.firstWeekDay)];
cell.cellTitle.text = [NSString stringWithFormat:#"%i", (int)(tempDay.day + indexPath.row + 1 - self.month.firstWeekDay)];
cell.cellTitle.textColor = [UIColor blackColor];
if ([[self.dateHelper addToDate:self.month.date days:(indexPath.row + 1 - self.month.firstWeekDay)] isEqualToDate:self.today]) {
cell.cellTitle.textColor = [UIColor redColor];
}
cell.userInteractionEnabled = YES;
cell.separator.hidden = NO;
cell.contentView.hidden = NO;
} else {
cell.userInteractionEnabled = NO;
}
And finally here is my implementation of CollectionViewCell:
#interface CollectionViewCell ()
#property (nonatomic, retain) UILabel *cellTitle;
#property (nonatomic, retain) NSString *titleText;
#property (nonatomic, retain) UILabel *separator;
#property (nonatomic, retain) NSDate *date;
#end
#implementation CollectionViewCell
#synthesize cellTitle;
#synthesize titleText;
#synthesize separator;
#synthesize date;
- (void)dealloc {
[cellTitle release];
[titleText release];
[separator release];
[date release];
[super dealloc];
}
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
self.cellTitle = [[[UILabel alloc] initWithFrame:self.bounds] autorelease];
self.cellTitle.textColor = [UIColor blackColor];
self.cellTitle.textAlignment = NSTextAlignmentCenter;
self.separator = [[[UILabel alloc] initWithFrame:CGRectMake(0, (self.frame.size.height - 0.25), self.frame.size.width, 0.5)] autorelease];
self.separator.backgroundColor = [UIColor lightGrayColor];
[self.contentView addSubview:self.separator];
[self.contentView addSubview:self.cellTitle];
}
return self;
}
- (void)prepareForReuse {
[super prepareForReuse];
self.cellTitle.text = nil;
self.separator.hidden = YES;
[self.cellTitle setTextColor:[UIColor blackColor]];
//the presence of this line is explained after the edit bellow
[self.subviews makeObjectsPerformSelector:#selector(removeFromSuperview)];
}
#end
The first time I run the app, the Month View is configured as expected, but as soon as it performs the dequeue, the cells turn blank.
After some debugging I found that the CollectionViewCell's cellTitle property is correctly set. The problem resides in the fact that after the dequeue the labels are hidden for some reason.
I know this is long question, but if you can help in anyway or know someone who can help please let me know!
Thanks a lot!
For clarification of what the problem is I'm adding some screens
Before scrolling:
After scrolling:
UPDATE 1
As #Alessandro Chiarotto answered, the
[self.subviews makeObjectsPerformSelector:#selector(removeFromSuperview)]
does make the cells empty. The caveat here is that I need that selector to remove from the cells some UILabels I'm adding to the yearView.
My yearView UICollection has 12 cells (one for each month). Each cell than has the number of UILabels as the days in each month. I'm adding this UILabels from the collectionView:collectionView cellForItemAtIndexPath:indexPath like this:
for (int d = 1; d < self.month.numberOfDays; d++) {
UILabel *day = [[[UILabel alloc] initWithFrame:dayRect] autorelease];
day.text = currentDay;
[cell addSubview:day];
}
If i don't perform the selector in prepareForReuse, after scrolling the view I have all the day labels in duplicate in each month cell.
If you scroll the collection then prepareForReuse will be called. In prepareForReuse you have the following call:
[self.subviews makeObjectsPerformSelector:#selector(removeFromSuperview)];
this will remove all the subviews of the cell (UILabel and so on).
At this point your cell will "white"...
I've found the problem. As #Alessandro said in his answer the [self.subviews makeObjectsPerformSelector:#selector(removeFromSuperview)]; was removing everything from the cell, therefore making it blank.
That would be it if it weren't for another aspect of the problem.
As I said in my question Update, I need that selector in order to remove some UILabels that are being added in the yearView. Those labels are the days in each month cell.
Solution:
I added a UIView property to the CollectionViewCell that has a frame equal to the cell's bounds. I than add the UILabels to this view and on the prepareForReuse I perform the removeFromSuperView on the self.labelsView.subviews. It works like a charm.
The correct implementation of prepareForReuse would be:
- (void)prepareForReuse {
[super prepareForReuse];
self.cellTitle.text = nil;
self.separator.hidden = YES;
[self.cellTitle setTextColor:[UIColor blackColor]];
[self.labelsView.subviews makeObjectsPerformSelector:#selector(removeFromSuperview)];
}
I am making my own drop down menu because I am not able to find an open source one that does exactly what I need.
I have implemented the dropdown as a UIView and am adding it to the superview of the button that is tapped in order to show it.
Code:
ViewController.m
#import "ViewController.h"
#import "MenuView.h"
#interface ViewController () <MenuViewDelegate>
#property (weak, nonatomic) IBOutlet UIView *fakeHeader;
#property (nonatomic, strong) MenuView *menuView;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)btnTapped:(id)sender {
NSArray *array = #[#"Item 1", #"Item 2", #"Item 3", #"Item 4"];
NSArray *imgArray = nil;
if (self.menuView == nil) {
self.menuView = [[MenuView alloc] showDropDownWith:sender txtArr:array imgArr:imgArray direction:#"down" delegate:self];
self.menuView.delegate = self;
} else {
[self.menuView hideDropDown:sender];
self.menuView = nil;
}
}
- (void) menuDelegateMethod:(MenuView *)sender {
self.menuView = nil;
}
#end
MenuView.h
#import <UIKit/UIKit.h>
#class MenuView;
#protocol MenuViewDelegate
- (void)menuDelegateMethod:(MenuView *)sender;
#end
#interface MenuView : UIView
#property (nonatomic, retain) id <MenuViewDelegate> delegate;
- (id)showDropDownWith:(UIButton *)button txtArr:(NSArray *)txtArr imgArr:(NSArray *)imgArr direction:(NSString *)direction delegate:(id)delegate;
- (void)hideDropDown:(UIButton *)button;
#end
MenuView.m
#import "MenuView.h"
#import "QuartzCore/QuartzCore.h"
#interface MenuView () <UITableViewDataSource, UITableViewDelegate>
#property (nonatomic, strong) UITableView *table;
#property (nonatomic, strong) UIButton *btnSender;
#property (nonatomic, retain) NSString *animationDirection;
#property (nonatomic, retain) NSArray *list;
#property (nonatomic, retain) NSArray *imageList;
#end
#implementation MenuView
- (id)showDropDownWith:(UIButton *)button txtArr:(NSArray *)txtArr imgArr:(NSArray *)imgArr direction:(NSString *)direction delegate:(id)delegate {
CGFloat width = [UIScreen mainScreen].bounds.size.width;
CGFloat origin = [UIScreen mainScreen].bounds.origin.x;
CGFloat realHeight = 40 * txtArr.count;
self.btnSender = button;
self.animationDirection = direction;
self.table = (UITableView *)[super init];
if (self) {
// Initialization code
CGRect btn = button.frame;
self.list = [NSArray arrayWithArray:txtArr];
self.imageList = [NSArray arrayWithArray:imgArr];
if ([direction isEqualToString:#"up"]) {
self.frame = CGRectMake(origin, (btn.origin.y - btn.size.height) , width, 0);
self.layer.shadowOffset = CGSizeMake(0, 1);
} else if ([direction isEqualToString:#"down"]) {
self.frame = CGRectMake(origin, (btn.origin.y + btn.size.height + 10), width, 0);
self.layer.shadowOffset = CGSizeMake(0, 1);
}
self.layer.masksToBounds = YES;
self.layer.shadowOpacity = 0.2;
self.table = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, width, 0)];
self.table.delegate = delegate;
self.table.dataSource = self;
self.table.backgroundColor = [UIColor colorWithRed:0.239 green:0.239 blue:0.239 alpha:1];
self.table.separatorStyle = UITableViewCellSeparatorStyleSingleLine;
self.table.separatorColor = [UIColor lightGrayColor];
self.table.backgroundColor = [UIColor whiteColor];
self.table.userInteractionEnabled = YES;
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.5];
if ([direction isEqualToString:#"up"]) {
self.frame = CGRectMake(origin, (btn.origin.y - realHeight), width, realHeight);
} else if([direction isEqualToString:#"down"]) {
self.frame = CGRectMake(origin, (btn.origin.y + btn.size.height + 10), width, realHeight);
}
self.table.frame = CGRectMake(0, 0, width, realHeight);
[UIView commitAnimations];
[button.superview addSubview:self];
self.table.tableFooterView = [[UIView alloc] initWithFrame:CGRectZero];
[self addSubview:self.table];
}
return self;
}
- (void)hideDropDown:(UIButton *)button {
CGRect btn = button.frame;
CGFloat width = [UIScreen mainScreen].bounds.size.width;
CGFloat origin = [UIScreen mainScreen].bounds.origin.x;
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.5];
if ([self.animationDirection isEqualToString:#"up"]) {
self.frame = CGRectMake(origin, btn.origin.y, width, 0);
} else if ([self.animationDirection isEqualToString:#"down"]) {
self.frame = CGRectMake(origin, (btn.origin.y + btn.size.height + 10), width, 0);
}
self.table.frame = CGRectMake(0, 0, width, 0);
[UIView commitAnimations];
}
#pragma mark - Table View DataSource
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [self.list count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
cell.textLabel.font = [UIFont systemFontOfSize:15];
cell.textLabel.textAlignment = NSTextAlignmentLeft;
}
cell.textLabel.text = [self.list objectAtIndex:indexPath.row];
cell.backgroundColor = [UIColor colorWithRed:48.0f/255.0f green:48.0f/255.0f blue:48.0f/255.0f alpha:1.0f];
cell.textLabel.textColor = [UIColor lightTextColor];
return cell;
}
#pragma mark - Table View Delegate
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return 40;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[self hideDropDown:self.btnSender];
[self myDelegate];
}
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
// Remove seperator inset
if ([cell respondsToSelector:#selector(setSeparatorInset:)]) {
[cell setSeparatorInset:UIEdgeInsetsZero];
}
// Prevent the cell from inheriting the Table View's margin settings
if ([cell respondsToSelector:#selector(setPreservesSuperviewLayoutMargins:)]) {
[cell setPreservesSuperviewLayoutMargins:NO];
}
// Explictly set your cell's layout margins
if ([cell respondsToSelector:#selector(setLayoutMargins:)]) {
[cell setLayoutMargins:UIEdgeInsetsZero];
}
}
#pragma mark - View Delegate
- (void)myDelegate {
[self.delegate menuDelegateMethod:self];
}
#end
Shows up perfectly but the didSelect method is never called.
I don't have any views over the top of it that would be stealing the touch events.
It seems that UIViews might not be able to be UITableviewDelegates. If that's true I don't know why, when I make the calling view controller the delegate, it still fails to didSelect.
NOTE: I am aware of the animation faux pas by not using newer methods. This is based on old example code. I will update the animation after I get this issue worked out.
Questions:
Is it true that UIViews can not be UITableView Delegates?
If so, how does one make a calling view controller the delegate for the table view that resides in the UIView? Other than the process of setting it up as a UITableViewDelegate and assigning the calling view controller as the delegate at the time of the creation of the table.
Did I miss something in the way I set this up that steals the cell taps so that didSelect does not get called, either in the view or the viewController?
Thanks for the help.
Agree with #Piotr that the menu must be the table's delegate, so replace self.table.delegate = delegate; with self.table.delegate = self; in MenuView.m.
But additionally, MenuView.m never invokes its delegate, which it should upon selection in the tableview....
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[self hideDropDown:self.btnSender];
// remove this, since it does nothing
//[self myDelegate];
// replace it with
[self.myDelegate menuDelegateMethod:self];
}
That last line says, tell the delegate of the menu that something happened.
Another problem is that the menu doesn't really tell the delegate what happened. Certainly the delegate will be interested in which item is selected. Consider changing the protocol to something like:
#protocol MenuViewDelegate
- (void)menuView:(MenuView *)sender didSelectOptionAtIndex:(NSInteger)index;
#end
// calling it
[self.myDelegate menuView:self didSelectOptionAtIndex:indexPath.row];
Another alternative is to hand back the selected string to the delegate. This can be found in the tableview's datasource at the indexPath.row.
Finally, its good practice to not-retain your delegate since the customer of the Menu might retain it, resulting in a retain cycle. Instead, declare the delegate:
// notice "weak"
#property (nonatomic, weak) id <MenuViewDelegate> delegate;
As I can see, you are passing ViewController (it is MenuViewDelegate) to showDropDownWith method, and then use it as table delegate. This is not correct.
You should pass self there (same as with data source), because you want MenuView to be delegate of table, not ViewController, right?
self.table.delegate = self;
I have a lot of section and items. When I touch button in cell it create cell with 4 items in 1 cell (like expanded table) touch again cell disappear.
I found WaterfallCollectionView and there I can change height of items.
What is the best way to achieve this???
Structure like:
----- -----
| +| | +| and other rows
| | | |
----- -----
When I touch my button (+) it should be like:
----- ----- -----
| -| |__|__| | +|
| | | | | | |
----- ----- -----
A new cell create with 4 UIImageView's inside 1 cell ( if 4 elements in 1 cell, if more create more cell). If 1 element create this cell but image will be in the top left corner.
Where in cells should be my info (like expanded table)
Is it better to create different type of cells?
I tried something out where a UICollectionViewCell is made to expand by changing its own size. The new 'cells' that pop out when you tap on a cell are not really new cells, but subviews of the original cell. Essentially it works something like this:
Use UICollectionView with a UICollectionViewFlowLayout.
The UICollectionView's delegate conforms to UICollectionViewDelegateFlowLayout and implements -collectionView:layout:sizeForItemAtIndexPath: to recognize unique cell sizes.
The UICollectionView's delegate implements -collectionView:didSelectCellAtIndexPath: where it adjusts the cell's size to reveal or hide the subviews that look like new cells.
A custom object representing the cell's contents and a custom UICollectionViewCell subclass are used to make it easier to perform and keep track of expansion.
I created an example project here which has a cells that expand to show a number's divisors. It looks something like this:
The project is pretty rough and uncommented, and the transition happens without animation, but if you find this approach interesting and cannot follow the code I can clean it up a bit.
And here's the code dump...
CollectionViewController.h
#import <UIKit/UIKit.h>
#interface CollectionViewController : UIViewController
#end
CollectionViewController.m
#import "CollectionViewController.h"
#import "CellDataItem.h"
#import "CollectionViewCell.h"
#interface CollectionViewController () <UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>
#property (strong, nonatomic) UICollectionView *collectionView;
#property (strong, nonatomic) NSMutableArray *cellData;
#end
NSString *const kCollectionViewCellIdentifier = #"Cell";
#implementation CollectionViewController
- (NSMutableArray *)cellData
{
if (!_cellData) {
NSInteger countValues = 20;
_cellData = [[NSMutableArray alloc] initWithCapacity:countValues];
for (NSInteger i = 0; i < countValues; i++) {
CellDataItem *item = [[CellDataItem alloc] init];
item.number = #(arc4random() % 100);
[_cellData addObject:item];
}
}
return _cellData;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
self.view.backgroundColor = [UIColor grayColor];
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
layout.itemSize = [CollectionViewCell sizeWithDataItem:nil];
layout.minimumInteritemSpacing = [CollectionViewCell margin];
layout.minimumLineSpacing = layout.minimumInteritemSpacing;
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
_collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero
collectionViewLayout:layout];
_collectionView.backgroundColor = [UIColor darkGrayColor];
_collectionView.dataSource = self;
_collectionView.delegate = self;
[self.view addSubview:_collectionView];
[_collectionView registerClass:[CollectionViewCell class] forCellWithReuseIdentifier:kCollectionViewCellIdentifier];
}
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout *)self.collectionView.collectionViewLayout;
self.collectionView.frame = CGRectMake(0.0f, 0.0f, self.view.bounds.size.width, layout.itemSize.height);
}
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
return 1;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return self.cellData.count;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
CollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kCollectionViewCellIdentifier
forIndexPath:indexPath];
cell.dataItem = [self.cellData objectAtIndex:indexPath.row];
return cell;
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
return [CollectionViewCell sizeWithDataItem:[self.cellData objectAtIndex:indexPath.row]];
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
CollectionViewCell *cell = (CollectionViewCell *)[collectionView cellForItemAtIndexPath:indexPath];
[cell toggleExpansion];
[collectionView reloadData];
}
#end
#import <Foundation/Foundation.h>
#interface CellDataItem : NSObject
#property (strong, nonatomic) NSNumber *number;
#property (nonatomic, readonly) NSArray *divisors;
#property (nonatomic, getter = isExpanded) BOOL expanded;
#end
CellDataItem.m
#import "CellDataItem.h"
#interface CellDataItem ()
#property (strong, nonatomic) NSArray *divisors;
#end
#implementation CellDataItem
+ (NSArray *)divisorsForNumber:(NSNumber *)number
{
NSMutableArray *divisors = [NSMutableArray arrayWithObjects:#(1), number, nil];
float root = pow(number.doubleValue, 0.5);
if (root == roundf(root)) {
[divisors addObject:[NSNumber numberWithInteger:(NSInteger)root]];
}
NSInteger maxDivisor = (NSInteger)root;
for (NSInteger divisor = 2; divisor < maxDivisor; divisor++) {
float quotient = number.floatValue / (float)divisor;
if (quotient == roundf(quotient)) {
[divisors addObject:[NSNumber numberWithInteger:divisor]];
[divisors addObject:[NSNumber numberWithInteger:(NSInteger)quotient]];
}
}
return [divisors sortedArrayUsingSelector:#selector(compare:)];
}
- (void)setNumber:(NSNumber *)number
{
if (_number == number) {
return;
}
_number = number;
self.divisors = [self.class divisorsForNumber:_number];
}
#end
CollectionViewCell.h
#import <UIKit/UIKit.h>
#class CellDataItem;
#interface CollectionViewCell : UICollectionViewCell
+ (CGFloat)margin;
+ (CGSize)sizeWithDataItem:(CellDataItem *)dataItem;
#property (strong, nonatomic) CellDataItem *dataItem;
- (void)toggleExpansion;
#end
#interface ChildView : UIView
- (UILabel *)labelAtIndex:(NSInteger)index;
- (void)clearLabels;
#end
CollectionViewCell.m
#import "CollectionViewCell.h"
#import <QuartzCore/QuartzCore.h>
#import "CellDataItem.h"
#interface CollectionViewCell ()
#property (strong, nonatomic) NSMutableArray *childViews;
#property (strong, nonatomic) UILabel *numberLabel;
#end
NSInteger const kCollectionViewCellSplitCount = 4;
CGFloat const kCollectionViewCellMargin = 20.0f;
CGSize const kCollectionViewCellDefaultSize = {200.0f, 200.0f};
#implementation CollectionViewCell
+ (CGFloat)margin
{
return kCollectionViewCellMargin;
}
+ (CGSize)sizeWithDataItem:(CellDataItem *)dataItem
{
if (dataItem && dataItem.isExpanded) {
CGSize size = kCollectionViewCellDefaultSize;
NSInteger childViewsRequired = [self childViewsRequiredForDataItem:dataItem];
size.width += childViewsRequired * ([self margin] + size.width);
return size;
} else {
return kCollectionViewCellDefaultSize;
}
}
+ (NSInteger)childViewsRequiredForDataItem:(CellDataItem *)dataItem
{
return (NSInteger)ceilf((float)dataItem.divisors.count / (float)kCollectionViewCellSplitCount);
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
_numberLabel = [[UILabel alloc] init];
_numberLabel.textAlignment = NSTextAlignmentCenter;
_numberLabel.layer.borderColor = [UIColor blackColor].CGColor;
_numberLabel.layer.borderWidth = 1.0f;
_numberLabel.backgroundColor = [UIColor whiteColor];
[self.contentView addSubview:_numberLabel];
}
return self;
}
- (void)setDataItem:(CellDataItem *)dataItem
{
if (_dataItem == dataItem) {
return;
}
_dataItem = dataItem;
self.numberLabel.text = [NSString stringWithFormat:#"%i", dataItem.number.integerValue];
if (!dataItem.expanded) {
[self collapse];
} else if (dataItem.expanded) {
[self expand];
}
}
- (void)collapse
{
for (ChildView *view in self.childViews) {
view.hidden = YES;
}
}
- (void)expand
{
NSInteger childViewsRequired = [self.class childViewsRequiredForDataItem:self.dataItem];
while (self.childViews.count < childViewsRequired) {
ChildView *childView = [[ChildView alloc] init];
[self.childViews addObject:childView];
[self.contentView addSubview:childView];
}
NSInteger index = 0;
for (ChildView *view in self.childViews) {
view.hidden = !(index < childViewsRequired);
if (!view.hidden) {
[view clearLabels];
}
index++;
}
for (NSInteger i = 0; i < self.dataItem.divisors.count; i++) {
NSInteger labelsPerChild = 4;
NSInteger childIndex = i / labelsPerChild;
NSInteger labelIndex = i % labelsPerChild;
[[[self.childViews objectAtIndex:childIndex] labelAtIndex:labelIndex] setText:[NSString stringWithFormat:#"%i", [[self.dataItem.divisors objectAtIndex:i] integerValue]]];
}
}
- (void)layoutSubviews
{
[super layoutSubviews];
CGFloat const unitWidth = kCollectionViewCellDefaultSize.width;
CGFloat const unitHeight = kCollectionViewCellDefaultSize.height;
CGFloat const margin = [self.class margin];
self.numberLabel.frame = CGRectMake(0.0f, 0.0f, unitWidth, unitHeight);
for (NSInteger i = 0; i < self.childViews.count; i++) {
ChildView *view = [self.childViews objectAtIndex:i];
view.frame = CGRectMake((i + 1) * (margin + unitWidth), 0.0f, unitWidth, unitHeight);
}
}
- (NSMutableArray *)childViews
{
if (!_childViews) {
_childViews = [[NSMutableArray alloc] init];
}
return _childViews;
}
- (void)toggleExpansion
{
self.dataItem.expanded = !self.dataItem.isExpanded;
if (self.dataItem.isExpanded) {
[self expand];
} else {
[self collapse];
}
}
#end
#interface ChildView ()
#property (strong, nonatomic) NSMutableArray *labels;
#end
#implementation ChildView
- (id)init
{
if ((self = [super init])) {
self.backgroundColor = [UIColor lightGrayColor];
}
return self;
}
- (UILabel *)labelAtIndex:(NSInteger)index
{
if (!self.labels) {
self.labels = [NSMutableArray array];
}
while (self.labels.count <= index) {
UILabel *label = [[UILabel alloc] init];
label.textAlignment = NSTextAlignmentCenter;
label.layer.borderColor = [UIColor blackColor].CGColor;
label.layer.borderWidth = 1.0f;
[self.labels addObject:label];
[self addSubview:label];
}
return [self.labels objectAtIndex:index];
}
- (void)clearLabels
{
for (UILabel *label in self.labels) {
label.text = nil;
}
}
- (void)layoutSubviews
{
[super layoutSubviews];
CGFloat labelWidth = self.bounds.size.width * 0.5f;
CGFloat labelHeight = self.bounds.size.height * 0.5f;
for (NSInteger i = 0; i < self.labels.count; i++) {
UILabel *label = [self.labels objectAtIndex:i];
NSInteger x = i % 2;
NSInteger y = i / 2;
label.frame = CGRectMake(x * labelWidth, y * labelHeight, labelWidth, labelHeight);
}
}
#end
If you want expandable cells you have to insert the new cells inside the collection view by calling insertItemsAtIndexPaths: on your collection view.
It is not trivial as you have to shift all your index paths, update the number of items in the section to reflect the newly added cells, etc. If you are looking for something simple then the Waterfall example is good. If you want a more complex / configurable solution then you have to rock your own custom layout. That's what I did.
I have a UIScrollView within a UITableViewCell for being able to scroll through images within a cell. However, as a cell is reused the scroll position/content is reloaded and therefore the cell doesn't remember what scroll position (page) it was on when it comes into view again.
What's the best way to have a scroll view within a UITableView cell and have it maintain position as it comes back into view. The AirBnB app (https://itunes.apple.com/us/app/airbnb/id401626263?mt=8) seems to have accomplished this for example.
You need to keep track of your scroll views' content offset in a property. In the example below, I do this with a mutable dictionary. In cellForRowAtIndexPath:, I give the scroll view a tag and set the controller as the delegate. In the scroll view delegate method, scrollViewDidEndDecelerating:, the scroll view's content offset is set as the object for the key corresponding to the scroll view's tag. In cellForRowAtIndexPath:, I check for whether the indexPath.row (converted to an NSNumber) is one of the keys of the dictionary, and if so, restore the correct offset for that scroll view. The reason I add 1 to the tags is because the table view has its own scroll view which has a tag of 0, so I don't want to use 0 as a tag for one of the cell's scroll views.
So in cellForRowAtIndexPath, you need something like this:
cell.scrollView.tag = indexPath.row + 1;
cell.scrollView.delegate = self;
if ([self.paths.allKeys containsObject:#(indexPath.row + 1)]) {
cell.scrollView.contentOffset = CGPointMake([self.paths[#(indexPath.row + 1)] floatValue],0);
}else{
cell.scrollView.contentOffset = CGPointZero;
}
return cell;
And in the delegate method:
-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
if (scrollView.tag != 0)
[self.paths setObject:#(scrollView.contentOffset.x) forKey:#(scrollView.tag)];
}
paths is a property (NSMutableDictionary) that I create in viewDidLoad.
Use my tableView customs cells
homeScrollCell.h
#import <UIKit/UIKit.h>
#import "MyManager.h"
#interface homeScrollCell : UITableViewCell<UIScrollViewDelegate>
{
MyManager *manager;
UIScrollView *__scrollView;
}
-(void)setPage:(int)page;
#property int currentPage;
#end
homeScrollCell.m
#import "homeScrollCell.h"
#implementation homeScrollCell
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
manager=[MyManager sharedManager];
__scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, self.bounds.size.width,self.bounds.size.height)];
[__scrollView setAutoresizingMask:UIViewAutoresizingFlexibleHeight];
NSInteger viewcount= 3;
for(int i = 0; i< viewcount; i++)
{
CGFloat x = i * self.bounds.size.width;
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(x, 0,self.bounds.size.width,self.bounds.size.height)];
[view setAutoresizingMask:UIViewAutoresizingFlexibleHeight];
UILabel *label=[[UILabel alloc] initWithFrame:CGRectMake(50, 20, 200, 50)];
[label setBackgroundColor:[UIColor redColor]];
label.text=[NSString stringWithFormat:#"Hi, I am label %i",i];
[view addSubview:label];
view.backgroundColor = [UIColor greenColor];
[__scrollView addSubview:view];
}
[__scrollView setBackgroundColor:[UIColor redColor]];
__scrollView.contentSize = CGSizeMake(self.bounds.size.width *viewcount, 100);
__scrollView.pagingEnabled = YES;
__scrollView.bounces = NO;
__scrollView.delegate=self;
[self addSubview:__scrollView];
// Initialization code
}
return self;
}
-(void)setPage:(int)page
{
CGFloat pageWidth = __scrollView.frame.size.width;
float offset_X=pageWidth*page;
[__scrollView setContentOffset:CGPointMake(offset_X, __scrollView.contentOffset.y)];
_currentPage=page;
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
static NSInteger previousPage = 0;
CGFloat pageWidth = scrollView.frame.size.width;
float fractionalPage = scrollView.contentOffset.x / pageWidth;
NSInteger page = lround(fractionalPage);
if (previousPage != page) {
_currentPage=page;
[manager setpage:_currentPage ForKey:[NSString stringWithFormat:#"%i",self.tag]];
previousPage = page;
}
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
[super setSelected:selected animated:animated];
// Configure the view for the selected state
}
#end
And use a Singleton file for keeping record or pages inside cell.
MyManager.h
#import <Foundation/Foundation.h>
#interface MyManager : NSObject
+ (id)sharedManager;
-(void)setpage:(int)page ForKey:(NSString*)key;
-(int)getpageForKey:(NSString*)key;
#end
MyManager.m
#import "MyManager.h"
static NSMutableDictionary *dictionary;
#implementation MyManager
#pragma mark Singleton Methods
+ (id)sharedManager {
static MyManager *sharedMyManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedMyManager = [[self alloc] init];
});
return sharedMyManager;
}
-(void)setpage:(int)page ForKey:(NSString*)key
{
[dictionary setValue:[NSString stringWithFormat:#"%i",page] forKey:key];
}
-(int)getpageForKey:(NSString*)key
{
return [[dictionary valueForKey:key] intValue];
}
- (id)init {
if (self = [super init]) {
dictionary=[[NSMutableDictionary alloc] init];
}
return self;
}
- (void)dealloc {
// Should never be called, but just here for clarity really.
}
#end
And use this custom cell in cellForRow as
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Home Scroll Cell";
homeScrollCell *cell =[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell==nil) {
cell = [[homeScrollCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
cell.tag=indexPath.row;
[cell setPage:[manager getpageForKey:[NSString stringWithFormat:#"%i",indexPath.row]]];
return cell;
}
Set your page inside CustomCell and scroll and after re scrolling get back to previous position will show you page set by you before scrolling.
It will persist your page moved as it is.
Download And Run
rdelmar's answer is missing something. scrollViewDidEndDecelerating will only be called if a users scroll triggers the deceleration behaviour.
A drag only scroll won't call this method so you will get inconsistent behaviour for the user who won't distinguish between the two types of scroll. You need to catch it with scrollViewDidEndDragging:willDecelerate
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
if (!decelerate) {
self.tableCellContentOffsets[#(scrollView.tag)] = #(scrollView.contentOffset.x);
}
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
self.tableCellContentOffsets[#(scrollView.tag)] = #(scrollView.contentOffset.x);
}
Please spare sometime as this is a long explanation
I have a UIViewController which consists of a UIButton and a UITableView which loads different types of UITableViewCells with Identifier Cell1 and Cell2, on event touchUpInside of the button. I m using storyboard.
The separator for both cells are customized.
Cell1 has a separator that occupies the entire width of cell and 1 pixel height at the bottom of the cell.
Whereas Cell2 has a separator which has offset of 5 pixels from the cell, both left and right.
Each time the button outside the tableView is clicked the tableViewCells are swapped, based on the cell identifier.
Initially the tableView occupies the complete width of viewController and consists of Cell1, but the the button is tapped , tableViewCells are changed to Cell2 and the frame of the tableView is Changed, The width is reduced by 10 and x-origin is increased by 5.
But when this happens, the separator of Cell2 is 5 pixels away from the cell on right but on the left it is away by 5 pixel.
This happens for all Cell2 which is loaded with data, and the cells which has no data the frame is changed appropriately.
But the cell after that has the width of Cell1 (larger width)
-(void)setSeperatorStyleForTableView :(UITableViewCell *)cell //this is called in cellForRowAtIndex
{
//cell- type of cell(Cell1 or Cell2)
CGRect seperatorFrame;
UIImageView *seperatorImage;
seperatorFrame = [self setSeperatorFrame:cell];
if(firstCellToBeLoaded)//BOOL used to change the button text and load appropriate cells
{
seperatorImage = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"table_row
2.png"]];
}
else
{
seperatorImage = [[UIImageView alloc] initWithImage:[UIImage
imageNamed:#"table_row.png"]];
}
seperatorImage.frame = seperatorFrame;
seperatorImage.autoresizingMask = YES;
[cell.contentView addSubview:seperatorImage];
}
//set the customized separator frame
-(CGRect)setSeperatorFrame :(UITableViewCell *)cell
{
CGRect seperatorFrame;
seperatorFrame.size.height = 1.0;
seperatorFrame.origin.y = cell.frame.origin.y + (cell.frame.size.height - 1.0);
if(firstCellToBeLoaded)
{
seperatorFrame.origin.x = cell.frame.origin.x ;
seperatorFrame.size.width = cell.frame.size.width;
}
else
{
seperatorFrame.origin.x = cell.frame.origin.x + 5.0;
seperatorFrame.size.width = cell.frame.size.width -10.0;
}
return seperatorFrame;
}
You can add tableView's standard separator line, and add your custom line at the top of each cell.
In following code Change hight/width/color/image of UIView for set your separatorLine.
The easiest way to add custom separator is to add simple UIView of 1px height:
UIView* separatorLineView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 1)];/// change size as you need.
separatorLineView.backgroundColor = [UIColor grayColor];// you can also put image here
[cell.contentView addSubview:separatorLineView];
This code might solve your problem :)
Right way to do this would be to have the separator with in the cell class, subclass UITableViewCell if you haven't add a separator image variable there and on each cell creation you can just change the image and the frame rather than adding that on each redraw. If you require a code for this i can supply that as well. Currently when the cell is redrawn it already has the image u added last time and you just adding that again, either you remove that in -prepareForReuse method or just do as i explained above.
***** Custom Cell *****
//
// CustomCell.m
// Custom
//
// Created by Syed Arsalan Pervez on 2/8/13.
// Copyright (c) 2013 SAPLogix. All rights reserved.
//
#import "CustomCell.h"
#implementation CustomCell
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// Initialization code
_separatorImage = [[UIImageView alloc] initWithFrame:CGRectZero];
[[self contentView] addSubview:_separatorImage];
}
return self;
}
- (void)prepareForReuse
{
_separatorImage.image = nil;
}
- (void)dealloc
{
[_separatorImage release];
[super dealloc];
}
#end
Using the above cell in the view controller.
***** Test View Controller *****
//
// TestViewController.m
// Custom
//
// Created by Syed Arsalan Pervez on 2/8/13.
// Copyright (c) 2013 SAPLogix. All rights reserved.
//
#import "TestViewController.h"
#import "CustomCell.h"
#interface TestViewController ()
#end
#implementation TestViewController
- (void)viewDidLoad
{
[self.tableView setSeparatorStyle:UITableViewCellSeparatorStyleNone];
#warning TODO: set the image name here
_separatorImage1 = [[UIImage imageNamed:#""] retain];
_separatorImage2 = [[UIImage imageNamed:#""] retain];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 2;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *_identifier = #"CustomCell";
CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:_identifier];
if (!cell)
{
cell = [[[CustomCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:_identifier] autorelease];
}
//Set Separator Image Here
//Preload the image so it doesn't effect the scrolling performance
CGRect frame = cell.contentView.frame;
switch (indexPath.row)
{
case 0:
cell.separatorImage.image = _separatorImage1;
cell.separatorImage.frame = CGRectMake(0, CGRectGetMaxY(frame)-1, frame.size.width, 1);
break;
case 1:
cell.separatorImage.image = _separatorImage2;
cell.separatorImage.frame = CGRectMake(frame.origin.x+5, CGRectGetMaxY(frame)-1, frame.size.width-10, 1);
break;
}
return cell;
}
- (void)dealloc
{
[_separatorImage1 release];
[_separatorImage2 release];
[super dealloc];
}
#end
I do it this way... hope this might help.
//
// UITableViewCell+MyAdditions.h
//
// Created by Roberto O. Buratti on 19/02/14.
//
#import <UIKit/UIKit.h>
#interface UITableViewCell (MyAdditions)
#property (nonatomic,assign) UITableViewCellSeparatorStyle cellSeparatorStyle;
#property (nonatomic,strong) UIColor *cellSeparatorColor;
#end
//
// UITableViewCell+MyAdditions.m
//
// Created by Roberto O. Buratti on 19/02/14.
//
#import "UITableViewCell+MyAdditions.h"
NSString *const kUITablewViewCellSeparatorLayerName = #"kUITablewViewCellSeparatorLayerName";
#implementation UITableViewCell (MyAdditions)
-(CALayer *)separatorLayer
{
for (CALayer *sublayer in self.layer.sublayers)
{
if ([sublayer.name isEqualToString:kUITablewViewCellSeparatorLayerName])
return sublayer;
}
return nil;
}
-(CALayer *)newSeparatorLayer
{
CALayer *separatorLayer = [CALayer layer];
separatorLayer.name = kUITablewViewCellSeparatorLayerName;
separatorLayer.frame = CGRectMake(0, self.bounds.size.height - 1, self.bounds.size.width, 1);
separatorLayer.backgroundColor = [UIColor whiteColor].CGColor;
[self.layer addSublayer:separatorLayer];
return separatorLayer;
}
-(UITableViewCellSeparatorStyle)cellSeparatorStyle
{
CALayer *separatorLayer = [self separatorLayer];
if (separatorLayer == nil)
return UITableViewCellSeparatorStyleNone;
else
return UITableViewCellSeparatorStyleSingleLine;
}
-(void)setCellSeparatorStyle:(UITableViewCellSeparatorStyle)separatorStyle
{
CALayer *separatorLayer = [self separatorLayer];
switch (separatorStyle)
{
case UITableViewCellSeparatorStyleNone:
[separatorLayer removeFromSuperlayer];
break;
case UITableViewCellSeparatorStyleSingleLine:
if (separatorLayer == nil)
separatorLayer = [self newSeparatorLayer];
break;
default:
#throw [NSException exceptionWithName:NSStringFromClass([self class]) reason:#"Unsupported separatorStyle" userInfo:nil];
break;
}
}
-(UIColor *)cellSeparatorColor
{
CALayer *separatorLayer = [self separatorLayer];
return [UIColor colorWithCGColor:separatorLayer.backgroundColor];
}
-(void)setCellSeparatorColor:(UIColor *)separatorColor
{
CALayer *separatorLayer = [self separatorLayer];
if (separatorLayer == nil)
separatorLayer = [self newSeparatorLayer];
separatorLayer.backgroundColor = separatorColor.CGColor;
}
#end
Now you can do things like
UITableViewCell *cell = ...
cell.cellSeparatorStyle = UITableViewCellSeparatorStyleSingleLine;
cell.cellSeparatorColor = [UIColor orangeColor];
As others said, usually there's two ways to do this, either create a CALayer or a UIView separator of 1px and add it to contentView.
Over time I've seen multiple projects do this differently, and sometimes, even multiple different ways in the same project. It's also easy to introduce bugs because of cell reuse and also, for proper rendering of pixel lines, one must incorporate the screen scale: (1.0 / [UIScreen mainScreen].scale).
I've created a library that simplifies this to just a single method class, with no subclassing required.
https://github.com/kgaidis/KGViewSeparators
Objective-C:
[view kg_show:YES separator:KGViewSeparatorTop color:[UIColor blackColor] lineWidth:KGViewSeparatorLineWidth(1.0) insets:UIEdgeInsetsMake(0, 15.0, 0, 15.0)];
Swift:
view.kg_show(true, separator: .Bottom, color: UIColor.blackColor(), lineWidth: KGViewSeparatorLineWidth(1.0), insets: UIEdgeInsetsZero)