TableviewCells 未按预期插入

Posted

技术标签:

【中文标题】TableviewCells 未按预期插入【英文标题】:TableviewCells are not inserted as expected 【发布时间】:2019-11-05 05:41:41 【问题描述】:

我有一个可重用的TablViewCell,只要用户在教科书中输入并单击发送按钮,它就会简单地回显到表格视图,我的问题是单元格的行为没有按预期进行几次单元格正确插入和几次他们不会。

预期行为:当用户在文本框中输入完某些文本后单击发送按钮时,应打印两次相同的值(如发送和接收相同的文本)。

//view somewhat looks like this on expected behaviour

         '''''''''
         '  hi   '
         '''''''''
'''''''''
'  hi   '
'''''''''

当前行为:有时它确实给了我预期的行为,但有时两个单元格都在同一侧

前:

//view when it doesn't work as expected

          ''''''' 
          ' hi  '
          '''''''

          '''''''
          ' hi  '
          '''''''

or something like 

''''''''
'  hi  '
''''''''

''''''''
'  hi  '
''''''''

有时当我们滚动时,单元格的位置(您可以在代码中看到的奇数单元格和偶数单元格)从发送者变为接收者,反之亦然。

我的代码

//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];
    

这基本上是一个聊天视图,我实际上正在努力实现一切都很好,除了这种异常行为。

我希望任何人都可以花点时间纠正我哪里错了。

更新:任何在 Objective-C 中寻找基本 chatView 的人都可以使用上面的代码作为参考,使用上面的代码并更正接受的答案中提到的内容。

【问题讨论】:

【参考方案1】:

这是一个典型的单元重用问题。在 iOS 中,所有集合 (UITableView/UICollectionView) 都会重用单元格,并且单元格 initWithStyle 仅在单元格初始化后才会被调用。一旦 tableView 有足够的单元格,它将重用单元格,因此initWithStyle 不会一直被调用。因此,您的一些单元格(最好是初始单元格)似乎没有问题。当您在init 中正确设置其约束时,对于其他未正确显示的单元格,init 从未被调用,因此您的约束从未更新。因此显示错误的气泡。

解决办法是什么?:

1.使用 PrepareforReuse 当每个单元格被重用时,iOS 会在单元格上调用prepareForReuse 给开发人员最后一次清理的机会

-(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。修改您的单元格方法并确保每次显示单元格时更新您的约束,而不仅仅是在 init 中

假设您添加了一个名为:

 -(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];


在您的初始化删除代码中,根据 isRecieved 值设置约束

-(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;

最后在cellForRowAtIndexPath 调用configureViewisReceived

- (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;

希望对你有帮助

【讨论】:

以上是关于TableviewCells 未按预期插入的主要内容,如果未能解决你的问题,请参考以下文章

材料表示例未按预期工作

range.insertNode() 未按预期插入文本节点

Sql 语句:在要插入的字段中未指定主键时,插入键更新未按预期工作

在 h2 数据库 oracle 模式下插入未按预期工作

C#“继续”未按预期运行

查询结果未按预期返回