拉伸图片
在图片的宽度/高度不够导致拉伸难看时可以在 Assets中点击图片右侧的Silcing设置图片拉伸方向
CocoaPods
- pod install
- 根据Podfile.lock中的版本号来安装Podfile中的依赖库,第一次安装库时会生成Podfile.lock,方便团队协作(使用同一版本库)
- pod update
- 直接根据Podfile下载最新的库,并且重新生成一个Podfile.lock文件
- pod install --no-repo-update
- pod update --no-repo-update
- 默认install和update都是会把服务器最新版本的库缓存到本机,无论用不用,后面如果加上--no-repo-update指令则表示不需要缓存。
事件响应
-
子视图超出父视图是没法响应时间的。
- 父视图查找最佳响应者的时候会调用"hitTest"和"pointInside"如果子视图不在父视图内则没有资格成员事件响应者。
-
子视图超出父视图默认是会显示的,只是不能响应时间
- 可以用$view.layer.masksToBounds = YES来禁止显示超过父控件bounds的视图。
按钮设置文字和图片
必须分状态设置,否则设置无效!
[btn setTitle:@"" forState:UIControlStateNormal];
[btn image:image forState:UIControlStateNormal];
[btn setTitleColor:$color forState:UIControlStateNormal];
tableFooterView异步获取高度,重新计算
//因为footerView的高度是通过网络获取到的数据计算的
//所以在设置tableView的footer时,它是没有高度的
//于是tableView的contentSize会忽略它,导致等footerView有了高度还是无法上下滚动
VJFooterView* footerView = [[VJFooterView alloc] init];
footerView.backgroundColor = [UIColor whiteColor];
self.tableView.tableFooterView = footerView;
- 解决方法
//在计算出高度后,让父控件(tableView)重新计算内容高度.
self.vj_height = squareList.count / 4 * buttonH;
UITableView *tableView = (UITableView*)self.superview;
[tableView reloadData];
常见查找字符串方法
//是否以http开头
[model.url hasPrefix:@"http"]
//是否以http结尾
[model.url hasSuffix:@"http"]
//是否包含http
[model.url containsString:@"http"]
//查找到http的起始角标和长度(为0则没找到)
NSRange range = [model.url rangeOfString:@"http"];
range.location;
range.length;
从View控件中拿到当前控制器
//拿到当前view所在的窗口的跟控制器
UITabBarController *tbVC = (UITabBarController *)self.window.rootViewController;
//拿到跟控制器(tabbarcontroller)中当前选中的控制器
UINavigationController *navigation = tbVC.selectedViewController;
VJWebViewViewController* webVC = [[VJWebViewViewController alloc] init];
webVC.url = model.url;
webVC.title = model.name;
[navigation pushViewController:webVC animated:YES];
获取文件夹信息
NSString* path = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).lastObject stringByAppendingPathComponent:@"default"];
NSFileManager* mgr = [NSFileManager defaultManager];
NSDictionary* attr = [mgr attributesOfItemAtPath:path error:nil];
NSLog(@"%@",attr);
//创建时间
NSFileCreationDate = "2017-12-16 08:30:44 +0000";
//文件扩展名是否隐藏
NSFileExtensionHidden = 0;
NSFileGroupOwnerAccountID = 20;
NSFileGroupOwnerAccountName = staff;
//修改日期
NSFileModificationDate = "2017-12-16 08:30:44 +0000";
NSFileOwnerAccountID = 501;
NSFilePosixPermissions = 493;
NSFileReferenceCount = 3;
//文件大小(注意,要知道文件夹里所有文件的大小必须要遍历计算)
NSFileSize = 96;
NSFileSystemFileNumber = 8591546449;
NSFileSystemNumber = 16777220;
//文件类型 (文件夹还是文件)
NSFileType = NSFileTypeDirectory;
获取某个文件下所有文件的大小
unsigned long long allFileSize = 0;
NSString* path = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).lastObject stringByAppendingPathComponent:@"default/com.hackemist.SDWebImageCache.default/2a887a10f755aa623ce2ea0dad4822a2.jpg"];
NSFileManager* mgr = [NSFileManager defaultManager];
// NSArray* files = [mgr contentsOfDirectoryAtPath:path error:nil];
// 当前文件夹和所有子文件夹孙文件夹一直到最底层的所有文件名称
NSArray<NSString*>* allFiles = [mgr subpathsAtPath:path];
for (int i =0; i<allFiles.count; i++) {
NSString* filePath = [path stringByAppendingPathComponent:allFiles[i]];
NSDictionary *dic = [mgr attributesOfItemAtPath:filePath error:nil];
//字典默认有个属性叫fileSize 对应的就是取出NSFileSize建对应的值
//等同于 dic[@"NSFileSize"]
allFileSize+=dic.fileSize;
}
异步线程计算文件
// 设置还没计算完文件时的样式
// 创建一个指示器(加载)视图作为单元格右边视图
UIActivityIndicatorView *loadingView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[loadingView startAnimating];
cell.accessoryView = loadingView;
cell.textLabel.attributedText = [[NSAttributedString alloc] initWithString:@"清除缓存" attributes:@{NSForegroundColorAttributeName:[UIColor purpleColor]}];
// 在子线程计算文件大小 防止卡死
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// 获取需要计算的文件路径
// NSString* path = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).lastObject stringByAppendingPathComponent:@"default"];
NSString* path = @"/Users/jiewang/Documents";
// 拼接文字
NSAttributedString *attrString = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"清除缓存(%.2fMB)",path.fileSize/1000.0/1000.0] attributes:@{NSForegroundColorAttributeName:VJRandomColor}];
// 回到主线程更改文字
dispatch_async(dispatch_get_main_queue(), ^{
cell.textLabel.attributedText = attrString;
// 清空指示器视图
cell.accessoryView = nil;
// 改为箭头
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
});
});
注册重用Cell
static NSString * const tableCellId = @"clearCell";
- (void)viewDidLoad {
[super viewDidLoad];
[self.tableView registerClass:[VJClearCellTableViewCell class] forCellReuseIdentifier:tableCellId];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
//上面已经使用register注册好了该标识对应的cell类
//如果这里取不到缓存的Cell则会用上面注册的cell来创建
//可以直接在cell类中重写initWithStyle方法初始化
VJClearCellTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:tableCellId];
return cell;
}
清除SDWebImage以及自定义的缓存
#import <SDImageCache.h>
//清除SDImage的缓存后,清除自己文件
[[SDImageCache sharedImageCache] clearDiskOnCompletion:^(){
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSFileManager *mgr = [NSFileManager defaultManager];
[mgr removeItemAtPath:CustomCacheDir error:nil];
/**
第一个参数是要创建的文件夹路径,
第二个是"如果文件夹路径前面的路径不存在是否补全"
第三个是文件夹属性
*/
[mgr createDirectoryAtPath:CustomCacheDir withIntermediateDirectories:YES attributes:nil error:nil];
dispatch_async(dispatch_get_main_queue(), ^{
[SVProgressHUD showSuccessWithStatus:@"清除成功"];
[SVProgressHUD dismissWithDelay:2];
});
});
}];
NSFileManger常用清除缓存方法
NSFileManager *mgr = [NSFileManager defaultManager];
// 获取文件属性的字典>包含文件类型(文件是文件夹还是文件)以及文件大小
[mgr attributesOfItemAtPath:$filePath error:nil];
// 获取指定路径下的所有子孙等文件名的迭代器(可以使用for in循环)
[mgr enumeratorAtPath:$filePath];
// 获取指定路径下的所有子孙等文件名的数组
[mgr subpathsAtPath:$filePath];
// 返回该路径是否存在并修改bool指针为是否该路径为文件夹
[mgr fileExistsAtPath:$filePath isDirectory:$Bool];
// 删除指定文件夹(文件)
[mgr removeItemAtPath:$filePath error:nil];
/**
第一个参数是要创建的文件夹路径,
第二个是"如果文件夹路径前面的路径不存在是否补全"
第三个是文件夹属性
*/
[mgr createDirectoryAtPath:$filePath withIntermediateDirectories:YES attributes:$fileAttrDictionary error:nil];
TableView循环利用注册多个Cell
//由于有些特定的cell里封装了不同的逻辑,所以不能与普通的cell循环利用,这时需要注册多个Cell
static NSString * const tableCellId = @"clearCell";
static NSString * const tableCellSettingId = @"settingCell";
//注册两个不同的cell 在clearCell中封装了与SettingCell不同的逻辑
//所以下面在获取settingCell时需要排除是clearCell的情况,反之亦然
- (void)viewDidLoad {
[super viewDidLoad];
[self.tableView registerClass:[VJClearCellTableViewCell class] forCellReuseIdentifier:tableCellId];
[self.tableView registerClass:[VJSettingCell class] forCellReuseIdentifier:tableCellSettingId];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.row == 0 ) {
return [tableView dequeueReusableCellWithIdentifier:tableCellId];
}else{
VJSettingCell *cell = [tableView dequeueReusableCellWithIdentifier:tableCellSettingId];
cell.textLabel.text = [NSString stringWithFormat:@"%zd",indexPath.row];
return cell;
}
}
关于子线程执行block强引用self延迟销毁
- block会强引用内部的self对象
- 即使控件的父控件被销毁了,但是某个线程的代码还没执行完,即内部block代码块强引用当前对象,所以该对象还是无法被销毁
解决方法
//使用弱指针执行当前对象,则block中强引用的是该弱指针对象,
//弱指针不会强引用当前对象所以不会妨碍销毁,并且当对象销毁时会指向nil.
//typeof($obj)是获取$obj的类型
__weak typeof(self) weakSelf = self;
如果不希望UIButton点击颜色变浅
需要自定义一个button并且重写父类的setHighlighted方法,就不会出现高亮状态
计算一段文字高度宽度
//计算UIButton的文字尺寸,
//注意:仅仅适用于单行文字,多行文字需要使用boundingRectWithSize
UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
NSString *title = [btn titleForState:UIControlStateNormal];
CGSize titleSize = [title sizeWithAttributes:@{NSFontAttributeName:titleBtn.titleLabel.font}];
多行文字高度计算
NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading一定要加这两个枚举值,否则无效
计算高度最好字体设置大1像素 比如字体是13的话 计算就写14,因为还有行高,否则计算不准确
boundingRectWithSize:maxBound
options:NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading
attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:13]}
context:nil
关于滚动视图和控制器的自动设置位置
- 在UINavigationControl滚动视图都会自动设置64的顶部内边距(EdgeInset)
- 使用self.automaticallyAdjustsScrollViewInsets = NO;关闭控制器自动调整布局
- UIView添加子控制器的view到视图上时默认会给控制器的view设置20的y值,并且高度为647
- 重新设置控制器的view的y值为0.
- 重新设置高度为667。
contentInset(内边距)和bounds.origin是一样作用
bounds是视图内部尺寸大小bounds的origin相当于contentInset,根据内容所在的位置进行上下从而显示出了滚动的效果,内容视图的坐标系是相对自身的
scrollView的滚动就是监听一个手指滑动事件然后移动bounds.origin来上下左右的显示内容
当设置了scrollView的contentInset最好也设置一下它的滚动条内边距
vc.tableView.scrollIndicatorInsets = vc.tableView.contentInset;
bounds.size则是内容的范围,显示区域,默认与frame.size相等
可以把bounds想象成一个矩形块,它的地下是所有的内容(所有的子控件),它在上面通过bounds.origin移动来显示范围内的东西
//示例代码,把控件加到超出视图然后隐藏超出视图的东西。
UIView* uv = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
UISwitch *sv = [[UISwitch alloc] initWithFrame:CGRectMake(120, 120, 0, 0)];
uv.backgroundColor = VJRandomColor;
//修改视图的bounds的坐标,移动到子控件所在的位置,将其显示出来,
//注意:坐标是相对视图本身的,所以看不出来移动
uv.bounds = CGRectMake(100, 100, 100, 100);
[uv addSubview:sv];
uv.clipsToBounds = YES;
[self.view addSubview:uv];
结果
viewWithTag相关
- 会检查自己的tag是否为传入参数,如果是则返回自己.如果不是,则遍历子控件孙控件直到找到对应的tag.
- 所有的UIView默认的tag都是0,如果传0则会把自己返回回去。
快速遍历数组执行方法
- [[NSArray array] makeObjectsPerformSelector:<#(nonnull SEL)#>]
xib子类是不会继承父类的样式的
- xib内的样式是在加载的时候实现,也就是加载子类时不会去加载父类的xib的,所以显示的样式还是子类的xib样式
设置UIButton文字和图片间距
- 修改$button.titleEdgeInsets (文字内边距)
- 修改$button.imageEdgeInsets (图片内边距)
修改自定义tableViewCell
由于tableview会自动计算尺寸,所以在控制器里设置高度都无效,需要在自定义的类中重写setFrame方法,因为不管哪个方法修改cell的尺寸 最后都要进入setFrame方法中,直接在里面重写即可。
- (void)setFrame:(CGRect)frame{
frame.origin.y += 10;
frame.size.height -= 10;
[super setFrame:frame];
}
自定义tableViewCell
-
创建xib->拿出tableViewCell的控件->设置自定义的类
- 如果要取消选中样式,设置selection为none
-
在自定义的类中,连线xib中的控件,添加模型属性,重写set方法(把模型属性设置到控件中)。
- 如果要修改tableViewCell的尺寸,看上面。
- 隐藏cell底部分割线,$tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
tableView的顶部刷新指示器
- 默认的UIRefresh有bug,最好是自己实现一个,或者使用MJRefresh。
- 自己实现:
- 设置一个view添加到tableView上面然后y为-高度(为了隐藏到tableView最上方)
- 默认透明,往下拉渐渐显示,当拉到距离为该view高度时切换文字,然后实现代理的EndDragging方法(在停止拖拽时调用)进行刷新并且修改tableView的内边距让指示器view保持显示,最后加载完内容修改内边距让指示器回去(隐藏)
- MJRefresh:
- 会给所有scrollView上添加一个mj_header和mj_footer可以用来设置MJRefreshHeader的子类和MJRefreshAutoFooter的子类。
- 可以继承MJRefreshHeader, MJRefreshAutoFooter基类在prepare方法中进行自定义的预设置,方便一起更改所有使用该类的指示器。
把字符串转换成NSDate并获取时分秒
一般情况下 NSCalendar 和NSDate NSDateFormatter是一起用的
NSCalendar用来处理NSDate,NSDateFormatter用来转换Date和string
比较日期差值使用NSCalender的components:xxx fromDate:xxx toDate:xxx
获取差值日期对象,可以直接获取到相差几天几小时几秒(会自动进位很方便)
// 获取当前系统日历对象
NSCalendar * calendar = [NSCalendar currentCalendar];
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
// 转换日期格式必须与返回的格式相同 否则不能把字符串转换成date(会返回nil)
formatter.dateFormat = @"yyyy-MM-dd HH:mm:ss";
// 把字符串转换成NSDate
NSDate *createDate = [formatter dateFromString:topic.created_at];
if (createDate) {
// 获取从帖子发布到现在的差值日期对象
NSDateComponents * components = [calendar components:NSCalendarUnitDay|NSCalendarUnitHour|NSCalendarUnitMinute fromDate:createDate toDate:[NSDate date] options:kNilOptions];
NSInteger diffDay = components.day;
NSInteger diffhour = components.hour;
NSInteger diffminute = components.minute;
// 超过一天
if (diffDay>0) {
self.createdAtLabel.text = [NSString stringWithFormat:@"%li天前",components.day];
}else if (diffhour>0){
self.createdAtLabel.text = [NSString stringWithFormat:@"%li小时前",components.hour];
}else if (diffminute>0){
self.createdAtLabel.text = [NSString stringWithFormat:@"%li分钟前",components.minute];
}
}
继承,实现协议,分类(类别),类拓展
//.h文件
xxx:superClass 继承
xxx:superClass<xxxprotocol> 实现协议
//.m文件
xxx() 类拓展
xxx(xxx) 分类
在xib中设置图片和按钮大小
- 默认情况(不给控件设置尺寸约束),UIImageView是以图片的尺寸作为尺寸的。UIButton是以控件里的UIImageView或者backgroundImageView(两者比较最大的图片)尺寸作为尺寸的,如果设置约束则imageView的图片和button的背景图片会以设置的contentMode格式来进行缩放。注意,xib中无法更改button里的imageView的图片尺寸,必须自定义继承它,在layoutSubviews里进行修改
关于tableViewCell循环利用
- 循环利用的cell会将之前添加上去的控件带到下一个单元格上,所以一般不是每个cell去处理添加什么控件删除什么控件的,而是一次把所有用到的cell都添加上去,判断条件去隐藏某个当前行不需要的控件。
- 如果多组cell的差异非常大时建议创建多个自定义的cell,然后注册不同的标识符,根据不同的标识符去创建不同类型的cell。
从xib中加载的view设置尺寸显示不正确
- 从xib中加载的view默认设置了autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight。这两个属性会使xib的内容跟随父控件进行放大缩小,所以设置的尺寸无效,所以需要改属性为UIViewAutoresizingNone即可
- 控件的autoresizingMask和frame尺寸会有冲突,所以凡是从xib中加载的控件,发现设置尺寸显示不对就优先查看是不是autoresizingMask的问题
NSInteger占两位,空值0代替
- [NSString stringWithFormat:@"%02zd",2]; //结果为02
-
0Xzd
X表示该数字要占多少位,0表示该位没值时用0 代替
父类子类通信
- 不同类之前通信,或者父子类大规模的通信时可以采用代理
- 如果只是小规模的通信,比如仅仅只是每个子类的type不一样时,仅需要父类创建一个type的get方法,子类分别实现即可。
手势事件获取的缩放或者平移都可以转换成CGATransform使用在控件上
- 每次调用完手势事件方法后都需要把手势数值设置为0,否则会累加,而我们设置给控件transform时是用的累加,如果都累加则会成倍增长,所以手势事件调用完后一定要设置手势对象的数值为0