iOS:小技巧(21-03-09更)

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

 

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:\n\n%@\n\n", [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类的再次封装。

  后续补充:学到了一招,子类 .h 重写外部声明,在方法后面加 NS_UNAVAILABLE ,就无法调父类的方法了。如

- (instancetype)init NS_UNAVAILABLE;

   不过,父类方法多的话,也很麻烦。

 

 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);
            }];
        }
    }];
}];

 

 78、网络请求,编码、解码

1)、编码(域名带中文,转成 %xx%xx )

str = [str stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];

 2)、解码(socket通信、或一段商品详情文字 \uxxxx\uxxxx)

str = [str stringByRemovingPercentEncoding];

 

79、遍历

0)、写在前面

  0-1)、参考自

    https://blog.csdn.net/jacker_2014/article/details/53453456?utm_medium=distribute.pc_aggpage_search_result.none-task-blog-2~all~first_rank_v2~rank_v25-2-53453456.nonecase&utm_term=forin遍历字典%20ios&spm=1000.2123.3001.4430 ,

    https://blog.csdn.net/wangyanchang21/article/details/50987387

    怕以后找不到了,做个笔记/备份。

  0-2)、forin不能边遍历边增删元素,枚举遍历可以

  0-3)、枚举遍历 其他优势:

      有正序/倒序/并发混序三种。

      在庞大的数据量下, 是比 for循环, forin 等方式,要快许多的方式.在其执行过程中可以利用到多核cpu的优势

1)、for循环

    // 数组遍历
    for (int i = 0; i < array.count; i++){
        NSLog(@"object = %@", array[i]);
    }
    
    // 字典遍历
    NSArray *keys = [dictionary allKeys];
    for (int i = 0; i < keys.count; i++){
        NSLog(@"object = %@", dictionary[keys[i]]);
    }
    
    // 集合遍历
    NSArray *objects = [set allObjects];
    for (int i = 0; i < objects.count; i++){
        NSLog(@"object = %@", objects[i]);
    }

2)、forin 快速遍历

    // 数组遍历
    for (id object in array){
        NSLog(@"%@", object);
    }
    
    // 字典遍历
    for (id key in dictionary){
        NSLog(@"%@", dictionary[key]);
    }
    
    // 集合遍历
    for (id object in set){
        NSLog(@"%@", object);
    }

3)、枚举遍历

    // 数组遍历
    NSEnumerator *enumerator = [array objectEnumerator];
    id object;
    while ((object = [enumerator nextObject]) != nil){
        NSLog(@"%@", object);
    }
    
    // 字典遍历
    NSEnumerator *enumerator = [set objectEnumerator];
    id object;
    while ((object = [enumerator nextObject]) != nil){
        NSLog(@"%@", object);
    }
    
    // 集合遍历
    NSEnumerator *enumerator = [dictionary keyEnumerator];
    id key;
    while ((key = [enumerator nextObject]) != nil){
        NSLog(@"%@", [dictionary objectForKey:key]);
    }

4)、枚举遍历,带block

    // 数组遍历
    [array enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOLBOOL * _Nonnull stop) {
        NSLog(@"index = %lu, object = %@", (unsigned long)idx, obj);
        if (/*拿到了*/) {
            *stop = YES;
        }
    }];
    
    // 字典遍历
    [dictionary enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOLBOOL * _Nonnull stop) {
        NSLog(@"key = %@, value = %@", key, obj);
        if (/*拿到了*/) {
            *stop = YES;
        }
    }];
    
    // 集合遍历
    [set enumerateObjectsUsingBlock:^(id  _Nonnull obj, BOOL * _Nonnull stop) {
        NSLog(@"value = %@", obj);
        if (/*拿到了*/) {
            *stop = YES;
        }
    }];

 

80、关于架构、有的app不能在模拟器上跑的问题:

i386是针对intel通用微处理器32位处理器
x86_64是针对x86架构的64位处理器

模拟器32位处理器测试需要i386架构,
模拟器64位处理器测试需要x86_64架构(iPhone5s以上),
真机32位处理器需要armv7,或者armv7s架构,
真机64位处理器需要arm64架构( iPhone5S以上,iPad Air, iPad mini2(iPad mini with Retina Display) )。

 

81、同一个类的两个实例,在一个函数里,使用 static 静态变量 i 计数,会出问题?

改成属性可解决,达到分开计数目的。

道理和单例一样,只不过,我单例习惯把 static 放外面全局,忘了放函数里面,也会“单例”效果。

 

 

 

 

posted on 2016-12-01 14:24  leonlincq  阅读(1053)  评论(0编辑  收藏  举报