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_enterdispatch_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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,264评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,549评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,389评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,616评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,461评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,351评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,776评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,414评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,722评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,760评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,537评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,381评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,787评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,030评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,304评论 1 252
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,734评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,943评论 2 336

推荐阅读更多精彩内容