• iOS:小技巧(19-09-23更)


    记录下一些不常用技巧,以防忘记,复制用。

     

    1、UIImageView 和UILabel 等一些控件,需要加这句才能成功setCorn

    _myLabel.layer.masksToBounds = YES;
    

     后续补充:

        clipsToBounds:View的属性。

        masksToBounds:layer的属性。

        效果一致,超出即剪裁,具体差别,以后再补充。

    后续再补充:

        设置圆角方法大概有这几种:

          1、setCorn

            优化:shouldRasterize 光栅化

               适用于图层不会被频繁地重绘。没有动画的界面。作为位图保存起来(类似PS的光栅化,合成当前显示的内容)。有透明度的View,必须图层混合,无法光栅化。

               view.layer.shouldRasterize = YES;

               view.layer.rasterizationScale = [UIScreen mainScreen].scale;

          2、layer.mask (蒙版、遮罩)

            优化:暂无

          3、中间透明圆形、剩余背景颜色 的正方形图片,叠加在最上面。

            优化:暂无

          。。。

    后续再再补充:设置阴影不能masksToBounds、但一些控件 UIImageView 切圆角又需要masksToBounds才能成功切除(以前是这样,现在不知有没改动)。

     

    2、视频截取缩略图,其中CMTimeMakeWithSeconds(5,1),调整截图帧数/秒数,一般不用特意去修改,不做参数传入,除非片头一段时间都一样的视频。

    #import <AVFoundation/AVFoundation.h>
    
    -(UIImage *)getThumbnailImage:(NSString *)videoURL
    {
        AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:[NSURL fileURLWithPath:videoURL] options:nil];
        
        AVAssetImageGenerator *gen = [[AVAssetImageGenerator alloc] initWithAsset:asset];
        
        gen.appliesPreferredTrackTransform = YES;
        //控制截取时间
        CMTime time = CMTimeMakeWithSeconds(5, 1);
        
        NSError *error = nil;
        
        CMTime actualTime;
        
        CGImageRef image = [gen copyCGImageAtTime:time actualTime:&actualTime error:&error];
        
        UIImage *thumb = [[UIImage alloc] initWithCGImage:image];
        
        CGImageRelease(image);
        
        return thumb;
    }
    

     

    3、去除xcode8冗余信息,虽然已经记住了。

    OS_ACTIVITY_MODE    disable

     

    4、播放音频

    1)头文件

    #import <AVFoundation/AVFoundation.h>
    #import <AudioUnit/AudioUnit.h>

    2)工程内音频

      2-1)、获取音频路径

    NSString *path = [[NSBundle mainBundle] pathForResource:@"shakebell" ofType:@"wav"];
        
    NSURL *url = [NSURL fileURLWithPath:path];

      2-2)、创建音频播放ID

    SystemSoundID soundID;
    AudioServicesCreateSystemSoundID((__bridge CFURLRef _Nonnull)(url), &soundID);

      2-3)、Play

    AudioServicesPlaySystemSound(soundID);

    3)系统音频,参数为1000-1351,具体查表,如1007为“sms-received1”

    AudioServicesPlaySystemSound(1007);
    

     

    5、AFNetworking 检测网络连接状态

    [[AFNetworkReachabilityManager sharedManager]startMonitoring];
    
    [[AFNetworkReachabilityManager sharedManager]setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
            NSLog(@"%ld",status);
        }];
    

     

    6、上传图片(头像)

    1)、把Image转成NSData

    NSData *imagedata = UIImageJPEGRepresentation(tempImage, 1.0);

    2)、AFNetworking的POST方法填如下。formData:POST方法里的Block参数,name:跟服务器有关,filename:随意填,mimeType:就image/jpg。

    [formData appendPartWithFileData:imagedata name:@"imgFile" fileName:@"idontcare.jpg" mimeType:@"image/jpg"];

     

    7、常用延时执行

    1)、dispatch_after

    dispatch_after 代码块,一敲就出来,好像是Xcode8之后的。
    

    2)、performSelector

    -(void)performSelector:  withObject:  afterDelay:  ;
    

    后续补充:1、如果延时的View,没到时间,可能被remove,在dealloc取消。

    - (void)dealloc
    {
        // 取消单个延时,参数好像要对上。
        [UIView cancelPreviousPerformRequestsWithTarget:  selector:  object: ];
        // 取消目标上的所有延时。
        [UIView cancelPreviousPerformRequestsWithTarget:self];
    }
    

        2、如果是加在window的几秒钟的提示弹窗,没到时间,想删除,可以直接hidden,时间到,让其自己删除自己。

        3、网络请求、延时调用,如果有实时变动的参数情况,要注意,看情况保存 请求前、延时前 的状态。

        4、动画,同,比如:

            动画1:是下移,动画结束block是隐藏。

            动画2:显示,上移,动画结束block无。

            目前是显示状态,快速点动画1、动画2,会出现:下移一半,又上移,然后隐藏。      

       

    8、打印当前函数

    NSLog(@"%s",__func__);
    

     

    9、屏幕旋转

    1)、添加系统旋转通知

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(notiPass:) name:UIDeviceOrientationDidChangeNotification object:nil];
    

    2)、得到通知

    // UIDeviceOrientationUnknown               // 状态未知
    // UIDeviceOrientationPortrait              // home在下
    // UIDeviceOrientationPortraitUpsideDown    // home在上
    // UIDeviceOrientationLandscapeLeft         // home在右
    // UIDeviceOrientationLandscapeRight        // home在左
    // UIDeviceOrientationFaceUp                // 屏幕在上
    // UIDeviceOrientationFaceDown              // 屏幕在下
    -(void)notiPass:(NSNotification*)noti
    {
        UIDevice *device = noti.object;
        UILabel *label = [self.view viewWithTag:101];
        if (device.orientation  != UIDeviceOrientationPortrait ) {
            label.center = CGPointMake(SCREEN_HEIGHT/2,SCREEN_WIDTH/2);
        }else{
            label.center = CGPointMake(SCREEN_WIDTH/2, SCREEN_HEIGHT/2);
        }
    }
    

     

    10、简易高斯模糊背景(利用ToolBar的特性)

    UIToolbar *toolbar = [[UIToolbar alloc]initWithFrame:CGRectMake(0, 0, WIDTH, HEIGHT)];
    [self.view addSubview:toolbar];
    

     

    11、广告、登陆界面(等待)

    [NSThread sleepForTimeInterval:1];
    

     

    12、优化:

    1)、只需要进行一次的处理,如字体颜色的设计、多次创建的自定义控件。

    2)、方法名后带有 UI_APPEARANCE_SELECTOR 都可以通过appearance,来设置所有的属性

    + (void)initialize
    {
        //仅当前这个类的UITabBarItem
    //    UITabBarItem *item = [UITabBarItem appearanceWhenContainedIn:[self class], nil];
    
        //所有的UITabBarItem
        UITabBarItem *item = [UITabBarItem appearance];
        [item setTitleTextAttributes:attrs forState:UIControlStateNormal];  
    }
    

     

    13、KVC可以用于强制赋值(破坏封装性?!)

    [self setValue:[[MyTabBar alloc] init] forKeyPath:@"tabBar"];
    

     

    14、APP 角标

    1)、设置角标值

      1-1)、设置通知类型为角标(还有其他通知的枚举)

    UIUserNotificationSettings *setting = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeBadge categories:nil];
    

      1-2)、注册通知(角标)

    [[UIApplication sharedApplication] registerUserNotificationSettings:setting];
    

      1-3)、角标数字为10,零是隐藏的,默认是0

    [UIApplication sharedApplication].applicationIconBadgeNumber = 10;

    2)、进入APP要清0(AppDelegate.m 的 applicationWillEnterForeground:)

    [UIApplication sharedApplication].applicationIconBadgeNumber = 0;
    

     

    15、分栏 角标

    1)、设置角标值

    shopItem.badgeValue = @"new";
    //shopItem.badgeValue = @“99+”;

    2)、清掉角标值

      2-1)、清掉角标值(在 viewWillAppear 或者 viewWillDisappear 的时候)

    self.tabBarItem.badgeValue = nil;

      2-2)、也可以用代理去角标,省得一个个 viewWillAppear 、viewWillDisappear 去设置

    tabBarCtr.delegate = self;
    
    - (void)tabBarController: didSelectViewController:
    {
    	if (viewController.tabBarItem.badgeValue !=nil) {
    		viewController.tabBarItem.badgeValue = nil;
    	}
    
    }
    

     

    16、背景透明。好用,但不宜多用,涉及到图层混合计算,浪费资源。

    [UIColor clearColor];
    

      后续补充:不设置背景颜色,好像默认透明。然后,push的时候,混合图层,会有“延迟”的感觉。 

    17、强制布局(配合UIView animation 可做动画,参照“iOS:Masonry约束经验”

    [self.view layoutIfNeeded];
    

     

     18、字典只有一个key的时候,快捷取值

    _label1.text = [[_dic allKeys] lastObject];
    _label2.text = [[_dic allValues] lastObject];
    

     

    19、tag。二级子视图的tag,不能与一级子视图的一样!

    1)、设置tag

    tempScaleView.tag = 1000+i;
    [baseMoveScrollView addSubview:tempScaleView];
    
    tempImageView.tag = 1000;
    [tempScaleView addSubview:tempImageView];
    

    2)、取tag

    UIScrollView *tempScrollView = [baseMoveScrollView viewWithTag:(1000+i)];
    UIImageView *tempImageView = [tempScrollView viewWithTag:1000];

    结果:在当前视图取一级子视图,和在一级子视图取二级子视图,两次都取到一级子视图:tempScrollView。

     

    20、给某个类NSLog的时候,打印出属性值

    -(NSString *)description
    {
        return [NSString stringWithFormat:@"%@,%lu",self.name,self.price];
    }
    

     

    21、判断是否响应

    //可判断是否是 ScrollView 及其 继承类
    [p1 isKindOfClass:[UIScrollView class]];
    
    //可区分ScrollView 和 TableView
    [p1 isMemberOfClass:[UITableView class]];
    
    //可用于判断代理是否有实现
    [p1 respondsToSelector:@selector(test:)];
    

     

    22、提示

    /**
     *  属性
     */
    
    #pragma MARK -代理
    
    #warning -未完成
    

     

    23、防屏幕变暗、关闭

    //[UIApplication sharedApplication].idleTimerDisabled = YES;
    [[UIApplication sharedApplication] setIdleTimerDisabled:YES];
    

     

    24、获取父VC、当前显示的VC、推模态、推(自定义)警告窗。

      1)、获取父VC

    + (UIViewController *)SuperViewController
    {
        UIResponder *theResponder = self.nextResponder;
        while (theResponder)
        {
            if ([theResponder isKindOfClass:[UIViewController class]])
            {
                  return (UIViewController *)theResponder;        
            }    
            theResponder = theResponder.nextResponder;  
        } 
        return nil;
    }
    

      2)、获取当前显示的VC

    - (UIViewController *)getCurrentVC
    {
        UIViewController *result = nil;
        
        UIWindow * window = [[UIApplication sharedApplication] keyWindow];
        if (window.windowLevel != UIWindowLevelNormal)
        {
            NSArray *windows = [[UIApplication sharedApplication] windows];
            for(UIWindow * tmpWin in windows)
            {
                if (tmpWin.windowLevel == UIWindowLevelNormal)
                {
                    window = tmpWin;
                    break;
                }
            }
        }
        
        UIView *frontView = [[window subviews] objectAtIndex:0];
        id nextResponder = [frontView nextResponder];
        if ([nextResponder isKindOfClass:[UIViewController class]])
        {
            result = nextResponder;
        }
        else
        {
            result = window.rootViewController;
        }
        return result;
    }
    

    后续补充,获取VC,按存在顺序,先是Tabbar,然后Navi,最后VC。  

        // 将要推的类
        xxxVC *vc = [[xxxVC(将要推的类) alloc]init];
        
        // 获取Navi
        UINavigationController * navi;
        UIViewController *currentVC = [XXXXX(自己写的类名) getCurrentVC];
        if ([currentVC isKindOfClass:[UITabBarController class]]) {
            UITabBarController * vcTabBar = (UITabBarController *)currentVC;
            NSArray * arrVCS = [vcTabBar viewControllers];
            navi = [arrVCS objectAtIndex:vcTabBar.selectedIndex];
        }else if ([currentVC isKindOfClass:[UINavigationController class]]) {
            navi = (UINavigationController*)currentVC;
        }
        
        //  判断是否推过
        NSArray *viewControllers = [navi viewControllers];
        if ([viewControllers count] != 0) {
            if ([[viewControllers lastObject] isKindOfClass:[xxxVC(将要推的类) class]] == NO) {
                [navi pushViewController:vc animated:YES];
            }
        }
    

     后续补充:也存在判断 是否推过 失效。

      原因:推的时候,还没完成,读不到子VC,又推。所以,可以加个BOOL,当前Push完成了吗?没完成不让再推新的。

     

       3)、获取Window,

        3-1-1)、先在appdDlegate里makeKey。

    [self.window makeKeyAndVisible];
    

        3-1-2)、再取

    UIWindow * window = [[UIApplication sharedApplication] keyWindow];
    

        亦可:

        3-2)直接用delegate取。

    UIWindow * window = [[[UIApplication sharedApplication] delegate] window];  
    

         3-3)、最后推模态视图、或者其他。

    window.rootViewController presentViewController:publish animated:NO completion:nil];

     

    25.生命周期

    用法:

    1)、比如从第2个视图返回,需要刷新第1个视图数据,而不用再多设个代理(如第2个视图消失前,把数据写入plist,返回第1个视图只要读取plist,重新刷新就行)。

    2)、如果要刷新的数据是异步请求的,就行不通,乖乖用代理(因为第2个视图消失前,不一定能加载到数据,并写入plist)。

    - (void)viewWillAppear:(BOOL)animated
    - (void)viewDidAppear:(BOOL)animated
    - (void)viewDidDisappear:(BOOL)animated
    - (void)viewWillDisappear:(BOOL)animated 

      

    26.[null intValue] = 0;  [@"" intValue] = 0;  [@"非数字开头123" intValue] = 0;

    所以在对字典取值的时候,看需求,先判断个数,再intValue。

    字符串判断 length 或 isEqualToString: 

     

    27.浮点型float,不要直接拿来比较。之前在判断缓冲100的时候出过问题。好像是99.0、99.01、99.09和99.0999的区别,不大清楚。

    1)、转成NSNumber

    NSNumber *floatNumber = [NSNumber numberWithFloat:floatData];
    

    2)、再判断升降序

    //NSOrderedAscending    //升序
    //NSOrderedSame         //相同
    //NSOrderedDescending   //降序
    
    if ([floatNumber compare:floatNumber100] == NSOrderedDescending)
    {
    
    }
    
    if ([floatNumber compare:@(100.0)] == NSOrderedDescending)
    {
    
    }
    

     

    28、UIView可以设背景图片,layer层

    //layer层的contents
    self.view.layer.contents = (__bridge id _Nullable)([UIImage imageNamed:@"0"].CGImage);
    //layer层的contents填充方式
    self.view.layer.contentsGravity = kCAGravityResizeAspectFill;
    

     

    29、Frame、Bounds

    //红色的View
    UIView *redView = [[UIView alloc]initWithFrame:CGRectMake(100, 100, 200, 200)];
    redView.backgroundColor = [UIColor redColor];
    [self addSubview:redView];
    
    //bounds:只有size,没有point,{0,0}(效果:覆盖在红色的View上)
    UIView *greenView = [[UIView alloc]initWithFrame:redView.bounds];
    greenView.backgroundColor = [UIColor greenColor];
    [redView addSubview:greenView];
    
    //frame:有size + point (效果:在红色View上偏移{redView.x,redView.y})
    UIView *orangeView = [[UIView alloc]initWithFrame:redView.frame];
    orangeView.backgroundColor = [UIColor orangeColor];
    [redView addSubview:orangeView];
    

    补充:bounds 赋值,会自动去掉 x、y。

    30、alloc init 相关

      1)、清空

        如果要不断创建二维数组(NSMutableArray<NSMutableArray*>)的话,

          1、想着在外面创建一次,里面,添加完后,再清空,省得每次都创建,结果不行,清空后,添加到newData里面的数据也清空!(添加对象,只是单纯的引用地址?)

          2、重新开辟个新的空间,来保存数据,等下给newData添加。

    tempArray = [[NSMutableArray alloc]init];
    
    for(int i = 0; i<5; i++)
    {
        //需要一个空的数组
        //方法1、移除该空间(起始地址)的所有数据
        //[tempArray removeAllObjects];
        //方法2、重新创建个新空间(起始地址)给tempArray
        tempArray = [[NSMutableArray alloc]init];
    
        [tempArray addObject:i+3];
        [newData add tempArray];
    }

      2)、赋值

    //1、指向同一个地方
    newArray= oldArray;
    
    //2、重新开辟
    newArray = [NSArray arrayWithArray:oldArray];
    
    newArray = [NSArray alloc]initWithArray:oldArray];
    
    //3、再1的基础上,改变oldArray
    //oldArray 指向新的,不会影响到之前指向oldArray(指向的地址)的newArray。
    oldArray = @[@"test"];
    
    //会影响到所有指向oldArray(指向的地址)的newArray。
    [oldArray removeAllObject];
    [oldArray addObject:@"1"];
    

       3)、字典赋值,同上

        如果使用注释掉的语句:方法1,而不是下面的方法2。会出现 array[0] 和 array[1] ,数据一样,因为地址一样,改了array[1] ,也就修改了array[0] 的数据。

        理解创建新空间!!!

        NSMutableArray *array = [NSMutableArray array];
        for (int i = 0; i< 5; i++) {
            NSMutableDictionary *dic = [NSMutableDictionary dictionary];
            if (i == 0) {
    
                // 方法1,直接拿临时通用字典变量来当array[0]
    //            dic[@"key"] = @"test -1";
    //            [array addObject:dic];
    
                // 方法2,创建新的字典来当array[0]
                NSMutableDictionary *tempDic = [NSMutableDictionary dictionary];
                tempDic[@"key"] = @"test -1";
                [array addObject:tempDic];
                
                dic[@"key"] = @"test 0";
                [array addObject:dic];
                
                continue;
            }
            dic[@"key"] = [NSString stringWithFormat:@"test %d",i];
            [array addObject:dic];
        }
    

    31、轻扫不一定都管用

    如果View的面积过小,可能没响应。考虑用拖动

    #define SWIPE_DISTANCE      15
    - (void)panAction:(UIPanGestureRecognizer*)pan
    {
        static CGFloat beganX = 0.0;
        switch (pan.state)
        {
            case UIGestureRecognizerStateBegan:
                beganX = [pan locationInView:self].x;
                break;
                
            case UIGestureRecognizerStateChanged:
                if (beganX != CGFLOAT_MAX)
                {
                    if ((int)([pan locationInView:self].x - beganX) > SWIPE_DISTANCE)
                    {
                        // beganX = CGFLOAT_MAX ,下一刻 changed 进不来,即结束本次拖动事件,等待下次重新开始,可减少计算。
                        // 也可因为隐藏,导致触摸点变化,如当前 beganX = 20 ,x = 50,隐藏,这时触摸点瞬间从50变成0,又显示
                        beganX = CGFLOAT_MAX;
                        // 隐藏 方法里有再判断,当前是 隐藏 还是 显示 状态
                        [self hide];
                        
                    }
                    else if ((int)(beganX - [pan locationInView:self].x) > SWIPE_DISTANCE)
                    {
                        beganX = CGFLOAT_MAX;
                        [self show];
                    }
                }
                break;
                
            default:
                break;
        }
    }
    

     

    32、control

    viewDidLoad 是在页面加载才运行,所以,如果把数据(属性:类)的初始化放在viewDidLoad,然后,你在外面对 control alloc init完,然后对数据(属性:类)进行赋值,会失败,因为,数据(属性:类)还没初始化,这时需要重写 init 。  

     

    33、类型修饰

    @property (strong, nonatomic) NSMutableArray<UIView*> *viewList;
    

    1)、直接赋值,检测得到

    viewList = array1;
    viewList = @[view1,@"错误数据"];
    

    2)、临时创建,检测不到?!

    viewList = [NSArray arrayWithArray:@[view1,@"错误数据"]];

      补充:由于检测不到,所以会有问题。如下:

    // 1、定义三层可变。
    @property (strong, nonatomic) NSMutableArray<NSMutableArray<NSMutableDictionary*>*> *testArray;
    
    // 2、初始化用不可变,检测不到,不会报错。
    self.testArray = [NSMutableArray arrayWithArray:@[
                                                      @[@{},@{}],
                                                      @[@{},@{}],
                                                      @[]
                                                      ]
                      ];
    
    // 3、直接赋值,奔溃
    self.testArray[0][0][@"content"] = @"text";
    

      修改如下:  

    // 1、如果定义为 三层可变。
    @property (strong, nonatomic) NSMutableArray<NSMutableArray<NSMutableDictionary*>*> *testArray;
    
    // 2、那么创建,也必须创建成三层可变,或用for循环添加创建。
    self.testArray = [NSMutableArray arrayWithArray:@[
                                                      [NSMutableArray arrayWithArray:@[
                                                                                       [NSMutableDictionary dictionary],
                                                                                       [NSMutableDictionary dictionary]
                                                                                       ]
                                                       ],
                                                      
                                                      [NSMutableArray arrayWithArray:@[
                                                                                       [NSMutableDictionary dictionary],
                                                                                       [NSMutableDictionary dictionary]
                                                                                       ]
                                                       ],
                                                      
                                                      [NSMutableArray arrayWithArray:@[
                                                                                       [NSMutableDictionary dictionary],
                                                                                       [NSMutableDictionary dictionary]
                                                                                       ]
                                                       ]
                                                      
                                                      ]
                      ];
    
    // 3、直接赋值,没问题。
    self.testArray[0][0][@"content"] = @"text";
    

      再补充:也可,自己写个类,类似直接赋值x、y、width、height。在类的方法内取出,变成可变,修改,再赋值回去。

     

    34、Table下拉,上面的图片,会跟着变大

    原理:使用 UIViewContentModeScaleAspectFill 的特性(保证最窄的长或宽都能充满,不留空),实时更改高度。

     

    35、borderWidth 边界/边框 包含在frame宽高里,而且,view的显示范围会随着边框增加而减小。

     

    36、批量调用方法(删除控件),相对于tag取值,更简洁。

    NSMutableArray <UILabel *> *itemViews;
    [itemViews makeObjectsPerformSelector:@selector(removeFromSuperview)];
    

     

    37、状态栏设置

    1)、设置该VC状态栏字体颜色(重写),以防和背景对比低。

    - (UIStatusBarStyle)preferredStatusBarStyle{
         return UIStatusBarStyleDefault;
    }
    

    2)、设置整条状态栏的背景颜色

    - (void)setStatusBarBackgroundColor:(UIColor *)color {
        UIView *statusBar = [[[UIApplication sharedApplication] valueForKey:@"statusBarWindow"] valueForKey:@"statusBar"];
        if ([statusBar respondsToSelector:@selector(setBackgroundColor:)]) {
            statusBar.backgroundColor = color;
        }
    }
    

    38、POD。

    好久没pod过,都快忘了。。。

    1、cd 项目位置
    2、使用第三方框架时,先查找是否存在: pod search Masonry
    3、pod init
    4、vi Podfile
    5、开始输入 : i
    6、输入:pod 'Masonry'
    7、结束输入 :esc  ->   “shift” +“:”
    8、 q:退出 wq:保存退出 q!:强制退出 没有保存
    9、pod install
    

      

    39、版本判断

    // 编译版本过低,iOS手机版本过高,报错,找不到新的方法、属性。用宏
    #if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_8_0
    
    #endif
    
    // 编译版本最新
    if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0){
        // 新方法
    }else{
        // 旧方法
    }
    

     

    40、重写 与 super 方法

    1)、基础、继承型

    - (void)creatUI
    {
        [super creatUI];
        [self creatCustomUI];
    }
    

    2)、自定义型

    - (void)creatUI
    {
        [self creatCustomUI];
    }
    

    3)、通用、判断型

    - (void)creatUI
    {
        if (isCustom)
        {
            [self creatCustomUI];
        }
        else
        {
            [super creatUI];
        }
    }
    

     

    41、cell里点击more展开。

    思路:1、刷数据

       自定义 cell 刷新数据 [xxcell initCellWithData:(id)data more:(BOOL)isMore] 里。

       根据 isMore 约束label相关,可用优先级、或者 安装/卸载 约束。

       2、刷高度

       自定义 cell 获取高度 [xxcell getCellHeightWithMore:(BOOL)isMore] 里。

       根据 isMore 判断给固定高度 还是 给计算高度   

     

    42、string 设为空

    NSString *string1 = nil;
    NSString *string2 = @"";
    

    string1 = nil ,string1.length = 0;

    string2 !=nil ,string2.length = 0;

     

    43、事件 / EVEN 响应

      0)、写在前面

        1)、下列情况默认会被 hitTest 忽略,可重写判断

          1)、hidden = YES

          2)、userInteractionEnabled = NO

          3)、alpha < 0.01

          4)、超出父视图的View

        2)、网上写先判断是否落在view -> pointInside,再判断hitTest。

            不过,我测试,hitTest返回指定View。pointInside返回NO,依然可以响应。所以,下面顺序,是先hitTest。

    - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
    {
    //    return [super hitTest:point withEvent:event];
        return [self viewWithTag:1000];
    //    return [[self viewWithTag:1000]viewWithTag:2000];
    //    return nil;
    }
    
    - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
    {
        return NO;
    }
    

      1)、先判断hitTest。

        1)、返回nil,不响应当前这个view的所有内容

        2)、返回指定View。点击这个view的所有地方,都会响应指定view的事件。 

          补充:看需求可以判断point,返回nil,还是指定view。

             点判断方法在“iOS:小技巧” -> “1、”。 也可,调用pointInside。

        3)、返回 super 方法(系统的流程)。

      2)、在 “1-3)” 返回 super 的方法之后,pointInside方法才有效。

        1)、返回NO。点没落在view里,不响应当前这个view的所有内容,同 hitTest 设nil。

        2)、返回YES。判断子view,哪个有响应(系统的流程)。

          补充:看需求可以判断point,返回YES,还是NO。      

      3)、hitTest,pointInside 也可以当成普通方法调用:

        如:1)、在 touchesBegan 里,调用hitTest,判断点落在哪个View;

          2)、在 hitTest 里,调用pointInside,判断点是否落在哪个View;

          补充:当然也可,用frame,和convertPoint ,去判断。

     

    44、阴影

    1)、和makeToBounds冲突

      多建个同样大小的阴影View。

    2)、在layer,设置contents的图片,连图片都带有阴影轮廓。

    3)、shadowPath,绘制特定形状的阴影效果。

     

    45、layer.mask(蒙版)

    可以设置蒙版形状(路径),从而裁剪view,如 爱心形view、切0-4个圆角view。

    参照 《iOS:绘图》 -> “1、UIBezierPath(贝塞尔曲线)” -> “3)、” 。

     

    46、用6个view + CATransform3D 构建立方体

    1、6个大小(size)相同的面,先叠在一起:center = contentView的中心。

    2、再分别对 view.layer 位移+旋转。如左面的view.layer,先x位移"-width/2",再绕y轴旋转-90度。

    3、旋转角度用contentView.layer.sublayerTransform属性,让contentView的所有子view一起旋转。

    可选:1)、doubleSided反面显示属性,可关闭,如果有透明效果,打开吧。

    注意:1)、各个view上事件响应,是按contentView的添加顺序,尽管旋转到显示最前面还是有可能不响应。

        2)、用contentView.layer.sublayerTransform变化无动画,各个view.layer.transform变化动画怪怪的。。。

     

    47、用6个layer + CATransform3D + CATransformLayer 构建立方体

    基本同“46、”,不过变化时操作CATransformLayer.transform(取代“46、”的contentView.layer.sublayerTransform)。

    补充六个面:

    // 设置正面,向视角(z轴)凸出50(半径)
    CATransform3D transform = CATransform3DMakeTranslation(0, 0, 50);
    // 设置背面,向视角(z轴)凹进50(半径),再绕y轴转180度。
    transform = CATransform3DMakeTranslation(0, 0, -50);
    transform = CATransform3DRotate(transform, M_PI, 0, 1, 0);
    // 设置左面,向左面(x轴)移动50(半径),再绕y轴转-90度。
    transform = CATransform3DMakeTranslation(-50, 0, 0);
    transform = CATransform3DRotate(transform, -M_PI_2, 0, 1, 0);
    // 设置右面,向右面(x轴)移动50(半径),再绕y轴转90度。
    transform = CATransform3DMakeTranslation(50, 0, 0);
    transform = CATransform3DRotate(transform, M_PI_2, 0, 1, 0);
    // 设置上面,向上面(y轴)移动50(半径),再绕x轴转90度。
    transform = CATransform3DMakeTranslation(0, -50, 0);
    transform = CATransform3DRotate(transform, M_PI_2, 1, 0, 0);
    // 设置下面,向下面(y轴)移动50(半径),再绕x轴转-90度。
    transform = CATransform3DMakeTranslation(0, 50, 0);
    transform = CATransform3DRotate(transform, -M_PI_2, 1, 0, 0);
    

      

    48、结构体赋值。

    static CGPoint 的时候,无法用CGPointMake赋值。

    point = (CGPoint){100,100};
    
    title.frame = (CGRect){CGPointZero, CGSizeMake(100, 100)};  
    

      

    49、边动画边响应点击事件

      1)、CALayer。利用 presentationLayer 呈现图层的实时位置。

    运动的layer,与view无关,view依然可以响应 touchesBegan 。

    只需判断,当前点击的位置是否落在layer.frame里。(layer本身就没button、响应事件之类的东西,所以只要判断frame)

    if ([self.moveLayer.presentationLayer hitTest:point]) {
        
    }
    

      或者

    if (CGRectContainsPoint(self.moveLayer.presentationLayer.frame, point)) {
    
    }
    

      

      2)、UIView。利用UIViewAnimationOptionAllowUserInteraction + 上面layer的presentationLayer。

        1)、设置 UIViewAnimationOptionAllowUserInteraction 枚举,运动的呈现视图可以响应点击事件 touchesBegan 。

    [UIView animateWithDuration:4.0 delay:0.0 options:UIViewAnimationOptionAllowUserInteraction animations:^{
        
    } completion:^(BOOL finished) {
        
    }];
    

        2)、上面设置完,运动的view可以响应touchesBegan,如果是button,还是无法响应。所以,需要判断是否落在button里,再调用button的事件。

    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
    {
        CGPoint point = [[touches anyObject] locationInView:self.view];
        
        if (CGRectContainsPoint(self.moveButton.layer.presentationLayer.frame, point)) {
            //[self buttonClickAction:self.contentView];
        }
    }
    

      

     50、一种编程模式。 

    // 从后面的VC回来,看需求刷新(一般是后面的VC有修改到前面的显示内容)。
    - (void)viewWillAppear:(BOOL)animated {
        [super viewWillAppear:animated];
    
        [self updataDataSourceWithReload:YES];
    }
    
    // 界面显示时,判断是否登录。(一般是在tabbarVC上的子VC,需要判断是否登录)
    -(void)viewWillAppear:(BOOL)animated{
        [super viewWillAppear:animated];
        
        if ( [xxx getToken].length == 0) {
            // 1、推登录界面
            //LoginVC *vc = [[LoginVC alloc] init];
            //[self.navigationController pushViewController:vc animated:YES];
            // 2、或者显示没登录时的界面
            [self showLogoutUI];
        }else{
            // 请求新数据(可选,看是要显示之前的界面,还是一切换就请求数据)
            //[self.scrollView.mj_header beginRefreshing];
            // 显示登录时的界面
            [self showLoginUI];
        }
    }
    
    //加载VC
    - (void)viewDidLoad {
        [self initData];
        [self setupUI];
    }
    
    // 初始化 属性/成员变量
    - (void)initData {
    
        [self updataDataSourceAndReload:NO];
    }
    
    // 读取 plist 保存的一些参数,到自身 属性/成员变量
    - (void)updataDataSourceAndReload:(BOOL)isReload {
        // 这里初始化数据
        if (isReload) {
            [self.tableView reloadData];
        }
    }
    
    // 加载UI
    - (void)setupUI {
    
    }
    

      

    51、修改MJRefresh的提示位置

    1、继承:
        如,继承默认下拉类,MJRefreshNormalHeader
    
    2、重写:
    // 放置位置
    - (void)placeSubviews
    {
        [super placeSubviews];
        self.mj_y = - self.mj_h - /* 偏移高度 */;
    }
    
    // 移动,状态机改变
    -(void)scrollViewPanStateDidChange:(NSDictionary *)change
    {
        [super scrollViewPanStateDidChange:change];
        if ([change[@"new"]integerValue] == MJRefreshStateRefreshing) {
    
        }
    }
    
    // 重写,刷新结束后,连着做其他事情,如reload表视图。
    -(void)endRefreshing
    {
        [super endRefreshing];
        if (self.delegate && [self.delegate respondsToSelector:@selector(refreshEnd)]) {
            [self.delegate refreshEnd];
        }
    }
    

    补充:刷新有两次变化

    1、下拉到松手:这里是利用 -(void)scrollViewPanStateDidChange:(NSDictionary *)change 的状态给代理通知。让其做响应的移动、动画。

           动画时间为:MJRefreshFastAnimationDuration(0.25s)

           最终停留高度为:- (void)placeSubviews 里新的偏移高度 + MJRefreshHeaderHeight(54.0)。( y = -高度 )

    2、松手到请求结束:这里是利用 -(void)endRefreshing ,通知代理,结束了。

             动画时间为:MJRefreshSlowAnimationDuration(0.4s)

             最终停留高度为:- (void)placeSubviews 里新的偏移高度。( y = -高度 )

     

    如果是与 scrollView / tableView 有头尾约束、变换比例一样。直接靠约束。

     

    52、重写set,内部尽量用 _成员 。不然,不经意 self.成员 ,会进入set,造成不经意的错误。

      当然也可以在set里判断合理性。如,是内部初始化赋值,还是外面数据源赋值。

    53、获取子View的信息,私有方法,调试用。如获取cell的左移删除按钮。(iOS11 支持删除图片,不过之前的不支持。为了兼容。)

    #ifdef DEBUG
      NSLog(@"Cell recursive description:
    
    %@
    
    ", [cell performSelector:@selector(recursiveDescription)]);
    #endif
    

    54、有固定最大的View个数,根据个数,显示、隐藏。

    1)、根据个数,自动显示、隐藏。

    NSInteger count = [dataArray count];
    self.view1.hidden = (count < 1 );
    self.view2.hidden = (count < 2 );
    self.view3.hidden = (count < 3 );
    self.view4.hidden = (count < 4 );
    self.view5.hidden = (count < 5 );
    

    2)、根据个数,自动显示、隐藏。显示的同时,刷内容

    NSInteger count = [dataArray count];
    for (NSInteger i = 0; i < 5; i++) {
        UIView *view = [self viewWithTag:(1000 + i)];
        if (i < count) {
            view.hidden = NO;
            // 刷内容
        }else{
            view.hidden = YES;
        }
    }
    

      

    55、SDImageCache

    // 通过 名字key 取回 image
    [[SDImageCache sharedImageCache] imageFromDiskCacheForKey:@"http://255.255.255.255:8080/Images/image0.jpeg"]
    
    // 清除内存、缓存
    [[SDImageCache sharedImageCache]clearMemory];
    [[SDImageCache sharedImageCache]clearDiskOnCompletion:^{
    	// 提示清除缓存成功
    	// 刷新
    }];
    

      后续补充:

        第三方分享,不能传http的url。所以,需要从自身的cache里取出image传过去。

      后续再补充:

        有可能存在,你要分享,结果SD还没加载完成,所以,获取不到,nil,可能会奔溃,所以,还是要判断是否加载完成,或是用其他第三方下载图片。

    56、performSelector 传多个变量

    - (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay; 

    1)、合并成数组

    1)、可把变量合并成数组
    [self performSelector:@selector(method:) withObject:@[dataA,dataB,dataC] afterDelay:1.0];
    
    2)、定义枚举
    typedef NS_ENUM(NSInteger,DataType){
        DataTypeA	= 0,    // A
        DataTypeB	= 1,    // B
        DataTypeC	= 2,    // C
    };
    
    3)、实现方法
    - (void)method:(id)data
    {
        a = data[TestTypeA];
        b = data[TestTypeB];
        c = data[TestTypeC];
    }
    

    2)、如果是第二、三个参数,是判断类型的,如BOOL,ENUM,可以分成多个方法(有点封装 及 穷举 的感觉)。

    1)、多个变量入口
    - (void)method:(id)data hasA:(Bool)hasA hasB:(Bool)haB haC:(Bool)haC
    {
    
    }
    
    2)、对1)的封装、穷举。
    - (void)methodA:(id)data
    {
        [self method:data  hasA:YES hasB:NO haC:NO];
    }
    
    - (void)methodB:(id)data
    {
        [self method:data  hasA:NO hasB:YES haC:NO];
    }
    
    - (void)methodC:(id)data
    {
        [self method:data  hasA:NO hasB:NO haC:YES];
    }
    
    3)、调用
    [self performSelector:@selector(methodA:) withObject:data afterDelay:1.0];
    [self performSelector:@selector(methodB:) withObject:data afterDelay:1.0];
    [self performSelector:@selector(methodC:) withObject:data afterDelay:1.0];
    

      补充:2)看起来,好像步骤多余,都可以直接在 methodA、methodB、methodC 实现方法, 为什么还要再去调用method: hasA: hasB: hasC ?

         原因:如果要实现的内容非常多 又复杂,且只需偶尔判断,A、B、C状态。这就很有必要,封装。

    57、编码:NSUTF8StringEncoding

    %E5%81%A5%E5%BA%B7%E5%BF%AB%E4%B9%90 
    

    58、欢迎界面

      方法1:在 AppDelegate.m 添加观察者监听通知,接收到完成欢迎界面通知,重新指定根视图

    59、登录界面

      1、在 网络请求、socket通信 被 踢下线 的时候,push。

      2、在需要获取 个人信息 ,才能进行下一步操作,可以考虑 push。

      注:1、push前要检查是否push过,否则,可能push2次的bug。

        2、有可能push还没完成,又push,所以检查子VC是否push过,不可靠,可能需要设变量标记push过登录界面。

    60、类的类别,实现类的代理方法,会报错,提示方法名重复。

    1)、可再封装一层方法,再调用类别。

    2)、代理的方法改成可选 @optional ,不是类必须实现的。等到运行的时候,在类找不到,会去类别再找。

    61、对 view 进行批量操作、删除等,除了给加 tag、取 tag 处理,还可以添加进Array,然后,用枚举器、或forin,批量处理

    for (UIView *view in array) 
    {
    	[view removeFromSuperview];
    }
    

     62、传值

      1、(前向后传)OneVC 向 SecVC 传
        1-1)、SecVC 添加属性 data
        1-2)、SecVC 添加方法 initWithData:
      2、(后向前传)SecVC 向 OneVC 传,
        2-1)、通知(全局都有,少用)
        2-2)、在SecVC ,有一个属性用来保存前面VC的地址,如 OneVC *oneVC。

            在OneVC跳转前,SecVC.oneVC = self。

            在SecVC,直接操作 [oneVC 方法]、oneVC.data = xx ;
        2-3)、协议。
            在SecVC,制定协议@protocol,添加代理属性id <xxxDelegate> delegate。要操作时判断[self.delegate respondsToSelector:],再[self.delegate 协议方法]
            然后,在OneVC,把 SecVC. delegate = self,实现 协议方法 。

        2-4)、写 NSUserDefaults、plist 保存,切换过来读取。

    后续补充:比如从帖子列表,push到帖子详情,返回,不需要刷新。

          在帖子列表,push到发帖页面,返回,自动刷新。

        此时如果用 后向前 传值、代理,会麻烦点,可以在前OneVC设置 isPushToDetail 属性,来判断是否需要刷新。

    63、取消编辑状态

    [self.view endEditing:YES];
    

       看情况使用,如果view的x、y 有移动过,好像会复原。如果是要取消输入框的键盘,谁成为第一响应,让谁去放弃第一响应。

    64、生命周期(按照顺序)

    + (void)initialize(App生命周期里,VC的该方法只运行一次)
    创建
    + (instancetype)alloc
    - (instancetype)init
    
    进入VC,才加载
    - (void)loadView(VC生命周期里,该方法只运行一次)
    - (void)viewDidLoad(VC生命周期里,该方法只运行一次)
    - (void)viewWillAppear:(BOOL)animated
    - (void)viewDidAppear:(BOOL)animated
    
    
    - (void)viewDidDisappear:(BOOL)animated
    - (void)viewWillDisappear:(BOOL)animated
    - (void)dealloc(不用重写,如写[super dealloc]会报错)
    

    切换时的顺序(和自己想的不一样)

    Sec : viewWillAppear
    One : viewWillDisappear
    One : viewDidDisappear
    Sec : viewDidAppear
    One : dealloc
    

     补充:一开始以为,会是One WillDisappear、DidDisappear,处理完再处理Sec 的WillAppear 、DidAppear。

        然后,两者调用用一个单例、数据,如:One WillDisappear 写数据、Sec 的WillAppear 读数据,就有问题了。

    65、不能在 dealloc 对 self 进行弱引用操作

    - (xxxxView *)footerV
    {
        if (_footerV == nil) {
            _footerV = [[xxxxView alloc]initWithDelegate:self];
        }
        return _limitFooterV;
    }
    
    - (void)dealloc {
            [self.footerV.timer invalidate];
            self.footerV.timer = nil;
    }

    如上,这个有定时器的 footerView 懒加载,在该VC不一定存在。

    如存在,无问题。

    如不存在,在 dealloc 会对 footerView 懒加载 ,相当于,在 dealloc 对 footerView 弱引用(代理)self 会崩溃。

    可改成,判断是否存在,才需要释放定时器

    - (void)dealloc {
        
        if (_footerV) {
            [self.footerV.timer invalidate];
            self.footerV.timer = nil;
        }
    }
    

    66、首字符大写

    capitalizedString,在首字符大写的同时,将小写其他所有的字符,直到遇到空格或者其他字符。

    如,一个类名"homeGoodsVC",调用后将变成"Homegoodsvc"

    正确的大写首字符:

    testStr = [NSString stringWithFormat:@"%@%@",[[testStr substringToIndex:1] capitalizedString],[testStr substringFromIndex:1]];
    

     67、合成对象

    比如,先有A类,后有需求B类,大部分功能、方法和A类一模一样,但A类有几个方法不适用于B类,如果B继承A,别人用B类调用父类A类的方法,就有问题(类似于UIButton.titleLabel,对其设置title无效,但它又是UILabel)所以

    1、要么,继承父类,重写方法,要注意很多方法、属性;

    2、要么,合成对象,在B类添加一个内部成员变量A类,再提供合适的接口给用户,相当于对A类的再次封装。

     68、removeFromSuperview

    并没有销毁,只是移除UI图,如果 view 上有定时器,且只在 dealloc 做定时器销毁,他还是在不断执行。

    另,和 addSubview 一样,可以不断调用,系统和自动判断是否有添加、移除过。

    69、对网络请求数据的数组变量,慎用index取变量

    空字典,或非空字典取空key,不会有大问题,除非把空value添加到数组、字典上。

    而,对空数组用index取值,肯定崩。一般都会用for循环count次数,也没问题。

    但也有例外,比如这回写签到,写死了7天对应的奖励view,结果崩了。

    70、导航栏返回到首页,TabbarVC 切换到其他子VC。

    正常思路是,先pop回去,再设置Tabbar,会各种异常,

    应先设 TabbarVC 的 selectedIndex ,再popVC。

    71、判断是否为字符串

    用 isKindOfClass。

    isMemberOfClass 不行。

    72、注意异步更新!!!

    网络请求成功,刷新UI,可能与当前状态不一致了!!!

    从   1、快速点击菜单栏,刷新列表   到   2、快速变更搜索输入内容,刷新列表   再到   3、只悬浮在特定某些界面的浮窗View,快速切换界面。

    一直都在被坑中。。。

    73、自定义导航栏(作为一种思路) 

    - (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
    {
        // 对VC进行统一设置
    }
    

    74、添加子控制器

    [self addChildViewController:vc];
    

    75、头文件

    @class 

    让编译器知道有这个类,如写代理,传自身类的时候,基本都会报错,需要 @class 自身的类。

    76、模型(记录,防忘)

    // MJExtension
    + (NSDictionary *)replacedKeyFromPropertyName
    {
        return @{
                 @"goodID" : @"id"
                 };
    }
    
    // YYModel
    + (NSDictionary *)modelCustomPropertyMapper {
        return @{
                 @"goodID" : @"id"
                 };
    }
    

    77、SDWebImage 缓存

    一个url字符串为一个key,保存对应的image 为value,对于固定的url,每次更新,都需要先移除

    NSString *urlStr = @"http://api.xxx.com/images/xxxx.jpg";
    // 先清空
    [[SDImageCache sharedImageCache] removeImageForKey:urlStr withCompletion:^{
        // 再加载新的
        [self.detailImgV sd_setImageWithURL:[NSURL URLWithString:urlStr]
                           placeholderImage:[UIImage imageNamed:@"default_image"]
                                  completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
    
            if (image != nil && image.size.width > 0.0f) {
                [self.detailImgV mas_updateConstraints:^(MASConstraintMaker *make) {
                    make.height.mas_equalTo((image.size.height/image.size.width) *SCREEN_WIDTH);
                }];
            }else{
                [self.detailImgV mas_updateConstraints:^(MASConstraintMaker *make) {
                    make.height.mas_equalTo(SCREEN_WIDTH);
                }];
            }
        }];
    }];
    
  • 相关阅读:
    xml中DTD关键字说明
    xml学习笔记
    HTTP请求方法:GET和POST区别
    三种方法从键盘输入
    crontab定时器
    收藏一篇关于Asp.net Response.Filter的文章
    MethodImplOptions.Synchronized的一点讨论
    需要知道关于struct的一些事情
    Excel使用技巧总结
    HTTP协议中POST、GET、HEAD、PUT等请求方法以及一些常见错误
  • 原文地址:https://www.cnblogs.com/leonlincq/p/6121752.html
Copyright © 2020-2023  润新知