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 放外面全局,忘了放函数里面,也会“单例”效果。