() translation by (you can also view the original English article)
在这个教程里面,我们将会开发一个Jabber的iOS客户端。 本系列开发的应用将会包含下面功能:登陆、新增好友以及发送消息。 而本教程主要讲述的内容是如何实现示例聊天客户端的界面。
iOS客户端总览
Jabber应用的核心部分就是基于XMPP的功能实现。 这些功能将会添加到应用主要委托里面,而这个委托会实现一个用于发送登陆事件和发送小心事件的消息的自定义协议。 而我们准备开发的应用,将会包含三个视图:登陆、好友列表以及会话窗口。



好友列表将会在应用开始运行时显示的默认视图。 在线好友将会在该视图里以列表形式显示。 如果用户设备里面不存在登陆信息的时候,登陆视图就会弹出。 好友列表上的“Account”按钮用来显示登陆视图,方便用户更改登陆账号。 在点击在线好友单元格后,会话实例会被初始化,同时显示会话窗口视图。 我们将会分别为这三个视图添加视图控制器。 这些控制器会实现一个简单的协议用来接收来自应用代理通知推送。 为了开发简便,登陆、会话视图将会以模态视图的形式出现。 当然,你也可以用导航视图来加载并显示这些视图。
创建项目
让我们一起来运行Xcode并创建一个新的项目。 在这里,我们选择单一视图应用并命名为“JabberClient”。 为了能够实现与服务端的交互,我们会导入一个名为“XMPP framework”的第三方库。 该库兼容Mac和iOS应用,同时能帮助我们实现连接XMPP服务的一些底层功能和管理通过socket的信息交互。 仓库不再支持下载链接,所以你需要安装git(点击这里查看更多信息) 安装完了git之后,你就可以在终端控制台里面输入以下命令:
$git clone https://code.google.com/p/xmppframework/ xmppframework
下载完成后,我们就会得到如下图所示的文件结构的文件夹:



我们需要的是图片里面被选中的。 选中这些文件(夹)之后,将他们拖到Xcode中。 记得钩上“Copy items into destination group's folder (if needed)”这个选项。



在这里并不需要集成Facebook,所以我们把“Extensions”文件夹下面的“X-FACEBOOK-PLATFORM”文件夹移除。

然后添加项目需要的框架。 在导航窗选择项目,然后选中“target”,打开“Link Binary With Libraries”。



需要添加的框架如下图所示:



最后,在编译之前,我们需要更改编译配置。 “project”和“target”都需要更改。 首先,我们搜索“User Header Search Paths”,然后把路径改为:'/user/include/libxml2'



之后,搜索“Other Linker Flags”并参数后面加上:“-lxml2”。



到这里,项目就已经配置完毕了。然后就是确认编译不会出错。
创建好友列表视图
好友列表里面会用表格视图来显示在线好友列表。 当点击其中一个在线好友的时候,就会跳转到相应的会话窗口视图。 项目创建想到已经帮我们新建了一个视图控制器。为了一致性,我们将会重命名这个视图控制器为“BuddyListViewController”。 在这个控制器里面会添加一个UITableView
和一个用来存放在线好友信息的数组。 同时还有一个IBAction
在用户想要更换登陆账号时用来响应点击事件,然后显示登陆视图。 接着是实现表格视图的委托内容。 实现文件内容如下所示:
1 |
|
2 |
@interface JabberClientViewController : UIViewController <UITableViewDelegate, UITableViewDataSource> { |
3 |
|
4 |
UITableView *tView; |
5 |
NSMutableArray *onlineBuddies; |
6 |
|
7 |
}
|
8 |
|
9 |
@property (nonatomic,retain) IBOutlet UITableView *tView; |
10 |
|
11 |
- (IBAction) showLogin; |
12 |
|
13 |
@end
|
在实现文件里,我们会实现这些属性并添加常规的方法来管理表格视图
。
1 |
|
2 |
@implementation JabberClientViewController |
3 |
|
4 |
@synthesize tView; |
5 |
|
6 |
- (void)viewDidLoad { |
7 |
|
8 |
[super viewDidLoad]; |
9 |
self.tView.delegate = self; |
10 |
self.tView.dataSource = self; |
11 |
onlineBuddies = [[NSMutableArray alloc ] init]; |
12 |
|
13 |
}
|
14 |
|
15 |
- (void) showLogin { |
16 |
|
17 |
// show login view
|
18 |
|
19 |
}
|
20 |
|
21 |
#pragma mark -
|
22 |
#pragma mark Table view delegates
|
23 |
|
24 |
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { |
25 |
|
26 |
NSString *s = (NSString *) [onlineBuddies objectAtIndex:indexPath.row]; |
27 |
static NSString *CellIdentifier = @"UserCellIdentifier"; |
28 |
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; |
29 |
|
30 |
if (cell == nil) { |
31 |
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease]; |
32 |
}
|
33 |
|
34 |
cell.textLabel.text = s; |
35 |
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; |
36 |
|
37 |
return cell; |
38 |
|
39 |
}
|
40 |
|
41 |
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { |
42 |
|
43 |
return [onlineBuddies count]; |
44 |
|
45 |
}
|
46 |
|
47 |
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { |
48 |
|
49 |
return 1; |
50 |
|
51 |
}
|
52 |
|
53 |
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { |
54 |
|
55 |
// start a chat
|
56 |
|
57 |
}
|
58 |
|
59 |
@end
|
在与之对应的xib文件添加一个表格式,一个带有按钮的工具条,如下所示:



