iOS项目总结

作者: 经天纬地 | 来源:发表于2017-03-24 17:05 被阅读184次

    本文章持续更新,总结在项目中遇到的问题并及时记录下来

    一.关于导航栏

    (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

    相关文章

      网友评论

        本文标题:iOS项目总结

        本文链接:https://www.haomeiwen.com/subject/lylfottx.html