IOS开发学习周报(四)
简介
课程名称 | IOS开发实训 | 任课老师 | 郑贵锋老师&字节跳动工程师 |
---|---|---|---|
学号 | 16340015 | 专业(方向) | 软件工程(计应) |
姓名 | 陈彬彬 | 944131226@qq.com | |
开始日期 | 2019/04/06 | 完成日期 | 2019/04/11 |
文章目录
本周概括
学习记录:
- 学习ios-oc项目纯代码开发
- 学习UI控件
UIAlertView
UIAlertController
UITextField
- 学习网络访问POST请求
工作记录:
- 继续完成一个简答的网络访问项目
worldcup-demo
- 重构项目,实现无main.storyboard的纯代码开发
- 增加登陆界面,简单的POST请求后台实现登陆校验功能
- 丰富查询API世界杯射手榜功能:
- 实现
UITableView
界面点击cell加载更多球员信息
- 实现
学习记录
ios-oc项目纯代码开发
参考
参考博客: https://www.cnblogs.com/chars/p/5150155.html
简要步骤:
-
新建一个
Single View Application
模板OC项目 -
删除项目文件
Main.storyboard
和LaunchScreen.xlb
(可不做) -
在project配置中,
General -> Deployment Info -> Main Interface
置为空 -
在
AppDelegate.m
中添加代码记得导入
ViewController.h
#import "ViewController.h"
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Override point for customization after application launch. self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; ViewController *viewController = [[ViewController alloc] init]; self.window.rootViewController = viewController; self.window.backgroundColor = [UIColor whiteColor]; [self.window makeKeyAndVisible]; return YES; }
-
这样,你APP的启动页就是
ViewController
控制的界面了
[UI控件] UIAlertView
参考:
参考博客:UIAlertView 和 UIAlertController 的用法
参考博客:如何使用 UIAlertController 实现各种样式的弹窗
实例:
使用 UIAlertView
实现一个简单弹窗:
.m
文件中声明实现UIAlertViewDelegate
协议
@interface NetworkAccessViewController() <UIAlertViewDelegate>
@end
- 创建、配置、展示
UIAlertView
弹窗
// 使用UIAlertView 弹出提示框
// UIAlertView 在 ios 9.0及以后不被推荐使用
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"提示框" message:@"网络访问出现了一点问题" delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil, nil];
[alert show];
效果:(忽略背景色)
[UI控件] UIAlertController
参考:
参考博客:UIAlertView 和 UIAlertController 的用法
参考博客:如何使用 UIAlertController 实现各种样式的弹窗
实例:
使用 UIAlertController
实现一个简单弹窗
// 使用UIAlertController来替换UIAlertView
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"提示框" message:@"网络访问出现了一点问题" preferredStyle:UIAlertControllerStyleAlert];
// 添加按钮的响应事件
UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"好的" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
// Do something
}];
[alert addAction:okAction];
// 弹出提示框
[self presentViewController:alert animated:YES completion:nil];
效果:
[UI控件] UITextField
参考:
常用方法和属性:
// 初始化textfield并设置位置及大小
UITextField *text = [[UITextField alloc]initWithFrame:CGRectMake(20, 20, 130, 30)];
//设置边框样式,只有设置了才会显示边框样式
text.borderStyle = UITextBorderStyleRoundedRect;
//设置输入框的背景颜色
text.backgroundColor = [UIColor whiteColor];
//设置背景
text.background = [UIImage imageNamed:@"dd.png"];
//首字母是否大写
text.autocapitalizationType = UITextAutocapitalizationTypeNone;
//是否纠错
text.autocorrectionType = UITextAutocorrectionTypeNo;
//密语输入
text.secureTextEntry = YES;
//最右侧加图片是以下代码 左侧类似
UIImageView *image=[[UIImageView alloc] initWithImage:[UIImage imageNamed:@"right.png"]];
text.rightView=image;
text.rightViewMode = UITextFieldViewModeAlways;
// 设置提示信息
[usernameTextField setPlaceholder:@"Username"];
实例:
// 用户名输入框
self.usernameTextField = ({
UITextField *usernameTextField = [[UITextField alloc] initWithFrame:CGRectMake(50, 200, self.view.frame.size.width - 100, 50)];
[usernameTextField setBorderStyle:UITextBorderStyleRoundedRect];
[usernameTextField setClearButtonMode:UITextFieldViewModeAlways];
[usernameTextField setAutocorrectionType:UITextAutocorrectionTypeNo];
[usernameTextField setAutocapitalizationType:UITextAutocapitalizationTypeNone];
[usernameTextField setPlaceholder:@"Username"];
usernameTextField;
});
// 添加控件
[self.view addSubview:self.usernameTextField];
效果:
网络访问 POST 请求
流程 :
以发起一个 POST 请求为例子:
- 创建Session。
- 构建可变的Request对象
- 配置Request为POST请求,添加Header头部和Body
- 创建NSURLSessionDataTask。
- 执行Task。
- Response处理。
允许IOS9 HTTP请求限制
用法实例:
以一次 HTTP POST请求为例:
// 构建并配置 Session
NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *delegateFreeSession = [NSURLSession sessionWithConfiguration:defaultConfigObject delegate:nil delegateQueue:[NSOperationQueue mainQueue]];
// 构建并配置 UrlRequest 网络请求
NSURL *url = [NSURL URLWithString:@"http://chenbb6.cn:3010/login"];
NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:url];
// 设置请求方法为 POST
[urlRequest setHTTPMethod:@"POST"];
// 设置请求 Header 头部
NSDictionary *headDict = [[NSDictionary alloc] initWithObjects:@[@"application/json"] forKeys:@[@"Content-Type"]];
[urlRequest setAllHTTPHeaderFields:headDict];
// 设置请求 Body 主体
NSString *username = self.usernameTextField.text;
NSString *password = self.passwordTextField.text;
NSDictionary *bodyDict = [[NSDictionary alloc] initWithObjects:@[username, password] forKeys:@[@"username", @"password"]];
NSData *bodyData = [NSJSONSerialization dataWithJSONObject:bodyDict options:NSJSONWritingPrettyPrinted error:nil];
[urlRequest setHTTPBody:bodyData];
// 构建 NSURLSessionDataTask
NSURLSessionDataTask *dataTask = [delegateFreeSession dataTaskWithRequest:urlRequest completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if(nil == error) {
// Response 处理
}
else {
// Error 处理
}
}];
// 执行 Task
[dataTask resume];
工作记录
项目要求
基于自己搭建的简单后台,继续完成一个简答的网络访问项目 worldcup-demo
- 重构代码,实现无main.storyboard的纯代码开发
- 增加登陆界面,简单的POST请求后台实现登陆校验功能
- 丰富查询API世界杯射手榜功能:
- 实现
UITableView
界面点击cell加载射手榜更多球员信息
- 实现
项目结构
登陆界面
实现步骤:
- UI布局:
- 添加两个
UITextField
和 一个UIButton
- 设置控件属性,设置背景色
- 去除导航栏标题栏
- 添加两个
- 后台响应:
- 添加
UIButton
的响应函数,进行POST网络请求 - POST 请求 Error处理
- 弹窗警示网络故障
- POST请求 Response 处理
- 若服务器返回登陆成功,跳转到主页界面(账号:root,密码:admin,为测试账号)
- 登陆失败,弹窗提示账号密码不匹配
- 添加
参考博客:
参考博客:UIAlertView 和 UIAlertController 的用法
参考博客:如何使用 UIAlertController 实现各种样式的弹窗
ios开发之–NSDictionary和NSData之间的互转/NSString和NSData之间的互转
关键代码:
密码输入框实现:
// 密码输入框
self.passwordTextField = ({
UITextField *passwordTextField = [[UITextField alloc] initWithFrame:CGRectMake(50, 300, self.view.frame.size.width - 100, 50)];
[passwordTextField setBorderStyle:UITextBorderStyleRoundedRect];
[passwordTextField setClearButtonMode:UITextFieldViewModeAlways];
[passwordTextField setAutocorrectionType:UITextAutocorrectionTypeNo];
[passwordTextField setAutocapitalizationType:UITextAutocapitalizationTypeNone];
[passwordTextField setPlaceholder:@"Password"];
[passwordTextField setSecureTextEntry:YES];
passwordTextField;
});
[self.view addSubview:self.passwordTextField];
POST请求设置 Header 头部 和 Body 主体:
// 构建并配置 UrlRequest 网络请求
NSURL *url = [NSURL URLWithString:@"http://chenbb6.cn:3010/login"];
NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:url];
// 设置请求方法为 POST
[urlRequest setHTTPMethod:@"POST"];
// 设置请求 Header 头部
NSDictionary *headDict = [[NSDictionary alloc] initWithObjects:@[@"application/json"] forKeys:@[@"Content-Type"]];
[urlRequest setAllHTTPHeaderFields:headDict];
// 设置请求 Body 主体
NSString *username = self.usernameTextField.text;
NSString *password = self.passwordTextField.text;
NSDictionary *bodyDict = [[NSDictionary alloc] initWithObjects:@[username, password] forKeys:@[@"username", @"password"]];
NSData *bodyData = [NSJSONSerialization dataWithJSONObject:bodyDict options:NSJSONWritingPrettyPrinted error:nil];
[urlRequest setHTTPBody:bodyData];
POST请求中进行Response 和 Error处理:
// 构建 NSURLSessionDataTask
NSURLSessionDataTask *dataTask = [delegateFreeSession dataTaskWithRequest:urlRequest completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
// Response 处理
if(nil == error) {
// NSData 转 NSDictionary
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:nil];
NSNumber *status_code = [dict valueForKey:@"status_code"];
if([status_code intValue] == 200) {
NSLog(@"登录成功");
NetworkAccessViewController *controller = [[NetworkAccessViewController alloc] init];
controller.hidesBottomBarWhenPushed = YES;
[self.navigationController pushViewController:controller animated:YES];
}else {
NSLog(@"登陆失败");
// 使用UIAlertController来替换UIAlertView
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"提示框" message:@"账号密码错误" preferredStyle:UIAlertControllerStyleAlert];
// 添加按钮的响应事件
UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"重试一次" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
// Do something
}];
[alert addAction:okAction];
// 弹出提示框
[self presentViewController:alert animated:YES completion:nil];
}
}
else {
// 使用UIAlertController来替换UIAlertView
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"提示框" message:@"网络访问出现了一点问题" preferredStyle:UIAlertControllerStyleAlert];
// 添加按钮的响应事件
UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"好的" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
// Do somethin
}];
[alert addAction:okAction];
// 弹出提示框
[self presentViewController:alert animated:YES completion:nil];
}
}];
实现效果:
网络错误或服务器错误:
账号密码错误:
正确账号登陆:(username=root, password = admin)
丰富射手榜界面
实现步骤:
- 实现一个
LoadingTableViewCell
类——继承于UITableViewCell
- 三种Cell状态:加载更多、正在加载中、加载完毕
- 两个子控件:
UILabel
+UIActivityIndicatorView
- 设置三种Cell状态下,
UILabel
的文字、位置和UIActivityIndicatorView
的位置、动画起停
- 修改射手榜界面
- 修改
UITableViewDataSource
的协议实现,一次展示射手榜十位运动员信息+一个加载Cell - 设置点击加载Cell的响应函数,每点击一次,添加其后10位运动员的信息到
UITableView
,加载Cell状态动态变换,直到加载完毕。
- 修改
参考博客:
参考博客:UITableView与自定义UITableViewCell
iOS layoutSubview的方法总结/重绘drawRect
关键代码:
自定义的加载Cell:
@implementation LoadingTableViewCell
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
self.tipsLabel = [[UILabel alloc] initWithFrame:CGRectZero];
[self.contentView addSubview:self.tipsLabel];
self.indicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[self.contentView addSubview:self.indicatorView];
[self updateData];
}
return self;
}
- (void)layoutSubviews {
[self.tipsLabel sizeToFit];
// 居中显示 Cell
if (LoadingStatusLoding == self.status) {
CGFloat indicatorWidth = self.indicatorView.frame.size.width;
CGFloat labelWidth = self.tipsLabel.frame.size.width;
CGFloat space = 5;
CGFloat leftMargin = (self.bounds.size.width-indicatorWidth-space-labelWidth)/2;
[self.tipsLabel setCenter:CGPointMake(leftMargin + labelWidth/2, self.bounds.size.height/2)];
[self.indicatorView setCenter:CGPointMake(leftMargin + labelWidth + space + indicatorWidth/2, self.bounds.size.height/2)];
} else{
[self.tipsLabel setCenter:CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2)];
}
}
-(void)setStatus:(LoadingStatus)status {
if(_status == status) {
return;
}
_status = status;
[self updateData];
[self setNeedsLayout];
}
- (void)updateData {
if (LoadingStatusDefault == _status) {
[self.tipsLabel setText:@"点击加载更多"];
[self.indicatorView stopAnimating];
} else if (LoadingStatusLoding == _status) {
[self.tipsLabel setText:@"正在加载..."];
[self.indicatorView startAnimating];
} else if (LoadingStatusNoMore == _status) {
[self.tipsLabel setText:@"没有更多数据了"];
[self.indicatorView stopAnimating];
}
}
@end
UITableViewDataSource
协议实现(关键代码)
- (UITableViewCell *)tableView: (UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
BOOL isLoadingCell = (indexPath.section == self.showCount);
NSString * cellID = [NSString stringWithFormat:@"cellID:%d", isLoadingCell];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID];
if(nil == cell) {
if(isLoadingCell) {
// 创建 loading cell
cell = [[LoadingTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellID];
}
else {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier: cellID];
}
}
// loading cell 加载
if(isLoadingCell) {
// 设置loading cell 的状态
[(LoadingTableViewCell *)cell setStatus:self.status];
// 是否可点击
[cell setSelectionStyle:((self.status == LoadingStatusDefault) ? UITableViewCellSelectionStyleDefault : UITableViewCellSelectionStyleNone)];
}
else {
NSDictionary *playerData = self.topScorerList[indexPath.section];
if(indexPath.row == 0) {
cell.textLabel.text = [NSString stringWithFormat:@"球员:%@", playerData[@"player_name"]];
}
else if(indexPath.row == 1) {
cell.textLabel.text = [NSString stringWithFormat:@"国家:%@", playerData[@"team_name"]];
}
else if(indexPath.row == 2) {
cell.textLabel.text = [NSString stringWithFormat:@"进球数:%@", playerData[@"goals"]];
}
else if(indexPath.row == 3) {
cell.textLabel.text = [NSString stringWithFormat:@"助攻数:%@", playerData[@"assists"]];
}
}
return cell;
}
UITableViewDelegate
协议实现:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(@"%zd-%zd", indexPath.section, indexPath.row);
BOOL isLoadingCell = (indexPath.section == self.showCount);
if(isLoadingCell) {
self.status = LoadingStatusLoding;
[tableView reloadData];
NSLog(@"isloading");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)1*NSEC_PER_SEC), dispatch_get_main_queue(),^{
if(self.showCount + 10 <= self.topScorerList.count) {
self.showCount += 10;
self.status = LoadingStatusDefault;
} else {
self.status = LoadingStatusNoMore;
}
[tableView reloadData];
NSLog(@"load finish");
});
}
}
实现效果:
遇到的问题
1.设置界面背景色为自定义RGB颜色
解决方法:
假如要设置背景颜色的RGB为:(51, 204, 255):
// 设置背景颜色
[self.view setBackgroundColor:[UIColor colorWithRed:51/255.0 green:204/255.0 blue:255/255.0 alpha:1]];
2.UI控件如何居中显示
解决方法:
提前计算好位置距离,例如有两个控件横向并排,一起居中显示:
// 控件 UIActivityIndicatorView 宽度
CGFloat indicatorWidth = self.indicatorView.frame.size.width;
// 控件 UILabel 宽度
CGFloat labelWidth = self.tipsLabel.frame.size.width;
// 控件中间间距
CGFloat space = 5;
// 计算好离 parent view 的 左间距 left margin
CGFloat leftMargin = (self.bounds.size.width-indicatorWidth-space-labelWidth)/2;
// 设置控件中心点实现居中
[self.tipsLabel setCenter:CGPointMake(leftMargin + labelWidth/2, self.bounds.size.height/2)];
[self.indicatorView setCenter:CGPointMake(leftMargin + labelWidth + space + indicatorWidth/2, self.bounds.size.height/2)];
3.输入框点击键盘以外的地方,如何收起键盘
解决方法:(重写 touchesBegan
方法)
// 点击键盘以外的地方收起键盘
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self.view endEditing:YES];
}
4.导航栏去除导航标题,并解决跳转后无法恢复导航标题的问题
解决方法:重写 viewWillAppear
方法
- (void)viewWillAppear:(BOOL)animated {
// 隐藏导航栏
[self.navigationController setNavigationBarHidden:YES animated:YES];
}
5.如何生成一个 Json 格式的 NSData 用于 POST 请求
解决方法:使用 NSDictionary
转换成 NSData
例如要封装的 Json
格式为:
{
"username":"root",
"password":"admin"
}
NSDictionary *bodyDict = [[NSDictionary alloc] initWithObjects:@[@"root", @"admin"] forKeys:@[@"username", @"password"]];
NSData *bodyData = [NSJSONSerialization dataWithJSONObject:bodyDict options:NSJSONWritingPrettyPrinted error:nil];
总结
- 这一周因为是自习,并没有老师过来上课,所以有时间可以温习巩固一下前面的知识点。
- 首先开始是尝试纯代码开发重构上一周做的网络访问demo。
- 然后是学习POST请求,由于之前项目有写一个简单的服务器端,处理登陆注册请求,所以干脆写了一个登陆、注册界面。但由于对IOS的界面View刷新机制还不太熟悉,也有可能是因为虚拟机卡顿的原因导致界面view刷新延迟的bug还未能修复,因此注册功能就暂时未能实现。另外这次是单纯用
Session
,Request
去简单网络访问,下周看看能不能重构使用AFNetWorking
第三方网络访问框架。 - 然后是学习自定义的
UITableViewCell
实现射手榜一个自定义的Cell样式,实现点击加载更多的功能。初步了解自定义子View过后,打算下周将 球员信息Cell 也进行自定义调整, 球员信息不单单一行行展示出来,而是使用自定义的一个UITableViewCell
来展示球员头像图片+数据等信息,当然这可能还涉及到UIImageView
异步加载网络图片的问题,或许可以考虑SDWebImage
图片加载框架,这又需要进一步去学习。