一、新建两个storyboard。一个用于实现用户登录及注册,一个负责实现即时通讯主体功能。
在主控制器的storyboard中拖入Tab Bar controller控制器 删除两个分支
再拖入几个Navigation Controller控制器
将Tab Bar controller的View controllers连线到对应的Navigation Controller控制器
在Navigation Controller控制器的子视图Table View控制器上>修改title标题。
将图片文件夹拖入images.xcassets中。点击Navigation Controller控制器按钮 在右侧修改Bar item名称及图片。
拖入图标时,记录勾选App icon 中的iphone ios6和ios7支持。
按钮背影拉伸,show slicing最低支持7.0,如果使用了不想用,得把slicing 选项改成None
二、
在storyboard中拖入一个View视图到界面(可以方便屏幕适配),再往视图上拖入button,Text添加Tool和Category文件夹
1.输入框需要用户使用回车键 就需要将输入框的delegate属性连线到控制器,再到文件中继承<UITextFieldDelegate>
-(void)textFieldShouldReturn:(UITextField *)textField{//监听文本输入方法
if (textField ==_userNameText){
[_passwordText becomeFirstResponder];
}else{
//登录
[self userLogin];
}
return YES;
}
2.1.将登录连接方法 -(IBAction)userLogin;
在方法中{
1.检查用户输入是否完整
//截断字符串前后的空格(密码除外),从而可以最大程度降低用户输入错误
NSString *userName = [_userNametext.text trimString];
NSString *password = _passwordText.text; //有些用户会使用空格做密码
if ([userName isEmptyString] || [password isEmptyString]){ //判断用户信息是否输入完整
UIAlertView *alter = [[UIAlertView alloc] initWithTitle:@"提示" message:@"登录信息不完整" delegate:nil cancelButtonTitle:@"确定" otherButtonTitles:nil,nil];
[alter show];
// 2. 将用户登录信息写入系统偏好
[[LoginUsersharedLoginUser] setUserName:userName];
[[LoginUsersharedLoginUser] setPassword:password];
[[LoginUsersharedLoginUser] setHostName:hostName];
// 3. 让AppDelegate开始连接
// 告诉AppDelegate,当前是注册用户
NSString *errorMessage = nil;
if (button.tag == 1) {
[selfappDelegate].isRegisterUser = YES;
errorMessage = @"注册用户失败!";
} else {
errorMessage = @"用户登录失败!";
}
[[selfappDelegate] connectWithCompletion:nilfailed:^{
UIAlertView *alter = [[UIAlertViewalloc] initWithTitle:@"提示"message:errorMessage delegate:nilcancelButtonTitle:@"确定"otherButtonTitles:nil, nil];
[alter show];
if (button.tag == 1) {
// 注册用户失败通常是因为用户名重复
[_userNameTextbecomeFirstResponder];
} else {
// 登录失败通常是密码输入错误
[_passwordTextsetText:@""];
[_passwordTextbecomeFirstResponder];
}
}];
return;
}
2.2.让AppDelegate开始连接、添加AppDelegate.h头文件
添加 -(AppDelegate *)appDelegate{ //并初始化这个方法
return [[UIApplication sharedApplication] delegate];
}
2.3.在viewDidLoad中添加拉伸按钮图片
// 1) 登录按钮
UIImage *loginImage = [UIImageimageNamed:@"LoginGreenBigBtn"];
loginImage = [loginImage stretchableImageWithLeftCapWidth:loginImage.size.width * 0.5topCapHeight:loginImage.size.height * 0.5];
相对上面布局:选中两个要对齐的元素,点击.Editor>pin>Vertical Spacing
2.4在AppDelegate.m文件中
的interface中新建方法:遵守XMPPStreamDelegate协议可以方便编写代码(不写也可以)
1.设置XMPPStream>setupStream
2.通知服务器用户上线>goOnline
3.通知服务器用户下线>goOffline
4.连接到服务器>connect
5.与服务器断开连接>disConnect
并实现。
3.//设置setupStream实例化流。
在 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
这个方法中// 2. 设置XMPPStream并调用[selfsetupStream];
。在.h头文件中定义XMPP相关的属性的方法定义> 全局的XMPPStream,只读属性readonly
{
这里可以使用断言,就可以不用判断xmppStream是否为nil,等于nil才实例化。
// 0. 方法被调用时,要求_xmppStream必须为nil,否则通过断言提示程序员,并终止程序运行!
NSAssert(_xmppStream == nil, @"XMPPStream被多次实例化!");
1.实例化流
_xmppStream = [[XMPPStream alloc] init];
// 2 设置重新连接模块,需要在XMPPStream文件中打开XMPPReconnect文件
,实例化,激活就可以使用了。在代理中添加成员变量XMPPReconnect *_xmppReconnect;
_xmppReconnect = [[XMPPReconnect alloc] init];
// 3将重新连接模块添加到XMPPStream
[_xmppReconnect activate:_xmppStream];
// 4.添加代理。因为所有网络请求都是做基于网络的数据处理,跟 UI无关,因此可以让代理yyifd其化线程中 ,从而提高程序的运行性能
[_xmppStream addDelegate:self delegateQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0)];
}
4.//注销已注册的扩展模块。当内存不足或注销的时候 销毁XMPPStream并注销已注册的扩展模块
- (void)teardownStream
{
// 1.删除代理
[_xmppStream removeDelegate:self];
// 2. 取消激活在setupStream方法中激活的扩展模块
[_xmppReconnectdeactivate];
// 3. 断开XMPPStream的连接
[_xmppStreamdisconnect];
// 4. 内存清理
_xmppStream = nil;
_xmppReconnect = nil;
}
5.- (void)dealloc
{
// 释放XMPP相关对象及扩展模块
[self teardownStream];
}
//通知服务器用户上线
6.goOnline{
//1。实例化一个上线报告,默认类型为:available
XMPPPresence *presence = [XMPPPressence presence];
[_xmppStream sendElement:presence]; //这个方法没有回调,只通知我的好友
}
7.goOffline{
//1.实例化一个下线报告
XMPPPresence *presence = [XMPPPresence presenceWithType:@"unavailable"];
//2.发送presence给服务器,通知服务器客户端下线
[_xmppStream sendElement:presence];
}
8.保持用户登录信息
在AppDelegate.h文件中 定义一个连接到服务器的方法 判断是否正确失败块代
typedef void(^CompletionBlock)();
-(void)connectWithUserName:(CompletionBlock *)completion password:(CompletionBlock *)faild; //completion 正确块代码 faild错误块代码方法新建一个类LoginUser.封装一个单独的类来保持用户登录信息
在.h文件中添加Singleton.h头文件在@interface定义属性
single_interface(LoginUser)
@property (strong, nonatomic) NSString *userName;
@property (strong, nonatomic) NSString *password;
@property (strong, nonatomic) NSString *
@property (strong, nonatomic, readonly) NSString *myJIDName;
hostName;
再到.m文件中定义single_implementation(LoginUser)和导入NSString+Helper.h头文件
以及定义#用户名,密码,服务器
#define kXMPPUserNameKey @"xmppUserName"
#define kXMPPPasswordKey @"xmppPassword"
#define kXMPPHostNameKey @"xmppHostName"
实现方法
- (NSString *)loadStringFromDefaultsWithKey:(NSString *)key
{
NSString *str = [[NSUserDefaults standardUserDefaults] stringForKey:key];
return (str) ? str : @"";
}
- (NSString *)userName
{ return [self loadStringFromDefaultsWithKey:kXMPPUserNameKey];}
- (void)setUserName:(NSString *)userName
{ [userName saveToNSDefaultsWithKey:kXMPPUserNameKey];}
- (NSString *)password
{return [self loadStringFromDefaultsWithKey:kXMPPPasswordKey];}
- (void)setPassword:(NSString *)password
{ [password saveToNSDefaultsWithKey:kXMPPPasswordKey];}
- (NSString *)hostName
{ return [self loadStringFromDefaultsWithKey:kXMPPHostNameKey];}
- (void)setHostName:(NSString *)hostName
{ [hostName saveToNSDefaultsWithKey:kXMPPHostNameKey];}
- (NSString *)myJIDName
{ return [NSStringstringWithFormat:@"%@@%@", self.userName, self.hostName];
}
9.连接connnect.在代理. m文件导入保持用户登录信息LoginUser头文件
{
// 1. 如果XMPPStream当前已经连接,直接返回 就不会需调用”[self setupStream];“
if ([_xmppStreamisConnected]) { return; }
// 2. 指定用户名、主机(服务器),连接时不需要password
NSString *hostName = [[LoginUsersharedLoginUser] hostName];
NSString *userName = [[LoginUsersharedLoginUser] myJIDName];
// 3. 设置XMPPStream的JID和主机
[_xmppStreamsetMyJID:[XMPPJIDjidWithString:userName]];
[_xmppStreamsetHostName:hostName];
// 4. 开始连接
NSError *error = nil;
[_xmppStreamconnectWithTimeout:XMPPStreamTimeoutNoneerror:&error];
// 提示:如果没有指定JID和hostName,才会出错,其他都不出错。
if (error) {
NSLog(@"连接请求发送出错 - %@", error.localizedDescription);
} else {
NSLog(@"连接请求发送成功!");
}
}
//断开连接
desConnect{
//通知服务器下线
[self goOffline];
//XMPPStream断开连接
[_xmppStream disconnect];
}
#pragma mark – 代理方法
在.h文件中,定义方法是否注册用户标示
@property (assign, nonatomic) BOOL isRegisterUser;
@property ( assign,nonatomic) BOOL isUserLogon;
在.m 文件中继续追加成员变量:
CompletionBlock _completionBlock; // 成功的块代码
CompletionBlock _faildBlock; // 失败的块代码
#pragma mark 连接完成(如果服务器地址不对,就不会调用此方法)
- (void)xmppStreamDidConnect:(XMPPStream *)sender
{
// 从系统偏好读取用户密码
NSString *password = [[LoginUsersharedLoginUser] password];
if (_isRegisterUser) {
// 用户注册,发送注册请求
[_xmppStreamregisterWithPassword:password error:nil];
} else {
// 用户登录,发送身份验证请求
[_xmppStreamauthenticateWithPassword:password error:nil];
}
}
#pragma mark 注册成功
- (void)xmppStreamDidRegister:(XMPPStream *)sender
{
_isRegisterUser = NO;
// 注册成功,直接发送验证身份请求,从而触发后续的操作
[_xmppStreamauthenticateWithPassword:[LoginUsersharedLoginUser].passworderror:nil];
}
#pragma mark 注册失败(用户名已经存在)
- (void)xmppStream:(XMPPStream *)sender didNotRegister:(DDXMLElement *)error
{
_isRegisterUser = NO;
if (_faildBlock != nil) {
dispatch_async(dispatch_get_main_queue(), ^{
_faildBlock();
});
}
}
#pragma mark 身份验证通过
- (void)xmppStreamDidAuthenticate:(XMPPStream *)sender
{
if (_completionBlock != nil) {
dispatch_async(dispatch_get_main_queue(), ^{
_completionBlock();
});
}
// 通知服务器用户上线
[selfgoOnline];
// 显示主Storyboard
[selfshowStoryboardWithLogonState:YES];
}
定义头文件#define kNotificationUserLogonState @"NotificationUserLogon"
在.m文件中
#pragma mark 根据用户登录状态加载对应的Storyboard显示
- (void)showStoryboardWithLogonState:(BOOL)isUserLogon
{
UIStoryboard *storyboard = nil;
if (isUserLogon) {
// 显示Main.storyboard
storyboard = [UIStoryboardstoryboardWithName:@"Main"bundle:nil];
} else {
// 显示Login.sotryboard
storyboard = [UIStoryboardstoryboardWithName:@"Login"bundle:nil];
}
// 在主线程队列负责切换Storyboard,而不影响后台代理的数据处理
dispatch_async(dispatch_get_main_queue(), ^{
// 如果在项目属性中,没有指定主界面(启动的Storyboard,self.window不会被实例化)
// 把Storyboard的初始视图控制器设置为window的rootViewController
[self.windowsetRootViewController:storyboard.instantiateInitialViewController];
if (!self.window.isKeyWindow) {
[self.windowmakeKeyAndVisible];
}
});
}
#pragma mark 密码错误,身份验证失败
- (void)xmppStream:(XMPPStream *)sender didNotAuthenticate:(DDXMLElement *)error
{
if (_faildBlock != nil) {
dispatch_async(dispatch_get_main_queue(), ^{
_faildBlock();
});
}
// 显示用户登录Storyboard
[selfshowStoryboardWithLogonState:NO];
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
// 应用程序被激活后,直接连接,使用系统偏好中的保存的用户记录登录
// 从而实现自动登录的效果!
[selfconnect];
}
- (void)applicationWillResignActive:(UIApplication *)application
{
[selfdisconnect];
}
添加一个注销按钮,退出到登录界面
新建一个UITableViewController控制器。选择Table View控制器连线到点M文件中。
在该方法中。添加AppDelegate.h头文件并在代理头文件写入注册方法。
在代理.M文件中实现方法{
// 1. 通知服务器下线,并断开连接
[selfdisconnect];
// 2. 显示用户登录Storyboard
[selfshowStoryboardWithLogonState:NO];
}
在logout方法中 设置_isUserLogon = No;
在注销文件中,初始化AppDelegate方法
在注销按钮中调用[self appDelegate] logout];就能实现注销用户登录了
----------------