02.项目实战 百思不得姐 自定义导航条,广告界面

@(iOS 项目实战)[项目实战]


目录

  • 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);
    
    ; 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;.
  • 全屏滑动返回实现参考代码

#pragma =======================================================================
#pragma mark - 设置滑动返回手势
- (void)viewDidLoad {
    [super viewDidLoad];
 
    // 恢复系统手势边缘滑动返回方式一(不可行)
    // 如果直接将系统手势清空,会有返回效果,但是在根控制器边缘滑动会导致出现假死状态,该方法不可行
//    self.interactivePopGestureRecognizer.delegate = nil;
    
    // // 恢复系统手势边缘滑动返回方式二(可行)
//    self.interactivePopGestureRecognizer.delegate = self;
    
    
    // 添加全屏滑动手势
    
    /** 
     系统手势打印结果:
     NSLog(@"%@", self.interactivePopGestureRecognizer);
     
     ; 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布局如图所示
02.项目实战 百思不得姐 自定义导航条,广告界面_第1张图片
广告界面xib布局.png

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;
      
  • 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数据

      02.项目实战 百思不得姐 自定义导航条,广告界面_第2张图片
      广告接口数据.png

    • 使用JSON解析工具分析广告展示所需数据(w_picurl , ori_curl , w , h)

      02.项目实战 百思不得姐 自定义导航条,广告界面_第3张图片
      广告接口JSON数据.png

  • 广告界面效果图


    广告界面效果图.png
  • 广告功能界面实现步骤

    • 1.设置广告界面启动背景图片,根据屏幕的高度做启动背景图片屏幕适配
    • 2.请求广告数据
      • 1.创建请求回话管理者
        1. 设置响应体的数据格式,添加@"text/html"
      • 3.拼接请求参数
      • 4.请求广告数据
        • 4.1 获取广告数据 ,返回的广告数据是数组,有[],所以要用lastObject取出数据
        • 需判断是否请求到数据,如果没有数据,则退出,如果没判断,请求失败会导致应用奔溃.
        • 4.2 字典转模型 mj_objectWithKeyValues:方法作用是将字典转换成对应的模型
        • 4.3 设置广告界面的数据,返回数据中有广告图片的尺寸
        • 4.4 添加点击手势,点击图片跳转到广告页
    • 3.通过定时器定时更新跳过按钮的标题数字更新
    • 4.定时时间到或用户点击跳过按钮,直接跳转到TabBarController.
  • 判断url是否有效

 if ([[UIApplication sharedApplication] canOpenURL:url]);
  • 广告界面功能参考代码
#import "WXAdViewController.h"
#import 
#import 
#import 
#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]];
    }
}

  • 广告界面效果图


    广告界面效果图.png

跳过按钮注意点

  • 按钮如果是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 : 比较快速导入第三份框架

你可能感兴趣的:(02.项目实战 百思不得姐 自定义导航条,广告界面)