不少朋友老是被个人标题给迷惑了,Swift?玩安卓App?都是些啥?git
我反思了一下,确实本身的标题取得不太好。加上有没有附图我到底在作啥,是个人失误。github
因此我决定仍是传一张Gif给你们看看,我都在写一个什么样的东东,一个没啥太多华丽UI,用Swift编写的iOS App,基本上我编写的代码和更文算是同步的:编程
给你们分享一个本身刚入行的经历,我刚刚从事iOS开发。markdown
那会还在写OC代码,新手老是从写UI开始的,我也不例外,因为事先大佬也没有叮嘱写什么,我就开始讲本身写的Controller一个个建立,大概就是这样网络
@interface XXXXController : UIViewController
复制代码
没什么啥毛病。ide
有天,不知道是产品仍是UI来了什么灵感,说咱们这页面的背景色须要作些许改动,大佬说,好的没事,一行代码的事。而后大佬确实改了一行代码提交看了效果,不错,而后就开开心心提测了。函数
因而,测试就过来了,你写的这页面好奇怪啊,大部分的页面背景色都是一致的,可是有几个怎么都看起来有色差,怎么回事?oop
不用多说,有色差的页面都是我写的,至于缘由很简单,大佬写的代码都是这样的:布局
@interface XXXXController : BaseViewController
复制代码
其余页面写的时候都是继承的基类控制器,只有我继承UIViewController,大佬也没有责怪我,由于他觉得我知道这种规则就没和我交代,因此出了差错,加上改改继承基本上就解决问题,因此也不是什么大事。post
分享个人这个经历,其实在以后的工做中给了我不少思考:
一个App中,大部分页面的UI风格、颜色、样式基本上都是一致,经过继承自定义的BaseViewController能够很快的完成基础配置。
其实不只是Controller层,有的时候包括View层,咱们须要定义一个BaseView,来进行基础配置,若是有业务须要BaseTableViewCell等都是能够考虑的。这个就和写BaseModel有些类似。
本身写项目,记得要作一些基类的编写,本身接手其余人的项目,我也总会先让别人给我介绍一下他们的基类。
基于上面的工做经历与思考,如今咱们就来玩安卓App的BaseViewController吧:
import UIKit
import RxSwift
import RxCocoa
class BaseViewController: UIViewController {
private lazy var errorImage: UIImageView = {
let imageView = UIImageView(image: R.image.saber())
imageView.contentMode = .scaleAspectFit
return imageView
}()
override func viewDidLoad() {
super.viewDidLoad()
/// 最简单的设置统一返回按钮的方法,全部的控制器继承该基类便可
let leftBarButtonItem = UIBarButtonItem(image: R.image.back(), style: .plain, target: self, action: #selector(leftBarButtonItemAction(_:)))
navigationItem.leftBarButtonItem = (navigationController?.viewControllers.count ?? 0) > 1 ? leftBarButtonItem : nil
navigationItem.hidesBackButton = true
/// 这里的代码有问题,须要注释掉
//navigationController?.interactivePopGestureRecognizer?.delegate = nil
view.backgroundColor = .white
setupErrorImage()
}
@objc
private func leftBarButtonItemAction(_ item: UIBarButtonItem) {
navigationController?.popViewController(animated: true)
}
deinit {
print("\(className)被销毁了")
}
}
//MARK:- 网络请求错误页面的配置项(待用)
extension BaseViewController {
private func setupErrorImage() {
view.addSubview(errorImage)
errorImage.snp.makeConstraints { make in
make.edges.equalTo(view)
}
errorImage.isHidden = true
}
func showErrorImage() {
errorImage.isHidden = false
view.bringSubviewToFront(errorImage)
}
func hiddenErrorImage() {
errorImage.isHidden = true
view.sendSubviewToBack(errorImage)
}
}
//MARK:- 绑定
extension Reactive where Base: BaseViewController {
/// 显示网络错误
var networkError: Binder<Void> {
return Binder(base) { vc, _ in
vc.showErrorImage()
}
}
}
复制代码
其实这样BaseViewController作了一下几件事情:
自定义返回按钮
这里咱们使用自定义的leftBarButtonItem去代替了系统的backButton,代码块中这种方式是目前我见过设置最简单、功能不会缺失的好办法。只要UINavigationControlle初始化方法传入的是BaseViewController的子类便可实现。
系统的侧滑没有失效。
点击leftBarButtonItem的返回事件。
勘误:上面这段话是有问题的,有大佬nlnlnull留言说,我这样写,会在根控制器中尝试使用侧滑手势后,会出现异常状况,已经验证,确实如此。
本身写的代码没有好好验证与追根朔源,仍是很是感谢大佬的提醒,具体地址的问题请看这篇文章:自定义leftBarButtonItem致使侧滑失效。
BaseViewController中须要删除这段代码,在代码块中,删除没法显示,这里单独说明:
navigationController?.interactivePopGestureRecognizer?.delegate = nil
所以,咱们还须要写一个BaseNavigationController,来避免这个问题的发生:
import UIKit
class BaseNavigationController: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
interactivePopGestureRecognizer?.delegate = self
delegate = self
}
}
extension BaseNavigationController: UIGestureRecognizerDelegate, UINavigationControllerDelegate {
func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
interactivePopGestureRecognizer?.isEnabled = true
/// 解决某些状况下push时的假死bug,防止把根控制器pop掉
if (navigationController.viewControllers.count == 1) {
interactivePopGestureRecognizer?.isEnabled = false
}
}
}
复制代码
配置了控制器的背景色为白色。
经过RxSwift,针对网络请求致使的页面进行了页面处理,这一块因为Rx我也是一点点学习,目前可能思路与处理都不算太好,这里只是写出来了。
在析构函数中添加了一段打印,用于查看控制器的销毁状况。
以前,我也说过,玩安卓App中有不少页面都是列表,考虑到这种状况,编写一个BaseTableViewController也是颇有的必要的。
首先BaseTableViewController它是继承于BaseViewController。
同时因为是为了展现列表,咱们须要在里面布局一个UITableView。
考虑列表可能会有数据为空的状况,咱们须要对页面作定制化处理,这里我选择使用了OC库——DZNEmptyDataSet
。
import UIKit
import RxSwift
import RxCocoa
import MJRefresh
import DZNEmptyDataSet
class BaseTableViewController: BaseViewController {
lazy var tableView = UITableView(frame: .zero, style: .plain)
let emptyDataSetButtonTap = PublishSubject<Void>()
let isEmpty = BehaviorRelay(value: false)
override func viewDidLoad() {
super.viewDidLoad()
setupTableView()
}
private func setupTableView() {
/// 设置tableFooterView
tableView.tableFooterView = UIView()
/// 设置代理
tableView.rx.setDelegate(self).disposed(by: rx.disposeBag)
/// 简单布局
view.addSubview(tableView)
tableView.snp.makeConstraints { make in
make.edges.equalTo(self.view)
}
/// 设置头部刷新控件
tableView.mj_header = MJRefreshNormalHeader()
/// 设置尾部刷新控件
tableView.mj_footer = MJRefreshBackNormalFooter()
/// 设置DZNEmptyDataSet的数据源和代理
tableView.emptyDataSetSource = self
tableView.emptyDataSetDelegate = self
/// 订阅点击了数据为空,请重试的行为,里面没有用状态去绑定tableView是由于没有ViewModel
emptyDataSetButtonTap.subscribe { [weak self] _ in
self?.tableView.mj_header?.beginRefreshing()
}.disposed(by: rx.disposeBag)
/// 数据为空的订阅(待用)
isEmpty.subscribe { event in
switch event {
case .next(let noContent):
break
default:
break
}
}.disposed(by: rx.disposeBag)
}
}
//MARK:- UITableViewDelegate
extension BaseTableViewController: UITableViewDelegate {}
//MARK:- DZNEmptyDataSetSource
extension BaseTableViewController: DZNEmptyDataSetSource {
func title(forEmptyDataSet scrollView: UIScrollView!) -> NSAttributedString! {
return NSAttributedString(string: "暂无数据")
}
func description(forEmptyDataSet scrollView: UIScrollView!) -> NSAttributedString! {
return NSAttributedString(string: "尝试点击刷新获取数据")
}
func backgroundColor(forEmptyDataSet scrollView: UIScrollView!) -> UIColor! {
return .clear
}
func verticalOffset(forEmptyDataSet scrollView: UIScrollView!) -> CGFloat {
return -60
}
}
//MARK:- DZNEmptyDataSetSource
extension BaseTableViewController: DZNEmptyDataSetDelegate {
func emptyDataSetShouldDisplay(_ scrollView: UIScrollView!) -> Bool {
return isEmpty.value
}
func emptyDataSetShouldAllowScroll(_ scrollView: UIScrollView!) -> Bool {
return true
}
func emptyDataSet(_ scrollView: UIScrollView!, didTap view: UIView!) {
emptyDataSetButtonTap.onNext(())
}
}
复制代码
其实这段DZNEmptyDataSet
的代码,基本上和OC时代写的代码没什么太多差异,我甚至去看了知名开源App——SwiftHub,想看看大佬有没有对DZNEmptyDataSet作一层RxSwift的封装,写起来更简单。
结论是没有!SwiftHub也是在分类里面写实现数据源和代理的方式对页面为空的状况作处理。
因而我也在想,费尽精力的去写第三库的RxSwift扩展,不如直接用来的省事。
因为以前写的积分排行页面——RxSwiftCoinRankListController是一个独立的讲解页面,没有过多去讲解BaseViewController。
虽然当时已经使用过了这个基类了,可是笔墨更多的是讲解网络请求和上拉与下拉的操做行为。
随着我开始写首页的HomeViewModel,我才意识到我漏掉了这一环。
编写基类,虽然不是必须的,可是有了基类,可能会让平时的编码中更为轻松一点,虽然Swift更偏向面向协议编程,可是面向对象编程已经存在这么多年了,它也有它的优点,继承使用的当心慎重,思考是否须要继承都是思考的结晶。
讲完基类控制器,下面该讲解首页的编写了。
你们加油!