I am trying to create an account page where user will be able to select his preferable language and voice. For this part of my app I was thinking to create 2 different UIButtons and once user has press one of them, a UITableView will appear on appropriate location tableFrame (something like drop down menu). Things were working great, initially, when I had just one array. Now, I have a general array that used from Table View delegate methods to load data and 2 other arrays that contain languages and voices.
My problem is that the very first time when I am tapping on any of the buttons, the table displays no content but numberOfRowsInSection returns correct value. Next, if tap on the same or the other button again .. everything is fine.
I believe the problem appears where I am, initially, initialise the general array with no object in viewDidLoad.
I have tried reloadData with now success.
Any ideas? Below my code:
Account.h
#import <Foundation/Foundation.h>
#interface AccountViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>{
NSMutableArray *content;
NSMutableArray *languages;
NSMutableArray *voices;
CGRect tableFrame;
}
#property (strong, nonatomic) IBOutlet UIButton *languageBtn;
#property (strong, nonatomic) IBOutlet UIButton *narratorBtn;
#property (strong, nonatomic) IBOutlet UITableView *contentTbl;
- (IBAction)selectLanguage:(id)sender;
- (IBAction)selectVoice:(id)sender;
#end
Account.m
- (void) viewDidLoad{
[super viewDidLoad];
content = [[NSMutableArray alloc] init];
languages = [[NSMutableArray alloc] initWithObjects:#"English", #"Ελληνικά", #"French", #"Spanish", #"Deutch", nil];
voices = [[NSMutableArray alloc] initWithObjects:#"English", #"US English", #"Ελληνικά", #"Spanish", nil];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [content 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.text = [content objectAtIndex:indexPath.row];
return cell;
}
- (IBAction)selectLanguage:(id)sender{
if ([(UIButton *)sender tag] == 0) {
content = [languages copy];
tableFrame = CGRectMake(50, 173, 220, 203);
[self startAnimationAndDisplayTable:YES];
[(UIButton *)sender setTag:1];
} else{
[self startAnimationAndDisplayTable:NO];
[(UIButton *)sender setTag:0];
}
}
- (IBAction)selectVoice:(id)sender{
if ([(UIButton *)sender tag] == 0) {
content = [voices copy];
tableFrame = CGRectMake(50, 243, 220, 203);
[self startAnimationAndDisplayTable:YES];
[(UIButton *)sender setTag:1];
} else{
[self startAnimationAndDisplayTable:NO];
[(UIButton *)sender setTag:0];
}
}
- (void)startAnimationAndDisplayTable:(BOOL)show{
if (show) {
[self.contentTbl setHidden:NO];
[self.contentTbl setFrame:tableFrame];
[UIView beginAnimations:#"Fade In" context:nil];
[UIView setAnimationDuration:0.7];
[self.contentTbl setAlpha:1.0];
[UIView commitAnimations];
[self.contentTbl reloadData];
} else{
[UIView beginAnimations:#"Fade Out" context:nil];
[UIView setAnimationDuration:0.5];
[self.contentTbl setAlpha:0.0];
[UIView commitAnimations];
}
}
#end
"content" is an empty mutable array until you touch whatever (button?) action that calls the "selectLanguage" and "selectVoice" methods (and then you reassign the "content" variable).
I don't see any other manipulations of the "content" mutable array, so it's no wonder to me that nothing appears until you actually touch something.
You also do not show where you are calling "reloadData". "content" needs to have array entries in there for "reloadData" to work.
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
When I click the UITableViewCell , It have selection effect (grey background in clicked cell),But didSelectRowAtIndexPath is not calling ,what happen?
EDIT
this is my code
tableView.h file
#interface PopCardView : MMPopupView <UITableViewDataSource, UITableViewDelegate>
#end
tableView.m file
#property (nonatomic, strong) NSMutableArray *tagsArray;
#property (nonatomic, strong) UIView *backView;
#property (nonatomic, strong) UITableView *tableView;
#property (nonatomic, assign) NSUInteger lastIndex;
#end
-(id)initWithTags:(NSMutableArray *)tags{
self = [super init];
if (self) {
self.backView = [[UIView alloc] init];
self.backView.backgroundColor = [UIColor whiteColor];
self.backView.layer.cornerRadius = 5;
self.backView.layer.masksToBounds = YES;
[self addSubview:self.backView];
[self.backView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.top.bottom.right.equalTo(self);
}];
_tableView = [[UITableView alloc]initWithFrame:CGRectMake(0, 0, 324, 300) style:UITableViewStylePlain];
_tableView.tableFooterView =[[UIView alloc] init];
[self.backView addSubview:_tableView];
[_tableView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.top.right.bottom.equalTo(self.backView).insets(UIEdgeInsetsMake(45,0, 45, 15));
make.size.mas_equalTo(CGSizeMake(324, 200));
}];
[_tableView registerClass:[PopCardTagViewCell class] forCellReuseIdentifier:#"cell"];
_tableView.allowsSelection = YES;
_tableView.allowsSelectionDuringEditing = YES;
[_tableView setUserInteractionEnabled:YES];
_tableView.dataSource = self;
_tableView.delegate = self;
}
return self;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[_tagsArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
PopCardData *data = (PopCardData *)obj;
data.selected = #"0";
if (idx == indexPath.row) {
data.selected = #"1";
}
}];
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *identifer = #"cell";
PopCardTagViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:identifer];
if (!cell) {
cell = [[PopCardTagViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifer];
}
[self configureCell:cell forIndexPath:indexPath];
return cell;
}
-(void)configureCell:(PopCardTagViewCell *)cell forIndexPath:(NSIndexPath *)indexPath {
PopCardData *data = (PopCardData *)[_tagsArray objectAtIndex:indexPath.row];
//configure cell
[cell setUserInteractionEnabled:YES];
}
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
return 1;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return _tagsArray.count;
}
EDIT2
this is my initialize code of PopCardView,it use swift
let pop = PopCardView(tags: self.m_model.getItmes())
pop.show()
The code you show does not set any delegate to the table view. Either this is the reason or you posted an incomplete code snippet.
self.tableView.delegate = self;
and add UITableViewDelegate to your interface like
#interface ClassName ()<UITableViewDelegate>
Make sure you don't have anything in the cell that can swallow the touch event. Things like buttons and textfields can cause this. Strip everything from your cell, test to see if it works, then add things back in slowly to find the culprit.
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;
This is weird, I know but my [UITableView reloadData] is not removing old cells, like this:
The mess you see happened after I clicked on the + button, came back and changed values again. plus button pushes the navigationController to an another controller, and after I came back with clicking on the back button and changed the value, this is what I see. How is it possible?? I used a custom view (subclassed from UIView), which I created as a UIStepper with a UILabel. Here is the codes, the controller.m, and the .h-.m files for the custom UIView.
controller.m
#interface ViewController ()
#property NSString *docsDir;
#property sqlite3 *DB;
#property NSArray *dirPaths;
#property NSString* databasePath;
#property (strong, nonatomic) IBOutlet UITableView *tableView;
#property BOOL isCreatedBefore;
#property NSArray *theList;
#end
#implementation ViewController
#synthesize docsDir;
#synthesize DB;
#synthesize dirPaths;
#synthesize databasePath;
- (void)viewDidLoad
{
[super viewDidLoad];
dirPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
docsDir = [dirPaths objectAtIndex:0];
databasePath = [[NSString alloc] initWithString: [docsDir stringByAppendingPathComponent: [NSString stringWithFormat:#"database.db"]]];
[self createDatabase];
self.theList = [self readAllEntries];
}
-(void) viewWillAppear:(BOOL)animated{
self.theList = [self readAllEntries];
[self.tableView reloadData];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [self.theList count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{ SeriesObject * obj = [self.theList objectAtIndex:indexPath.row];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"cellocan"];
UILabel *nameLabel = (UILabel *)[cell.contentView viewWithTag:1];
[nameLabel setText:obj.name];
CounterView *seasonCounter = [[CounterView alloc] initWithX:220 WithY:28 WithName:obj.name withCount:obj.session withCustomTag:indexPath.row];
seasonCounter.tag = 2;
CounterView *episodeCounter = [[CounterView alloc] initWithX:268 WithY:28 WithName:obj.name withCount:obj.episode withCustomTag:indexPath.row];
episodeCounter.tag = 4;
[cell.contentView addSubview:seasonCounter];
[cell.contentView addSubview:episodeCounter];
return cell;
}
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
// Return YES if you want the specified item to be editable.
return YES;
}
// Override to support editing the table view.
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
SeriesObject *obj = [self.theList objectAtIndex:indexPath.row];
[self deleteEntryWithID:obj.idd];
self.theList = [self readAllEntries];
[self.tableView reloadData];
}
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
return 88;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
______and more about reading from database or deleting; not about the view. (this is a very simple app, as a free time hobby; so I didn't spend time to follow MVC)
CounterView.h
#interface CounterView : UIView
#property NSString* name;
#property NSInteger count;
#property UILabel *label;
#property NSInteger customTag;
- (id)initWithX:(CGFloat)xPoint WithY:(CGFloat)yPoint WithName:(NSString*)newName withCount:(NSInteger)newCount withCustomTag:(NSInteger)newTag;
#end
CounterView.m
#interface CounterView()
#property NSString *docsDir;
#property sqlite3 *DB;
#property NSArray *dirPaths;
#property NSString* databasePath;
#end
#implementation CounterView
#synthesize docsDir;
#synthesize DB;
#synthesize dirPaths;
#synthesize databasePath;
- (id)initWithX:(CGFloat)xPoint WithY:(CGFloat)yPoint WithName:(NSString*)newName withCount:(NSInteger)newCount withCustomTag:(NSInteger)newTag
{
self = [super initWithFrame:CGRectMake(xPoint, yPoint, 24, 52)];
if (self) {
self.customTag = newTag;
self.count = newCount;
self.name = newName;
UIButton *btnUp = [[UIButton alloc] initWithFrame:CGRectMake(3, 2, 18, 12)];
[btnUp setImage:[UIImage imageNamed:#"top.png"] forState:UIControlStateNormal];
[btnUp addTarget:self action:#selector(increaseValue) forControlEvents:UIControlEventTouchUpInside];
UIButton *btnDown = [[UIButton alloc] initWithFrame:CGRectMake(3, 38, 18, 12)];
[btnDown setImage:[UIImage imageNamed:#"bottom.png"] forState:UIControlStateNormal];
[btnDown addTarget:self action:#selector(decreaseValue) forControlEvents:UIControlEventTouchUpInside];
self.label = [[UILabel alloc] initWithFrame:CGRectMake(0, 14, 24, 24)];
[self.label setText:[NSString stringWithFormat:#"%ld", (long)self.count]];
self.label.textAlignment = NSTextAlignmentCenter;
[self addSubview:btnUp];
[self addSubview:btnDown];
[self addSubview:self.label];
}
return self;
}
- (void)addTarget:(id)target action:(SEL)action forControlEvents:(UIControlEvents)controlEvents{
}
-(void) increaseValue{
self.count++;
[self.label setText:[NSString stringWithFormat:#"%ld", (long)self.count]];
}
-(void) decreaseValue{
self.count--;
[self.label setText:[NSString stringWithFormat:#"%ld", (long)self.count]];
}
____and some more database codes too..
In your cellForRowAtIndexPath: you are adding a subview every time. Cells are reused by UITableView, so when you reload you're getting a "dirty" cell. You need to check if the view is already there. If so, modify the view instead of adding one. Google "UITableViewCell viewWithTag" to see some sample code.
- (void)prepareForReuse
{
//remove your subviews here
[self.subViewToRemove removeFromSuperView];
}
I would recommend that you create a custom cell class derived from UITableViewCell and have it include your custom CounterView instances. In this case you won't need to add the subviews each time in your cellForRowAtIndexPath (Which is causing the problem you're having), you can instead pass it whatever values it requires.
after some modifications, using this in the cellForRowAtIndexpath: method solved everything. Thanks everyone.
if (cell != nil)
{
NSArray* subviews = [cell.contentView subviews];
for (UIView* view in subviews)
{
[view removeFromSuperview];
}
}
I have a problem with refreshing the content of a UITableView. I'm trying to display a list of members in a UITableView. Above this list I have some kind of dropdown-menu for a group selection. So, if I change the group, selected in the dropdown-menu, I want to change the content of the UITableView underneath it. It's actually not working correctly.
The first time I load the ViewController that contains the UITableView, it shows the correct list of members in the default group. Now, when I change the group, it should display my new list of members, but it doesn't. It seems that the UITableView would not even reload its table content.
Here is my code for the TableViewController:
MemberTableViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
self.members = [NSMutableArray new];
self.selectedRows = [NSMutableArray new];
self.groupManager = [GroupManager new];
int *number;
if (nil != [self gruppeId]) {
number = [[self gruppeId] intValue]; // Here will be the id of the new selected group
} else {
number = 4010292; // This is just for testing. It's the id of my default group
}
for (Group *group in [self.groupManager findGroupWithID:[NSNumber numberWithInt:number]]) {
for (Member *member in group.member) {
[self.members addObject:member];
}
}
NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey:#"name" ascending:YES selector:#selector(caseInsensitiveCompare:)];
[self.gruppenMitglieder sortUsingDescriptors:[NSArray arrayWithObject:sort]];
[self.tableView setAllowsMultipleSelection:YES];
[self.tableView reloadData];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return [[self members] count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
GreenTexturedCell *cell = nil;
Member *member = [self members][indexPath.row];
NSArray* views = [[NSBundle mainBundle] loadNibNamed:#"GreenTexturedCell" owner:nil options:nil];
for (UIView *view in views) {
if([view isKindOfClass:[UITableViewCell class]]) {
cell = (GreenTexturedCell*)view;
}
}
cell.textLabel.text = member.name;
cell.textLabel.font = [UIFont fontWithName:#"JandaCloserToFree" size:14.0];
cell.textLabel.textColor = [UIColor whiteColor];
return cell;
}
Now I have another ViewController the MemberListViewController, it contains the MemberTableViewController (I know, the naming is not the best...). The MemberListViewController implements a protocol of the GroupSelectionTableViewController. Its delegate function will be called when the selected group changes. Here is the code for the MemberListViewController:
MemberListViewController.h
#interface MemberListViewController : UIViewController <GroupSelectionDelegate>
#property (weak, nonatomic) IBOutlet UIButton *groupSelection;
#property (nonatomic, strong) GroupSelectionTableViewController *groupTableView;
- (IBAction)groupSelectionPressed:(id)sender;
#end
MemberListViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
[self setGroupTableView:[GroupSelectionTableViewController new]];
[[self groupTableView] setDelegate:self];
}
- (IBAction)gruppeAuswahlPressed:(id)sender {
if ([self groupListShown]) {
[[[self groupTableView] view] removeFromSuperview];
[self setGroupListShown:NO];
} else {
[self.view addSubview:[[self groupTableView] view]];
[self setGroupListShown:YES];
}
}
- (void) groupSelectionController:(GroupSelectionTableViewController *)groupSelection DidSelectGroupWithId:(NSString *) groupId {
[[self groupSelection] setTitle:name forState:UIControlStateNormal];
[[[self groupTableView] view] removeFromSuperview];
[self setGroupListShown:NO];
MemberTableViewController *memberTableView = [self.storyboard instantiateViewControllerWithIdentifier:#"memberTable"];
[memberTableView setGroupId:[NSNumber numberWithInt:4017411]]; // Id for testing
[memberTableView refreshControl];
}
Now, if I change the group selection, the -groupSelectionController:DidSelectGroupWithId: Method is called and with [memberTableView refreshControl] I'm able to get into the viewDidLoad Method of the MemberTableViewController again and set the groupId to my testing id. Now i want the TableView to reload its content to get the members of the selected group. With the statement [self.tableView reloadData] I'm getting into the tableView:NumberOfRowsInSection: Method but not into the tableView:CellForRowAtIndexPath: Method and the TableView Content is not redrawn.
What am I doing wrong ?
This problem is driving me nuts, so I hope there's someone to help me out.
Thanks
With instantiateViewControllerWithIdentifier you are creating a new instance of MemberTableViewController, which isn't the one you are showing on screen. Hence, you won't see the changes which should be shown by calling reloadData.
To be able to reload the table data, you can add a link to the table at your MemberListViewController. Change the code of the .h to:
#import "MemberTableViewController.h"
#interface MemberListViewController : UIViewController <GroupSelectionDelegate>
{
IBOutlet MemberTableViewController *memberTableView;
}
#property (weak, nonatomic) IBOutlet UIButton *groupSelection;
#property (nonatomic, strong) GroupSelectionTableViewController *groupTableView;
- (IBAction)groupSelectionPressed:(id)sender;
#end
and then, in the .m, you can call [memberTableView reloadData];
Do not forget to set memberTableView as "new referencing outlet" within the storyboard file.