iOS项目总结
本文章持续更新,总结在项目中遇到的问题并及时记录下来
一.关于导航栏
(1).设置背景颜色
第一种情况:全局统一设置
新建自定义导航栏的类.在viewDidLoad获取全局导航栏,设置setBarTintColor设置导航栏颜色.
UINavigationBar *bar = [UINavigationBar appearance];
[bar setBarTintColor:[UIColor whiteColor]];
第二种情况:分页设置
这种情况也就是每个控制器的导航栏由当前自己的控制器进行设置。(如果想避免每个控制器写大量的重复设置代码,可以新建一个控制器的基类,之后的控制器继承该基类控制器即可)
self.navigationController.navigationBar.barTintColor = NavColor;
另外,如果要设置成自定义的背景图片,则可以
[[UINavigationBar appearance] setBackgroundImage:[UIImage imageNamed:@ "navigation_bg.png" ] forBarMetrics:UIBarMetricsDefault];
(2).设置导航栏字体样式
同样在viewDidLoad设置
NSDictionary *dict = @{NSForegroundColorAttributeName:CJWThemColor};
[bar setTitleTextAttributes:dict];
//更多Attributes可以进头文件进行查看
(3).系统按钮设置:
UINavigationBar *bar = [UINavigationBar appearance];
[bar setTintColor:[UIColor whiteColor]];
(4).显示和隐藏导航栏
- (void) viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self.navigationController.navigationBar setHidden:YES];
[self.rdv_tabBarController setTabBarHidden:YES animated:NO];
}
-(void) viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[self.navigationController.navigationBar setHidden:NO];
[self.rdv_tabBarController setTabBarHidden:NO animated:NO];
}
(5)导航条背景透明
[self.navigationController.navigationBar setBackgroundImage:[[UIImage alloc] init] forBarMetrics:UIBarMetricsDefault];
[self.navigationController.navigationBar setShadowImage:[[UIImage alloc] init]];
(6).统一设置导航栏自定义返回按钮
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
[viewController.view endEditing:YES];
if (self.childViewControllers.count > 0) { // 如果viewController不是最早push进来的子控制器
viewController.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc]initWithImage:IMAGE_NAMED(@"返回按钮") style:UIBarButtonItemStylePlain target:self action:@selector(back)];
// 隐藏底部的工具条
viewController.hidesBottomBarWhenPushed = YES;
}
// 所有设置搞定后, 再push控制器
[super pushViewController:viewController animated:animated];
}
- (void)back
{
[self popViewControllerAnimated:YES];
}
(7).由于自定义了返回按钮,系统默认的右滑手势将会失效,因此需要手动设置滑动手势操作.记得遵守<UIGestureRecognizerDelegate>协议
- (void)viewDidLoad {
[super viewDidLoad];
self.interactivePopGestureRecognizer.delegate = self;
}
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
// 手势何时有效 : 当导航控制器的子控制器个数 > 1就有效
return self.childViewControllers.count > 1;
}
(8).如果项目中导航栏样式比较复杂,比如某些导航栏是蓝色,进入另外一个控制器的导航栏又是红色,或者透明.最直接的方法还是用第三方框架,当然你也可以自己自定义一个,不过我觉得导航栏这块有点复杂,细节很多.要全部自己实现处理好有点考验技术.这里推荐一个star超过3.7k的导航栏框架:FDFullscreenPopGesture,该框架只有一个分类,做到低耦合效果!
补充:
如果当前控制器有导航栏并且视图为UIScrollView(包括子类UITableView,UITextView)的时候,控制器会默认自动将UIScrollView的可视范围向下偏移64的高度.如不需要这种效果,可以设置补偏移
self.automaticallyAdjustsScrollViewInsets = NO;
二.关于状态栏
对状态栏的控制分也两种情况:全局设置和分页面设置。控制两种模式的开关是info.plist文件的View controller-based status bar appearance配置项。
该字段的值为bool值,默认为YES. YES意味着当前控制器的设置优先级最高.NO为当前控制器设置均无效.
(1).全局设置状态栏
第一步:先把info.plist文件的View controller-based status bar appearance设置为No
第二步:通过下面代码在 didFinishLaunchingWithOptions 中设置
//设置状态栏的字体颜色模式
[[UIApplication sharedApplication]setStatusBarStyle:UIStatusBarStyleLightContent];
//设置状态栏是否隐藏
[[UIApplication sharedApplication] setStatusBarHidden:NO];
另外,还可以通过直接在info.plist添加Status bar style字段,值为UIStatusBarStyleLightContent便可以设置全局状态栏颜色为白色.(如果不设置默认当然为黑色)
(2).分页设置状态栏。
由各控制器来控制状态栏的功能,在这种模式下,全局的设置将无效!!所以我们必须逐个页面对状态栏进行设置,否则状态栏将维持默认的黑色字体和默认为显示状态。
- (UIStatusBarStyle)preferredStatusBarStyle{
//返回白色
return UIStatusBarStyleLightContent;
//返回黑色
//return UIStatusBarStyleDefault;
}
(3).设置状态栏背景颜色
在当前控制器下,添加以下代码
-(void)viewWillAppear:(BOOL)animated{
[self setStatusBarBackgroundColor:CJWThemColor];
}
//设置状态栏颜色
- (void)setStatusBarBackgroundColor:(UIColor *)color {
UIView *statusBar = [[[UIApplication sharedApplication] valueForKey:@"statusBarWindow"] valueForKey:@"statusBar"];
if ([statusBar respondsToSelector:@selector(setBackgroundColor:)]) {
statusBar.backgroundColor = color;
}
}
三.关于UITabBarController
(1).设置UITabBarItem
关于设置UITabBarItem选中后的图片,一般会有UI设置提供图片;
设置选中后字体颜色,可以通过以下代码:
- (void)viewDidLoad {
[super viewDidLoad];
[UITabBar appearance].translucent = NO;
self.tabBar.barTintColor = CJWColor(240, 241, 242);
[[UITabBarItem appearance] setTitleTextAttributes:@{NSForegroundColorAttributeName: CJWThemColor} forState:UIControlStateSelected];
//添加tabbar子控制器
[self setupChildViewControllers];
}
四.关于Tableview
(1).Tableview分组样式自定义间距
self.tableView.sectionHeaderHeight = 0;
self.tableView.sectionFooterHeight = 10;
self.tableView.contentInset = UIEdgeInsetsMake(0 - 35, 0, 0, 0);
(2). 设置tableview样式
- (instancetype)init
{
return [self initWithStyle:UITableViewStyleGrouped];
}
(3).滑动tableview取消键盘(使键盘失去第一响应者)
self.tableView.keyboardDismissMode = UIScrollViewKeyboardDismissModeOnDrag;
(4).一个方法解决cell分割线显示不完整问题
-(void)viewDidLayoutSubviews {
if ([self.tableView respondsToSelector:@selector(setSeparatorInset:)]) {
[self.tableView setSeparatorInset:UIEdgeInsetsZero];
}
if ([self.tableView respondsToSelector:@selector(setLayoutMargins:)]) {
[self.tableView setLayoutMargins:UIEdgeInsetsZero];
}
}
五.UITextView
(1).设置占位文字
总所周知UITextView并没有一个类似UITextField的placeholder可以占位符.那该怎办呢?网上有些博客也有解决方案,大概是往UITextView添加一个label,设置label的文字为占位文字.当点击输入时候设置label隐藏.这种方法也可以.不过这里提供另外一种方法:
直接设置UITextView的text为想要的占位文字例如"请输入内容",然后在开始编辑的代理方法判断如果TextView的值为@"请输入内容",如果是则设置TextView的值为空值:@"";在结束编辑的代理方法判断TextView的值是否为空值,如果yes则重新设置TextView的值为@"请输入内容".
//内容控件
UITextView *TV = [[UITextView alloc] initWithFrame:CGRectMake(UIScreenW*0.05, 64, UIScreenW*0.9, UIScreenH*0.3)];
TV.font = [UIFont systemFontOfSize:15];
TV.backgroundColor = [UIColor whiteColor];
TV.textContainerInset = UIEdgeInsetsMake(10, 10, 10, 0);//设置页边距
TV.text = @"内容:";
TV.textColor = [UIColor grayColor];
TV.layer.borderWidth = 1;
TV.layer.borderColor = JWrayColor(226).CGColor;
TV.layer.cornerRadius = 7;
TV.clipsToBounds = YES;
TV.delegate = self;
[bgSCR addSubview:TV];
self.TV = TV;
#pragma UITextViewDelegate方法
//开始编辑
- (void)textViewDidBeginEditing:(UITextView *)textView {
if ([textView.text isEqualToString:@"内容:"]) {
textView.text = @"";
textView.textColor = [UIColor blackColor];
JWLog(@"%ld",(unsigned long)textView.text.length);
}
}
//结束编辑
- (void)textViewDidEndEditing:(UITextView *)textView {
if (textView.text.length<1) {
textView.text = @"内容:";
textView.textColor = [UIColor grayColor];
}
JWLog()
}
六.关于TableFootView
项目中遇到一种情况就是底部试图用TableFootView做容器的情况.遇到一个问题就是想要TableFootView的高度能自动适TableFootView子控件内容的高度.一开始尝试过-(instancetype)initWithCoder:(NSCoder *)aDecoder (通过xib创建) 和
-(instancetype)initWithFrame:(CGRect)frame (代码创建)均获取不到最后一个子控件的高度.
原因 initWithFrame和initWithCoder为视图初始化就执行的方法,此时并不能获取到控件的frame值.
解决方法需要在layoutSubviews方法获取
-(void)layoutSubviews{
[super layoutSubviews];
[self setup];
}
- (void)setup{
self.jw_height = self.orderContentsLabel.jw_bottom+20;
}
七.关于多个操作异步操作执行
例子:
有a、b、c、d 4个异步请求,如何判断a、b、c、d都完成执行?如果需要a、b、c、d顺序执行,该如何实现?
// 串行队列的创建方法
dispatch_queue_t queue1= dispatch_queue_create(nil, DISPATCH_QUEUE_SERIAL);
// 并发队列的创建方法1
dispatch_queue_t queue2= dispatch_queue_create(nil, DISPATCH_QUEUE_CONCURRENT);
//并发队列的创建方法2(也叫全局队列,GCD默认创建的就是并发队列)
dispatch_queue_t queue3 = dispatch_get_global_queue(0, 0);
//创建任务组
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue1, ^{
NSLog(@"A---%@",[NSThread currentThread]);
});
dispatch_group_async(group, queue1, ^{
NSLog(@"B---%@",[NSThread currentThread]);
});
dispatch_group_async(group, queue1, ^{
NSLog(@"C---%@",[NSThread currentThread]);
});
dispatch_group_async(group, queue1, ^{
NSLog(@"D---%@",[NSThread currentThread]);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"主线程---%@",[NSThread currentThread]);
});
QQ20170325-153417@2x.png
解释:要求顺序执行,那么可以将任务放到串行队列中,自然就是按顺序来异步执行了。
// 串行队列的创建方法
dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);
// 并发队列的创建方法
dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);
如果A,B,C,D任务都是耗时操作,上面方法是否还有效?需要怎么改进?
答案:有两种方法可以解决:
一种是通过dispatch_semaphore_t函数创建信号量,当进行任务A操作时,信号量加1,执行完毕,信号量减1,当信号量为0时就会执行下一个任务.
/**
操作依赖--并发执行,没有顺序
*/
- (void)group{
dispatch_group_t group = dispatch_group_create();
dispatch_semaphore_t semap = dispatch_semaphore_create(0);
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
// 执行1个耗时的异步操作
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
dispatch_semaphore_signal(semap);
NSLog(@"----1-----%@", [NSThread currentThread]);
});
dispatch_semaphore_wait(semap, DISPATCH_TIME_FOREVER);
});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
// 执行1个耗时的异步操作
dispatch_semaphore_signal(semap);
NSLog(@"----2-----%@", [NSThread currentThread]);
dispatch_semaphore_wait(semap, DISPATCH_TIME_FOREVER);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 等前面的异步操作都执行完毕后,回到主线程...
NSLog(@"----完成-----%@", [NSThread currentThread]);
});
}
如果需要按顺序执行,看下面代码:
/**
操作依赖--顺序执行
*/
- (void)group2{
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
dispatch_queue_t queue = dispatch_queue_create("testBlock", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
//第一个延时操作
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"1");
dispatch_semaphore_signal(sem);
});
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
});
dispatch_async(queue, ^{
//第2个延时操作
dispatch_semaphore_signal(sem);
NSLog(@"2");
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
});
dispatch_async(queue, ^{
//第3个延时操作
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"3");
dispatch_semaphore_signal(sem);
});
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
});
dispatch_async(queue, ^{
//第4个延时操作
dispatch_semaphore_signal(sem);
NSLog(@"4");
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
});
}
第二种方法:通过dispatch_group_enter和dispatch_group_leave组合使用
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("queue",DISPATCH_QUEUE_SERIAL);
dispatch_group_enter(group);
dispatch_group_async(group, queue, ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"----1-----%@", [NSThread currentThread]);
dispatch_group_leave(group);
});
});
dispatch_group_enter(group);
dispatch_group_async(group, queue, ^{
NSLog(@"----2-----%@", [NSThread currentThread]);
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_group_async(group, queue, ^{
NSLog(@"----3-----%@", [NSThread currentThread]);
dispatch_group_leave(group);
});
dispatch_group_notify(group, queue, ^{
NSLog(@"----完成-----%@", [NSThread currentThread]);
});
8.动画切换window的根控制器
// options是动画选项
[UIView transitionWithView:[UIApplication sharedApplication].keyWindow duration:0.5f options:UIViewAnimationOptionTransitionCrossDissolve animations:^{
BOOL oldState = [UIView areAnimationsEnabled];
[UIView setAnimationsEnabled:NO];
[UIApplication sharedApplication].keyWindow.rootViewController = [RootViewController new];
[UIView setAnimationsEnabled:oldState];
} completion:^(BOOL finished) {
}];
9.截图功能
UIGraphicsBeginImageContextWithOptions(view.bounds.size, YES, 0.0);
[view.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
10.获取设备唯一标识
此功能一般用来获取用户手机唯一标志码,比如游客登录使用唯一标志码进行用户注册登录。
+ (NSString *)getDeviceID {
// 读取keyChain存储的UUID
NSString * strUUID = (NSString *)[AppKeyChain loadForKey: @"uuid"];
// 首次运行生成一个UUID并用keyChain存储
if ([strUUID isEqualToString: @""] || !strUUID) {
// 生成uuid
CFUUIDRef uuidRef = CFUUIDCreate(kCFAllocatorDefault);
strUUID = (NSString *)CFBridgingRelease(CFUUIDCreateString (kCFAllocatorDefault,uuidRef));
// 将该uuid用keychain存储
[AppKeyChain saveData: strUUID forKey: @"uuid"];
}
return strUUID;
}
11 禁用init,new初始化方法
有时候我们想要自定义一个初始化方法,并且制定只能用这个初始化方法创建对象,为了防止同事习惯性用init或者new方法创建,就有必要对init和new初始化方法进行禁用.
//指定初始化方法:
- (nullable instancetype)initWithPath:(NSString *)path NS_DESIGNATED_INITIALIZER;
// 禁用init,new初始化方法
- (instancetype)init UNAVAILABLE_ATTRIBUTE;
+ (instancetype)new UNAVAILABLE_ATTRIBUTE;
12. 单例宏
个快速添加单利方法,只需要把以下代码放到pch文件后,在想要用单利类的.h文件和.m文件定义好单利方法名字就可以了.
使用例子:
.h
@interface AYBlueHelp : NSObject
singleH(shareBlue)
@end
.m
@implementation AYBlueHelp
singleM(shareBlue)
@end
#define singleH(name) +(instancetype)name;
#if __has_feature(objc_arc)
#define singleM(name) static id _instance;\
+(instancetype)allocWithZone:(struct _NSZone *)zone\
{\
static dispatch_once_t onceToken;\
dispatch_once(&onceToken, ^{\
_instance = [super allocWithZone:zone];\
});\
return _instance;\
}\
\
+(instancetype)name\
{\
return [[self alloc]init];\
}\
-(id)copyWithZone:(NSZone *)zone\
{\
return _instance;\
}\
\
-(id)mutableCopyWithZone:(NSZone *)zone\
{\
return _instance;\
}
#else
#define singleM static id _instance;\
+(instancetype)allocWithZone:(struct _NSZone *)zone\
{\
static dispatch_once_t onceToken;\
dispatch_once(&onceToken, ^{\
_instance = [super allocWithZone:zone];\
});\
return _instance;\
}\
\
+(instancetype)shareTools\
{\
return [[self alloc]init];\
}\
-(id)copyWithZone:(NSZone *)zone\
{\
return _instance;\
}\
-(id)mutableCopyWithZone:(NSZone *)zone\
{\
return _instance;\
}\
-(oneway void)release\
{\
}\
\
-(instancetype)retain\
{\
return _instance;\
}\
\
-(NSUInteger)retainCount\
{\
return MAXFLOAT;\
}
#endif
13.为项目添加允许HTTP访问白名单
现在苹果已经明确不允许全部使用http网络请求了,不过允许个别http请求,只需要添加白名单就可以了。
设置域(把不支持https协议的接口设置成http的接口)
(1)、在info.plist中增加一个key:NSAppTransportSecurity,类型为字典类型。
(2)、然后添加一个NSExceptionDomains,其类型是字典类型。
(3)、把需要支持的域给添加到NSExceptionDomains里,其中域作为key,类型为字典类型。
(4)、每个域下面需要设置3个属性,分别为NSIncludesSubdomains、NSExceptionRequiresForwardSecrecy、NSExceptionAllowsInsecureHTTPLoads,均为Boolean类型,其值分别为YES,NO,YES。
image.png