务必记得将表格视图还有showLogin
响应方法和对应的组件关联在一起,如下所示:

现在运行应用的话,将会看到一个空白的表格视图,和下面截图一样:

我们可以稍后再来实现这个类。 XMPP的功能特性随时都可以整合到一起。 现在,来看看登陆视图。
实现登陆视图界面
该视图会在没有用户登录信息或者用户点击“Account”按钮的时候显示。 它包含了两个文本框和一个按钮。 增加一个判断,当用户没有改变任何东西的时候,隐藏改视图。
1 |
|
2 |
@interface SMLoginViewController : UIViewController { |
3 |
|
4 |
UITextField *loginField; |
5 |
UITextField *passwordField; |
6 |
|
7 |
}
|
8 |
|
9 |
@property (nonatomic,retain) IBOutlet UITextField *loginField; |
10 |
@property (nonatomic,retain) IBOutlet UITextField *passwordField; |
11 |
|
12 |
- (IBAction) login; |
13 |
- (IBAction) hideLogin; |
14 |
|
15 |
@end
|
实现文件很简单。 当登陆按钮按下之后,两个文本框中的内容会用两个关键字“userID”和“userPassword”保存到NSUserDefaults
中。 这些数据将会被XMPP所使用,以及发送到服务端。
1 |
|
2 |
@implementation SMLoginViewController |
3 |
|
4 |
@synthesize loginField, passwordField; |
5 |
|
6 |
- (IBAction) login { |
7 |
|
8 |
[[NSUserDefaults standardUserDefaults] setObject:self.loginField.text forKey:@"userID"]; |
9 |
[[NSUserDefaults standardUserDefaults] setObject:self.passwordField.text forKey:@"userPassword"]; |
10 |
[[NSUserDefaults standardUserDefaults] synchronize]; |
11 |
|
12 |
[self dismissModalViewControllerAnimated:YES]; |
13 |
|
14 |
}
|
15 |
|
16 |
- (IBAction) hideLogin { |
17 |
|
18 |
[self dismissModalViewControllerAnimated:YES]; |
19 |
|
20 |
}
|
21 |
|
22 |
@end
|
和上面一样,记得将文本框、动作响应和xib文件关联在一起。



现在就可以让好友列表控制器在需要的时候显示登陆视图了。 导入对应的类,然后做如下所示的更改:
1 |
|
2 |
- (IBAction) showLogin { |
3 |
|
4 |
SMLoginViewController *loginController = [[SMLoginViewController alloc] init]; |
5 |
[self presentModalViewController:loginController animated:YES]; |
6 |
|
7 |
}
|
我们还需要实现viewDidAppear
这个方法,在没有登录数据的时候弹出登陆视图。
1 |
|
2 |
- (void)viewDidAppear:(BOOL)animated { |
3 |
|
4 |
[super viewDidAppear:animated]; |
5 |
|
6 |
NSString *login = [[NSUserDefaults standardUserDefaults] objectForKey:@"userID"]; |
7 |
|
8 |
if (!login) { |
9 |
|
10 |
[self showLogin]; |
11 |
|
12 |
}
|
13 |
}
|
在编译运行的时候,我们应该确保登陆视图是否和预期一样出现,尤其是当用户点击按钮的时候。
新建会话窗口视图
会话窗口视图的可视元素如下:
- 一个带有关闭按钮的工具条
- 一个用来输入内容的文本框
- 一个用来发送消息的按钮
- 一个用来显示已发送和收到的消息的表格视图



