背景介绍
互联网金融行业,一般是工具类的产品。一种是P2P
,另外一种就是钱包,当然支付宝就是一个最大的钱包。
简单点,就用Wallet
作为项目名字,bundle ID就定义为com.xxx.wallet
创建工程
通过XCode创建一个新工程,比较简单。这里要选的就是语言用Object-C
还是Swift
。这是一个很让人纠结的问题,怎么选都对,也都感觉不爽。
- 虽然
Swift
是未来,不过现状还是Object-C
占比较大,所以保守起见,还是选Object-C
。 - 工程类型一般就默认的
Single View App
好了,反正以后都要改,默认的文件都会被删掉。 -
UI Test
和Unit Test
前面的勾都去掉,说实话,在客户端开发中,这两个还真没什么大用处。
- 版本控制,目前主流是
git
,没什么好纠结的,相比之下,svn
相差太大。至于是用命令行,还是用XCode
自带的,或者是第三方工具,根据个人自己的喜好来定。本人习惯了Sourece Tree,所以一般就用这个了,所以这里的勾要去掉。实际上命令行和XCode
自带的git
工具都很好用。
-
.gitignore
文件。这是用来说明哪些文件不进行版本控制,比如build
文件夹下面的内容就没有必要提交到版本库。这是一个隐藏文件,可以通过快捷键shift+cmmand+.
来切换显示和隐藏。
.gitignore
文件具体内容,可以上GitHub
上去查Objective-C 版
也可以上网站gitignore.io去生成,只要输入关键字Objective-C
,点击Create
按钮就可以生成相关的内容。.gitignore
虽然是隐藏文件,也要加入版本管理,不能忽略。
包管理工具
在语言选定Object-C
的情况下,第三方库的管理工具基本上就是CocoaPods
了。如果是Swift
的话,还要纠结一下是否用Carthage
CocoaPods
是一个用Ruby
写的工具,需要事先安装。由于国内防火墙的原因,需要注意一些事情,详情可以参考下面这篇文章
最新的CocoaPods的使用教程(一)切换到工程目录,执行命令
pod init
,生成文件Podfile
,修改文件内容,以适合工程的需要。
# Uncomment the next line to define a global platform for your project
# 最低支持版本
platform :ios, '10.0'
# 去掉第三方库的warning
inhibit_all_warnings!
target 'Wallet' do
# Uncomment the next line if you're using Swift or would like to use dynamic frameworks
# 目前framework是主流
use_frameworks!
# Pods for HaiLeBao
# 网络库
pod 'AFNetworking'
# 代码加限制
pod 'Masonry'
# Hud,Toast等
pod 'SVProgressHUD'
# 表格上拉下拉
pod 'MJRefresh'
# 缓存
pod 'YYCache'
# 字典转模型
pod 'YYModel'
# 图片下载和处理,依赖YYImage
pod 'YYWebImage'
# 类别形式的工具
pod 'YYCategories'
# 友盟;基础库
pod 'UMCCommon'
pod 'UMCSecurityPlugins'
# 友盟;统计 SDK
pod 'UMCAnalytics'
# 友盟;错误分析 SDK
pod 'UMCErrorCatch'
# U-Share SDK UI模块(分享面板,建议添加)
pod 'UMCShare/UI'
# 集成微信(完整版14.4M)
pod 'UMCShare/Social/WeChat'
# key chain,存用户名密码
pod 'SAMKeychain'
# OC版本ReactiveCocoa
pod 'ReactiveObjC'
# hook
pod 'Aspects'
# 键盘管理
pod 'IQKeyboardManager'
# 表格空视图
pod 'DZNEmptyDataSet'
end
注释应该用
#
,不能用//
,否则安装不了,有语法错误
- 编辑完
Podfile
之后,执行pod install
命令,下载第三方库,稍等一下就好,一般情况,源码没有多少的。
用
pod update
也是可以的,除了可以安装新的,还可以升级旧的
-
XCode
分workspace, project, target
三级管理项目。大多数情况,这三者用同一个名字。CocoaPods
在默认情况下就是这样的,以后就打开xxx.xcworkspace
文件就可了,第三方库,统一在Pods
工程里,用户不用关心。
-
Pods
目录是否要忽略,这里有争议。本人的观点是不需要忽略。整个项目中,由一个人进行管理CocoaPods
,其他人只需要更新使用就可以了。
工程目录
组件化:业务无关的组件,类似于后台的微服务。模块间不要相互调用。
UI
组件也是那种无状态的组件。按照功能划分。如果用到第三方库,也只是简单封装,增加一个隔离层。比如AFNetworking
,封装后仅提供POST
接口... ....服务化:按照业务进行隔离,类似于后台的服务。可以相互调用,也可以调用组件层的功能。是按照业务逻辑进行的一种封装。比如个人信息服务,网络服务,校验服务,等等
模块化:这是基于页面的一种划分方式。可以将一个复杂页面划分成几个子页面,比如首页。也可将几个页面组合在一起,比如登录注册,实名认证等等。这个就相当于把一个APP拆成了好几个独立的小模块,降低复杂度。
启动页,可以提供一个独立的故事版,单列出来
基类:最大程序的复用,用一个单独的文件夹独立出来
入口:比如
main.storyboard
,是程序的首页。图标:
iOS10.3
之后,系统提供API,可以切换程序icon
至于
public
或者common
文件夹,含义太笼统,不提供。常数:一些公用的常数定义,很多以前在
public
或者common
中的内容可以放在这里:
- 图片文件,界面文件等不提供专门的集中文件夹,而是跟随具体的使用场景,分散在合适的位置。
PCH文件
pch不是必须的,不过引入的话,可以带来很大的方便。这里是引入了,内容如下:
#ifndef WalletPrefixHeader_pch
#define WalletPrefixHeader_pch
// Include any system framework and library headers here that should be included in all compilation units.
// You will also need to set the Prefix Header build setting of one or more of your targets to reference this file.
// 常数定义
#import "KJTConst.h"
// 基类
#import "KJTBase.h"
// 组件
#import "KJTKit.h"
// 服务
#import "KJTService.h"
#endif /* WalletPrefixHeader_pch */
这个文件最好像
AFNetworking.h
那样,作为顶级文件,不要包含具体的内容,作为一个隔离层,包含其他的头文件就可以了。比如,框架类的一些第三方库,就放在KJTThirdPart.h
中。
#ifndef KJTThirdPart_h
#define KJTThirdPart_h
// 框架类的第三方库,直接引用,不适合添加隔离层
#import <MJRefresh/MJRefresh.h>
#import <ReactiveObjC/ReactiveObjC.h>
#import <Masonry/Masonry.h>
#endif /* KJTThirdPart_h */
Xcode中PCH文件的使用
Xcode中的基本设置(iOS9的数据加载问题,pch文件问题)
架构
组件、服务这两块主要是为了复用,或者是隔离;一般情况下,很少涉及界面;就算是
UI
组件,也是没有状态的,一般是UIView
的自定义子类。当然,如果确实与业务无关,也是可以引入界面的。比如扫描二维码,拍照片等等。模块是按照页面维度进行划分的。把整个应用放在一个
main.storyboard
种,很难看,一堆的warning
。像以前的xib
一样,固定一个,也不是很好。按照页面维度,按照关联性,拆成一小块一小块,感觉会好很多至于复杂页面,可以当做一个单独的模块,并且引入
UIContainer
,利用父子UIViewController
的概念,进行进一步的拆分,可以很好的降低复杂度。比如首页模块。UIViewController
页面之间的传值,是一个比较麻烦的事情。比如,省市县的选择页面。展示页面和选择页面之间是强关联的,可以作为一个模块,放在一个storyboard
中。页面之间的传值也是比较多的,操作起来比较麻烦,更好的做法是做一个单例,作用相当于全局变量。界面只是展示,用户的选择状态都放在单例之中统一管理。是否引入
MVVM
,根据个人喜好吧。引入的目的是为了给UIViewController
减负,并不是替代,比如页面跳转,消息侦听等工作,还是UIViewController
来做比较好。如果引入
MVVM
,那么ReactiveObjC.h
这个第三方库最好用起来。当然,不需要信号系统,不需要函数编程,只需要很小的一块,就是UIViewController
和ViewModel
之间的双向绑定,用好RAC
和RACObserve
这两个宏就可以了。就像这篇文章介绍的那样iOS-ReactiveCocoa(RAC)的高级使用之视图与模型的双向绑定如果界面设计现出来,而接口设计迟迟没有出,那么就可以先开发界面,这个时候,引入
MVVM
就非常有必要了。先把页面写出来再说,并且属性名字按照自己的喜好来。等后台文档出来了,再对接一下就好了。
常量模块
- 将一些工程通用的常量集中在一起,包括宏定义,枚举,宏定义等等
- 强弱引用,log输出,一些方便方法等。
#ifndef KJTMacro_h
#define KJTMacro_h
///=============================================================================
/// 判断系统版本
///=============================================================================
#define kIosVersion [[[UIDevice currentDevice] systemVersion] floatValue]
#define kIsIOS9 ([[[UIDevice currentDevice] systemVersion] intValue] == 9)
#define kIsIOS10 ([[[UIDevice currentDevice] systemVersion] intValue] == 10)
#define kIsIOS11 ([[[UIDevice currentDevice] systemVersion] intValue] == 11)
#define kIsIOS12 ([[[UIDevice currentDevice] systemVersion] intValue] == 12)
#define kIsAfterIOS9 ([[[UIDevice currentDevice] systemVersion] intValue] > 9)
#define kIsAfterIOS10 ([[[UIDevice currentDevice] systemVersion] intValue] > 10)
#define kIsAfterIOS11 ([[[UIDevice currentDevice] systemVersion] intValue] > 11)
#define kIsAfterIOS12 ([[[UIDevice currentDevice] systemVersion] intValue] > 12)
///=============================================================================
/// 一些常用的缩写
///=============================================================================
#define kApplication [UIApplication sharedApplication]
#define kKeyWindow [UIApplication sharedApplication].keyWindow
#define kAppDelegate [UIApplication sharedApplication].delegate
#define kUserDefaults [NSUserDefaults standardUserDefaults]
#define kNotificationCenter [NSNotificationCenter defaultCenter]
///=============================================================================
/// 一些常用的路径
///=============================================================================
// 获取temp
#define kTempPath NSTemporaryDirectory()
// 获取沙盒 Document
#define kDocumentPath [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]
// 获取沙盒 Cache
#define kCachePath [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject]
///=============================================================================
/// 判断数据是否为空
///=============================================================================
#define kStringIsEmpty(str) ([str isKindOfClass:[NSNull class]] || str == nil || [str length] < 1 ? YES : NO )
#define kArrayIsEmpty(array) (array == nil || [array isKindOfClass:[NSNull class]] || array.count == 0)
#define kDictIsEmpty(dic) (dic == nil || [dic isKindOfClass:[NSNull class]] || dic.allKeys == 0)
#define kObjectIsEmpty(_object) (_object == nil \
|| [_object isKindOfClass:[NSNull class]] \
|| ([_object respondsToSelector:@selector(length)] && [(NSData *)_object length] == 0) \
|| ([_object respondsToSelector:@selector(count)] && [(NSArray *)_object count] == 0))
///=============================================================================
/// 强弱引用
///=============================================================================
#define kWeakSelf(type) __weak typeof(type) weak##type = type;
#define kStrongSelf(type) __strong typeof(type) type = weak##type;
///=============================================================================
/// 强弱引用
///=============================================================================
#define kDegreesToRadian(x) (M_PI * (x) / 180.0)
#define kRadianToDegrees(radian) (radian * 180.0) / (M_PI)
///=============================================================================
/// 打印日志
///=============================================================================
#ifdef DEBUG
#define NSLog(...) NSLog(@"%s 第%d行 \n %@\n\n",__func__,__LINE__,[NSString stringWithFormat:__VA_ARGS__])
#else
#define NSLog(...)
#endif
#endif /* KJTMacro_h */
- 颜色、字体、通知等内容较多,较集中,可以单独成为一个文件。比如,颜色:
#ifndef KJTColor_h
#define KJTColor_h
///=============================================================================
/// 颜色函数相关
///=============================================================================
// RGB
#define kRGBColor(r,g,b) [UIColor colorWithRed:(r)/255.f green:(g)/255.f blue:(b)/255.f alpha:1.f]
// RGBA
#define kRGBAColor(r,g,b,a) [UIColor colorWithRed:(r)/255.f green:(g)/255.f blue:(b)/255.f alpha:(a)]
// 随机颜色
#define kRandomColor [UIColor colorWithRed:arc4random_uniform(256)/255.0 green:arc4random_uniform(256)/255.0 blue:arc4random_uniform(256)/255.0 alpha:1.0]
// 16进制颜色值,比如kColorWithHex(0xfdf4e1)
#define kColorWithHexA(rgbValue, a) [UIColor colorWithRed:((float)((rgbValue & 0xFF0000) >> 16)) / 255.0 green:((float)((rgbValue & 0xFF00) >> 8)) / 255.0 blue:((float)(rgbValue & 0xFF)) / 255.0 alpha:(a)]
#define kColorWithHex(rgbValue) kColorWithHexA(rgbValue, 1.0)
///=============================================================================
/// 定义常用的颜色
///=============================================================================
// !! 蓝色(购买按钮、服务协议)
#define kBlueColor kColorWithHex(0x508CEE)
// !! 灰色(商品名、商户名标题)
#define kGrayColor kColorWithHex(0x868B90)
// !! 浅灰色(『同意协议』使用)
#define kGrayishColor kColorWithHex(0xA8ACAF)
// !! 黑色(商户名、商品名、银行卡四要素等使用)
#define kBlackColor kColorWithHex(0x3D4449)
// !! 红色(金额使用)
#define kRedColor kColorWithHex(0xFF6633)
// !! 分割线颜色
#define kLineColor kColorWithHex(0xE5E5E5)
#define kPageColor kColorWithHex(0xF5F5F9)
#endif /* KJTColor_h */
- 下面几篇参考文章很不错,值得参考:
iOS-常用宏定义大全
iOS常见宏定义
iOS常用宏定义CSDN
基类模块
是否引入基类,也是有争论的。比如,利用AOP,通过Hook的方式,来代替基类。相对来说,还是采用基类方便。当然,基类的功能要保持尽量简单,放在基类中的内容要严格控制。基类的目的是最大程度地复用
- 引入友盟模块,进行页面时长统计,下面的函数只是对友盟相关接口的简单封装。
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// 页面时长统计 开始
[KJTUmeng beginLogPage:NSStringFromClass([self class])];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
// 页面时长统计 结束
[KJTUmeng endLogPage:NSStringFromClass([self class])];
}
- 设置页面背景色,默认的是白色,一般可以统一设置一下。
// 设置背景色
self.view.backgroundColor = kPageColor;
- 统一添加返回按钮,第一个页面要除外:
#pragma mark - selector
- (void)leftBarItemAction:(id)sender {
[self.navigationController popViewControllerAnimated:YES];
}
#pragma mark - private
- (void)setup {
// 设置背景色
self.view.backgroundColor = kPageColor;
// 统一添加返回按钮
if ([self.navigationController.viewControllers indexOfObject:self] != 0) {
UIBarButtonItem * leftBarItem = [[UIBarButtonItem alloc] initWithImage:[[UIImage imageNamed:@"返回按钮"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal] style:UIBarButtonItemStylePlain target:self action:@selector(leftBarItemAction:)];
self.navigationItem.leftBarButtonItem = leftBarItem;
}
}
不过这样设置返回按钮有一个问题,就是手势左滑功能没有了,感觉不是很好。
- 后续的页面统一隐藏TabBar
// 后续的页面都自动隐藏TabBar
if ([self.navigationController.viewControllers indexOfObject:self] != 0) {
self.hidesBottomBarWhenPushed = YES;
}
- 设置导航栏标题的颜色和字体
[self.navigationController.navigationBar setTitleTextAttributes:@{NSForegroundColorAttributeName: [UIColor redColor],NSFontAttributeName:[UIFont systemFontOfSize:19.0]}];
- 设置导航栏背景颜色
// 设置导航栏背景颜色
self.navigationController.navigationBar.barTintColor = [UIColor redColor];
- 设置导航栏半透明属性
translucent
为NO
。不透明,能够解决很多布局问题。
// 设置导航栏不透明;默认的半透明模糊效果会导致布局偏移
self.navigationController.navigationBar.translucent = NO;
- 设置返回按钮旁边的文字。默认是上一个页面的标题,感觉不是很好。统一给一个@“返回”,或者干脆给一个@“”。
// 设置返回按钮旁边的文字
self.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"返回" style:(UIBarButtonItemStylePlain) target:nil action:nil];
关于导航栏,有下面这些参考文章:
iOS导航栏使用总结
iOS导航栏设置
iOS_做一个透明的导航栏
iOS导航栏背景,标题和返回按钮文字颜色导航栏的设置,分为两种。全局设置,也就是
[UINavigationBar appearance]
开头;单独设置,也就是self.navigationController.navigationBar
。既然是放在基类中,那么就固定用一种。
推荐采用单独设置的方式,也就是以self.navigationController.navigationBar
开头的方法。导航栏的设置,不要放在
viewDidLoad
中,不然iOS11
之后会不起作用。应该放在viewWillAppear
中。详情可以看下面的参考文章:
iOS新版本导航栏透明度设置发现的问题