ios - 如何在 iOS 中向 UITableView 动态添加行?

标签 ios uitableview chat

我正在为聊天应用程序开发一个 View Controller ,我想展示一个包含 UITableView 的 UIViewController (其中消息以不同的格式显示[如果是您的消息或来自其他人的消息],一个 UITextField (写你的消息)和一个 UIButton (发送消息)

我正在使用 SRWebSocket 示例,但他们使用 UITableViewController (运行完美,但不允许我修改 tableview 大小或通过 Storyboard 将其他组件添加到 View 中)

这是我在 Controller 中的代码:

ChatViewController.h

#import <UIKit/UIKit.h>
#import "SRWebSocket.h"
#import "ChatCell.h"
#import "Message.h"
#import "Person.h"
#import "Program.h"
#import "DateFactory.h"

@interface ChatViewController : UIViewController     <UITableViewDataSource,UITableViewDelegate,SRWebSocketDelegate, UITextViewDelegate, UITextFieldDelegate>

@property (strong, nonatomic) NSDictionary *programSegue;

@property (retain, nonatomic) IBOutlet UITableView *tableView;
@property (nonatomic, retain) IBOutlet UITextView *inputView;

- (IBAction)goingUp:(id)sender;
@property (weak, nonatomic) IBOutlet UITextField *inputText;

@end

聊天 View Controller .m

失败的代码:
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:_messages.count - 1 inSection:0]] withRowAnimation:UITableViewRowAnimationNone];

在:
- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message;
{
    NSLog(@"Received \"%@\"", message);
    NSError *e;
    NSDictionary *allJSON =
    [NSJSONSerialization JSONObjectWithData: [message dataUsingEncoding:NSUTF8StringEncoding]
                                options: NSJSONReadingMutableContainers
                                  error: &e];

    NSString *kindJSON = [allJSON objectForKey:@"kind"];
    NSString *userJSON = [allJSON objectForKey:@"user"];
    NSString *messageJSON = [allJSON objectForKey:@"message"];
    NSArray *membersJSON = [allJSON objectForKey:@"members"];

    DateFactory *dateFactory = [DateFactory alloc];
    NSString *formatDate = @"dd/MM/YYYY HH:mm";
    NSString *dateString = [dateFactory dateToString:[NSDate date] withFormat:formatDate];

    switch([@[@"join", @"talk", @"quit"] indexOfObject:kindJSON]){
            // join
        case 0:

            break;
            // talk
        case 1:
            [_messages addObject:[[Message alloc] initWithMessage:messageJSON fromMe:NO]];

            [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:_messages.count - 1 inSection:0]] withRowAnimation:UITableViewRowAnimationNone];
            [self.tableView scrollRectToVisible:self.tableView.tableFooterView.frame animated:YES];
            break;
            // quit
        case 2:
            [[self.navigationItem.titleView.subviews objectAtIndex:1] setText:
             [NSString stringWithFormat:@"Sin conexión desde %@", dateString]];
            break;
    }
}

错误
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'attempt to insert row 0 into section 0, but there are only 0 rows in section 0 after the update'

完整代码:
#import "ChatViewController.h"

@interface ChatViewController ()

@end

@implementation ChatViewController{
    SRWebSocket *_webSocket;
    NSMutableArray *_messages;
    Person *person;
    Program *program;
}

@synthesize programSegue;
@synthesize tableView;
@synthesize inputText;
@synthesize inputView = _inputView;

- (BOOL)textFieldShouldReturn:(UITextField *)textField {
    return [inputText resignFirstResponder];
}

#pragma mark - View lifecycle

