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)];
}
Related
I have a reusableTablViewCell that will be simply echoed to the tableview whenever a user gives an input in the textbook and clicks send button, my problem is that the cells are not behaving as expected few times the cells are inserted correctly and few times they won't.
Expected Behaviour: when the user clicks send button after he has finished typing some text in the text box the same value should be printed twice (like sending and receiving the same text).
//view somewhat looks like this on expected behaviour
'''''''''
' hi '
'''''''''
'''''''''
' hi '
'''''''''
Current Behaviour: Sometimes it does give me the expected behaviour, but some times both the cells are on the same side
EX:
//view when it doesn't work as expected
'''''''
' hi '
'''''''
'''''''
' hi '
'''''''
or something like
''''''''
' hi '
''''''''
''''''''
' hi '
''''''''
And sometimes when we scroll, the cells change their position(being odd Cell and Even-cell which u can see in the code) from sender to receiver and vice-versa.
My code
//FirstTableViewController.h
#import <UIKit/UIKit.h>
#class SecondViewController;
#interface FirstTableViewController : UITableViewController
#property (strong, nonatomic) IBOutlet UITableView *messageView;
#property (nonatomic,readwrite) NSInteger counter;
#property (nonatomic,readwrite) NSMutableArray *userInput;
#property (nonatomic,readwrite) NSMutableDictionary *heightAtIndexPath;
#property (nonatomic, assign) BOOL shouldScrollToLastRow;
+ (id)sharedInstance;
#end
#interface ChatMessageCellTableViewCell : UITableViewCell
#property (nonatomic, retain) UILabel *formLabel;
#property (nonatomic, retain) UIView *bubbleBackView;
#end
//FirstTableViewController.m
#import "FirstTableViewController.h"
BOOL isReceived;
#interface ChatMessageCellTableViewCell (){
NSLayoutConstraint *leadingConstraint;
NSLayoutConstraint *trailingConstraint;
}
#end
#implementation ChatMessageCellTableViewCell
-(void) loaded{
if(isReceived){
[self.bubbleBackView setBackgroundColor:[UIColor whiteColor]];
[self.formLabel setTextColor:[UIColor blackColor]];
}
else{
[[self bubbleBackView] setBackgroundColor:[UIColor colorWithRed:(66/255) green:(137/255.0) blue:1 alpha:1.0]];
[self.formLabel setTextColor:[UIColor whiteColor]];
}
}
-(id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{
[self setBackgroundColor:[UIColor clearColor]];
self.formLabel = [UILabel new];
self.bubbleBackView = [UIView new];
//[self.bubbleBackView setBackgroundColor:[UIColor yellowColor]];
[self.bubbleBackView.layer setCornerRadius:12];
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if(self){
[[self contentView] addSubview:self.bubbleBackView
];
[self loaded];
[self.bubbleBackView setTranslatesAutoresizingMaskIntoConstraints:NO];
[[self contentView] addSubview:self.formLabel];
[self.formLabel setTranslatesAutoresizingMaskIntoConstraints:NO];
if (#available(iOS 9.0, *)) {
[self.formLabel.topAnchor constraintEqualToAnchor:self.topAnchor constant:32].active=YES;
[self.formLabel.bottomAnchor constraintEqualToAnchor:self.bottomAnchor constant:-32].active=YES;
[self.formLabel.widthAnchor constraintLessThanOrEqualToConstant:250].active=YES;
[self.bubbleBackView.topAnchor constraintEqualToAnchor:_formLabel.topAnchor constant:-16].active=YES;
[self.bubbleBackView.bottomAnchor constraintEqualToAnchor:_formLabel.bottomAnchor constant:16].active=YES;
[self.bubbleBackView.trailingAnchor constraintEqualToAnchor:_formLabel.trailingAnchor constant:16].active=YES;
[self.bubbleBackView.leadingAnchor constraintEqualToAnchor:_formLabel.leadingAnchor constant:-16].active=YES;
leadingConstraint= [self.formLabel.leadingAnchor constraintEqualToAnchor:self.leadingAnchor constant:32];
trailingConstraint = [self.formLabel.trailingAnchor constraintEqualToAnchor:self.trailingAnchor constant:-32];
if(isReceived){
[leadingConstraint setActive:YES];
[trailingConstraint setActive:NO];
}
else{
[leadingConstraint setActive:NO];
[trailingConstraint setActive:YES];
}
} else {
// Fallback on earlier versions
}
[self.formLabel setLineBreakMode:NSLineBreakByWordWrapping];
[self.formLabel setNumberOfLines:0];
[self.formLabel sizeToFit];
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-40-[bodyLabel]-40-|" options:0
metrics:nil
views:#{ #"bodyLabel":self.formLabel}]];
}
return self;
}
#end
#interface FirstTableViewController ()
{
NSArray *messages;
FirstTableViewController *classA;
}
#end
#implementation FirstTableViewController
+(id)sharedInstance
{
static FirstTableViewController *sharedClassA = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedClassA = [[self alloc] init];
});
return sharedClassA;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.heightAtIndexPath = [NSMutableDictionary new];
self.userInput = [[NSMutableArray alloc] init];
[self.tableView registerClass:[ChatMessageCellTableViewCell class] forCellReuseIdentifier:#"id"];
[[self tableView] setSeparatorStyle:UITableViewCellSeparatorStyleNone];
[self.tableView setBackgroundColor:[UIColor colorWithWhite:0.95 alpha:1]];
[[self navigationController] setTitle:#"Meetings"];
classA = [FirstTableViewController sharedInstance];
[classA setCounter:(classA.userInput.count)];
[classA setMessageView:(self.messageView)];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
classA.counter=classA.userInput.count;
return classA.counter;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *cellIdentifier = (indexPath.row % 2 == 0 ? #"EvenCell" : #"OddCell"); //just to differentiate the sending and receiving cell.
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
ChatMessageCellTableViewCell *messageCell = (ChatMessageCellTableViewCell*) cell;
if (messageCell == nil) {
messageCell = [[ChatMessageCellTableViewCell alloc] initWithStyle: UITableViewCellStyleDefault
reuseIdentifier:cellIdentifier];
}
if(indexPath.row % 2 == 0) // simple logic to differentiate and apply my constraints to the sending and receiving cells.
{
isReceived =TRUE;
}
else{
isReceived = FALSE;
}
[[messageCell formLabel]setText:classA.userInput[indexPath.row]];
[messageCell setSelectionStyle:UITableViewCellSelectionStyleNone];
[[self tableView] setEstimatedRowHeight:50.0];
[self.tableView setRowHeight:UITableViewAutomaticDimension];
return messageCell;
}
-(void)viewWillLayoutSubviews{
if(classA.shouldScrollToLastRow){
[classA setShouldScrollToLastRow:NO];
dispatch_async(dispatch_get_main_queue(),^{
NSIndexPath *path = [NSIndexPath indexPathForRow:(self->classA.counter)-1 inSection:0];
//Basically maintain your logic to get the indexpath
[self->classA.messageView scrollToRowAtIndexPath:path atScrollPosition:UITableViewScrollPositionBottom animated:NO];
});
}
}
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
}
-(void)dealloc{
NSLog(#"Dealloc!!!");
}
#end
//SecondViewController.m
//sendButtonClicked is the function from where the data is passed to the FirstViewController's tableview cell.
-(IBAction)sendButtonClicked{
NSString *input = self.ChatTextInput.text;
if([input isEqualToString:#""]){
NSLog(#"this is a nil ");
}
else{
[inputValues addObject:input];
[inputValues addObject:input];
[classA setUserInput:inputValues];
[classA setCounter:inputValues.count];
[self.ChatTextInput setText:nil];
[classA setShouldScrollToLastRow:YES];
[classA.messageView reloadData];
}
}
This is basically a chat view which I'm actually trying to achieve everything is good except this abnormal behaviour.
I hope anyone can take some time and correct me where am I wrong.
UPDATE: Any one who is looking for basic chatView in objective-C can use the code above as an reference, use the code above and correct the mentioned things in the accepted answer.
This is a typical cell reuse issue. In iOS all collections (UITableView/UICollectionView) reuses the cell and cells initWithStyle gets called only once the cell is initialised. Once tableView has enough cells with it, it will reuse the cell so initWithStyle will not get called all the time. Hence few of your cells (preferably initial ones) seems all right. As you set its constraints properly in init and for other cells which are not shown properly init was never called so your constraints were never updated. Hence shows wrong bubble.
Whats the solution?:
1. Use PrepareforReuse
every cell when it gets reused, iOS calls prepareForReuse on the cell to give developer a last chance to do all clean up
-(void) prepareForReuse {
[super prepareForReuse];
[self.formLabel setText: nil];
//set default background color or change bubble view
// do whatever clean up you wanna do here
}
2. Modify your cells method and ensure you update your constraints every time cell is shown and not just in init
lets say you add a method called:
-(void)configureView:(BOOL) isRecieved {
isReceived = isRecieved;
if(isReceived){
[leadingConstraint setActive:YES];
[trailingConstraint setActive:NO];
}
else{
[leadingConstraint setActive:NO];
[trailingConstraint setActive:YES];
}
//[self layoutIfNeeded]; might be needed here
[self loaded];
}
In your init remove code to set constraint based on isRecieved Value
-(id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{
[self setBackgroundColor:[UIColor clearColor]];
self.formLabel = [UILabel new];
self.bubbleBackView = [UIView new];
//[self.bubbleBackView setBackgroundColor:[UIColor yellowColor]];
[self.bubbleBackView.layer setCornerRadius:12];
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if(self){
[[self contentView] addSubview:self.bubbleBackView
];
[self loaded];
[self.bubbleBackView setTranslatesAutoresizingMaskIntoConstraints:NO];
[[self contentView] addSubview:self.formLabel];
[self.formLabel setTranslatesAutoresizingMaskIntoConstraints:NO];
if (#available(iOS 9.0, *)) {
[self.formLabel.topAnchor constraintEqualToAnchor:self.topAnchor constant:32].active=YES;
[self.formLabel.bottomAnchor constraintEqualToAnchor:self.bottomAnchor constant:-32].active=YES;
[self.formLabel.widthAnchor constraintLessThanOrEqualToConstant:250].active=YES;
[self.bubbleBackView.topAnchor constraintEqualToAnchor:_formLabel.topAnchor constant:-16].active=YES;
[self.bubbleBackView.bottomAnchor constraintEqualToAnchor:_formLabel.bottomAnchor constant:16].active=YES;
[self.bubbleBackView.trailingAnchor constraintEqualToAnchor:_formLabel.trailingAnchor constant:16].active=YES;
[self.bubbleBackView.leadingAnchor constraintEqualToAnchor:_formLabel.leadingAnchor constant:-16].active=YES;
leadingConstraint= [self.formLabel.leadingAnchor constraintEqualToAnchor:self.leadingAnchor constant:32];
trailingConstraint = [self.formLabel.trailingAnchor constraintEqualToAnchor:self.trailingAnchor constant:-32];
} else {
// Fallback on earlier versions
}
[self.formLabel setLineBreakMode:NSLineBreakByWordWrapping];
[self.formLabel setNumberOfLines:0];
[self.formLabel sizeToFit];
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-40-[bodyLabel]-40-|" options:0
metrics:nil
views:#{ #"bodyLabel":self.formLabel}]];
}
return self;
}
Finally in cellForRowAtIndexPath call configureView with isReceived value
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *cellIdentifier = (indexPath.row % 2 == 0 ? #"EvenCell" : #"OddCell"); //just to differentiate the sending and receiving cell.
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
ChatMessageCellTableViewCell *messageCell = (ChatMessageCellTableViewCell*) cell;
if (messageCell == nil) {
messageCell = [[ChatMessageCellTableViewCell alloc] initWithStyle: UITableViewCellStyleDefault
reuseIdentifier:cellIdentifier];
}
if(indexPath.row % 2 == 0) // simple logic to differentiate and apply my constraints to the sending and receiving cells.
{
isReceived =TRUE;
}
else{
isReceived = FALSE;
}
[messageCell configureView: isReceived];
[[messageCell formLabel]setText:classA.userInput[indexPath.row]];
[messageCell setSelectionStyle:UITableViewCellSelectionStyleNone];
[[self tableView] setEstimatedRowHeight:50.0];
[self.tableView setRowHeight:UITableViewAutomaticDimension];
return messageCell;
}
Hope it helps
I am trying to create a collection view cell programmatically without the use of storyboards or nibs.
This is my cell .h file
#import <UIKit/UIKit.h>
#interface TestCVCell : UICollectionViewCell {
UILabel *label;
}
#property (nonatomic, retain) UILabel *label;
#end
and this is the .m file
#import "TestCVCell.h"
#implementation TestCVCell
#synthesize label;
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
if (!self.label) {
label = [[UILabel alloc] initWithFrame:self.frame];
label.textColor = [UIColor whiteColor];
self.contentView.backgroundColor = [UIColor orangeColor];
[self.contentView addSubview:label];
}
}
return self;
}
- (void)prepareForReuse
{
self.label.text = #"";
}
#end
The issue I am having is that when I register class, it sporadically shows the cells and it doesn't show them at the correct locations. The cells will also randomly disappear/appear while scrolling (often not in the same location).
The weird thing is, if I create a nib and register the nib as opposed to the class, everything works perfectly fine.
The only difference I can think of between the nib and the class is with the nib, I use -(id)initWithCoder:(NSCoder *)aDecoder instead of -(instancetype)initWithFrame:(CGRect)frame.
Here is the collection view files for reference
.h
#import <UIKit/UIKit.h>
#interface TestCollectionView : UICollectionView
#end
.m
#import "TestCollectionView.h"
#import "TestCVCell.h"
#interface TestCollectionView () <UICollectionViewDataSource>
#property (nonatomic, strong) NSMutableArray *itemArray;
#end
#implementation TestCollectionView
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame collectionViewLayout:[self collectionViewLayout]];
if (self) {
[self registerClass:[TestCVCell class] forCellWithReuseIdentifier:#"cell"];
self.itemArray = [[NSMutableArray alloc] init];
self.dataSource = self;
for (int idx = 0; idx < 50; idx++) {
[self.itemArray addObject:[NSNumber numberWithInt:idx]];
}
}
return self;
}
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
return 1;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return self.itemArray.count;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
TestCVCell *cell = (TestCVCell *)[collectionView dequeueReusableCellWithReuseIdentifier:#"cell" forIndexPath:indexPath];
cell.label.text = [NSString stringWithFormat:#"%#", [self.itemArray objectAtIndex:indexPath.item]];
return cell;
}
- (UICollectionViewLayout *)collectionViewLayout
{
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
layout.itemSize = CGSizeMake(50, 50);
return layout;
}
#end
Has anyone ever encountered this issue or something similar?
Thanks in advanced for the help.
Here is your problem, Your TestCVCell Class is initializing the label during init, then the collectionView is reusing the same cell, remember during reuse we don't initialize a new object, we just recycle an existing one, therefore your label is never set up on subsequent reuse. Make the following changes to your TestCVCell class.
TestCVCell.h
#interface TestCVCell : UICollectionViewCell
#property (nonatomic) UILabel *label;
#end
You don't need the iVar label anymore. We'll use the automatically synthesized _label iVar if need be.
Use lazy loading to initialize the label, its not only clever but a cleaner alternative, customize the getter to look like below, also remember to call super on prepareForReuse
#import "TestCVCell.h"
#implementation TestCVCell
- (UILabel *)label {
if (!_label) {
_label = [[UILabel alloc] initWithFrame:self.bounds];
_label.textColor = [UIColor whiteColor];
_label.textAlignment = NSTextAlignmentCenter;
self.contentView.backgroundColor = [UIColor orangeColor];
[self.contentView addSubview:_label];
}
return _label;
}
- (void)prepareForReuse
{
[super prepareForReuse];
self.label.text = #"";
}
#end
The above code only adds the label to the contentView if its not been initialized before, it will only initialize a new one when needed, thus lazy loading. Delete your entire init method, it's not needed. The above is your entire implementation of TestCVCell. Clean and easy to understand.
What I am trying to do is reveal a hidden menu when swiping on a UITableViewCell.
I have no idea how to do it, any guidance will be appreciated.
Here is an image so that you get an idea:
Image http://imageshack.com/a/img855/9134/j6k8.png
u can do it by using custom cell i give the ruff idea and sample code how to achieve this,
first create a new file objective-c, and name it as CustomCell subclass of UITableViewCell and in CustomCell.h file
i took some dummy views and labels
#import <UIKit/UIKit.h>
#interface CustomCell : UITableViewCell
{
}
#property (nonatomic, retain) UIView *optionsView; //this is your options to show when swiped
#property (nonatomic, retain) UIView *mainVIew; //this is your view by default present on top of options view
#property (nonatomic, retain) UILabel *messageLabel;
#property (nonatomic, retain) UILabel *optionsLabel;
#end
in CustomCell.m
#import "CustomCell.h"
#implementation CustomCell
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
//since we are not using the xib we hav to initilise hear
_optionsView = [[UIView alloc]init];
_mainVIew = [[UIView alloc]init];
_optionsView.backgroundColor = [UIColor greenColor];
_mainVIew.backgroundColor = [UIColor redColor];
_optionsView.alpha = 0.0f;
_mainVIew.alpha = 1.0f;
_messageLabel = [[UILabel alloc]init];
_optionsLabel = [[UILabel alloc] init];
[_optionsView addSubview:_optionsLabel]; //hear u can add image view or buttons to options view i just added the label
[_mainVIew addSubview:_messageLabel];
//add the gesture to main view
UISwipeGestureRecognizer *swipeGesture = [[UISwipeGestureRecognizer alloc]initWithTarget:self action:#selector(whenSwiped:)];
swipeGesture.direction = UISwipeGestureRecognizerDirectionRight;
[_mainVIew addGestureRecognizer:swipeGesture];//add it to main view
UISwipeGestureRecognizer *swipeReverse = [[UISwipeGestureRecognizer alloc]initWithTarget:self action:#selector(whenSwipedReversed:)];
swipeReverse.direction = UISwipeGestureRecognizerDirectionLeft;
[_optionsView addGestureRecognizer:swipeReverse]; //add it to options view so that user can swipe back
[self.contentView addSubview:_optionsView]; //first add the optionsVIew
[self.contentView addSubview:_mainVIew]; //then main view
}
return self;
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
[super setSelected:selected animated:animated];
// Configure the view for the selected state
}
- (void)layoutSubviews
{
[super layoutSubviews];
_optionsView.frame = self.bounds;
_mainVIew.frame = self.bounds;
_messageLabel.frame = _mainVIew.bounds;
_optionsLabel.frame = _optionsView.bounds;
_optionsView.alpha = 0.0f;
_mainVIew.alpha = 1.0f;
}
//handle swipe call backs in cell only and make a delegate to controller about the button actions of options view
- (void)whenSwiped:(id)sender
{
CGRect frameRect = _mainVIew.frame;
frameRect.origin.x = 300.0f;
[UIView animateWithDuration:0.5 animations:^{
_mainVIew.frame = frameRect;
_optionsView.alpha = 1.0f;
}];
}
- (void)whenSwipedReversed:(id)sender
{
CGRect frameRect = _mainVIew.frame;
frameRect.origin.x = 0.0f;
[UIView animateWithDuration:0.5 animations:^{
_mainVIew.frame = frameRect;
_optionsView.alpha = 0.0f;
}];
}
#end
in view controller import #import "CustomCell.h"
in datasource method of tableview
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
CustomCell *Cell = [tableView dequeueReusableCellWithIdentifier:#"CELL"];
if(Cell == nil)
{
Cell = [[CustomCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"CELL"];
}
Cell.selectionStyle = UITableViewCellSelectionStyleNone;
Cell.optionsLabel.text = #"options";
Cell.messageLabel.text = #"swipe me";
return Cell;
}
above is example, hope this helps u .. :)
and comment if don't get
Here is some guidance:
Each UITableViewCell could have two views, one on top of the other. The top-most view is what is shown by default, and the bottom-most view is what you call the "hidden menu."
You will need to register a UISwipeGestureRecognizer in your storyboard that manages the table cells you are showing above. You will create an IBAction for that gesture recognizer in your custom UITableViewCell class. In that action, you will handle the swipes that occur and then displace the top-most view of the cell by your desire amount, up to a maximum displacement (in the x-direction).
If you need more than this, let me know and I'll provide more info. I can't tell from your original question just how experienced you are, e.g., do you know how to create UITableViews with custom UITableViewCells?
UPDATE:
Make sure that you are creating a XIB file that houses your custom UITableViewCell. Then you can easily add the UISwipeGestureRecognizer to the XIB, and connect it to an IBAction in your cell class. In your UIViewController, you will register the XIB with a reuse identifier and populate your UITableView this way.
I created a Custom Cell:
#import <UIKit/UIKit.h>
#import "SevenSwitch.h"
#interface cellaMain : UITableViewCell {
SevenSwitch *subscribed;
}
#property (nonatomic, retain) IBOutlet UIImageView *imageMain;
#property (nonatomic, retain) IBOutlet UILabel *titleMain;
#property (nonatomic, retain) SevenSwitch *subscribed;
#end
The UIImage and the Label are added to cell by the storyboard but the sevenswitch is added in the method:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
With this code:
/* Switch Inside the cell */
cella.subscribed = [[SevenSwitch alloc] initWithFrame:CGRectMake(cella.frame.size.width-60, cella.frame.size.height / 2 - 12, 50, 25)];
cella.subscribed.offImage = [UIImage imageNamed:#"off.png"];
cella.subscribed.onImage = [UIImage imageNamed:#"on.png"];
cella.subscribed.thumbTintColor = [UIColor colorWithRed:(230/255.0) green:(230/255.0) blue:(230/255.0) alpha:1];
cella.subscribed.activeColor = [UIColor colorWithRed:(204/255.0) green:(204/255.0) blue:(204/255.0) alpha:1];
cella.subscribed.inactiveColor = [UIColor colorWithRed:(204/255.0) green:(204/255.0) blue:(204/255.0) alpha:1];
cella.subscribed.onTintColor = [UIColor colorWithRed:(204/255.0) green:(204/255.0) blue:(204/255.0) alpha:1];
cella.subscribed.isRounded = NO;
cella.subscribed.tag = [[tempCat objectForKey:#"Id"] intValue];
[cella.subscribed addTarget:self action:#selector(changeSingleCategory:) forControlEvents:UIControlEventValueChanged];
if ([[tempCat objectForKey:#"Subscribed"] isEqualToString:#"Y"]) {
cella.subscribed.on = YES;
} else {
cella.subscribed.on = NO;
}
[cella.contentView addSubview:cella.subscribed];
/* End Switch Editing */
The problem is that the scroll lags a lot.
How can I do to add the SevenSwitch object in the cellaMain.m and let the image and the label be added by the Storyboard?
Or maybe is it better add to my cell view all the objects (Label, Image and SeveSwitch) in my cellaMain.m file?
Adding to matt's answer. When you scroll UITableView the function below get called everytime, and you are initializing the switch again and again which has already been created and that is actually causing lag in scroll.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
There is a very simple solution for that, follow these steps
Put a UISwitch in your custom cell xib and follow the instructions in the images below
Create an IBOutlet of the UISwitch in the .h class of your CustomCell, remember to import 'SevenSwitch.h'. When you will create an IBOutlet for the UISwitch, your code should look like below
#property(nonatomic, strong) IBOutlet SevenSwitch *subscribed;
Now your code in cellForRowAtIndexPath should look like below
/* Switch Inside the cell */
cella.subscribed.offImage = [UIImage imageNamed:#"off.png"];
cella.subscribed.onImage = [UIImage imageNamed:#"on.png"];
cella.subscribed.thumbTintColor = [UIColor colorWithRed:(230/255.0) green:(230/255.0) blue:(230/255.0) alpha:1];
cella.subscribed.activeColor = [UIColor colorWithRed:(204/255.0) green:(204/255.0) blue:(204/255.0) alpha:1];
cella.subscribed.inactiveColor = [UIColor colorWithRed:(204/255.0) green:(204/255.0) blue:(204/255.0) alpha:1];
cella.subscribed.onTintColor = [UIColor colorWithRed:(204/255.0) green:(204/255.0) blue:(204/255.0) alpha:1];
cella.subscribed.isRounded = NO;
cella.subscribed.tag = [[tempCat objectForKey:#"Id"] intValue];
[cella.subscribed addTarget:self action:#selector(changeSingleCategory:) forControlEvents:UIControlEventValueChanged];
if ([[tempCat objectForKey:#"Subscribed"] isEqualToString:#"Y"]) {
cella.subscribed.on = YES;
} else {
cella.subscribed.on = NO;
}
/* End Switch Editing */
You will notice I have removed the first and the last line of your code, so now your switch is getting initialized from xib only and only once, and in the function your are just changing the properties.
Hope it helps.
The problem is that you are saying
addSubview:cella.subscribed
for every cell. But cells are reused. So you are adding this subview even if it has already been added. You need to make all of that code conditional; don't add the subview if it is already there.
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.)