@(iOS 项目实战)[项目实战]
- 作者: Liwx
- 邮箱: 1032282633@qq.com
目录
- 09.项目实战 百思不得姐 设置帖子圆形头像,最热评论处理,修复重复点击bug,刷新控件的使用.
- 1.设置帖子头像
- SDWebImage图片下载失败处理
- Xcode插件安装路径
- KSImageName添加自定义方法提示功能
- 2.最热评论
- 最热评论
- 3.拖动触发按钮重复点击bug
- 4.苹果官方刷新控件
- UIRefreshControl刷新控件
- 5.使用MJRefresh刷新框架
- MJRefresh的使用
- 补充
- AFNetworking的使用
- 使用第三方框架时子类化
- github使用
- 真机测试问题
1.设置帖子头像
设置思路: 项目中多处用到设置圆形图片的功能,考虑
封装生成圆形图片的分类UIImage+Circle
.因为头像图片资源都是通过url获取,所以可以封装设置圆形头像分类
,将使用SDWebImage获取图片的操作也封装到设置圆形头像分类中UIImageView+Header
.
SDWebImage图片下载失败处理
-
图片下载失败处理
- 图片下载失败时存在问题: 如果图片下载失败时,image为nil,又将nil赋值给UIImageView,
会出现图片下载失败时,显示为空白,连占位图片都不显示
. - 解决方案: 在
使用sd_setImageWithURL:方法加载图
片的时候,判断SDWebImage获取回来的image是否为nil
,如果为nil,直接返回. - 参考代码
// 2.加载图片 [self sd_setImageWithURL:[NSURL URLWithString:url] placeholderImage:placeholderImage completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) { // 2.1 如果图片获取失败,直接退出,否则会出现图片加载失败,占位图片不显示 if (image == nil) return; // 2.2 图片加载成功,显示图片 self.image = image; }];
- 图片下载失败时存在问题: 如果图片下载失败时,image为nil,又将nil赋值给UIImageView,
-
圆形头像处理
- 封装
生成圆形图片UIImage+Circle
分类- 提供生成圆形图片的
对象方法wx_circleImage
方法. - 提供通过
图片名
快速生成圆形图片的类方法wx_circleImageName:
方法.
- 提供生成圆形图片的
// ---------------------------------------------------------------------------- // 生成圆形图片 - (instancetype)wx_circleImage { // 1.开启图形上下文 UIGraphicsBeginImageContextWithOptions(self.size, NO, 0); // 2.描述裁减路径 UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, self.size.width, self.size.height)]; // 3.设置裁减路径 [path addClip]; // 4.绘制图片 [self drawAtPoint:CGPointZero]; // 5.生成新图片 UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); // 6.关闭图形上下文 UIGraphicsEndImageContext(); return image; } // ---------------------------------------------------------------------------- // 通过图片名生成圆形图片 + (instancetype)wx_circleImageName:(NSString *)imageName { return [[UIImage imageNamed:imageName] wx_circleImage]; }
- 设置圆形头像分类
UIImageView+Header
- 设置
圆形占位图片
. - 将使用
SDWebImage获取图片的操作封装到设置圆形头像的分类
中.
- 设置
// ---------------------------------------------------------------------------- // 设置圆形头像 - (void)wx_setHeader:(NSString *)url { // 1.创建占位图片 UIImage *placeholderImage = [UIImage wx_circleImageName:@"defaultUserIcon"]; // 2.加载图片 [self sd_setImageWithURL:[NSURL URLWithString:url] placeholderImage:placeholderImage completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) { // 2.1 如果图片获取失败,直接退出,否则会出现图片加载失败,占位图片不显示 if (image == nil) return; // 2.2 图片加载成功,显示图片 self.image = [image wx_circleImage]; }]; }
- 封装
Xcode插件安装路径
- Xcode插件安装路径
- 路径一:
/Users/用户名/Library/Application Support/Developer/Shared/Xcode/Plug-ins
- 路径二:
/Users/用户名/Library/Developer/Xcode/Plug-ins
- 路径一:
KSImageName添加自定义方法提示功能
- 1.查找插件安装路径
获取KSImageName在Xcode7的安装路径(
/Users/用户名/Library/Developer/Xcode/Plug-ins
).-
选中KSImageNamed.ideplugin,右击显示包内容,打开/Contents/Resources/Completions.plist文件,在
Completions.plist
文件中进行添加配置.- 复制插件默认的提示imageName选项,复制item0.
- 将自定义方法添加到新增的item中.
-
注意
: 修改KSImageNamed的plist配置文件,其中有个配置方法中左边有个空格
.
Completions.plist
文件配置图解
-
2.在KSImageName源码中修改配置
- 从github下载KSImageName的项目,在源码中修改配置.
3.如果
找不到资源库
是因为本地化文件.localized
被删除了,可以从其他文件夹中复制一份到Library中即可重新显示资源库
.注意:.localized
文件是隐藏文件,要确保Mac电脑已经开启显示隐藏文件选项.
-
4.设置帖子头像为圆形头像
- 在WXTopicCell.m的模型的set方法
setTopicItem:
方法中设置圆形头像.
// 重写模型的set方法 - (void)setTopic:(XMGTopic *)topic { // 设置帖子圆形头像 [self.profileImageView xmg_setHeader:topic.profile_image]; // 其他设置... }
- 在WXTopicCell.m的模型的set方法
2.最热评论
分析: 如果服务器返回的数据有最热评论有数据,则需要显示最热评论,否则无需显示最热评论。最热评论中包含语音评论,需对语音评论处理。
最热评论
-
最热评论请求数据分析
- 请求所需参数字段(a, c, type)
- 最热评论所需数据: 用户名(
user.username
) + 评论内容(content
).
/**
// 服务器返回的JSON数据,其中包含用户名 + 评论内容
{
content = "\U4e94\U767e\U5e74\U524d\U4e00\U4eba\U8e0f\U5e73\U5929\U754c\Uff0c\U4eca\U5929\U5341\U4e09\U4ebf\U4eba\U5e2e\U4f60\U8e0f\U5e73\U6625\U665a\U3002";
ctime = "2016-01-28 16:54:32";
"data_id" = 17032894;
id = 42164160;
"like_count" = 678;
precid = 0;
precmt = (
);
preuid = 0;
status = 0;
user = {
id = 16413351;
"is_vip" = 0;
"personal_page" = "http://user.qzone.qq.com/DFA2DC83F91D296282FD922FA4C45181";
"profile_image" = "http://qzapp.qlogo.cn/qzapp/100336987/DFA2DC83F91D296282FD922FA4C45181/100";
"qq_uid" = "";
"qzone_uid" = DFA2DC83F91D296282FD922FA4C45181;
sex = f;
username = "\U3001Just .";
"weibo_uid" = "";
};
voicetime = 0;
voiceuri = "";
}
*/
1.给WXTopicItem模型添加最热评论属性.(
NSArray *top_cmt;
)-
2.在模型的
set方法setTopicItem:
处理最热评论(隐藏/显示).- 最热评论: 用户名 + 评论内容
- 最热评论处理参考代码
// ---------------------------------------------------------------------------- // 设置最热评论 - (void)setTopCmt { // 1.获取最热评论数据,返回数据为字典,只显示第一条,只需取出第一条即可 NSDictionary *dict = self.topicItem.top_cmt.firstObject; // 2.判断是否有最热评论数据 if (dict) { // 1.获取最热评论 用户名 + 评论内容 NSString *username = dict[@"user"][@"username"]; NSString *content = dict[@"content"]; // 判断评论内容是否为空串,空串是语音评论 if (content.length == 0) { content = @"[语音评论]"; } self.topCmtContentLabel.text = [NSString stringWithFormat:@"%@ : %@", username, content]; self.topCmtView.hidden = NO; } else { self.topCmtContentLabel.text = @""; self.topCmtView.hidden = YES; } }
-
注意: 判断
数组有没有内容用count方法
,判断字符串是不是空串,用length方法
.- 错误写法
// 如果判断数组有没有内容,不能使用下面的判断 NSArray *array = @[@"abc", @"cdf"]; if (array != nil) { } if (array) { } // 如果判断字符串是不是空串,不能使用下面的判断 NSString *content = @"abcdef"; if (content != nil) { }
3.拖动触发按钮重复点击bug
-
拖动scrollView时,没滚动到其他界面,还是在原来界面时,又会触发按钮重复点击.
- bug重现
原因: 在监听scrollView停止拖动的scrollViewDidEndDecelerating:方法中调用了titleButtonClick:方法,导致只要用户稍微左右滑动的时候就触发标题按钮重复点击.
解决方案: 抽取titleButtonClick:方法在
处理标题按钮、下划线、scrollView
,封装dealTitleButtonClick:
方法.在scrollViewDidEndDecelerating:方法中调用了dealTitleButtonClick:
方法即可.-
1.在标题按钮监听的方法中,处理按钮,下划线滚动,scrollView滚动触发标题按钮重复点击
- 重复点击标题按钮才发通知
- 拖动时不需要发通知
// ---------------------------------------------------------------------------- // 处理标题按钮、下划线、scrollView - (void)dealTitleButtonClick:(WXTitleButton *)button { // 切换中状态 self.selectedButton.selected = NO; button.selected = YES; self.selectedButton = button; // 1.获取索引,按钮的tag值 NSInteger index = button.tag; // 2.执行下划线动画,动画执行完成修改scrollView的偏移量,显示对应子控制器的view [UIView animateWithDuration:0.25 animations:^{ // TODO: 设置下划线的宽度和中心点 self.underLineView.wx_width = button.titleLabel.wx_width; self.underLineView.wx_centerX = button.wx_centerX; // 切换到对应的view self.scrollView.contentOffset = CGPointMake(self.scrollView.wx_width * index, self.scrollView.contentOffset.y); } completion:^(BOOL finished) { // 更新偏移量 CGPoint offset = self.scrollView.contentOffset; offset.x = index * self.scrollView.wx_width; [self.scrollView setContentOffset:offset]; // 添加对应子控制器的view [self addChildVcViewIntoScrollView:index]; }]; } #pragma ======================================================================= #pragma mark - UIScrollViewDelegate代理方法 - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { // 1.获取索引 NSInteger index = self.scrollView.contentOffset.x / self.scrollView.wx_width; // 2.根据索引获取按钮 WXTitleButton *titleButton = self.titleView.subviews[index]; // 3.调用按钮的点击事件 [self dealTitleButtonClick:titleButton]; }
-
2.重复点击标题按钮下拉刷新
- 设置偏移量将
header偏移到titleView下面
. - 连续重复点击时,如果
正在刷新,就不需要再刷新
. - 重复点击标题按钮代码参考
// ---------------------------------------------------------------------------- // 监听tabBarButton重复点击通知 - (void)tabBarButtonDidRepeatClick { // ------------------------------------------------------------------------ // 1.判断控制器的view有没有在window上,有没有和window重叠 // 如果控制器的view不在window上,则直接返回 if (self.view.window == nil) return; // 如果控制器的view没有和window重叠,则直接返回 if (![self.view wx_intersectWithView:nil]) return; // ------------------------------------------------------------------------ // 2.重复点击,执行下拉刷新 // 2.1 判断当前是否在刷新,如果正在刷新直接退出 if (self.isHeaderRefreshing) return; // 2.2 更新为刷新状态 self.headerRefreshing = YES; self.headerLabel.text = @"正在刷新数据..."; self.headerLabel.backgroundColor = [UIColor greenColor]; // 2.3 设置内边距和偏移量,让header处于titleView的下面 [UIView animateWithDuration:0.25 animations:^{ // 2.3.1 修改内边距,增加顶部内边距 UIEdgeInsets inset = self.tableView.contentInset; inset.top += self.header.wx_height; self.tableView.contentInset = inset; // 2.3.2 修改偏移量 CGPoint offset = self.tableView.contentOffset; offset.y = -(WXNavMaxY + WXTitlesViewH + self.header.wx_height); [self.tableView setContentOffset:offset]; }]; // ------------------------------------------------------------------------ // 3.请求数据, 延迟模拟 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [self loadNewTopics]; }); }
- 设置偏移量将
- 最好考虑footer用
addSubview添加
,不用tableViewFooterView
,不要占用tableViewFooterView.
4.苹果官方刷新控件
UIRefreshControl刷新控件
UIRefreshControl
继承自UIControl
,可以使用addTarget方法监听ValueChange事件
.手动调用begin方法,不会触发ValueChange
使用系统的刷新控件
UIRefreshControl控件有个bug
,在UIRefreshControl的刷新指示器正在刷新时切换到其他控制器
,再返回控制器,会出现刷新指示器停止动画
了.
// TODO: 1.使用系统的刷新控件UIRefreshControl, UIRefreshControl控件有个问题,在UIRefreshControl的刷新指示器
// 正在刷新时切换到其他控制器,再返回控制器,会出现刷新指示器停止动画了.
UIRefreshControl *header = [[UIRefreshControl alloc] init];
[header addTarget:self action:@selector(loadNewTopics) forControlEvents:UIControlEventValueChanged];
[self.tableView addSubview:header];
self.header = header;
5.使用MJRefresh刷新框架
MJRefresh的使用
-
MJRefresh的类结构图
- MJRefresh刷新框架中上拉刷新有2个类,下拉刷新有4个类可以使用.
- 上拉刷新: MJRefreshNormalHeader, MJRefreshGifHeader
- 下拉刷新: MJRefreshBackNormalFooter, MJRefreshBackGifFooter,MJRefreshAutoNormalFooter,MJRefreshAutoGifFooter
- MJRefresh刷新框架中上拉刷新有2个类,下拉刷新有4个类可以使用.
-
下拉scrollView时或程序进入时开始刷新,当请求完成/失败时结束刷新.
- 开始刷新: [self.tableView.mj_header beginRefreshing];
- 结束刷新: [self.tableView.mj_footer endRefreshing];
-
统一设置下拉刷新显示内容.
- 使用继承MJRefreshNormalHeader的自定义类WXRefreshHeader.
- 使用重写框架的
prepare方法
或系统的initWithFrame方法初始化
.封装下拉控件的属性设置
,无需每个地方都设置. - 使用方式,只需导入自定义类WXRefreshHeader.h,设置tableView的mj_header属性为自定义类WXRefreshHeader即可
// WXRefreshHeader.m
// ----------------------------------------------------------------------------
// 初始化子控件
- (void)prepare
{
[super prepare];
// ------------------------------------------------------------------------
// 1.设置刷新控件属性
// 1.1 设置自动改变透明度
self.automaticallyChangeAlpha = YES;
// 1.2 设置隐藏时间
self.lastUpdatedTimeLabel.hidden = YES;
// 1.3 设置状态文字颜色
self.stateLabel.textColor = [UIColor orangeColor];
// ------------------------------------------------------------------------
// 2.设置刷新状态文字
[self setTitle:@"下拉刷新" forState:MJRefreshStateIdle];
[self setTitle:@"松开🐴上刷新" forState:MJRefreshStatePulling];
[self setTitle:@"正在玩命刷新中..." forState:MJRefreshStateRefreshing];
// ------------------------------------------------------------------------
// 添加loginImageView
UIImageView *loginImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"MainTitle"]];
[self addSubview:loginImageView];
self.loginImageView = loginImageView;
}
// ------------------------------------------------------------------------
// 使用方式, 设置为tableView的mj_header属性,下拉刷新调用loadNewTopics方法加载网络数据.
self.tableView.mj_header = [WXRefreshHeader headerWithRefreshingTarget:self refreshingAction:@selector(loadNewTopics)];
// 开始刷新
[self.tableView.mj_header beginRefreshing];
-
添加下拉刷新显示公司logo图片
- 在
placeSubviews
或layoutSubviews
中重新布局loginImageView
// ---------------------------------------------------------------------------- // 布局子控件 - (void)placeSubviews { [super placeSubviews]; // ------------------------------------------------------------------------ // 布局loginImageView self.loginImageView.wx_centerX = self.wx_width * 0.5; self.loginImageView.wx_centerY = - self.loginImageView.wx_height; }
- 在
补充
写UI框架考虑控件成为属性
-
NS_REQUIRES_SUPER
: 如果在方法后面使用NS_REQUIRES_SUPER
,该方法重写时必须调用父类的方法,如果没有调用父类方法[super prepare]
会有警告.// 如果在方法后面使用NS_REQUIRES_SUPER,该方法重写时必须调用父类的方法 - (void)prepare NS_REQUIRES_SUPER;
AFNetworking的使用
-
自定义继承AFHTTPSessionManager的子类WXHTTPSessionManager
- 在子类中统一设置请求会话管理者的属性
- 重写
initWithBaseURL:sessionConfiguration:
方法,在方法中对会话管理者进行设置. - 设置请求头,在请求头可以添加设备类型和系统版本方法,参考代码如下所示
// ---------------------------------------------------------------------------- // 初始化设置会话管理者 - (instancetype)initWithBaseURL:(NSURL *)url sessionConfiguration:(NSURLSessionConfiguration *)configuration { if (self = [super initWithBaseURL:url sessionConfiguration:configuration]) { // 设置请求头 [self.requestSerializer setValue:@"iPhone 6s" forHTTPHeaderField:@"device"]; [self.requestSerializer setValue:@"9.2" forHTTPHeaderField:@"version"]; } return self; }
-
使用自定义类WXHTTPSessionManager执行网络请求,用法和AFHTTPSessionManager一样.
-
查看请求头信息
self.mgr.requestSerializer.HTTPRequestHeaders
-
使用第三方框架时子类化
使用第三方框架时如果能子类化,最好子类化封装第三方框架,这样如果第三方框架更新,只需更改自定义封装的子类即可.
github使用
- Pull requests: 别人提交
- 紫色代表有整合进去
- 绿色代表有待审核
- 红色代表没有处理
- 点击蓝色线,查看使用的编程语言
点击后显示使用语言的比例
CocoaPods如果没指定版本号,默认是最新的版本.
例如:pod 'AFNetworking'
-
第三方框架
.podspec文件
里面描述最新版本等信息.- version表示版本号.
Pod::Spec.new do |s| s.name = 'AFNetworking' s.version = '3.0.4' s.license = 'MIT' s.summary = 'A delightful iOS and OS X networking framework.' s.homepage = 'https://github.com/AFNetworking/AFNetworking' s.social_media_url = 'https://twitter.com/AFNetworking' s.authors = { 'Mattt Thompson' => 'm@mattt.me' } s.source = { :git => 'https://github.com/AFNetworking/AFNetworking.git', :tag => s.version, :submodules => true } s.requires_arc = true s.public_header_files = 'AFNetworking/AFNetworking.h' s.source_files = 'AFNetworking/AFNetworking.h'
-
github下载指定版本
- 在release中下载指定版本
真机测试问题
- 登录界面一点程序崩溃
- 在精华控制器点击顶部状态栏区域,没反应,模拟器有反应
- 推荐标签订阅按钮不好点