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)
Related
I have a menuView in a list view controller. The menuView added on the UITableViewCell when a more button in the cell being taped.
I achieved the effect with singleton.
#implementation ProductsOperationMenu
static ProductsOperationMenu *_instance;
+ (instancetype)sharedInstance{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [[self alloc] initWithFrame:CGRectZero];
});
return _instance;
}
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
[self setup];
}
return self;
}
ZBMyProductsCell.m
#implementation ZBMyProductsCell
- (void)awakeFromNib
{
[super awakeFromNib];
_operationMenu = [[ProductsOperationMenu alloc] initWithFrame: CGRectZero];
}
- (IBAction)operationButtonClick:(UIButton *)sender {
if ([self.contentView.subviews containsObject:_operationMenu]) {
_operationMenu.hidden = ![_operationMenu isHidden];
} else{
[self.contentView addSubview:_operationMenu];
_operationMenu.hidden = NO;
}
[_operationMenu mas_makeConstraints:^(MASConstraintMaker *make) {
make.width.mas_equalTo(205);
make.height.mas_equalTo(60);
make.bottom.mas_equalTo(self.operationButton).offset(0);
make.right.mas_equalTo(self.operationButton.mas_left).offset(-10);
}];
}
Without Singleton, it became this:
So the question come.
I want to put the menuView on the controller's view, because it is unique or hidden, which used to belong to the cell.
How to convert layout of the more button selected to the controller's view?
How to use the methods to calculate?
- convertPoint:toView:
- convertPoint:fromView:
......
I did it in a simple way. Here is the code:
- (void)clickOperationButtonOfProductsCell:(ZBMyProductsCell *)myProductsCell{
NSUInteger * operationIndex = [self.myProductsTableView.visibleCells indexOfObject:myProductsCell];
CGFloat originY = operationIndex.row * 110 + 50 + 40;
CGRect originFrame = CGRectMake(KScreenWidth - 55, originY, 0, 60);
CGRect finalFrame = CGRectMake(KScreenWidth - 260, originY, 205, 60);
self.operationMenuView.frame = originFrame;
[UIView animateWithDuration: 0.5 delay: 0 options: UIViewAnimationOptionCurveEaseIn animations:^{
self.operationMenuView.frame = finalFrame;
} completion:^(BOOL finished) { }];
}
How to achieve it more adaptively?
Maybe you can try it like this:
// here is your cell where the more button belongs to
#interface ZBMyProductsCell: UITableViewCell
#property (nonatomic, copy) void(^moreAction)(ZBMyProductsCell *cell);
#end
#implementation ZBMyProductsCell
- (void)_moreButtonClicked:(UIButton *)sender {
!self.moreAction ?: self.moreAction(self);
}
#end
// here is your view controller where your table view belongs to
// this is a `UITableViewDataSource` delegate method
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
ZBMyProductsCell *cell = [tableView dequeueReusableCellWithIdentifier:#"ZBMyProductsCell" forIndexPath:indexPath];
// Configure the cell...
cell.moreAction = ^(ZBMyProductsCell *cell) {
CGRect rect = [tableView rectForRowAtIndexPath:indexPath];
// write your own code to show/hide the menu
};
return cell;
}
Create a variable in each cell model called cellIndexpath and in cellForRow init it
cell.cellIndexpath = indexpath
have a look of UIPopoverPresnetationController and see if it can do the job. It should be available on iPhone. Putting menu view on the controller’viewwill cause issue when you scroll the table view.
use
UIMenuController *menu = [UIMenuController sharedMenuController];
[menu setTargetRect:self.detaiLabel.frame inView:self];
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;
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.)
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);
}