- (void)viewDidLoad;
{
    [super viewDidLoad];
    [inputText setDelegate:self];

    person = [programSegue objectForKey:@"PERSON"];
    program = [programSegue objectForKey:@"PROGRAM"];
    self.navigationItem.title = person.name;

    // Creates picture to be shown in navigation bar
    UIButton* picture = (UIButton *) [[UIImageView alloc] initWithImage:[UIImage imageNamed:person.imageURL]];
    CGRect buttonFrame = picture.frame;
    buttonFrame.size = CGSizeMake(38, 38);
    picture.frame = buttonFrame;
    UIBarButtonItem *pictureItem = [[UIBarButtonItem alloc] initWithCustomView:picture];
    self.navigationItem.rightBarButtonItem = pictureItem;


    // Set title and subtitle
    CGRect frame = self.navigationController.navigationBar.frame;

    UIView *twoLineTitleView = [[UIView alloc] initWithFrame:CGRectMake(CGRectGetWidth(frame), 0, CGRectGetWidth(frame), CGRectGetHeight(frame))];

    UILabel *titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 6, CGRectGetWidth(frame), 20)];
    titleLabel.backgroundColor = [UIColor clearColor];
    [titleLabel setTextColor:[UIColor whiteColor]];
    titleLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth;
    [titleLabel setTextAlignment:NSTextAlignmentCenter];
    [titleLabel setFont:[UIFont boldSystemFontOfSize:16]];
    [titleLabel setShadowColor:[UIColor grayColor]];
    titleLabel.text = person.name;
    [twoLineTitleView addSubview:titleLabel];

    UILabel *subTitleLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 26, CGRectGetWidth(frame), 14)];
    subTitleLabel.backgroundColor = [UIColor clearColor];
    [subTitleLabel setTextColor:[UIColor whiteColor]];
    subTitleLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth;
    [subTitleLabel setTextAlignment:NSTextAlignmentCenter];
    [subTitleLabel setFont:[UIFont boldSystemFontOfSize:12]];
    [titleLabel setShadowColor:[UIColor grayColor]];
    subTitleLabel.text = @"subtitleg";
    [twoLineTitleView addSubview:subTitleLabel];

    self.navigationItem.titleView = twoLineTitleView;

    // Start messages
    _messages = [[NSMutableArray alloc] init];

    [self.tableView reloadData];
}

- (void)_reconnect;
{
    _webSocket.delegate = nil;
    [_webSocket close];

    _webSocket = [[SRWebSocket alloc] initWithURLRequest:
                  [NSURLRequest requestWithURL:
                   [NSURL URLWithString:
                    [NSString stringWithFormat:@"ws://81.45.19.228:8000/room/chat?username=enrimr&amp;pid=%@", person.name]]]];

    _webSocket.delegate = self;

    //self.title = @"Opening Connection...";
    [[self.navigationItem.titleView.subviews objectAtIndex:1] setText:@"Conectando..."];

    [_webSocket open];

}

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    [self _reconnect];
}

- (void)reconnect:(id)sender;
{
    [self _reconnect];
}

- (void)viewDidAppear:(BOOL)animated;
{
    [super viewDidAppear:animated];

    [_inputView becomeFirstResponder];

    [self.tableView scrollRectToVisible:self.tableView.tableFooterView.frame animated:YES];
}

- (void)viewDidDisappear:(BOOL)animated
{
    [super viewDidDisappear:animated];

    _webSocket.delegate = nil;
    [_webSocket close];
    _webSocket = nil;
}

#pragma mark - UITableViewController


- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
{
    return _messages.count;
}

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
    return 1;
}

- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath;
{
    ChatCell *chatCell = (id)cell;
    Message *message = [_messages objectAtIndex:indexPath.row];
    chatCell.text.text = message.message;
    chatCell.date.text = message.fromMe ? @"Me" : @"Other";
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
{
    Message *message = [_messages objectAtIndex:indexPath.row];

    ChatCell *cell = (ChatCell *)[self.tableView dequeueReusableCellWithIdentifier:@"programCell" forIndexPath:indexPath];

    if (!cell) {
        if (message.fromMe){
            cell = [[ChatCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"SentCell"];
            [cell.text setText:message.message];
            [cell.date setText:@"00:00"];
        }
        else {
            cell = [[ChatCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"ReceivedCell"];
            [cell.text setText:message.message];
            [cell.date setText:@"00:00"];
        }
    }

    return cell;
}

#pragma mark - SRWebSocketDelegate

- (void)webSocketDidOpen:(SRWebSocket *)webSocket;
{
    NSLog(@"Websocket Connected");
    //self.title = @"Connected!";
    [[self.navigationItem.titleView.subviews objectAtIndex:1] setText:@"Conectado"];
}

- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error;
{
    NSLog(@":( Websocket Failed With Error %@", error);

    self.title = @"Connection Failed! (see logs)";
    _webSocket = nil;
}

- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message;
{
    NSLog(@"Received \"%@\"", message);
    NSError *e;
    NSDictionary *allJSON =
    [NSJSONSerialization JSONObjectWithData: [message dataUsingEncoding:NSUTF8StringEncoding]
                                    options: NSJSONReadingMutableContainers
                                      error: &e];

    NSString *kindJSON = [allJSON objectForKey:@"kind"];
    NSString *userJSON = [allJSON objectForKey:@"user"];
    NSString *messageJSON = [allJSON objectForKey:@"message"];
    NSArray *membersJSON = [allJSON objectForKey:@"members"];

    DateFactory *dateFactory = [DateFactory alloc];
    NSString *formatDate = @"dd/MM/YYYY HH:mm";
    NSString *dateString = [dateFactory dateToString:[NSDate date] withFormat:formatDate];

    switch([@[@"join", @"talk", @"quit"] indexOfObject:kindJSON]){
            // join
        case 0:

            break;
            // talk
        case 1:
            [_messages addObject:[[Message alloc] initWithMessage:messageJSON fromMe:NO]];

            [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:_messages.count - 1 inSection:0]] withRowAnimation:UITableViewRowAnimationNone];
            [self.tableView scrollRectToVisible:self.tableView.tableFooterView.frame animated:YES];
            break;
            // quit
        case 2:
            [[self.navigationItem.titleView.subviews objectAtIndex:1] setText:
             [NSString stringWithFormat:@"Sin conexión desde %@", dateString]];
            break;
    }
}

- (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean;
{
    NSLog(@"WebSocket closed");
    //self.title = @"Connection Closed! (see logs)";
    [[self.navigationItem.titleView.subviews objectAtIndex:1] setText:@"Offline"];
    _webSocket = nil;
}

- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text;
{
    if ([text rangeOfString:@"\n"].location != NSNotFound) {
        NSString *message = [[textView.text stringByReplacingCharactersInRange:range withString:text] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
        [_webSocket send:message];
        [_messages addObject:[[Message alloc] initWithMessage:message fromMe:YES]];

        [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:_messages.count - 1 inSection:0]] withRowAnimation:UITableViewRowAnimationNone];
        [self.tableView scrollRectToVisible:self.tableView.tableFooterView.frame animated:YES];

        textView.text = @"";
        return NO;
    }
    return YES;
}

- (void) animateTextField: (UITextField*) textField up: (BOOL)up
{
    const int movementDistance = 218;
    const float movementDuration = 0.3f;
    int movement = (up ? -movementDistance : movementDistance);

    [UIView beginAnimations: @"anim" context: nil];
    [UIView setAnimationBeginsFromCurrentState: YES];
    [UIView setAnimationDuration: movementDuration];
    self.view.frame = CGRectOffset(self.view.frame, 0, movement);
    [UIView commitAnimations];
}

- (IBAction)goingUp:(id)sender {
    [self animateTextField:inputText up:TRUE];
}
@end

最佳答案

当您使用 insertRowsAtIndexPaths您必须首先更新 TableView 数据源。因此,在您调用 insertRowsAtIndexPaths 之前你应该做一些类似 _messages addObject:newMessage .

作为辅助规则,每当您更新 TableView 的行而不使用 reloadData方法,您必须更新 tableView 的数据源以反射(reflect)将要更新的索引路径。因此,如果您从 TableView 中删除行,则必须从数据源中删除与该行关联的数据,此外,如果您向 TableView 添加行,则必须将新行的关联数据添加到数据源中.始终首先更新数据源。

并且每次更新 TableView 的行时,都应该使用 beginUpdates 之间的更新方法。和 endUpdates方法调用。

关于ios - 如何在 iOS 中向 UITableView 动态添加行?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18313184/

相关文章:

iphone - 在 iOS 7 中,viewForHeaderInSection 部分是从 1 而不是从 0 开始

iphone - 向空白文本字段添加字符

ios - 在顶部插入新行后如何使 UITableViewCell 保持在同一位置?

android - 我将如何在 Android 上实时共享聊天/草图?

linux - 在 Linux 中使用 pppd 和聊天通过 gprs/gsm 调制解调器连接到 gprs 网络

ios - 过滤唯一值以在消息收件箱中显示唯一用户名

ios - SwiftUI 允许 Struct 中的所有字符串枚举

ios - 在一个 UITableView 中添加两个标题 View ?

ios - uitableview 不会为每次点击打印行索引值

css - ie6 中的布局、绝对位置问题