再看返回按钮,简单演练

2020-03-24 06:53 来源:未知

经常大家在类型中会建一个BaseViewController也许CommonViewController作为基类,在里头完毕全局性的底子功用,现在全体的ViewController均世袭该基类。

图片 1

小说结构


1.存世的二种情势
2.结尾方式

近来一贯没什么意况,静不下心来看别的新的东西。后天在上班的公共交通上突兀想起上次有关互联网央求时的加载动漫,及加载后的加载失利、数据为空等视图在上篇笔记中(《UITextField一箩筐——输入长度约束、自定义placeholder、键盘遮挡难点》State of Qatar未有写完整。在这里篇笔记里,TipsView只写了三种体制的正在加载动画的视图。既然前不久其他东西看不走入,就先把那块的事物先完备一下吗。

既然如此要在这里CommonViewController里写“全局性的基本作用”,那用脑筋想都有何东西吧?笔者列出了下边那个:

斯威夫特-MVVM 轻松练习(二State of Qatar

文章更新记录


与正在加载动漫是增多在keyWindow上差别,加载退步和空数据的视图得增多在调整器的视图上,也正是在原来view的地方覆盖了一层视图。由此依赖上次写的代码,大家只需要在抬高或创立这个视图的点子里,依据视图类型的参数来判定,假使要加多的是加载战败的视图,那我们就相应地成立其视图,然后添加到该控制器的view上

1.自定义分界面:为了使项目拥有较好的狡猾及宽容性,小编并不曾使用系统的导航栏和view,而是举行了自定义。那样不光越来越灵敏,何况布局更了解简洁。

斯威夫特-MVVM 轻易演习(三State of Qatar

2017-01-06日

1. 添加注意事项

除此以外,加载退步分歧王芸在加载的地点是它是可点击的,有一点击事件的。即点击之后再一次加载,大家得把那些点击事件回调给调节器,在调节器里造成对互连网数据的重新央浼。上篇笔记中只写了正在加载视图,它是未有事件回调的。所以,大家得经过给艺术增添叁个block参数,用以回调点击事件。

2.导航栏的一部分通用点击事件:举个例子说点击侧边按键再次回到上个分界面,点击右边按键扫一扫,点击导航栏scrollerView滑动到最最上部等。CommonViewController中写全局性的点击事件管理,各子类若有例外管理,只需重写事件管理方法就能够。

Swift-MVVM 简单练习(四卡塔尔国

一、现存的拜谒定制格局


从贯彻原理上来说唯有三种:

关键点正是上边两点,当然其余地点也可能有微调。具体看上边包车型客车代码。

3.局部功效方法(更改导航栏的样式,举例背景象,开关的title、图标等):每一个分界面包车型地铁那个东西差别,供给提供形式定制。

前言

近来在上学swiftMVVM布局形式,目标只是将自身的就学笔记记录下来,方便自个儿自此寻觅,如此而已!!!

理当如此筹划一篇全体消除的,不过简书每篇小说只好写大概不超越15000字的从头到尾的经过,由此只可以分别写了。

若果有此外难点,招待和自己联合谈谈。当然如若有怎样存在的主题材料,迎接商议指正,作者会积极更改的!


第一种:

使用UIBarButtonItem的两个函数setBackButtonBackgroundImagesetBackButtonTitlePositionAdjustment

setBackButtonBackgroundImage 用来设置重返开关的背景图片
setBackButtonTitlePositionAdjustment 用来设置再次回到题指标职位,使其偏移出荧屏。

    UIImage *backButtonImage = [[UIImage imageNamed:@"nav_icon_back"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 25, 0, 0)];
    [[UIBarButtonItem appearance] setBackButtonBackgroundImage:backButtonImage forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
    [[UIBarButtonItem appearance] setBackButtonTitlePositionAdjustment:UIOffsetMake(NSIntegerMin, NSIntegerMin) forBarMetrics:UIBarMetricsDefault];

优点:

  1. 能够写在appdelegate里面,app运转的时候统一安装。

缺点:
1.IOS11下自定义的图片会向下偏移。
2.那边只是设置了title的舞狮使其偏移出显示屏,title所占的空中照旧留存。

图片 2

WX20171216-142041@2x.png

不思索,提出用上面二种艺术。

** NetworkTipView.h **

4.对展现“加载动漫”、“加载战败”、“数据为空”两种视图的卷入:

那篇小说都写吗

  • 自定义NavgationBar
  • 收取便利布局函数
  • 始发的下拉刷新/上拉加载的精简管理
  • 未登陆逻辑的拍卖
  • 苹果原生布局NSLayoutConstraint
  • 如何用VFL布局(VisualFormatLanguage)
  • 宪章互连网加载应用程序的片段配备tabBar的标题和图片样式
  • 简单的网络工具单例的卷入
  • 隔开项目中的网络诉求方法
  • 始于的视图模型的经历
  • 以致一些会见包车型地铁语法难题的大约研讨

第二种

利用IOS7系统后提供的backIndicatorImagebackIndicatorTransitionMaskImage属性。

图片 3

image.png

基类UINavgationController里调用,或然基类调控器里面调用都行。
听他们讲程序的运行功效建议松开基类UINavgationControllerviewDidload里调用,毕竟假使设置壹次。

#import <UIKit/UIKit.h>typedef NS_ENUM(NSInteger, TipType){ TipType_LoadingCenter = 0, // 只在屏幕中心的加载动画 TipType_loadingFull, // 覆盖整个屏幕的加载动画 TipType_loadedNull, // 数据为空 TipType_loadedFailure, // 加载失败};typedef void(^ClickBlock)(TipType tipType); // 点击事件回调(如一般加载失败后,点击重新加载)@interface NetworkTipView : UIView@property (nonatomic, assign)CGRect tipFrame;@property (nonatomic, assign)CGSize tipImageSize;@property (nonatomic, strong)UIColor *tipBgColor;// 添加、创建tipView- addTipViewTarget:target tipType:type clickBlock:(ClickBlock)clickBlock;// 移除tipView- removeNetworkTipView;@end

5.对发送互联网央求,并展现加载动漫的归总封装:貌似大家恳请了互连网接口后将要使其出示加载动画。方便起见,我们将两个归并封装。那样,大家假若发送了接口乞请,便自行显示加载动漫。

GitHub 上创建项目

如有需求,请移步上边两篇文章

  • iOS-将项目上传到 GitHub 上
  • iOS-将品种上盛传 Git.OSChina 上,创设和睦的民用途目

BSENavigationController.m
self.navigationController.navigationBar.backIndicatorImage = [UIImage imageNamed:@"backPic"];
self.navigationController.navigationBar.backIndicatorTransitionMaskImage = [UIImage imageNamed:@"backPic"];

基类调节器里调用

** NetworkTipView.m **

** 6.全局滑入手势:**假定项目供给每一个分界面须要自定义侧滑再次来到,那是全局性的事物,所以得写。

花色安排

  • 删除ViewController.swiftMain.storyboardLaunchScreen.storyboard
  • 设置APPIconLaunchImage
  • 设置项目目录结构
    • HQMainViewController继承自UITabBarController
    • HQNavigationController继承自UINavigationController
    • HQBaseViewController继承自UIViewController(基类调节器卡塔尔国

图片 4

BSEViewController.m
UIBarButtonItem *backButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:nil];
    [self.navigationItem setBackBarButtonItem:backButtonItem];

UIBarButtonItem属性说明:

图片 5

ios自定义图片替换系统导航栏重返开关样式那篇文章种关系一种艺术:

在基类UINavgationController里实现UINavigationBarDelegate 方法如下:

- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPushItem:(UINavigationItem *)item{

UIBarButtonItem *back = [[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:nil];

item.backBarButtonItem = back;

return YES;
}

能够落成,不过有几个神秘的主题素材:

1.片段二级以往的分界面题目重新现身
2.有的分界面有断定的可能率会并发再次来到键消失的情事

那边思忖到不是负有调节器都会三番一回基类,特别是引进的第三方库。所以不利用基类调控器的写法:
直白上代码:

#import "NetworkTipView.h"#import "CommonViewController.h"@interface NetworkTipView (){ UIView *_bgView; CommonViewController *_vc; ClickBlock _clickBlock;}@end@implementation NetworkTipView#pragma mark ---- 公共方法:提供给外部的功能方法// 添加bgView到window上。- addTipViewTarget:target tipType:type clickBlock:(ClickBlock)clickBlock{ if{ [_bgView removeFromSuperview]; _bgView = nil; } // 确定frame if([target isKindOfClass:[CommonViewController class]]){ _vc = (CommonViewController *)target;// _tipFrame = _vc.contentView.frame; } _clickBlock = clickBlock; _bgView = [[UIView alloc] initWithFrame:_vc.contentView.frame]; if(_tipBgColor){ _bgView.backgroundColor = _tipBgColor; }else{ _bgView.backgroundColor = [UIColor whiteColor]; // 默认背景是白色 } switch  { case TipType_loadingFull: { _bgView = [self createLoadingFullView]; // 创建bgView,即加载动画的核心视图 UIWindow *window = [UIApplication sharedApplication].keyWindow; if  { window = [[UIApplication sharedApplication].windows lastObject]; } [window addSubview:_bgView]; } break; case TipType_LoadingCenter: { _bgView = [self createLoadingCenterView]; // 创建加载动画的核心视图 UIWindow *window = [UIApplication sharedApplication].keyWindow; if { window = [[UIApplication sharedApplication].windows lastObject]; } [window addSubview:_bgView]; } break; case TipType_loadedNull: case TipType_loadedFailure: { _bgView = [self createLoadedNullOrFailureView:type]; [_vc.view addSubview:_bgView]; } break; }}// 移除tipView- removeNetworkTipView{ if { if([_bgView.superview isKindOfClass:[UIWindow class]]){ [_bgView removeFromSuperview]; _bgView = nil; } }}#pragma mark ---- 内部功能方法// 创建加载动画核心视图- createLoadingFullView{ UIImage *image = [UIImage imageNamed:@"Network_loading1"]; if(_tipImageSize.width == 0){ _tipImageSize = CGSizeMake(image.size.width/2.f, image.size.height/2.f); } UIImageView *animationView = [[UIImageView alloc] initWithFrame:CGRectMake((_bgView.frame.size.width-_tipImageSize.width)/2.f, (_bgView.frame.size.height-_tipImageSize.height)/2.f, _tipImageSize.width, _tipImageSize.height)]; animationView.image = image; animationView.backgroundColor = [UIColor whiteColor]; animationView.animationImages = [NSArray arrayWithObjects: image, [UIImage imageNamed:@"Network_loading2.png"], [UIImage imageNamed:@"Network_loading3.png"], [UIImage imageNamed:@"Network_loading4.png"], [UIImage imageNamed:@"Network_loading5.png"], [UIImage imageNamed:@"Network_loading6.png"], [UIImage imageNamed:@"Network_loading7.png"], [UIImage imageNamed:@"Network_loading8.png"], [UIImage imageNamed:@"Network_loading9.png"], [UIImage imageNamed:@"Network_loading10.png"], [UIImage imageNamed:@"Network_loading11.png"], [UIImage imageNamed:@"Network_loading12.png"], [UIImage imageNamed:@"Network_loading13.png"], [UIImage imageNamed:@"Network_loading14.png"], [UIImage imageNamed:@"Network_loading15.png"], [UIImage imageNamed:@"Network_loading16.png"], [UIImage imageNamed:@"Network_loading17.png"], [UIImage imageNamed:@"Network_loading18.png"], nil]; [animationView setAnimationDuration:1.0f]; [animationView setAnimationRepeatCount:-1]; [animationView startAnimating]; [_bgView addSubview:animationView]; return _bgView; }- createLoadingCenterView{ CGFloat black_w = 60.f; UIView *blackView = [[UIView alloc] initWithFrame:CGRectMake((_bgView.frame.size.width-black_w)/2.f, (_bgView.frame.size.height-black_w)/2.f, black_w, black_w)]; blackView.backgroundColor = [UIColor blackColor]; blackView.alpha = 0.6f; blackView.layer.masksToBounds = YES; blackView.layer.cornerRadius = 5.f; [_bgView addSubview:blackView]; UIActivityIndicatorView *activityV = [[UIActivityIndicatorView alloc] initWithFrame:CGRectMake(blackView.frame.size.width/4.f, blackView.frame.size.height/4.f, blackView.frame.size.width/2.f, blackView.frame.size.height/2.f)]; activityV.center = CGPointMake(blackView.bounds.size.width/2.f, blackView.bounds.size.height/2.f); activityV.backgroundColor = [UIColor clearColor]; [activityV startAnimating]; [blackView addSubview:activityV]; [_bgView addSubview:blackView]; return _bgView;}// 创建加载失败和空数据的视图。因写的是demo,就写在一个方法里了,文字不同而已。- createLoadedNullOrFailureView:tipType{ UIView *whiteView = [[UIView alloc] init]; if(_tipFrame.size.width!=0){ whiteView.frame = _tipFrame; }else{ whiteView.frame = CGRectMake(0, 0, _bgView.frame.size.width, _bgView.frame.size.height); } whiteView.backgroundColor = [UIColor whiteColor]; [_bgView addSubview:whiteView]; UILabel *nullLab = [[UILabel alloc] initWithFrame:CGRectMake((whiteView.frame.size.width-200.f)/2.f, (whiteView.frame.size.height-80.f)/2.f, 200.f, 80.f)]; nullLab.textAlignment = NSTextAlignmentCenter; nullLab.textColor = [UIColor lightGrayColor]; nullLab.font = [UIFont systemFontOfSize:12]; if(tipType == TipType_loadedNull){ nullLab.text = @"没有内容"; }else if(tipType == TipType_loadedFailure){ nullLab.text = @"加载失败,点击重新加载"; nullLab.userInteractionEnabled = YES; UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(labGestureHandle:)]; [nullLab addGestureRecognizer:tapGesture]; } [whiteView addSubview:nullLab]; return _bgView; }- labGestureHandle:(UITapGestureRecognizer *)tapGesture{ _clickBlock(TipType_loadedFailure);}@end

7.方可接受是不是监听键盘公告:纸包不住火给外界多少个isListensKeyboard质量,意为是不是监听键盘文告,私下认可是NO。仁同一视写该属性的setter方法,在中间判别若为YES,则供给监听键盘布告,就设置该self为管理键盘通知的代办;若为NO,即该VC无需监听键盘,那大家便不设代理。

设置子调整器

HQMainViewController中安装多少个子调节器

  • extension将代码拆分
  • 由此反射机制,获取子调节器类名,创建子调控器
  • 安装每种子调控的tabBar图表及标题

HQMainViewController中代码如下所示

class HQMainViewController: UITabBarController {

    override func viewDidLoad() {
        super.viewDidLoad()

        setupChildControllers()
    }
}

/*
 extension 类似于 OC 中的分类,在 Swift 中还可以用来切分代码块
 可以把功能相近的函数,放在一个extension中
 */
extension HQMainViewController {

    /// 设置所有子控制器
    fileprivate func setupChildControllers() {

        let array = [
            ["className": "HQAViewController", "title": "首页", "imageName": "a"],
            ["className": "HQBViewController", "title": "消息", "imageName": "b"],
            ["className": "HQCViewController", "title": "发现", "imageName": "c"],
            ["className": "HQDViewController", "title": "我", "imageName": "d"]
        ]
        var arrayM = [UIViewController]()
        for dict in array {
            arrayM.append(controller(dict: dict))
        }
        viewControllers = arrayM
    }
    /*
     ## 关于 fileprvita 和 private

     - 在`swift 3.0`,新增加了一个`fileprivate`,这个元素的访问权限为文件内私有
     - 过去的`private`相当于现在的`fileprivate`
     - 现在的`private`是真正的私有,离开了这个类或者结构体的作用域外面就无法访问了
     */

    /// 使用字典创建一个子控制器
    ///
    /// - Parameter dict: 信息字典[className, title, imageName]
    /// - Returns: 子控制器
    private func controller(dict: [String: String]) -> UIViewController {

        // 1. 获取字典内容
        guard let className = dict["className"],
            let title = dict["title"],
            let imageName = dict["imageName"],
            let cls = NSClassFromString(Bundle.main.namespace   "."   className) as? UIViewController.Type else {

                return UIViewController()
        }

        // 2. 创建视图控制器
        let vc = cls.init()
        vc.title = title

        // 3. 设置图像
        vc.tabBarItem.image = UIImage(named: "tabbar_"   imageName)
        vc.tabBarItem.selectedImage = UIImage(named: "tabbar_"   imageName   "_selected")?.withRenderingMode(.alwaysOriginal)
        // 设置`tabBar`标题颜色
        vc.tabBarItem.setTitleTextAttributes(
            [NSForegroundColorAttributeName: UIColor.orange],
            for: .selected)
        // 设置`tabBar`标题字体大小,系统默认是`12`号字
        vc.tabBarItem.setTitleTextAttributes(
            [NSFontAttributeName: UIFont.systemFont(ofSize: 12)],
            for: .normal)

        let nav = HQNavigationController(rootViewController: vc)
        return nav
    }
}

BSENavigationController.h

#import <UIKit/UIKit.h>
#import "UIViewController WLBackButton.h"
@interface BSENavigationController : UINavigationController

@end

** CommonViewController.m **只列出有关代码:在丰硕那些视图的不二等秘书籍里拉长了block回调,用于拍卖回调事件。这里的情事是该block的参数是TipType品类,表示近年来所成立的视图类型,若为加载失利视图的平地风波点击回调,那大家得重复进行网络数据哀告呀。

8.安装意况栏颜色:有各个措施,plist文件配置和代码校正,这里指通过代码修改。

安装中间加号开关

  • 通过增添tabBarItem的措施,给中间留出一个 按键的职位
  • 自定义三个UIButton的分类HQButton Extension,封装火速创制自定义按键的点子

HQButton.swift

extension UIButton {

    /// 便利构造函数
    ///
    /// - Parameters:
    ///   - imageName: 图像名称
    ///   - backImageName: 背景图像名称
    convenience init(hq_imageName: String, backImageName: String?) {
        self.init()

        setImage(UIImage(named: hq_imageName), for: .normal)
        setImage(UIImage(named: hq_imageName   "_highlighted"), for: .highlighted)

        if let backImageName = backImageName {
            setBackgroundImage(UIImage(named: backImageName), for: .normal)
            setBackgroundImage(UIImage(named: backImageName   "_highlighted"), for: .highlighted)
        }

        // 根据背景图片大小调整尺寸
        sizeToFit()
    }
}

HQMainViewController.swift

/// 设置撰写按钮
fileprivate func setupComposeButton() {
    tabBar.addSubview(composeButton)

    // 设置按钮的位置
    let count = CGFloat(childViewControllers.count)
    // 减`1`是为了是按钮变宽,覆盖住系统的容错点
    let w = tabBar.bounds.size.width / count - 1
    composeButton.frame = tabBar.bounds.insetBy(dx: w * 2, dy: 0)

    composeButton.addTarget(self, action: #selector(composeStatus), for: .touchUpInside)
}

// MARK: - 监听方法
// @objc 允许这个函数在运行时通过`OC`消息的消息机制被调用
@objc fileprivate func composeStatus() {
    print("点击加号按钮")
}

// MARK: - 撰写按钮
fileprivate lazy var composeButton = UIButton(hq_imageName: "tabbar_compose_icon_add",
                                          backImageName: "tabbar_compose_button")

BSENavigationController.m

- (void)viewDidLoad {
    [super viewDidLoad];
    [self setUpNavitems];
    [self setupNavigationBarTheme];
    // Do any additional setup after loading the view.
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

-(void)setUpNavitems{
    UIImage *backButtonImage = [[UIImage imageNamed:@"nav_icon_back"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
    self.navigationBar.backIndicatorImage = backButtonImage;
    self.navigationBar.backIndicatorTransitionMaskImage = backButtonImage;
}

#pragma mark - 设置navbar的字体和背景
- (void)setupNavigationBarTheme {
    //设置全局navBar字体颜色与背景颜色
    UINavigationBar *navBar = [UINavigationBar appearance];
    //设置字体颜色
    NSDictionary *attrNavBar = @{NSFontAttributeName : [UIFont systemFontOfSize:16.0f], NSForegroundColorAttributeName : HEXCOLOR(0x111111)};
    [navBar setTitleTextAttributes:attrNavBar];
    //设置背景颜色 首页导航透明
//    [navBar lt_setBackgroundColor:[UIColor whiteColor]];
    //设置背景颜色一般的方法
    [navBar setBarTintColor:[UIColor whiteColor]];
    if ([UIDevice currentDevice].systemVersion.integerValue >= 8.0) {
        [navBar setTranslucent:NO];
    }
}
// 显示、出现tipView- showNetworkTipsView:tipType{ if(!_networkTipView){ _networkTipView = [[NetworkTipView alloc] init]; } __block id weakSelf = self; [_networkTipView addTipViewTarget:self tipType:tipType clickBlock:^(TipType tipType) { if(tipType == TipType_loadedFailure){ [weakSelf loadedFailureHandle]; // 目前只有数据加载的视图是有点击事件回调的 } }];}

// 网络请求失败 点击重试-loadedFailureHandle{ // 在子类中重写}

** 9.别的各类全局性的底蕴意义.....**

自定义最上部导航栏

  • 系统本人的绝大非常多状态下不能够满意我们的平时须要
  • 有一部分系统的样式自个儿处理的不好,举例侧滑重回的时候,系统的会出现渐溶的功效,这种客商体验不太好
  • 急需减轻push出一个调节器后,尾部TabBar遮掩/突显难点

UIViewController WLBackButton.h

#import <UIKit/UIKit.h>
@interface UIViewController (WLBackButton)
@end

loadedFailureHandle这是在基类CommonViewController中定义,我们在相关类中重写它,达成具体的网络央求等。举例在调节器HomeViewController中重写了它,在其间再一次号令了学子列表的互联网数据。

talk is cheap,so...

Push 出调节器后,尾部 TabBar 掩盖/彰显难点

  • 在导航调整器的基类里面重写一下push方法
  • 看清假若不是根控制器,那么push的时候就暗藏BottomBar
  • 只顾调用super.pushViewController要在重写方法之后

HQNavigationController.swift

override func pushViewController(_ viewController: UIViewController, animated: Bool) {

    if childViewControllers.count > 0 {
        viewController.hidesBottomBarWhenPushed = true
    }
    super.pushViewController(viewController, animated: true)
}

UIViewController WLBackButton.m

#import "UIViewController WLBackButton.h"
#import <objc/runtime.h>

@implementation UIViewController (WLBackButton)


  (void)load{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];
        SEL originalSelector = @selector(viewDidLoad);
        SEL swizzledSelector = @selector(wl_viewDidLoad);

        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

        BOOL success = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
        if (success) {
            class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}

-(void)wl_viewDidLoad{
    //执行原有方法
    [self wl_viewDidLoad];
    [self wl_setUpNavItems];
}

-(void)wl_setUpNavItems{
    UIBarButtonItem *backButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:nil];
    [self.navigationItem setBackBarButtonItem:backButtonItem];
}

ok,完美消除难点。
该措施从规律上来说只是替换系统原来的回来提示图片,然后在调整器里定义了标题大小为空的backbutton。不隐蔽类别本来的归来事件,也不会招致系统的滑行重回失效。再则有个别分界面须求定制退出拦截提醒时也正如好管理。推荐!!!!

- loadedFailureHandle{ [self startNetworkRequestAction:@selector(requestStudentList) tipType:TipType_LoadingCenter];}

** CommonViewController.h **

抽出 BarButtonItem 便利构造函数

  • 系统的UIBarButtonItem主意不可能造福的满足大家创造所需的leftBarButtonItemrightBarButtonItem
  • 一经自定义创设需求些好几行代码
  • 而那么些代码又只怕在众多地方选取,所以尽可能抽出个低价结构函数

平常自定义ftBarButtonItem时候大概会写如下代码

  • 最反感的便是btn.sizeToFit()那句,如若不加,rightBarButtonItem就展现不出来
  • 一经封装起来,就再也不要思谋这难题了
let btn = UIButton()
btn.setTitle("下一个", for: .normal)
btn.setTitleColor(UIColor.lightGray, for: .normal)
btn.setTitleColor(UIColor.orange, for: .highlighted)
btn.addTarget(self, action: #selector(showNext), for: .touchUpInside)
// 最讨厌的就是这句,如果不加,`rightBarButtonItem`就显示不出来
btn.sizeToFit()
navigationItem.rightBarButtonItem = UIBarButtonItem(customView: btn)

假如抽取三个有益于布局函数,代码或许会简化成如下

  • 一行代码解决,轻巧了广大
navigationItem.rightBarButtonItem = UIBarButtonItem(hq_title: "下一个", target: self, action: #selector(showNext))

方便人民群众布局函数的成效:简化控件的创建


第三种

设置调整器的leftBarButtonItem

//创建一个UIButton
UIButton *backButton = [[UIButton alloc]initWithFrame:CGRectMake(0, 0, 40, 40)];
//设置UIButton的图像
[backButton setImage:[UIImage imageNamed:@"left_select_img.png"] forState:UIControlStateNormal];
//给UIButton绑定一个方法,在这个方法中进行popViewControllerAnimated
[backButton addTarget:self action:@selector(backItemClick) forControlEvents:UIControlEventTouchUpInside];
//然后通过系统给的自定义BarButtonItem的方法创建BarButtonItem
UIBarButtonItem *backItem = [[UIBarButtonItem alloc]initWithCustomView:backButton];
//覆盖返回按键
self.navigationItem.leftBarButtonItem = backItem;

特出的接收办法:

拜访效果:

#import <UIKit/UIKit.h>#import "RedPointView.h"#import "TipView.h"@interface CommonViewController : UIViewController@property (nonatomic, strong)UIView *contentView;@property (nonatomic, strong)UIView *titleBarView; // 导航栏视图@property (nonatomic, strong)RedPointView *leftBtnView; // 导航栏上左按钮@property (nonatomic, strong)RedPointView *rightBtnView; // 导航栏上右按钮@property (nonatomic, strong)UILabel *titleBarLab; // 导航栏中间标题Label@property (nonatomic, strong)UIView *titleBarBottomLine; // 导航栏底部分隔线@property (nonatomic, strong)TipView *tipView; // loading视图@property (nonatomic, assign)BOOL isListensKeyboard; //是否监听键盘改变,默认是NO@property (nonatomic,assign)BOOL isBanGesturesSliding; // 是否禁止手势滑动// 修改导航栏左右按钮为文本形式- changeTitleBarButtonText:(NSString *)text buttonType:(NSInteger)btnType;// 修改导航栏左右按钮为图标形式- changeTitleBarButtonImage:(NSString *)imgName buttonType:(NSInteger)btnType;// 显示、出现tipView- showTipsView:tipType;// 隐藏、消失tipView- hideTipsView;// 将请求网络数据和显示加载动画合并,使其一请求网络同时便开始loading动画。- startNetworkRequestAction:action tipType:tipType;// 带参数object- startNetworkRequestAction:action tipType:tipType withObject:object;@end

缓和导航栏侧滑再次回到进度中,开关及标题标休戚与共难点

  • 因为侧滑再次来到的时候,leftBarButtonItemtitle的字体有渐融的主题材料,我们又想消除那样的主题材料。
  • 于是就要自定义NavigationBar
  • 要想完成那么些功效,一定尽量要少动超多调控器的代码。假设在某一个地点就能够写好,对任何调控器的代码侵略的越少越好,这是贰个程序好的布局的条件

首先,在HQNavigationController中暗藏系统的navigationBar

override func viewDidLoad() {
    super.viewDidLoad()

    navigationBar.isHidden = true
}

协理,在基类调整器HQBaseViewController里自定义

class HQBaseViewController: UIViewController {

    /// 自定义导航条
    lazy var navigationBar = UINavigationBar(frame: CGRect(x: 0, y: 0, width: UIScreen.hq_screenWidth(), height: 64))
    /// 自定义导航条目 - 以后设置导航栏内容,统一使用`navItem`
    lazy var navItem = UINavigationItem()

    override func viewDidLoad() {
        super.viewDidLoad()

        setupUI()
    }

    override var title: String? {
        didSet {
            navItem.title = title
        }
    }
}

// MARK: - 设置界面
extension HQBaseViewController {

    func setupUI() {

        view.backgroundColor = UIColor.hq_randomColor()
        view.addSubview(navigationBar)
        navigationBar.items = [navItem]
    }
}

细心:这里有七个小bug

  • push出下三个调整器的时候,导航栏左边会有一段白色的体裁现身
  • 原因是:系统默许的导航栏的发光度太高,自定义设置多个颜色就好了

HQBaseViewController.swift

// 设置`navigationBar`的渲染颜色
navigationBar.barTintColor = UIColor.hq_color(withHex: 0xF6F6F6)

BSENavigationController.m

#pragma mark - 全局返回键以及抽屉事件设置
-(void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated{
    if(self.childViewControllers.count > 0){
        //设置返回键
        [self setUpBackBtn:viewController];
        //隐藏tabbar
        viewController.hidesBottomBarWhenPushed = YES;
    }
    [super pushViewController:viewController animated:animated];
}

#pragma mark - 返回键样式设置
-(void)setUpBackBtn:(UIViewController *)Controller
{
    UIButton *LeftItem = [UIButton new];
    LeftItem.frame = CGRectMake(0, 0, 25, 35);
    [LeftItem.imageView setContentMode:UIViewContentModeLeft];
    LeftItem.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft;
    [LeftItem setImage:[UIImage imageNamed:@"back"] forState:UIControlStateNormal];
    [LeftItem addTarget:self action:@selector(BackBtnClick) forControlEvents:UIControlEventTouchUpInside];
    UIBarButtonItem *customLeftItem = [[UIBarButtonItem alloc] initWithCustomView:LeftItem];
    Controller.navigationItem.leftBarButtonItem = customLeftItem;
}

-(void)BackBtnClick{
    [self popViewControllerAnimated:YES];
}

优点:1.定制相比有利
症结:1.剧情会相对偏右,须求代码调度。
2.一旦写在基类导航调节器里面,部分调控器要阻止重回事件就能够相比较费心,写在基类调整器内又会比较局限。
3.这种方法会失去手势滑动重回的意义 要能使用须要新鲜的拍卖才行

既是是再看再次来到按键,这种措施利用的相当多。这里下文也会贴出相应的裁撤办法。

图片 6loadFailure.gif

** CommonViewController.m**

安装左边 leftBarButtonItem

  • 左侧都是回到(第二级页面以下State of Qatar
  • 抑或是上一流title的称号(只在第二级页面那样展现卡塔尔

在重写pushViewController的措施里面去看清,假如子调节器的个数childViewControllers.count == 1的时候,就设置重临按键文字为根调整器的title

override func pushViewController(_ viewController: UIViewController, animated: Bool) {

    if childViewControllers.count > 0 {
        viewController.hidesBottomBarWhenPushed = true

        /*
         判断控制器的类型
         - 如果是第一级页面,不显示`leftBarButtonItem`
         - 只有第二级页面以后才显示`leftBarButtonItem`
         */
        if let vc = viewController as? HQBaseViewController {

            var title = "返回"

            if childViewControllers.count == 1 {
                title = childViewControllers.first?.title ?? "返回"
            }

            vc.navItem.leftBarButtonItem = UIBarButtonItem(hq_title: title, target: self, action: #selector(popToParent))
        }
    }

    super.pushViewController(viewController, animated: true)
}
标题一:内容会相对偏右,需求代码调解
 UIButton *btnBack = [UIButton new];
    [btnBack setContentHorizontalAlignment:UIControlContentHorizontalAlignmentLeft];
    [btnBack setContentEdgeInsets:UIEdgeInsetsMake(0, -10, 0, 0)];

前方大家说的加载战败分界面和空数据分界面的贯彻都以在调控器的view上增添子视图。这种完毕方案在多少情况下是失常的。比方在某种情况下,当现身加载失败或空数据景况时没有必要覆盖任何荧屏,那个时候就算你也足以安装覆盖在上海广播台图的frame,但它是不得以滑动的,而被遮住在下的,拆穿来的tableView是能够滑动的。举例下图:该分界面全部上是个tableView,上边菜单部分是tableViewtableHeadView,当是空数据情况时,空分界面并不是满载整个显示屏的,而是要揭发上边的菜单部分。若以覆盖一层view的方案达成的话,那当顾客的手指落在菜单上海滑稽剧团动时,它竟然是能够世襲滑动的。这种体验很刚毅,能让客商显然得认为到是有个东西覆盖在显示器上最浮层遮住了背后的事物。

#import "CommonViewController.h"#import "AppDelegate.h"@interface CommonViewController ()<YWKeyboardDelegate>@end@implementation CommonViewController#pragma mark ---- life cycyle- (instancetype)init{ self = [super init]; if { if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0) { self.edgesForExtendedLayout = UIRectEdgeNone; self.extendedLayoutIncludesOpaqueBars = NO; self.automaticallyAdjustsScrollViewInsets = NO; } } return self;}- viewDidLoad{ [super viewDidLoad]; // init views [self loadCommonTitleView]; [self loadCommonContentView]; // 添加自定义的全局的侧滑返回 [self fullScreenSlide];}- viewWillAppear:animated{ [super viewWillAppear:animated]; // 使状态栏和导航栏隐藏(必须写在viewWillAppear:方法里,否则无效) self.navigationController.navigationBarHidden = YES;}#pragma mark ---- init View- loadCommonTitleView{ // _titleBarView _titleBarView = [UIView create]; _titleBarView.backgroundColor = [UIColor grayColor]; [self.view addSubview:_titleBarView]; LAY(_titleBarView.left, self.view.left, 1, 0); LAY(_titleBarView.right, self.view.right, 1, 0); LAY(_titleBarView.top, self.view.top, 1, 0); LAYC(_titleBarView.height, 64.f); // _titleBarBottomLine _titleBarBottomLine = [UIView create]; _titleBarBottomLine.backgroundColor = [UIColor redColor]; [_titleBarView addSubview:_titleBarBottomLine]; LAY(_titleBarBottomLine.left, _titleBarView.left, 1, 0); LAY(_titleBarBottomLine.right, _titleBarView.right, 1, 0); LAY(_titleBarBottomLine.bottom, _titleBarView.bottom, 1, 0); LAYC(_titleBarBottomLine.height, 1.f); // _titleBarLab _titleBarLab = [UILabel create]; _titleBarLab.font = [UIFont boldSystemFontOfSize:17]; _titleBarLab.textColor = [UIColor whiteColor]; _titleBarLab.backgroundColor = [UIColor redColor]; _titleBarLab.textAlignment = NSTextAlignmentCenter; [_titleBarView addSubview:_titleBarLab]; LAY(_titleBarLab.top, _titleBarView.top, 1, 20.f); LAY(_titleBarLab.centerX, _titleBarView.centerX, 1, 0); LAYC(_titleBarLab.width, 200.f); LAYC(_titleBarLab.height, 44.f); // _leftBtn _leftBtnView = [[RedPointView alloc] initWithFrame:CGRectMake(10.f, 20.f, 44.f, 44.f)]; _leftBtnView.backgroundColor = [UIColor clearColor]; [_titleBarView addSubview:_leftBtnView]; [_leftBtnView refreshImageName:@"pubCommon_back" target:self action:@selector(leftBtnViewClick:)]; LAY(_leftBtnView.left, _titleBarView.left, 1, 15.f); LAY(_leftBtnView.centerY, _titleBarLab.centerY, 1, 0); // _rightBtn _rightBtnView = [[RedPointView alloc] initWithFrame:CGRectMake(self.view.frame.size.width-10.f-44.f, 20.f, 44.f, 44.f)]; _rightBtnView.backgroundColor = [UIColor clearColor]; [_rightBtnView refreshImageName:@"pubCommon_more" target:self action:@selector(rightBtnViewClick:)]; [_titleBarView addSubview:_rightBtnView]; LAY(_rightBtnView.right, _titleBarView.right, 1, -15.f); LAY(_rightBtnView.centerY, _titleBarLab.centerY, 1, 0);}- loadCommonContentView{ _contentView = [[UIView alloc] initWithFrame:CGRectMake(0, 64.f, self.view.frame.size.width, self.view.frame.size.height-64.f)]; _contentView.backgroundColor = [UIColor greenColor]; [self.view addSubview:_contentView];}#pragma mark ---- event response- leftBtnViewClick:(UITapGestureRecognizer *)gesture{ NSLog(@"-----leftBtnViewClick----"); [self.navigationController popToRootViewControllerAnimated:YES];}- rightBtnViewClick:(UITapGestureRecognizer *)gesture{ NSLog(@"-----rightBtnViewClick----");}#pragma mark ---- 提供给外部的功能方法#pragma mark - 修改导航栏控件样式/*注意:一般来说,当我们想改变一个视图的某属性时,首先想到的方案应该是在视图的层级关系中找出该视图,然后改变其属性就行。但是此情况却有所不同。当为文本时视图为UILabel,当为图片时视图为UIImageView。因此在方法内部首先要清除原有视图,然后根据是显示文本或图片,而创建添加UILabel或者UIImageView。*/// 修改导航栏左右按钮为文本形式- changeTitleBarButtonText:(NSString *)text buttonType:(NSInteger)btnType{ UIView *bgView = (btnType==-1 ? _leftBtnView:_rightBtnView); UIImageView *imgView = [bgView viewWithTag:11]; [imgView removeFromSuperview]; imgView = nil; if{ UILabel *lab = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, bgView.frame.size.width, bgView.frame.size.height)]; lab.text = text; lab.textColor = [UIColor whiteColor]; lab.font = [UIFont boldSystemFontOfSize:15]; lab.backgroundColor = [UIColor clearColor]; lab.tag = 11; lab.userInteractionEnabled = YES; lab.contentMode = UIViewContentModeCenter; lab.textAlignment = NSTextAlignmentCenter; [bgView addSubview:lab]; }}// 修改导航栏左右按钮为图标形式- changeTitleBarButtonImage:(NSString *)imgName buttonType:(NSInteger)btnType{ UIView *bgView = (btnType==-1 ? _leftBtnView:_rightBtnView); UIImageView *imgView = [bgView viewWithTag:11]; [imgView removeFromSuperview]; imgView = nil; if{ UIImageView *imgView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, bgView.frame.size.width, bgView.frame.size.height)]; imgView.image = [UIImage imageNamed:imgName]; imgView.backgroundColor = [UIColor clearColor]; imgView.tag = 11; imgView.userInteractionEnabled = YES; imgView.contentMode = UIViewContentModeCenter; [bgView addSubview:imgView]; }}#pragma mark - 加载动画的显示、隐藏// 显示、出现tipView- showTipsView:tipType{ if(!_tipView){ _tipView = [[TipView alloc] init]; } [_tipView addTipViewTarget:self tipType:TipType_loadingFull];}// 隐藏、消失tipView- hideTipsView{ if { [_tipView removeTipView]; }}#pragma mark - 发送网络请求同时显示加载动画- startNetworkRequestAction:action tipType:tipType{ [self startNetworkRequestAction:action tipType:tipType withObject:nil];}- startNetworkRequestAction:action tipType:tipType withObject:object{ [self showTipsView:tipType]; // 显示loading动画。 if{ return; } [self performSelector:action withObject:object afterDelay:0];}////网络请求失败 点击重试//-failureClickedRetry//{// //}#pragma mark ---- 内部功能方法- fullScreenSlide{ // 代码略。}// 修改状态栏颜色- (UIStatusBarStyle)preferredStatusBarStyle{ return UIStatusBarStyleLightContent;}#pragma mark ---- setter、getter- setIsListensKeyboard:isListensKeyboard{ if(isListensKeyboard){ AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate; appDelegate.ywKeyboardDelegate = self; }}@end

给 leftBarButtonItem 加上 icon

如故前边的标准,当改正某一处的代码时候,尽量对原来代码做尽也许小的变动

  • 以前大家曾经设置好leftbarButtonItem文字展现的情事难点
  • 作者们的要求又是在这里基本功上直接加三个回来的icon而已
  • 故而,大家只要对自定义神速成立leftBarButtonItem那边假设能直接改好了就最佳

小技巧:

  • 当您想查看某三个艺术都在哪个文件内被如何措施调用的时候
  • 您能够在此个格局的主意明上右键->Find Call Hierarchy
    Hierarchy : 层级

图片 7

UIBarButtonItem的自定义火速成立leftbarButtonItem的艺术扩张一下,扩展一个参数isBack,暗许值是false

/// 字体 target action
///
/// - Parameters:
///   - hq_title: title
///   - fontSize: fontSize
///   - target: target
///   - action: action
///   - isBack: 是否是返回按钮,如果是就加上箭头的`icon`
convenience init(hq_title: String, fontSize: CGFloat = 16, target: Any?, action: Selector, isBack: Bool = false) {

    let btn = UIButton(hq_title: hq_title, fontSize: fontSize, normalColor: UIColor.darkGray, highlightedColor: UIColor.orange)

    if isBack {
        let imageName = "nav_back"
        btn.setImage(UIImage.init(named: imageName), for: .normal)
        btn.setImage(UIImage.init(named: imageName   "_highlighted"), for: .highlighted)
        btn.sizeToFit()
    }

    btn.addTarget(target, action: action, for: .touchUpInside)
    // self.init 实例化 UIBarButtonItem
    self.init(customView: btn)
}

在前头决断重回按键显示文字的地点重新载入参数一下,只要求扩大四个参数isBack: true

vc.navItem.leftBarButtonItem = UIBarButtonItem(hq_title: title, target: self, action: #selector(popToParent), isBack: true)

经过那样的变异,笔者乍然发掘swift在那处是比objective-c团结超多的,要是您给参数设置了一个暗许值。那么,就能够不对原方法变成损害,不影响原方法的调用。

但是,objective-c就从未那样团结,要是在原方法上加码参数,那么在此之前调用过此形式的地点,就能够整整报错。即便不想对原方法有退换,那么将在重复写七个通通等同的只是终极面扩充了那一个急需的参数而已的一个新的章程。

你看swift是或不是真的轻易了不菲。

主题材料二:部分界面重回拦截难点
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    [self LVSetUpNavItem];
}

-(void)LVSetUpNavItem{
    self.navigationItem.leftBarButtonItem = nil;//移除原有的统一定制的返回键
    UIButton *LeftItem = [UIButton new];
    LeftItem.frame = CGRectMake(0, 0, 24, 24);
    [LeftItem.imageView setContentMode:UIViewContentModeCenter];
    [LeftItem setImage:[UIImage imageNamed:@"back"] forState:UIControlStateNormal];
    [LeftItem setBackgroundImage:[UIImage imageNamed:@"nav_fanhui"] forState:UIControlStateNormal];
    [LeftItem addTarget:self action:@selector(LVbackBtnClick) forControlEvents:UIControlEventTouchUpInside];
    UIBarButtonItem *customLeftItem = [[UIBarButtonItem alloc] initWithCustomView:LeftItem];
    self.navigationItem.leftBarButtonItem = customLeftItem;
}

-(void)LVbackBtnClick{
    NSNumber *value = [NSNumber numberWithInt:UIInterfaceOrientationPortrait];
    [[UIDevice currentDevice] setValue:value forKey:@"orientation"];
    [self.navigationController popViewControllerAnimated:YES];
}

图片 8WechatIMG1.png

上边是加载动漫视图的代码:

设置 navigationBar 的 title 的颜色

navigationBar.tintColor = UIColor.red那般是非平常的,因为tintColor不是安装标题颜色的。

barTintColor是拘禁整个导航条的背景象

tintColor是管理导航条上item文字的水彩

titleTextAttributes是安装导航栏title的颜色

举个例子你找不到安装的措施,最棒去UINavigationItem的头文件之中去找一下,你能够control 6马上搜索color首要字,如果未有的话,提出你追寻attribute试跳,因为日常安装属性的艺术都能够解决大多数您想缓和的标题标。

// 设置`navigationBar`的渲染颜色
navigationBar.barTintColor = UIColor.hq_color(withHex: 0xF6F6F6)
// 设置导航栏`title`的颜色
navigationBar.titleTextAttributes = [NSForegroundColorAttributeName : UIColor.darkGray]
// 设置系统`leftBarButtonItem`渲染颜色
navigationBar.tintColor = UIColor.orange

难题三:系统滑动再次回到事件失效难题

此地下工作程里面用的是UINavigationController FDFullscreenPopGesture其一工具类所以不设有那个主题材料,这里照旧贴出管理代码。感兴趣的心上大家得以自个儿看下那边小说ios全局重回开关和全屏侧滑功用。

  1. 当重写了系统的push方法时,系统的侧滑功用就能失效.
  2. 侧滑效用完毕原理:当客商选取侧滑功用的时候,系统是通过代办去文告落到实处侧滑成效,但是如若重写了push,那么代理知道,既然重写了push方法,那么代理就能打招呼系统不要去落到实处侧滑功效,这样就使得侧滑功效失效,或许产生bug.
  3. 思路:大家就经过对代理的操纵,来促成对侧滑功用的支配
    3.1 设置六本性质,用来保存在push方法重写早前的代办
//设置一个属性保存系统的代理
@property (nonatomic, strong) id popDelegate;

//设置一个属性保存系统的代理
@property (nonatomic, strong) id popDelegate;

3.2 在重写的push方法中,让代理等于空—-意思是不让代理去文告系统侧滑功用失效

self.interactivePopGestureRecognizer.delegate = nil;

3.3 在代理方法中,将原来保存的代办属性,赋值给系统,让系统代理保持原本的行事

#pragma mark - 实现代理方法

- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
    //判断控制器是否为根控制器
    if (self.childViewControllers.count == 1) {
        //将保存的代理赋值回去,让系统保持原来的侧滑功能
        self.interactivePopGestureRecognizer.delegate = self.popDelegate;
    }
}

前天还遇上了一种情况是tableViewtableFooterView是个开关,有操作事件。那时,当现身空数据情状时,tableView便一行都不曾,tableFooterView便跑到tableView顶头了。此时给view地点覆盖一层展现空数据的视图的话,会隐讳住tableFooterView,客户看不到可点击的按钮了,这即是主题材料了。所以这种景况下方面所说的掩瞒视图的方案也是不可行的。

** TipView.h **

设置设备方向

有些时候我们的APP或是会在某个分界面里面供给扶植横屏而是任何的地点又愿意它只扶助竖屏,那就供给大家用代码去设置

override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
    return .portrait
}

设置扶助的趋势之后,当前的调节器及子调节器都会遵从那些趋向,因而写在HQMainViewController里面


三、注意事项


简单的讲,当加载战败分界面或空数据分界面不是覆盖整个荧屏时,直接在viewaddSubView:视图的方案依旧现身挡住不应该挡住的标题,要么固然没挡住什么东西,可是经历很猛烈。那时,应当考虑有没其余方案能比较康健的解决这一个标题。

#import <UIKit/UIKit.h>typedef NS_ENUM(NSInteger, TipType){ TipType_LoadingCenter = 0, // 只在屏幕中心的加载动画 TipType_loadingFull, // 覆盖整个屏幕的加载动画};@interface TipView : UIView@property (nonatomic, assign)CGRect tipFrame;@property (nonatomic, assign)CGSize tipImageSize;@property (nonatomic, strong)UIColor *tipBgColor;// 添加、创建tipView- addTipViewTarget:target tipType:type;// 移除tipView- removeTipView;@end

利用 extension 隔开 TableView 数据源方法

在基类设置datasourcedelegate,这样子类就足以一向促成方式就足以了,不用种种tableView的页面都去设置tableView?.dataSource = selftableView?.delegate = self了。

  • 基类只是达成形式,子类担当具体的兑现
  • 子类的数据源方法无需super
  • 返回UITableViewCell()只是为了未有语法错误

HQBaseViewController里,完成如下代码

extension HQBaseViewController: UITableViewDataSource, UITableViewDelegate {

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 0
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        return UITableViewCell()
    }
}

设置一个加载数据的措施loadData,在此边并不去做别的职业,只是为了方便子类重写此措施加载数据就能够了。

/// 加载数据,具体的实现由子类负责
func loadData() {

}

问题1:UIViewController BackButtonItemTitle导致标题文字出现

主题素材:开采一些分界面再次回到标题又现身了,检查全部UIViewController相关分类开采UIViewController BackButtonItemTitle.m切实内部贯彻就不贴出来了,删除就能够。

图片 9

参谋文献:
ios全局重回开关和全屏侧滑作用
改iOS重回开关的两种艺术
iOS拦截导航栏重回按键事件的科学方法
简书App适配iOS 11
iOS关于设置三个自定义的领航调节器重临开关
ios自定义图片替换系统导航栏重返按键样式
Custom back indicator image and iOS 11
SFEmptyBackButton
自定义iOS的Back按键(backBarButtonItem)和pop交互作用手势(interactivepopgesturerecognizer卡塔尔(قطر‎

方案是一些:我们得以以二个UITableViewCell作为空数据提醒分界面,那样便能够息灭地方所说的主题材料。符合规律常有多少的景色下,tableView的数据model是坐落二个数量源数组里tableViewData里的,且有其自定义样式的cell来突显数据。 当状态成为空数据景况时,大家得以将数据源清空,只放入贰个代表空分界面包车型大巴字符串nullKey元素,同时,在tableView的代办方法里遵照数据源成分的项目是model还是NSString来推断应该出示符合规律的cell恐怕表示空数据的NullDataCell。那样,当状态成为空数据状态,数据源清空并只归入nullKey后,刷新tableViewtableView就能够显得表示空分界面包车型大巴NullDataCell

** TipView.m **

绑定假数据测量试验

由于HQBaseViewController里面完毕了tableViewtableViewDataSourcetableViewDelegate以及loadData(自定义加载数据的方法),下一步大家即就要子调控器里面测量试验一下功用了。

  • 创制一些假数据
fileprivate lazy var statusList = [String]()

/// 加载数据
override func loadData() {

    for i in 0..<10 {
        statusList.insert(i.description, at: 0)
    }
}
  • 贯彻数据源方法
// MARK: - tableViewDataSource
extension HQAViewController {

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return statusList.count
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath)
        cell.textLabel?.text = statusList[indexPath.row]
        return cell
    }
}

从那之后,分界面上应当可以展现出多少了,如下所示

图片 10

可是细心调查是存在难题的

  • 率先行应有是从9开始的,说明tableView的起第二地方不对
  • 若是数额丰裕多的事态下(多到能够超越一个显示屏的多寡卡塔尔国,能够开采上边也是停在tabBar的前面,后面部分地方也非常

经过例子来证实。下图中是个tableViewcell的体制是暗许的UITableViewCell品种,用于体现一行字符串。tableViewtableFootView是古金色部分,固然当出现空数据情形时不可能被遮挡住,即空数据分界面只可以出以后反动部分。在导航栏的左边手有个"null data"按键,大家将其用于模拟现身空数据的动静。

#import "TipView.h"#import "CommonViewController.h"@interface TipView (){ UIView *_bgView;}@end@implementation TipView#pragma mark ---- 公共方法:提供给外部的功能方法// 添加bgView到window上。- addTipViewTarget:target tipType:type{ if{ [_bgView removeFromSuperview]; _bgView = nil; } // 确定frame if([target isKindOfClass:[CommonViewController class]]){ CommonViewController *vc = (CommonViewController *)target; _tipFrame = vc.contentView.frame; } _bgView = [[UIView alloc] initWithFrame:_tipFrame]; if(_tipBgColor){ _bgView.backgroundColor = _tipBgColor; }else{ _bgView.backgroundColor = [UIColor whiteColor]; // 默认背景是白色 } switch  { case TipType_loadingFull: { _bgView = [self createLoadingFullView]; // 创建bgView,即加载动画的核心视图 UIWindow *window = [UIApplication sharedApplication].keyWindow; if  { window = [[UIApplication sharedApplication].windows lastObject]; } [window addSubview:_bgView]; } break; case TipType_LoadingCenter: { // 没写,略。 } break; }}// 移除tipView- removeTipView{ if { [_bgView removeFromSuperview]; _bgView = nil; }}#pragma mark ---- 内部功能方法// 创建加载动画核心视图- createLoadingFullView{ UIImage *image = [UIImage imageNamed:@"Network_loading1"]; if(_tipImageSize.width == 0){ _tipImageSize = CGSizeMake(image.size.width/2.f, image.size.height/2.f); } UIImageView *animationView = [[UIImageView alloc] initWithFrame:CGRectMake((_bgView.frame.size.width-_tipImageSize.width)/2.f, (_bgView.frame.size.height-_tipImageSize.height)/2.f, _tipImageSize.width, _tipImageSize.height)]; animationView.image = image; animationView.backgroundColor = [UIColor whiteColor]; animationView.animationImages = [NSArray arrayWithObjects: image, [UIImage imageNamed:@"Network_loading2.png"], [UIImage imageNamed:@"Network_loading3.png"], [UIImage imageNamed:@"Network_loading4.png"], [UIImage imageNamed:@"Network_loading5.png"], [UIImage imageNamed:@"Network_loading6.png"], [UIImage imageNamed:@"Network_loading7.png"], [UIImage imageNamed:@"Network_loading8.png"], [UIImage imageNamed:@"Network_loading9.png"], [UIImage imageNamed:@"Network_loading10.png"], [UIImage imageNamed:@"Network_loading11.png"], [UIImage imageNamed:@"Network_loading12.png"], [UIImage imageNamed:@"Network_loading13.png"], [UIImage imageNamed:@"Network_loading14.png"], [UIImage imageNamed:@"Network_loading15.png"], [UIImage imageNamed:@"Network_loading16.png"], [UIImage imageNamed:@"Network_loading17.png"], [UIImage imageNamed:@"Network_loading18.png"], nil]; [animationView setAnimationDuration:1.0f]; [animationView setAnimationRepeatCount:-1]; [animationView startAnimating]; [_bgView addSubview:animationView]; return _bgView; }@end

杀鸡取蛋 TableView 之处难点

主要在HQBaseViewController里,重新载入参数tableViewContentInsets

/*
 取消自动缩进,当导航栏遇到`scrollView`的时候,一般都要设置这个属性
 默认是`true`,会使`scrollView`向下移动`20`个点
 */
automaticallyAdjustsScrollViewInsets = false

tableView?.contentInset = UIEdgeInsets(top: navigationBar.bounds.height,
                                       left: 0,
                                       bottom: tabBarController?.tabBar.bounds.height ?? 49,
                                       right: 0)

因为平常的小卖部里,页面多数都是ViewController TableView。所以,相符的供给,直接在基类调整器设置好就能够了。


图片 11荧屏快速照相2017-01-17 23.03.24.png

谈到底是HomeViewController中的代码:

丰硕下拉刷新控件

  • 在基类调控器中定义下拉刷新控件,那样就不要每一个子调整器页面单独设置了
  • refreshControl加上监听方法,监听refreshControlvalueChange事件
  • 当班值日退换的时候,重新执行loadData方法
  • 子类会重写基类的loadData办法,由此不用在去子类重写此措施
// 设置刷新控件
refreshControl = UIRefreshControl()
tableView?.addSubview(refreshControl!)
refreshControl?.addTarget(self, action: #selector(loadData), for: .valueChanged)

看代码:当点击右上角的“null data”按键时,便推行上边包车型大巴代码:将数据源数组清空并只增多叁个意味空分界面包车型客车字符串成分。然后刷新该tableView。此时,tableView的数据源唯有二个,即tableView将唯有一行,这一行便是空数据分界面。

** HomeViewController.h **

依傍延时加载数据

  • 诚如互连网必要都会有延时,为了模仿的绘身绘色一点,这里大家也做了效仿延时加载数据。
  • 还要相比一下swiftobjective-c的延迟加载异同点

宪章延迟加载数据

/// 加载数据
override func loadData() {

    // 模拟`延时`加载数据
    DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()   1) {

        for i in 0..<15 {
            self.statusList.insert(i.description, at: 0)
        }
        self.refreshControl?.endRefreshing()
        self.tableView?.reloadData()
    }
}

swift 延迟加载

// 模拟`延时`加载数据
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()   5) {

    print("5 秒后,执行闭包内的代码")
}

objective-c 延迟加载

/*
 dispatch_time_t when,      从现在开始,经过多少纳秒(delayInSeconds * 1000000000)
 dispatch_queue_t queue,    由队列调度任务执行
 dispatch_block_t block     执行任务的 block
 */
dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC));

dispatch_after(when, dispatch_get_main_queue(), ^{
    // code to be executed after a specified delay
    NSLog(@"5 秒后,执行 Block 内的代码");
});

虽说都以一句话,可是swift语法的可读性明显比objective-c要好一些。


- rightBtnClicked{ [_tableViewData removeAllObjects]; [_tableViewData addObject:[NullDataCell nullKey]]; [_tableView reloadData];}
#import "CommonViewController.h"@interface HomeViewController : CommonViewController@end

上拉刷新

最近超过半数APP做无缝的上拉刷新,正是当tableView滚动到最终一行cell的时候,自动刷新加载数据。

用二个属性来记录是还是不是是上拉加载数据

/// 上拉刷新标记
var isPullup = false

滚动到最后一行 cell 的时候加载数据

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {

    let row = indexPath.row
    let section = tableView.numberOfSections - 1

    if row < 0 || section < 0 {
        return
    }

    let count = tableView.numberOfRows(inSection: section)

    if row == (count - 1) && !isPullup {

        isPullup = true
        loadData()
    }
}

在首页调控器里面模拟加载数据的时候,依照属性isPullup看清是上拉加载,照旧下拉刷新

/// 加载数据
override func loadData() {

    // 模拟`延时`加载数据
    DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()   1) {

        for i in 0..<15 {

            if self.isPullup {
                self.statusList.append("上拉 (i)")
            } else {
                self.statusList.insert(i.description, at: 0)
            }
        }
        self.refreshControl?.endRefreshing()
        self.isPullup = false
        self.tableView?.reloadData()
    }
}

NullDataCellnullKey办法就是回去三个意味着空分界面包车型客车字符串:

** HomeViewController.m **

未登入视图展现(访客视图卡塔尔

实际中日常会碰到一些一时扩充的必要,举例登入后显得的是一种视图,未登陆又显得其余一种视图,即使您的营业所是面向公司里面包车型地铁APP,那么您可能相会临更加的多的顾客角色。这里大家一时半刻只谈谈已登录未登录二种情况下的情景。

要么前边的尺码,不管做如何新职能,扩充哪些不时的供给,大家要做的都以想艺术对原来的代码及结构做最小的调治,特别是对原先的Controller里面包车型大巴代码入侵的越小越好。

在基类调整器的setupUI(设置界面)的艺术里面,我们一向开立了tableView,那么大家只要有三个符号,能依赖这几个符号来筛选是成立普通视图,依然创立来访的客人视图。就足以很好的减轻此类主题素材了。

  • 扩大二个客户登陆标识
/// 用户登录标记
var userLogon = false
  • 基于标志判别视图呈现
userLogon ? setupTableView() : setupVistorView()
  • 创拜见客视图的代码
/// 设置访客视图
fileprivate func setupVistorView() {

    let vistorView = UIView(frame: view.bounds)
    vistorView.backgroundColor = UIColor.hq_randomColor()
    view.insertSubview(vistorView, belowSubview: navigationBar)
}

自定义四个 View,世袭自UIView,在内部安装访客视图的界面

class HQVistorView: UIView {

    override init(frame: CGRect) {
        super.init(frame: frame)

        setupUI()
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

// MARK: - 设置访客视图界面
extension HQVistorView {

    func setupUI() {
        backgroundColor = UIColor.white
    }
}
 (NSString*)nullKey{ return @"NullData";}
#import "HomeViewController.h"#import "AppDelegate.h"@interface HomeViewController ()@end@implementation HomeViewController- viewDidLoad { [super viewDidLoad]; [self loadTitleView]; [self loadContentView]; self.isListensKeyboard = YES; // 已经重写了isListensKeyboard的setter方法,会在setter方法里帮你设置键盘通知处理的代理,只需实现其代理方法即可。 // [self showTipsView:TipType_loadingFull]; [self startNetworkRequestAction:@selector(requestStudentList) tipType:TipType_loadingFull]; //发送网络请求,同时出现加载动画。 // 模拟网络回调完成,3秒后隐藏加载动画。 dispatch_time_t time=dispatch_time(DISPATCH_TIME_NOW, 3ull *NSEC_PER_SEC); dispatch_after(time, dispatch_get_main_queue(), ^{ //执行操作 NSLog(@"----假装后台已做回应,数据已经请求到。所以隐藏加载视图----"); [self hideTipsView]; });}#pragma mark ---- init views- loadTitleView{ self.titleBarView.backgroundColor = [UIColor blackColor]; self.titleBarLab.text = @"老王的Demo"; [self changeTitleBarButtonText:@"其他" buttonType:1]; [self changeTitleBarButtonImage:@"PubCommon_more" buttonType:-1];}- loadContentView{ UITextField *textfield = [[UITextField alloc] initWithFrame:CGRectMake(20, 400, self.contentView.frame.size.width-40.f, 40.f)]; textfield.borderStyle = UITextBorderStyleRoundedRect; textfield.placeholder = @"请输入姓名"; [self.contentView addSubview:textfield];}#pragma mark ---- event response// 重写基类中的点击事件,进行点击事件处理定制。- leftBtnViewClick:(UITapGestureRecognizer *)gesture{ UIAlertView *alertV = [[UIAlertView alloc] initWithTitle:nil message:@"老王子点击了导航栏左按钮" delegate:self cancelButtonTitle:@"取消" otherButtonTitles:nil]; [alertV show];}- rightBtnViewClick:(UITapGestureRecognizer *)gesture{ UIAlertView *alertV = [[UIAlertView alloc] initWithTitle:nil message:@"老王子点击了导航栏右按钮" delegate:self cancelButtonTitle:@"取消" otherButtonTitles:nil]; [alertV show];}#pragma mark ---- network request- requestStudentList{ NSLog(@"----这里写请求学生列表接口的方法----");}#pragma mark ---- ywkeyboardDelegate- ywKeyboardChangeStatus:(KeyBoardChangeType)changeType beginFrame:beginFrame endFrame:endFrame duration:duration keyboardHeight:kbHeight userInfo:(NSDictionary *)info{ if(changeType == KeyBoardWillShow){ [UIView animateWithDuration:duration animations:^{ self.contentView.transform = CGAffineTransformMakeTranslation(0, -kbHeight); }]; } else if(changeType == KeyBoardWillHide){ [UIView animateWithDuration:duration animations:^{ self.contentView.transform = CGAffineTransformIdentity; }]; }}- touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ [self.contentView endEditing:YES];}@end

运用原生布局体系定义访客视图分界面

在自定义访客视图HQVistorView中布局各种子控件

  • 懒加载控件
/// 图像视图
fileprivate lazy var iconImageView: UIImageView = UIImageView(hq_imageName: "visitordiscover_feed_image_smallicon")
/// 遮罩视图
fileprivate lazy var maskImageView: UIImageView = UIImageView(hq_imageName: "visitordiscover_feed_mask_smallicon")
/// 小房子
fileprivate lazy var houseImageView: UIImageView = UIImageView(hq_imageName: "visitordiscover_feed_image_house")
/// 提示标签
fileprivate lazy var tipLabel: UILabel = UILabel(hq_title: "关注一些人,回这里看看有什么惊喜关注一些人,回这里看看有什么惊喜")
/// 注册按钮
fileprivate lazy var registerButton: UIButton = UIButton(hq_title: "注册", color: UIColor.orange, backImageName: "common_button_white_disable")
/// 登录按钮
fileprivate lazy var loginButton: UIButton = UIButton(hq_title: "登录", color: UIColor.darkGray, backImageName: "common_button_white_disable")
  • 加多视图
addSubview(iconImageView)
addSubview(maskImageView)
addSubview(houseImageView)
addSubview(tipLabel)
addSubview(registerButton)
addSubview(loginButton)

// 取消 autoresizing
for v in subviews {
    v.translatesAutoresizingMaskIntoConstraints = false
}
  • 原生构造

机动布局本质公式 : A控件的性质a = B控件的性质b * 常数 约束

firstItem.firstAttribute {==,<=,>=} secondItem.secondAttribute * multiplier   constant

let margin: CGFloat = 20.0

/// 图像视图
addConstraint(NSLayoutConstraint(item: iconImageView,
                                 attribute: .centerX,
                                 relatedBy: .equal,
                                 toItem: self,
                                 attribute: .centerX,
                                 multiplier: 1.0,
                                 constant: 0))
addConstraint(NSLayoutConstraint(item: iconImageView,
                                 attribute: .centerY,
                                 relatedBy: .equal,
                                 toItem: self,
                                 attribute: .centerY,
                                 multiplier: 1.0,
                                 constant: -60))
/// 小房子
addConstraint(NSLayoutConstraint(item: houseImageView,
                                 attribute: .centerX,
                                 relatedBy: .equal,
                                 toItem: iconImageView,
                                 attribute: .centerX,
                                 multiplier: 1.0,
                                 constant: 0))
addConstraint(NSLayoutConstraint(item: houseImageView,
                                 attribute: .centerY,
                                 relatedBy: .equal,
                                 toItem: iconImageView,
                                 attribute: .centerY,
                                 multiplier: 1.0,
                                 constant: 0))
/// 提示标签
addConstraint(NSLayoutConstraint(item: tipLabel,
                                 attribute: .centerX,
                                 relatedBy: .equal,
                                 toItem: iconImageView,
                                 attribute: .centerX,
                                 multiplier: 1.0,
                                 constant: 0))
addConstraint(NSLayoutConstraint(item: tipLabel,
                                 attribute: .top,
                                 relatedBy: .equal,
                                 toItem: iconImageView,
                                 attribute: .bottom,
                                 multiplier: 1.0,
                                 constant: margin))
addConstraint(NSLayoutConstraint(item: tipLabel,
                                 attribute: .width,
                                 relatedBy: .equal,
                                 toItem: nil,
                                 attribute: .notAnAttribute,
                                 multiplier: 1.0,
                                 constant: 236))
/// 注册按钮
addConstraint(NSLayoutConstraint(item: registerButton,
                                 attribute: .left,
                                 relatedBy: .equal,
                                 toItem: tipLabel,
                                 attribute: .left,
                                 multiplier: 1.0,
                                 constant: 0))
addConstraint(NSLayoutConstraint(item: registerButton,
                                 attribute: .top,
                                 relatedBy: .equal,
                                 toItem: tipLabel,
                                 attribute: .bottom,
                                 multiplier: 1.0,
                                 constant: margin))
addConstraint(NSLayoutConstraint(item: registerButton,
                                 attribute: .width,
                                 relatedBy: .equal,
                                 toItem: nil,
                                 attribute: .notAnAttribute,
                                 multiplier: 1.0,
                                 constant: 100))
/// 登录按钮
addConstraint(NSLayoutConstraint(item: loginButton,
                                 attribute: .right,
                                 relatedBy: .equal,
                                 toItem: tipLabel,
                                 attribute: .right,
                                 multiplier: 1.0,
                                 constant: 0))
addConstraint(NSLayoutConstraint(item: loginButton,
                                 attribute: .top,
                                 relatedBy: .equal,
                                 toItem: registerButton,
                                 attribute: .top,
                                 multiplier: 1.0,
                                 constant: 0))
addConstraint(NSLayoutConstraint(item: loginButton,
                                 attribute: .width,
                                 relatedBy: .equal,
                                 toItem: registerButton,
                                 attribute: .width,
                                 multiplier: 1.0,
                                 constant: 0))

那儿,大家更换了数据源,并刷新了tableView想让其再一次加载一遍,是因为大家须求在tableView的代办方法里平等沟通cell,将UITableViewCell调换为NullDataCell

看看效果:

运用 VFL 布局子控件

  • VFL 可视化语言,多用来三回九转参照关系,如遭遇居中对其,平常Dolly用参照
  • H水平方向
  • V竖直方向
  • |边界
  • []富含控件的名目字符串,对应提到在views字典中定义
  • ()概念控件的宽/高,能够在metrics中指定

VFL 参数的解释 :

  • views: 定义 VFL 中央调节件名称和实在名称的光彩夺目关系
  • metrics: 定义 VFL 中 ()内钦点的常数映射关系,防止在代码中冒出法力数字
let viewDict: [String: Any] = ["maskImageView": maskImageView,
                "registerButton": registerButton]
let metrics = ["spacing": -35]

addConstraints(NSLayoutConstraint.constraints(
    withVisualFormat: "H:|-0-[maskImageView]-0-|",
    options: [],
    metrics: nil,
    views: viewDict))
addConstraints(NSLayoutConstraint.constraints(
    withVisualFormat: "V:|-0-[maskImageView]-(spacing)-[registerButton]",
    options: [],
    metrics: metrics,
    views: viewDict))

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ id obj = _tableViewData[indexPath.row]; if([obj isKindOfClass:[NSString class]]) { if([obj isEqualToString:[NullDataCell nullKey]]){ static NSString *nullCellId = @"nullCellId"; NullDataCell *cell = [tableView dequeueReusableCellWithIdentifier:nullCellId]; if(cell==nil){ cell = [[NullDataCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nullCellId]; } return cell; } } else { CellModel *model = (CellModel *)obj; static NSString *cellId = @"cellId"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellId]; if(cell==nil){ cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellId]; } cell.textLabel.text = model.title; return cell; } return nil;}

图片 12显示屏摄像.gif

管理各类子调控器访客视图突显难题

到如今停止,即便大家只是在基类调节器里面创制了来访的客人视图setupVistorView,独有四个访客视图的HQVistorView,不过实际上当大家点击不一样的子调整器的时候,每一种子调整器都会创制一个访客视图。点击八个子调控器的时候,访客视图打字与印刷之处都不相符。

<HQSwiftMVVM.HQVistorView: 0x7fea6970ed30; frame = (0 0; 375 667); layer = <CALayer: 0x608000036ec0>>
<HQSwiftMVVM.HQVistorView: 0x7fea6940d3b0; frame = (0 0; 375 667); layer = <CALayer: 0x600000421e60>>
<HQSwiftMVVM.HQVistorView: 0x7fea6973cf60; frame = (0 0; 375 667); layer = <CALayer: 0x608000036a40>>
<HQSwiftMVVM.HQVistorView: 0x7fea6943d990; frame = (0 0; 375 667); layer = <CALayer: 0x600000423760>>

概念贰特性能词典,把图片名称和提醒标语传入到HQVistorView中,通过重写didSet方法设置

/// 设置访客视图信息字典[imageName / message]
var vistorInfo: [String: String]? {
    didSet {
        guard let imageName = vistorInfo?["imageName"],
            let message = vistorInfo?["message"]
        else {
            return
        }
        tipLabel.text = message
        if imageName == "" {
            return
        }
        iconImageView.image = UIImage(named: imageName)
    }
}

HQBaseViewController概念三个同一的访客视图信息字典,方便外部流传。那样做的目标是外部盛传到HQBaseViewController中国国投息词典,能够由此setupVistorView措施传到HQVistorView中,再重写HQVistorView中的访客视图新闻字典的didSet办法以达成设置的指标。

/// 设置访客视图信息字典
var visitorInfoDictionary: [String: String]?

/// 设置访客视图
fileprivate func setupVistorView() {

    let vistorView = HQVistorView(frame: view.bounds)
    view.insertSubview(vistorView, belowSubview: navigationBar)
    vistorView.vistorInfo = visitorInfoDictionary
}

下一步正是商讨在哪儿给访客视图新闻词典传值的主题材料了。

tableView的代办方法里,决断数据源数组的因素是怎么着项目,即便是CellModel连串,就认证是例市场价格形,设置其相应的行高,并出示其相应的视图;假使数组成分是NSString连串,就认证是空数据的动静,设置其对应的行高,并呈现其对应的视图。

需求注脚的是地点代码里提到的两处代码在本文中尚无付诸。但在自个儿的别样笔记中早已写过了。一处是写在AppDelegate里关于键盘布告的:《UITextField一箩筐——输入长度节制、自定义placeholder、键盘遮挡难点》另一处是关于自定义侧滑重返的代码:《iOS使其支持侧滑再次来到》

修正设置子调节器的参数配置

  • 校正设置子调控器的布署
fileprivate func setupChildControllers() {

    let array: [[String: Any]] = [
        [
            "className": "HQAViewController",
            "title": "首页",
            "imageName": "a",
            "visitorInfo": [
                "imageName": "",
                "message": "关注一些人,回这里看看有什么惊喜"
            ]
        ],
        [
            "className": "HQBViewController",
            "title": "消息",
            "imageName": "b",
            "visitorInfo": [
                "imageName": "visitordiscover_image_message",
                "message": "登录后,别人评论你的微博,发给你的信息,都会在这里收到通知"
            ]
        ],
        [
            "className": "UIViewController"
        ],
        [
            "className": "HQCViewController",
            "title": "发现",
            "imageName": "c",
            "visitorInfo": [
                "imageName": "visitordiscover_image_message",
                "message": "登录后,最新、最热微博尽在掌握,不再会与时事潮流擦肩而过"
            ]
        ],
        [
            "className": "HQDViewController",
            "title": "我",
            "imageName": "d",
            "visitorInfo": [
                "imageName": "visitordiscover_image_profile",
                "message": "登录后,你的微博、相册,个人资料会显示在这里,显示给别人"
            ]
        ]
    ]

    (array as NSArray).write(toFile: "/Users/wanghongqing/Desktop/demo.plist", atomically: true)

    var arrayM = [UIViewController]()
    for dict in array {
        arrayM.append(controller(dict: dict))
    }
    viewControllers = arrayM
}

fileprivate func controller(dict: [String: Any]) -> UIViewController {

    // 1. 获取字典内容
    guard let className = dict["className"] as? String,
        let title = dict["title"] as? String,
        let imageName = dict["imageName"] as? String,
        let cls = NSClassFromString(Bundle.main.namespace   "."   className) as? HQBaseViewController.Type,
        let vistorDict = dict["visitorInfo"] as? [String: String]

        else {

            return UIViewController()
    }

    // 2. 创建视图控制器
    let vc = cls.init()
    vc.title = title
    vc.visitorInfoDictionary = vistorDict
}

最后的功效:

将数组写入plist并保留到地头

swfit语法里,并未平素将array通过write(toFile:)的法子。由此,这里需求转一下,方便查看数据格式。

(array as NSArray).write(toFile: "/Users/wanghongqing/Desktop/demo.plist", atomically: true)

图片 13荧屏快速照相2017-01-17 23.12.59.png

安装首页动画旋转效果

有几点要求注意的

  • 动漫旋转须求平素维系,切换来其余调节器或许退到后台再重回,要保险动漫仍旧能三番三遍转动
  • 安装动漫的转动周数tiValueM_PIswift 3.0然后曾经不能够再用了,须要用Double.pi替代
if imageName == "" {
    startAnimation()
    return
}

/// 旋转视图动画
fileprivate func startAnimation() {

    let anim = CABasicAnimation(keyPath: "transform.rotation")
    anim.toValue = 2 * Double.pi
    anim.repeatCount = MAXFLOAT
    anim.duration = 15

    // 设置动画一直保持转动,如果`iconImageView`被释放,动画会被一起释放
    anim.isRemovedOnCompletion = false
    // 将动画添加到图层
    iconImageView.layer.add(anim, forKey: nil)
}

应用 json 配置文件设置分界面调整器内容

将之前HQMainViewController写好的安排内容(调节各类调整器标题等剧情的数组State of Qatar输出main.json文件,并保存。

let data = try! JSONSerialization.data(withJSONObject: array, options: [.prettyPrinted])
(data as NSData).write(toFile: "/Users/wanghongqing/Desktop/main.json", atomically: true)

main.json拖入到文件中,通过加载这几个main.json布置分界面调整器内容。

/// 设置所有子控制器
fileprivate func setupChildControllers() {

    // 从`Bundle`加载配置的`json`
    guard let path = Bundle.main.path(forResource: "main.json", ofType: nil),
        let data = NSData(contentsOfFile: path),
    let array = try? JSONSerialization.jsonObject(with: data as Data, options: []) as? [[String: Any]]
        else {
        return
    }

    var arrayM = [UIViewController]()
    for dict in array! {
        arrayM.append(controller(dict: dict))
    }
    viewControllers = arrayM
}

仿照互连网加载应用程序配置

现行看不尽应用程序都是包罗三个布置文件的.json文本,当应用程序运转的时候去查看沙盒里面有没有该.json文件。

  • 一旦未有
    • 透过网络诉求加载暗中同意的.json文件
  • 如果有
    • 直接运用沙盒里面保存的.json文件
    • 互连网乞请异步加载新的.json文本,等下三回顾客再度运行APP的时候就足以显得比较新的安顿文件了

AppDelegate中效仿加载数据

extension AppDelegate {

    fileprivate func loadAppInfo() {

        DispatchQueue.global().async {
            let url = Bundle.main.url(forResource: "main.json", withExtension: nil)
            let data = NSData(contentsOf: url!)
            let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
            let jsonPath = (path as NSString).appendingPathComponent("main.json")
            data?.write(toFile: jsonPath, atomically: true)
        }
    }
}

HQMainViewController中设置

/// 设置所有子控制器
fileprivate func setupChildControllers() {

    /// 获取沙盒`json`路径
    let docPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
    let jsonPath = (docPath as NSString).appendingPathComponent("main.json")

    /// 加载 `data`
    var data = NSData(contentsOfFile: jsonPath)

    /// 如果`data`没有内容,说明沙盒没有内容
    if data == nil {
        // 从`bundle`加载`data`
        let path = Bundle.main.path(forResource: "main.json", ofType: nil)
        data = NSData(contentsOfFile: path!)
    }

    // 从`Bundle`加载配置的`json`
    guard let array = try? JSONSerialization.jsonObject(with: data! as Data, options: []) as? [[String: Any]]
        else {
        return
    }

    var arrayM = [UIViewController]()
    for dict in array! {
        arrayM.append(controller(dict: dict))
    }
    viewControllers = arrayM
}

解释一下 try

在头里的代码中,json的反体系化的时候,我们蒙受了try,上面用多少个简易的例子说爱他美下

推荐用法,弱 try->try?

let jsonString = "{"name": "zhang"}"
let data = jsonString.data(using: .utf8)

let json = try? JSONSerialization.jsonObject(with: data!, options: [])
print(json ?? "nil")

// 输出结果
{
    name = zhang;
}

如果jsonString的格式有毛病来讲,譬喻改成上面那样

let jsonString = "{"name": "zhang"]"

则输出

nil

不推荐用法 强 try->try!

当大家改成强try!并且jsonString有题指标时候

let jsonString = "{"name": "zhang"]"
let data = jsonString.data(using: .utf8)

let json = try! JSONSerialization.jsonObject(with: data!, options: [])
print(json)

则会直接崩溃,崩溃到try!的地方

图片 14

Error Domain=NSCocoaErrorDomain Code=3840 "Badly formed object around character 16." UserInfo={NSDebugDescription=Badly formed object around character 16.}: file /Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-802.0.53/src/swift/stdlib/public/core/ErrorType.swift, line 182

虽说会将错误新闻完整的打印出来,可是程序崩溃对于客户来说是非常不友好的,因而不建议。

do...catch...

对于第二种景况,大家能够采纳do...catch...幸免程序崩溃。

let jsonString = "{"name": "zhang"]"
let data = jsonString.data(using: .utf8)

do {
    let json = try JSONSerialization.jsonObject(with: data!, options: [])
    print(json)
} catch {
    print(error)
}

程序能够防于崩溃,但是会增加语法布局的纷纭,何况ARC付出中,编译器自动抬高retainreleaseautorelease,如果用do...catch...如果不平衡,就相会世内部存储器败露的难点。所以只要当真用的时候要谨严!


监听注册和登录开关的点击事件

HQVistorView里将五个开关暴流露来,然后径直在HQBaseViewController中增多监听方法就能够。

/// 注册按钮
lazy var registerButton: UIButton = UIButton(hq_title: "注册", color: UIColor.orange, backImageName: "common_button_white_disable")
/// 登录按钮
lazy var loginButton: UIButton = UIButton(hq_title: "登录", color: UIColor.darkGray, backImageName: "common_button_white_disable")

vistorView.loginButton.addTarget(self, action: #selector(login), for: .touchUpInside)
vistorView.registerButton.addTarget(self, action: #selector(register), for: .touchUpInside)

// MARK: - 注册/登录 点击事件
extension HQBaseViewController {

    @objc fileprivate func login() {
        print(#function)
    }
    @objc fileprivate func register() {
        print("bbb")
    }
}

此间之所以接收直接addTarget措施,是因为这么最简易,如若用代理 / 闭包等艺术会大增超多代码。代理的合宗旨是解耦,当一个控件可以不停的被复用的时候就分选代理,比如TableViewDelegate中的didSelectRowAt indexPath:该办法是足以在别之处就算创设TableView都大概被用到的主意。由此,设置成Delegate

在这里HQVistorViewHQBaseViewController是紧耦合的涉嫌,HQVistorView能够看作是从归于HQBaseViewController。基本不会被在此外地点被用到。纵然是紧耦合,不过增多监听方法极其轻松。是还是不是必要解耦必要基于实况判别,没须要为精晓耦而解耦,为了情势而情势。

总结

  • 选择代理传递音讯是为着在调节器和视图之间解耦,让视图能够被三个调控器复用,如TableView
  • 然则,若是视图仅仅是为了封装代码,而从调控器中分离出来的,何况能够确认该视图不会被别的调控器援引,则能够直接通过addTarget的不二等秘书技为该视图中的按键增加监听方法
  • 那般做的代价是耦合度高,调整器和视图绑定在协同,不过轻巧部分冗余代码

调动未登陆时导航按键

一经单纯的在setupVistorView中设置leftBarButtonItemrightBarButtonItem,那么在首页就能够现身侧边包车型客车leftBarButtonItem变成了好友了,再点击好友按钮push出来的调节器的装有的回来开关都改为了注册

而在未登入状态下,导航栏下面的按键都是展示注册登录。登入之后才显得其余,因此,大家得以将HQBaseViewController中的setupUI办法设置成fileprivate不让外部访问到,并且将setupTableView设置成外部能够访谈,要是急需在报到后的调控器里面展现所需的体制,只供给在各子类重写setupTableView的情势里再一次设置leftBarButtonItem就足以了。

/// 设置访客视图
fileprivate func setupVistorView() {

    navItem.leftBarButtonItem = UIBarButtonItem(title: "注册", style: .plain, target: self, action: #selector(register))
    navItem.rightBarButtonItem = UIBarButtonItem(title: "登录", style: .plain, target: self, action: #selector(login))
}

使用CocoaPods管制一些大家须求动用的第三方工具,这里跳过。


包装互连网工具单例

swift单例写法

static let shared = HQNetWorkManager()

objective-c单例写法

  (instancetype)sharedTools {

    static HQNetworkTools *tools;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{

        NSURL *baseURL = [NSURL URLWithString:HQBaseURL];
        tools = [[self alloc] initWithBaseURL:baseURL];

        tools.requestSerializer = [AFJSONRequestSerializer serializer];
        tools.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript", @"text/html", @"text/plain", nil];
    });
    return tools;
}

到此,大家绝不打草惊蛇包装网络央求方法,应该先测验一下互联网央浼通不通,实际中我们也是一成不改变,先把要兑现的主要对象先成功,然后再拓展深等级次序的探究。

HQAViewController中加载数据测量检验

/// 加载数据
override func loadData() {

    let urlString = "https://api.weibo.com/2/statuses/home_timeline.json"
    let para = ["access_token": "2.00It5tsGQ6eDJE4ecbf2d825DCpbBD"]

    HQNetWorkManager.shared.get(urlString, parameters: para, progress: nil, success: { (_, json) in
        print(json ?? "")
    }) { (_, error) in
        print(error)
    }

乞请到的数额

{
    ad =     (
    );
    advertises =     (
    );
    "has_unread" = 0;
    hasvisible = 0;
    interval = 2000;
    "max_id" = 4130532835237793;
    "next_cursor" = 4130532835237793;
    "previous_cursor" = 0;
    "since_id" = 4130540976425281;
    statuses =     (
                {
            "attitudes_count" = 0;
            "biz_feature" = 0;
            "bmiddle_pic" = "http://wx3.sinaimg.cn/bmiddle/9603cdd7ly1fhmz6ui42tj20l414a0w7.jpg";
            "comment_manage_info" =             {
                "comment_permission_type" = "-1";
            };
            "comments_count" = 0;
            "created_at" = "Mon Jul 17 16:46:13  0800 2017";

封装AFNetworkingGETPOST请求

注意:

万一你的闭包是如此的写法

func request(method: HQHTTPMethod = .GET, URLString: String, parameters: [String: Any], completion: (json: Any?, isSuccess: Bool)->()) {

那么在您调用completion本条闭包的时候,你或者会超出下边包车型地铁谬误

Closure use of non-escaping parameter 'completion' may allow it to escape

扫除办法直接依据Xcode的提醒就能够修改了,应该是底下的表率

func request(method: HQHTTPMethod = .GET, URLString: String, parameters: [String: Any], completion: @escaping (_ json: Any?, _ isSuccess: Bool)->()) {

From the Apple Developer docs

A closure is said to escape a function when the closure is passed as an argument to the function, but is called after the function returns. When you declare a function that takes a closure as one of its parameters, you can write @escaping before the parameter’s type to indicate that the closure is allowed to escape.

简单易行计算:

因为该函数中的网络要求方法,有二个参数completion: (json: Any?, isSuccess: Bool)->()是闭包。是在网络央求方法执行完事后的做到回调。即闭包在函数实施完事后被调用了,调用的地点超越了request函数的节制,这种闭包叫做逃逸闭包

swift 3.0中对闭包做了改观,默许请款下都以非逃逸闭包,不再要求@noescape修饰。而一旦你的闭包是在函数试行完事后再调用的,比方作者譬喻子的网络需要实现回调,这种逃逸闭包,就必要用@escaping修饰。

假诺你先留心理解那有助于的难点请阅读Swift 3必看:@noescape走了, @escaping来了

网络工具类HQNetWorkManager中的代码

enum HQHTTPMethod {
    case GET
    case POST
}

class HQNetWorkManager: AFHTTPSessionManager {

    static let shared = HQNetWorkManager()

    /// 封装 AFN 的 GET/POST 请求
    ///
    /// - Parameters:
    ///   - method: GET/POST
    ///   - URLString: URLString
    ///   - parameters: parameters
    ///   - completion: 完成回调(json, isSuccess)
    func request(method: HQHTTPMethod = .GET, URLString: String, parameters: [String: Any], completion: @escaping (_ json: Any?, _ isSuccess: Bool)->()) {

        let success = { (task: URLSessionDataTask, json: Any?)->() in
            completion(json, true)
        }

        let failure = { (task: URLSessionDataTask?, error: Error)->() in
            print("网络请求错误 (error)")
            completion(nil, false)
        }

        if method == .GET {
            get(URLString, parameters: parameters, progress: nil, success: success, failure: failure)
        } else {
            post(URLString, parameters: parameters, progress: nil, success: success, failure: failure)
        }

    }
}

调动后的HQAViewController中加载数据的代码

let urlString = "https://api.weibo.com/2/statuses/home_timeline.json"
let para = ["access_token": "2.00It5tsGQ6eDJE4ecbf2d825DCpbBD"]

//        HQNetWorkManager.shared.get(urlString, parameters: para, progress: nil, success: { (_, json) in
//            print(json ?? "")
//        }) { (_, error) in
//            print(error)
//        }
HQNetWorkManager.shared.request(URLString: urlString, parameters: para) { (json, isSuccess) in
    print(json ?? "")
}

利用extension装进项目中网络央求方法

HQAViewController中的互联网伏乞方法即使实行了一部分包裹,可是照旧要在调整器中填入urlStringpara,倘若能把这个也直接封装到二个便于管理的地点,就越来越好了。那样,当我们偶三个互连网接口的url或者para有浮动的话,大家决不开销非常短的时间去苦苦搜索到底是在特别Controller中。

再有正是,重返的数额格式是如此的

{
    ad =     (
    );
    advertises =     (
    );
    "has_unread" = 0;
    hasvisible = 0;
    interval = 2000;
    "max_id" = 4130532835237793;
    "next_cursor" = 4130532835237793;
    "previous_cursor" = 0;
    "since_id" = 4130540976425281;
    statuses =     (
                {
            "attitudes_count" = 0;
            "biz_feature" = 0;
            "bmiddle_pic" = "http://wx3.sinaimg.cn/bmiddle/9603cdd7ly1fhmz6ui42tj20l414a0w7.jpg";
            "comment_manage_info" =             {
                "comment_permission_type" = "-1";
            };
            "comments_count" = 0;
            "created_at" = "Mon Jul 17 16:46:13  0800 2017";

其实,只有statuses对应的数组才是大家供给的博客园数量,此外的对于大家来讲,临时都以绝非用的。平时的店堂支付中,也回到形似的格式,只不过没有博客园如此复杂罢了。

故而,假诺能向来给调节器提供statuses的数码就最棒了,controller直接取得最管用的数据,况且包装又少了一层。词典转模型也可能有利一层。

extension HQNetWorkManager {

    /// 微博数据字典数组
    ///
    /// - Parameter completion: 微博字典数组/是否成功
    func statusList(completion: @escaping (_ list: [[String: AnyObject]]?, _ isSuccess: Bool)->()) {

        let urlString = "https://api.weibo.com/2/statuses/home_timeline.json"
        let para = ["access_token": "2.00It5tsGQ6eDJE4ecbf2d825DCpbBD"]

        request(URLString: urlString, parameters: para) { (json, isSuccess) in
            /*
             从`json`中获取`statuses`字典数组
             如果`as?`失败,`result = nil`
             */
            let result = (json as AnyObject)["statuses"] as? [[String: AnyObject]]
            completion(result, isSuccess)
        }
    }
}

注意:

若果您上边那句话那样写,像objective-c那样写json["statuses"]就能报错的。

let result = json["statuses"] as? [[String: AnyObject]]

报如下错误:

Type 'Any?' has no subscript members

内需改成这么

let result = (json as AnyObject)["statuses"] as? [[String: AnyObject]]

接下去,调节器中HQAViewController的代码就足以简化成这么

HQNetWorkManager.shared.statusList { (list, isSuccess) in
    print(list ?? "")
}

至此,HQAViewController中取得的正是最管用的数组数据,下一步就径直词典转模型就足以了。和事情发生早前把网络伏乞urlpara都放在controller相对而言,是还是不是,调整器轻易了几许呢!

封装Token

类型中,全数的网络恳求,除了登入以外,基本都亟需token,由此,假使大家能将token包裹起来,现在传参数的时候,不用再思量token有关的难点就最佳了。

HQNetWorkManager中新建一个tokenRequest措施,该办法只是把以前的request格局调用一下,同一时间把token追加到该形式里。使得在刻意管理网络需要的方法里HQNetWorkManager Extension不用再去思谋token相关的难点了。

/// token
var accessToken: String? = "2.00It5tsGQ6eDJE4ecbf2d825DCpbBD"

/// 带`token`的网络请求方法
func tokenRequest(method: HQHTTPMethod = .GET, URLString: String, parameters: [String: AnyObject]?, completion: @escaping (_ json: Any?, _ isSuccess: Bool)->()) {

    guard let token = accessToken else {
        print("没有 token 需要重新登录")
        completion(nil, false)
        return
    }

    var parameters = parameters

    if parameters == nil {
        parameters = [String: AnyObject]()
    }

    parameters!["access_token"] = token as AnyObject

    request(URLString: URLString, parameters: parameters, completion: completion)
}

与此相类似封装现在,在HQNetWorkManager Extension中不再要求考虑token连带的难题,何况对controller代码无加害。

token 过期管理

因为token存在时间效益性,由此我们必要对其决断是还是不是可行,假诺token过期供给让客商重新登陆,也许拓宽其它页面包车型地铁跳转等操作。

假如token过期,我们照样向服务器哀求数据,那么就能够报错

Error Domain=com.alamofire.error.serialization.response Code=-1011 
"Request failed: forbidden (403)"
UserInfo={
    com.alamofire.serialization.response.error.response=<NSHTTPURLResponse: 0x608000225bc0> 
        { 
            URL: https://api.weibo.com/2/statuses/home_timeline.json?access_token=2.00It5tsGQ6eDJE4ecbf2d825DCpbBD111 

        } 
{ 
    status code: 403, 
        headers {
            "Content-Encoding" = gzip;
            "Content-Type" = "application/json;charset=UTF-8";
            Date = "Tue, 18 Jul 2017 07:54:51 GMT";
            Server = "nginx/1.6.1";
            Vary = "Accept-Encoding";
    }
}, 
NSErrorFailingURLKey=https://api.weibo.com/2/statuses/home_timeline.json?access_token=2.00It5tsGQ6eDJE4ecbf2d825DCpbBD111,
com.alamofire.serialization.response.error.data=<7b226572 726f7222 3a22696e 76616c69 645f6163 63657373 5f746f6b 656e222c 22657272 6f725f63 6f646522 3a323133 33322c22 72657175 65737422 3a222f32 2f737461 74757365 732f686f 6d655f74 696d656c 696e652e 6a736f6e 227d>, 
NSLocalizedDescription=Request failed: forbidden (403)}

咱俩需求在互联网央浼失利的时候做个管理

let failure = { (task: URLSessionDataTask?, error: Error)->() in

    if (task?.response as? HTTPURLResponse)?.statusCode == 403 {
        print("token 过期了")

        // FIXME: 发送通知,提示用户再次登录
    }

    print("网络请求错误 (error)")
    completion(nil, false)
}

树立和讯数据模型

HQStatus.swift中简单定义五个属性

import YYModel

/// 微博数据模型
class HQStatus: NSObject {

    /*
     `Int`类型,在`64`位的机器是`64`位,在`32`位的机器是`32`位
     如果不写明`Int 64`在 iPad 2 / iPhone 5/5c/4s/4 都无法正常运行
     */
    /// 微博ID
    var id: Int64 = 0

    /// 微博信息内容
    var text: String?

    override var description: String {

        return yy_modelDescription()
    }
}

树立视图模型,封装加载搜狐数据方式

viewModel的使命

  • 词典转模型逻辑
  • 上拉 / 下拉数据管理逻辑
  • 下拉刷新数据数量
  • 本地缓存数据管理

初体验

因为MVVMswift中都以尚未父类的,所以先说下关于父类的选料主题材料

  • 设若分类要求接受KVC要么词典转模型框架设置对象时,类就需求继续自NSObject
  • 假设类只是包装一些代码逻辑(写了部分函数卡塔尔,能够不用接二连三任何父类,好处: 尤其轻量级

HQStatusListViewModel.swift不三番五次任何父类

/// 微博数据列表视图模型
class HQStatusListViewModel {

    lazy var statusList = [HQStatus]()

    func loadStatus(completion: @escaping (_ isSuccess: Bool)->()) {

        HQNetWorkManager.shared.statusList { (list, isSuccess) in

            guard let array = NSArray.yy_modelArray(with: HQStatus.classForCoder(), json: list ?? []) as? [HQStatus] else {

                completion(isSuccess)

                return
            }

            self.statusList  = array

            completion(isSuccess)
        }
    }
}

然后HQAViewController中加载数据的代码就能够简化成这样

fileprivate lazy var listViewModel = HQStatusListViewModel()

/// 加载数据
override func loadData() {

    listViewModel.loadStatus { (isSuccess) in
        self.refreshControl?.endRefreshing()
        self.isPullup = false
        self.tableView?.reloadData()
    }
}

tableViewDataSource中央机关单位接调用HQStatusListViewModel中多少就可以

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return listViewModel.statusList.count
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath)
    cell.textLabel?.text = listViewModel.statusList[indexPath.row].text
    return cell
}

接下去运路程序应该能见到如此的界面,前段时间是因为未有管理下拉/下拉加载管理,因而只美观到20条网易数量。

图片 15

DEMO传送门:HQSwiftMVVM

参考:

  1. Swift 3 :Closure use of non-escaping parameter may allow it to escape
  2. Swift 3必看:@noescape走了, @escaping来了
TAG标签:
版权声明:本文由美高梅网投平台发布于计算机网络,转载请注明出处:再看返回按钮,简单演练