头文件内容如下所示:
1 |
|
2 |
@interface SMChatViewController : UIViewController { |
3 |
|
4 |
UITextField *messageField; |
5 |
NSString *chatWithUser; |
6 |
UITableView *tView; |
7 |
NSMutableArray *messages; |
8 |
|
9 |
}
|
10 |
|
11 |
@property (nonatomic,retain) IBOutlet UITextField *messageField; |
12 |
@property (nonatomic,retain) NSString *chatWithUser; |
13 |
@property (nonatomic,retain) IBOutlet UITableView *tView; |
14 |
|
15 |
- (id) initWithUser:(NSString *) userName; |
16 |
- (IBAction) sendMessage; |
17 |
- (IBAction) closeChat; |
18 |
|
19 |
@end
|
和好友列表视图一样,这里也要实现表格委托。 该视图实现方法会根据chatWithUser
的字符串变量来保存接收的信息,同时实现两个方法响应:closeChat
和sendMessage
。 对应的实现内容如下所示:
1 |
|
2 |
@implementation SMChatViewController |
3 |
|
4 |
@synthesize messageField, chatWithUser, tView; |
5 |
|
6 |
- (void)viewDidLoad { |
7 |
|
8 |
[super viewDidLoad]; |
9 |
self.tView.delegate = self; |
10 |
self.tView.dataSource = self; |
11 |
messages = [[NSMutableArray alloc ] init]; |
12 |
|
13 |
[self.messageField becomeFirstResponder]; |
14 |
|
15 |
}
|
16 |
|
17 |
#pragma mark -
|
18 |
#pragma mark Actions
|
19 |
|
20 |
- (IBAction) closeChat { |
21 |
|
22 |
[self dismissModalViewControllerAnimated:YES]; |
23 |
|
24 |
}
|
25 |
|
26 |
- (IBAction)sendMessage { |
27 |
|
28 |
NSString *messageStr = self.messageField.text; |
29 |
|
30 |
if([messageStr length] > 0) { |
31 |
|
32 |
// send message through XMPP
|
33 |
|
34 |
self.messageField.text = @""; |
35 |
|
36 |
NSString *m = [NSString stringWithFormat:@"%@:%@", messageStr, @"you"]; |
37 |
|
38 |
NSMutableDictionary *m = [[NSMutableDictionary alloc] init]; |
39 |
[m setObject:messageStr forKey:@"msg"]; |
40 |
[m setObject:@"you" forKey:@"sender"]; |
41 |
|
42 |
[messages addObject:m]; |
43 |
[self.tView reloadData]; |
44 |
[m release]; |
45 |
|
46 |
}
|
47 |
|
48 |
}
|
49 |
|
50 |
#pragma mark -
|
51 |
#pragma mark Table view delegates
|
52 |
|
53 |
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { |
54 |
|
55 |
NSDictionary *s = (NSDictionary *) [messages objectAtIndex:indexPath.row]; |
56 |
static NSString *CellIdentifier = @"MessageCellIdentifier"; |
57 |
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; |
58 |
|
59 |
if (cell == nil) { |
60 |
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease]; |
61 |
}
|
62 |
|
63 |
cell.textLabel.text = [s objectForKey:@"msg"]; |
64 |
cell.detailTextLabel.text = [s objectForKey:@"sender"]; |
65 |
cell.accessoryType = UITableViewCellAccessoryNone; |
66 |
cell.userInteractionEnabled = NO; |
67 |
|
68 |
return cell; |
69 |
|
70 |
}
|
71 |
|
72 |
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { |
73 |
|
74 |
return [messages count]; |
75 |
|
76 |
}
|
77 |
|
78 |
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { |
79 |
|
80 |
return 1; |
81 |
|
82 |
}
|
83 |
|
84 |
#pragma mark -
|
85 |
#pragma mark Chat delegates
|
86 |
|
87 |
// react to the message received
|
88 |
|
89 |
- (void)dealloc { |
90 |
|
91 |
[messageField dealloc]; |
92 |
[chatWithUser dealloc]; |
93 |
[tView dealloc]; |
94 |
[super dealloc]; |
95 |
|
96 |
}
|
当视图加载完后,键盘就会弹出。 而表格部分看着就和好友列表视图一样。 在这里,会用到一个稍微不同类型的单元格来显示消息和昵称。 下面就是应用运行时的情况:

记得将IBAction
和对应的按钮链接在一起。

我们应用的可视部分已经完成了! 剩下的就是消息相关的核心功能了。而这些内容将会在本系列的下一章节里面介绍!
源代码
完整的项目源代码可以在我们的Github里面找到。