@(iOS 项目实战)[项目实战]
- 作者: Liwx
- 邮箱: 1032282633@qq.com
目录
- 1.设置TabBarItem标题颜色,文字大小
- 使用appearance统一设置TabBarItem的颜色
- 2.设置导航条按钮
- 抽取UIBarButtonItem分类
- 3.统一设置导航条标题字体和背景图片
- 4.统一设置返回按钮
- 设置返回按钮的位置,调整返回按钮的边距
- 跳转控制器出现黑色卡顿现象解决方案
- 5.设置手势滑动返回
- 边缘滑动返回
- 全屏滑动返回
- 6.广告界面的搭建
- 广告界面xib搭建
- 7.广告界面的功能实现
- 广告界面第三方框架AFNetworking,MJExtension,SDWebImage的使用
- 获取广告数据
- 跳过按钮注意点
- 8.CocoaPods的介绍
1.设置TabBarItem标题颜色,文字大小
使用appearance统一设置TabBarItem的颜色
- 只要属性有UI_APPEARANCE_SELECTOR这个宏描述,就可以使用UIAppearance设置
// ----------------------------------------------------------------------------
// 加载类进内存的时候调用,只会调用一次(子类不会加载的适合不再加载父类的load方法)
+ (void)load
{
// 谁才能使用appearance?只要遵守了这个UIAppearance协议,就能调用appearance
// 注意:UIAppearance并不是所有属性都能设置
// 哪些属性可以通过UIAppearance设置?只要属性有UI_APPEARANCE_SELECTOR这个宏描述,就可以使用UIAppearance设置
// UIAppearance使用场景
// 一次性设置tabBarItem字体颜色
// ------------------------------------------------------------------------
// 1.判断是否是WXTabBarController类
// 1.1 获取item
UITabBarItem *item = [UITabBarItem appearanceWhenContainedIn:self, nil];
// 1.2 设置
NSDictionary *attrSelected = @{NSForegroundColorAttributeName : [UIColor blackColor]};
[item setTitleTextAttributes:attrSelected forState:UIControlStateSelected];
// ----------------------------------------------------------------------------
// 2.设置普通状态下的TabBar字体大小, 注意:一定要先设置正常状态下字体大小
NSDictionary *attrNormal = @{NSFontAttributeName : [UIFont systemFontOfSize:13]};
[item setTitleTextAttributes:attrNormal forState:UIControlStateNormal];
}
2.设置导航条按钮
多个控制器设置导航条按钮都要设置UIBarButtonItem,所有抽取设置UIBarButtonItem信息的分类
抽取UIBarButtonItem分类
-
导航条的按钮的点击范围大
,点击按钮以外的区域也会触发点击事件- 解决方案,
用一个UIView对按钮进行包装
- 解决方案,
3.统一设置导航条标题字体和背景图片
- 设置导航条标题字体/背景图片
// ----------------------------------------------------------------------------
// 第一次加载到内存调用,只会调用一次,在load方法中统一设置导航条的背景图片和标题文字大小
+ (void)load
{
// 1.获取全局的导航条
UINavigationBar *navBar = [UINavigationBar appearanceWhenContainedIn:self, nil];
// 2.设置导航条标题文字大小
NSDictionary *titleAttr = @{NSFontAttributeName : [UIFont systemFontOfSize:20]};
[navBar setTitleTextAttributes:titleAttr];
// 3.设置导航条背景图片:一定要是UIBarMetricsDefault
// iOS8和iOS9适配: iOS9之前:UIBarMetricsDefault,导航控制器跟控制器的view尺寸会减少64,iOS9就没有减少64了
[navBar setBackgroundImage:[UIImage imageNamed:@"navigationbarBackgroundWhite"] forBarMetrics:UIBarMetricsDefault];
}
4.统一设置返回按钮
如果通过自定义导航条返回按钮,则导航控制器默认的边缘滑动返回效果失效.
设置返回按钮的位置,调整返回按钮的边距
- 要统一设置返回按钮,重写push方法
- 要调整导航条的按钮位置,可以设置按钮的内边距
backButton.contentEdgeInsets = UIEdgeInsetsMake(0, -20, 0, 0); - 设置内边距之前,需
先设置按钮的尺寸
.否则位置会有偏差.[backButton sizeToFit];
- 要调整导航条的按钮位置,可以设置按钮的内边距
// ----------------------------------------------------------------------------
// 重写pushViewController:方法,在跳转前统一设置返回按钮
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
// ------------------------------------------------------------------------
// 1.判断如果不是根控制器,则设置viewController控制器返回按钮
if (self.childViewControllers.count > 0) {
viewController.navigationItem.leftBarButtonItem = [UIBarButtonItem backItemWithImage:[UIImage imageNamed:@"navigationButtonReturn"] highImage:[UIImage imageNamed:@"navigationButtonReturnClick"] target:self action:@selector(back) title:@"返回"];
}
[super pushViewController:viewController animated:animated];
}
跳转控制器出现黑色卡顿现象解决方案
- 如果push目标控制器的view的背景色为透明就会出现卡顿现象
- 只要将要push的目标控制器view的背景色设置为其他颜色即可解决.
5.设置手势滑动返回
边缘滑动返回
- 边缘滑动返回实现方式一,清空代理(该方法不可行)
// 如果直接将系统手势清空,会有返回效果,但是在根控制器边缘滑动会导致出现假死状态,该方法不可行
self.interactivePopGestureRecognizer.delegate = nil;
- 边缘滑动返回实现方式二,设置导航控制器为系统手势代理,只通过gestureRecognizer代理方法返回手势只在非根控制器滑动有效.
- (void)viewDidLoad {
[super viewDidLoad];
// // 恢复系统手势边缘滑动返回方式二(可行)
self.interactivePopGestureRecognizer.delegate = self;
}
// ----------------------------------------------------------------------------
// 监听系统滑动手势,如果返回YES表示
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
// 设置只有在非根控制器滑动有效,因为如果根控制器滑动如果触发手势会调用pop,根控制器不能再pop,会导致假死.
return self.childViewControllers.count > 1;
}
全屏滑动返回
- 全屏滑动思路:
只要触发UIScreenEdgePanGestureRecognizer,就会调用_UINavigationInteractiveTransition的handleNavigationTransition:
_UINavigationInteractiveTransition的handleNavigationTransition有滑动返回功能
- 全屏滑动返回思路:
为什么导航控制器只能边缘触发手势 -> 打印系统手势分析 -> 添加Pan手势
-> pan有滑动返回功能
系统手势打印结果:
NSLog(@"%@", self.interactivePopGestureRecognizer);
<UIScreenEdgePanGestureRecognizer: 0x7feab3e30c40; state = Possible; delaysTouchesBegan = YES; view = <UILayoutContainerView 0x7feab3c8fb10>; target= <(action=handleNavigationTransition:, target=<_UINavigationInteractiveTransition 0x7feab3e306d0>)>>
系统手势代理打印结果:
NSLog(@"%@", self.interactivePopGestureRecognizer.delegate);
<_UINavigationInteractiveTransition: 0x7feab3e306d0>
打印结果分析: 系统手势代理是
-
经以上验证可得
- UIScreenEdgePanGestureRecognizer:边缘滑动手势
- UIPanGestureRecognizer:全屏,整个范围
- target:_UINavigationInteractiveTransition
- action=handleNavigationTransition:
-
添加全屏滑动返回手势功能实现方案
- 添加Pan拖动手势, Pan手势监听到拖动手势,调用
target(代理self.interactivePopGestureRecognizer.delegate)
:_UINavigationInteractiveTransition 的handleNavigationTransition:
方法. - 取消系统的手势
self.interactivePopGestureRecognizer.enabled = NO;
.
- 添加Pan拖动手势, Pan手势监听到拖动手势,调用
全屏滑动返回实现参考代码
#pragma =======================================================================
#pragma mark - 设置滑动返回手势
- (void)viewDidLoad {
[super viewDidLoad];
// 恢复系统手势边缘滑动返回方式一(不可行)
// 如果直接将系统手势清空,会有返回效果,但是在根控制器边缘滑动会导致出现假死状态,该方法不可行
// self.interactivePopGestureRecognizer.delegate = nil;
// // 恢复系统手势边缘滑动返回方式二(可行)
// self.interactivePopGestureRecognizer.delegate = self;
// 添加全屏滑动手势
/**
系统手势打印结果:
NSLog(@"%@", self.interactivePopGestureRecognizer);
<UIScreenEdgePanGestureRecognizer: 0x7feab3e30c40; state = Possible; delaysTouchesBegan = YES; view = <UILayoutContainerView 0x7feab3c8fb10>; target= <(action=handleNavigationTransition:, target=<_UINavigationInteractiveTransition 0x7feab3e306d0>)>>
系统手势代理打印结果:
NSLog(@"%@", self.interactivePopGestureRecognizer.delegate);
<_UINavigationInteractiveTransition: 0x7feab3e306d0>
打印结果分析: 系统手势代理是
*/
// 1.获取方法的调用者
id target = self.interactivePopGestureRecognizer.delegate;
// 2.给系统的view添加手势,监听到手势调用原来系统手势代理调用的方法
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:target action:@selector(handleNavigationTransition:)];
// 2.1 设置手势代理,用于在代理方法中设置非控制器才能滑动返回
pan.delegate = self;
[self.view addGestureRecognizer:pan];
// 3.取消系统手势
self.interactivePopGestureRecognizer.enabled = NO;
}
// ----------------------------------------------------------------------------
// 监听系统滑动手势,如果返回YES表示允许滑动手势
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
// 设置只有在非根控制器滑动有效,因为如果根控制器滑动如果触发手势会调用pop,根控制器不能再pop,会导致假死.
return self.childViewControllers.count > 1;
}
6.广告界面的搭建
广告界面xib搭建
- 启动完成添加广告界面
- 直接设置窗口的根控制器为广告控制器
- 用xib创建广告界面
- 确保按钮和广告图片的层级结构,采用
占位视图思想
.添加一个全屏的view和一个按钮 - 广告界面xib布局如图所示
- 确保按钮和广告图片的层级结构,采用
7.广告界面的功能实现
广告界面第三方框架AFNetworking,MJExtension,SDWebImage的使用
-
完整的URL
完整的URL = 基本URL + 参数
-
AFNetworking发送GET请求获取text/html类型的数据
-
设置支持响应体text/html类型的数据解析
- 方式一(不建议直接修改AFN框架源码): 直接
修改AFURLResponseSerialization.m文件中支持text/html类型的数据
,在init方法
中添加支持@"text/html"
self.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript", @"text/html", nil];
- 方式二: 外部设置支持响应体text/html类型的数据解析
// 1.创建请求回话管理者 AFHTTPSessionManager *manager = [AFHTTPSessionManager manager]; // ------------------------------------------------------------------------ // 2. 设置响应体的数据格式,添加@"text/html" AFJSONResponseSerializer *serializer = [AFJSONResponseSerializer serializer]; serializer.acceptableContentTypes = [NSSet setWithObjects:@"text/html", nil]; manager.responseSerializer = serializer;
- 方式一(不建议直接修改AFN框架源码): 直接
-
AFNetworking发送GET请求获取text/html类型的数据核心代码
// ------------------------------------------------------------------------
// 1.创建请求回话管理者
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
// ------------------------------------------------------------------------
// 2. 设置响应体的数据格式,添加@"text/html"
AFJSONResponseSerializer *serializer = [AFJSONResponseSerializer serializer];
serializer.acceptableContentTypes = [NSSet setWithObjects:@"text/html", nil];
manager.responseSerializer = serializer;
// ------------------------------------------------------------------------
// 3.拼接请求参数
NSMutableDictionary *parameters = [NSMutableDictionary dictionary];
parameters[@"code2"] = code2;
// ------------------------------------------------------------------------
// 4.请求广告数据
[manager GET:@"http://mobads.baidu.com/cpro/ui/mads.php" parameters:parameters progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSLog(@"请求成功");
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@"%@", error);
}];
-
MJExtension字典转模型
-
mj_objectWithKeyValues:方法
作用是取出服务器返回的众多JSON数据中的有用的模型参数
.
WXAdItem *adItem = [WXAdItem mj_objectWithKeyValues:adDict];
-
获取广告数据
-
查看百思接口文档,点击广告API获取进入程序后展示的广告页数据URL跳转到数据页
-
获取广告数据
真实的URL
和服务器返回的JSON数据
-
使用JSON解析工具分析广告展示所需数据(
w_picurl , ori_curl , w , h
)
-
-
广告界面效果图
-
广告功能界面实现步骤
- 1.设置广告界面启动背景图片,
根据屏幕的高度做启动背景图片屏幕适配
- 2.请求广告数据
- 1.创建请求回话管理者
- 设置响应体的数据格式,添加@"text/html"
- 3.拼接请求参数
- 4.请求广告数据
- 4.1 获取广告数据 ,返回的广告数据是数组,有[],所以要用lastObject取出数据
- 需判断是否请求到数据,如果没有数据,则退出,如果没判断,请求失败会导致应用奔溃.
- 4.2 字典转模型 mj_objectWithKeyValues:方法作用是将字典转换成对应的模型
- 4.3 设置广告界面的数据,返回数据中有广告图片的尺寸
- 4.4 添加点击手势,点击图片跳转到广告页
- 3.通过定时器定时更新跳过按钮的标题数字更新
- 4.定时时间到或用户点击跳过按钮,直接跳转到TabBarController.
- 1.设置广告界面启动背景图片,
判断url是否有效
if ([[UIApplication sharedApplication] canOpenURL:url]);
- 广告界面功能参考代码
#import "WXAdViewController.h"
#import <MJExtension.h>
#import <UIImageView+WebCache.h>
#import <AFNetworking.h>
#import "WXTabBarController.h"
#import "WXAdItem.h"
static NSString * const code2 = @"phcqnauGuHYkFMRquANhmgN_IauBThfqmgKsUARhIWdGULPxnz3vndtkQW08nau_I1Y1P1Rhmhwz5Hb8nBuL5HDknWRhTA_qmvqVQhGGUhI_py4MQhF1TvChmgKY5H6hmyPW5RFRHzuET1dGULnhuAN85HchUy7s5HDhIywGujY3P1n3mWb1PvDLnvF-Pyf4mHR4nyRvmWPBmhwBPjcLPyfsPHT3uWm4FMPLpHYkFh7sTA-b5yRzPj6sPvRdFhPdTWYsFMKzuykEmyfqnauGuAu95Rnsnbfknbm1QHnkwW6VPjujnBdKfWD1QHnsnbRsnHwKfYwAwiu9mLfqHbD_H70hTv6qnHn1PauVmynqnjclnj0lnj0lnj0lnj0lnj0hThYqniuVujYkFhkC5HRvnB3dFh7spyfqnW0srj64nBu9TjYsFMub5HDhTZFEujdzTLK_mgPCFMP85Rnsnbfknbm1QHnkwW6VPjujnBdKfWD1QHnsnbRsnHwKfYwAwiuBnHfdnjD4rjnvPWYkFh7sTZu-TWY1QW68nBuWUHYdnHchIAYqPHDzFhqsmyPGIZbqniuYThuYTjd1uAVxnz3vnzu9IjYzFh6qP1RsFMws5y-fpAq8uHT_nBuYmycqnau1IjYkPjRsnHb3n1mvnHDkQWD4niuVmybqniu1uy3qwD-HQDFKHakHHNn_HR7fQ7uDQ7PcHzkHiR3_RYqNQD7jfzkPiRn_wdKHQDP5HikPfRb_fNc_NbwPQDdRHzkDiNchTvwW5HnvPj0zQWndnHRvnBsdPWb4ri3kPW0kPHmhmLnqPH6LP1ndm1-WPyDvnHKBrAw9nju9PHIhmH9WmH6zrjRhTv7_5iu85HDhTvd15HDhTLTqP1RsFh4ETjYYPW0sPzuVuyYqn1mYnjc8nWbvrjTdQjRvrHb4QWDvnjDdPBuk5yRzPj6sPvRdgvPsTBu_my4bTvP9TARqnam";
@interface WXAdViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *launchImageView;
@property (weak, nonatomic) IBOutlet UIView *adView;
@property (weak, nonatomic) IBOutlet UIButton *jumpButton;
@property (nonatomic, strong) NSTimer *timer;
@property (nonatomic, strong) WXAdItem *adItem;
@end
@implementation WXAdViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 1.设置启动图片,屏幕适配
[self setupLaunchImageView];
// 2.请求广告数据
[self loadAdData];
// 3.创建定时器
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timeChange) userInfo:nil repeats:YES];
[self timeChange];
}
#pragma =======================================================================
#pragma mark - 启动图片屏幕适配,请求广告数据,定时器
// ----------------------------------------------------------------------------
// 设置启动图片,屏幕适配,根据屏幕的高度
- (void)setupLaunchImageView
{
UIImage *image = nil;
// ------------------------------------------------------------------------
// 适配启动背景图片
if (iPhone4) {
image = [UIImage imageNamed:@"LaunchImage"];
} else if (iPhone5) {
image = [UIImage imageNamed:@"LaunchImage-568h"];
} else if (iPhone6) {
image = [UIImage imageNamed:@"LaunchImage-800-667h"];
} else if (iPhone6p) {
image = [UIImage imageNamed:@"LaunchImage-800-Portrait-736h@3x"];
}
// 设置启动背景图片
self.launchImageView.image = image;
}
// ----------------------------------------------------------------------------
// 请求广告数据
- (void)loadAdData
{
// ------------------------------------------------------------------------
// 1.创建请求回话管理者
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
// ------------------------------------------------------------------------
// 2. 设置响应体的数据格式,添加@"text/html"
AFJSONResponseSerializer *serializer = [AFJSONResponseSerializer serializer];
serializer.acceptableContentTypes = [NSSet setWithObjects:@"text/html", nil];
manager.responseSerializer = serializer;
// ------------------------------------------------------------------------
// 3.拼接请求参数
NSMutableDictionary *parameters = [NSMutableDictionary dictionary];
parameters[@"code2"] = code2;
// ------------------------------------------------------------------------
// 4.请求广告数据
[manager GET:@"http://mobads.baidu.com/cpro/ui/mads.php" parameters:parameters progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
// 判断取回来的数据是否正确
// 4.1 获取广告数据 ,返回的广告数据是数组,有[],所以要用lastObject取出数据
NSDictionary *adDict = [responseObject[@"ad"] lastObject];
// 判断是否请求到数据,如果没有数据,则退出
if (adDict == nil) {
return;
}
// 4.2 字典转模型 mj_objectWithKeyValues:方法作用是将字典转换成对应的模型
WXAdItem *adItem = [WXAdItem mj_objectWithKeyValues:adDict];
self.adItem = adItem;
// 4.3 设置广告界面的数据,返回数据中有广告图片的尺寸
CGFloat w = screenW;
CGFloat h = screenW / adItem.w * adItem.h;
UIImageView *adImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, w, h)];
[adImageView sd_setImageWithURL:[NSURL URLWithString:adItem.w_picurl]];
[self.adView addSubview:adImageView];
adImageView.userInteractionEnabled = YES;
// 4.4 添加点击手势,点击图片跳转到广告页
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap)];
[adImageView addGestureRecognizer:tap];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@"%@", error);
}];
}
// ----------------------------------------------------------------------------
// 定时更新按钮的标题,定时时间到则跳转
- (void)timeChange
{
static NSInteger timeIndex = 3;
// 更新跳过按钮标题
[self.jumpButton setTitle:[NSString stringWithFormat:@"跳过 (%ld)", timeIndex] forState:UIControlStateNormal];
if (timeIndex-- < 0) {
[self.timer invalidate];
[self jump];
}
}
#pragma =======================================================================
#pragma mark - 跳过按钮点击, 点击广告图片跳转
// ----------------------------------------------------------------------------
// 监听点击跳过按钮
- (IBAction)jump {
// 关闭定时器
[self.timer invalidate];
WXTabBarController *tabBarVc = [[WXTabBarController alloc] init];
[UIApplication sharedApplication].keyWindow.rootViewController = tabBarVc;
}
// ----------------------------------------------------------------------------
// 监听广告图片点击
- (void)tap
{
// 检查url是否能打开
NSURL *url = [NSURL URLWithString:self.adItem.ori_curl];
if ([[UIApplication sharedApplication] canOpenURL:url]) {
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:self.adItem.ori_curl]];
}
}
-
广告界面效果图
跳过按钮注意点
- 按钮如果是
System类型的,在重新设置按钮标题时会闪烁
.需把按钮的类型改成Custom自定义
类型按钮.
8.CocoaPods的介绍
CocoaPods:管理第三方框架
podfile文件:描述加载哪些第三方框架
- cocoapods的使用步骤
- 1.创建podfile,在当前工程目录下
- 2.touch podfile touch:新建
- 3.open podfile打开这个文件
- 4.
pod search MJExtension
搜索框架 - 5.描述好podfile
- 6.
pod install --no-repo-update
不执行pod repo update,比较快速导入第三份框架
- cocoapods指令简介
Podfile.lock:第一次pod完,自动生成这个文件,记录当前需要加载框架的版本号
终端指令 -help 学习
pod install 1.判断下有没有Podfile.lock,如果有,根据Podfile.lock加载,没有,根据Podfile文件去加载
pod update 2.更新需要加载框架的版本号,并且创建新的Podfile.lock
--no-repo-update:不执行pod repo update
pod repo update : 更新仓库索引,获取所有框架最新版本
pod install --no-repo-update : 比较快速导入第三份框架