iOS:小技巧(不断更新)

时间:2022-06-17 05:09:33

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

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类的再次封装。

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