UIVIew
1、在Storyboard中 只能给UIView添加子控件,像按钮这些是不能添加子控件的,需要通过代码实现
2、UIView的常见属性:
-
supervView:控件的父控件,唯一一个。
- 控制器的View的父控件为UIWindow(在viewDidAppear方法中打印)
subViews:控件的子控件数组,指的是直系的子控件,子控件的子控件不属于。
tag:通过tag获得控件的方式效率较低,内部实现可能需要遍历。
frame:设置控件的位置和尺寸(以父控件的左上角为原点,自身的左上角相对原点的位置就是控件的位置)
bounds:以自己的左上角为原点(也就是只能改变尺寸,不能改变位置,iOS9以后,改变bounds,中心点不变,向四周延伸)
在测试其他控件的属性的时候,我们可以通过storyboard快速切换属性。
图片的资源路径问题
当我们往工程中拖入图片的时候,如果拖入的图片放在文件夹中,此时如果选择如下的选项,显示在工程中的图片将是蓝色的文件夹,这是一个真实的文件夹
我们在使用imgV.image = [UIImage imageNamed:]; 方法时需要写入相对路径。
无论图片以哪种方式放入工程中,最终都会存在于Bundle中
对于存放在Asset中的图片,只能使用imageNamed:方式加载图片。
图片的两种加载方式:
1、imageNamed:(就算它的指针被销毁,该资源也不会被移出内存,所以推断出放到Assets.scassets中的图片,默认就有缓存)
使用场景:经常需要使用的图片。
2、imageWithContentsOfFile:(指针被销毁时,资源从内存中移除)
颜色的认知
RGB 都为0是黑色,都为255是白色,一样为灰色,一样时值越大灰色越浅,反之也是。
注释的写法
我们在给属性写注释的时候,要是想在调用的时候看到我们写的注释,正确的格式是:/** 注释内容 */
Xcode使用
-
替换类名除了使用rename 还可以如下图:
但是需要手动修改.h.m的文件名称
-
默认添加类前缀
右下方有class prefix
写一个标准的类
在写一个类的时候,为了方便其他人调用,我们通常要写一些构造方法
下面是一个类名为Shop的写法
这里返回值是instancetype的含义是,当有类继承这个类的时候,构造函数中调用父类构造方法时,可以返回子类的实例。
自定义控件
当设置了父控件的frame时,内部调用layoutSubviews,布局子控件可以在这个方法中进行,记得先调用[super layoutSubviews]
属性的访问
当使用self.foo的方式访问时,实质是调用set或者get方法,set方法内部其实实现了深拷贝。而通过 _foo 来赋值是浅拷贝,假如赋值对象后面还要用到,变量的值也会改变。
控件的封装思路
1、确定哪些子控件并初始化子控件 (在 initWithFrame中初始化,因为调用[alloc init]方法时 initWithFrame也会被调用,反之则不是)
2、布局子控件
3、设置模型
xib加载方式 (这里的firstObject是指如果nib文件中有多个平级的View,先添加进去的在数组的位置靠前)
NSArray *viewArr = [[NSBundle mainBundle] loadNibNamed:@"CarView" owner:nil options:nil];
UIView *carView = [viewArr firstObject];
设置属性的时候,我们需要创建一个View建立关联,创建关联后,不能通过alloc init 和 alloc initWithFrame方法初始化
通过代码在xib上添加控件时(在父控件上添加) 可以使用
//如果View从xib加载就会调用这个方法
- (instancetype)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (self) {
}
return self;
}
注意:如果子控件是从xib中创建,是处于未唤醒状态
此时添加xib中创建子控件的子控件在这个方法中进行
- (void)awakeFromNib {
}
综上
1、如果View从nib中加载,就不能用 init 和 initWithFrame创建
2、如果xib经常被使用,应该提供快速构造的类方法
3、通过代码在xib上添加子控件,我们可以都放在awakeFromNib中进行。
更改按钮文字图片的位置的方法
1、继承UIButton,重写titleRect和imageRect
2、继承UIButton,在layoutSubviews中重新设置frame
3、设置按钮的内边距,有个一小技巧,可以在storyboard中设置查看效果,分为内容(包括图片和文字)、图片、文字的内边距。
图片的拉伸保护
1、以图片中心店1X1像素拉伸,保护四个角
UIImage * image = [UIImage imageNamed:@"1.png"];
CGFloat imageWidth = image.size.width;
CGFloat imageHeight = image.size.height;
UIImage *resizableImage = [image resizableImageWithCapInsets:UIEdgeInsetsMake(imageHeight * 0.5, imageWidth * 0.5 , imageHeight * 0.5 - 1, imageWidth*0.5 - 1 )];
2、保护图片四个角的第二种方法
UIImage * image = [UIImage imageNamed:@"1.png"];
CGFloat imageWidth = image.size.width;
CGFloat imageHeight = image.size.height;
UIImage *resizableImage = [image stretchableImageWithLeftCapWidth:imageWidth * 0.5 topCapHeight:imageHeight*0.5];
3、直接在Xcode中设置一下,如果系统保护的不好,下面有选项微调即可
UIScrollView
- UISCrollView可滚动的尺寸是contentSize的尺寸减去scrollView的尺寸,比如scrollView的frame是CGRectMake(100,100,100,100) contentSize(150,150) 则可滚动的尺寸是水平方向上可滚动50 数值方向上可滚动50 ( 可能拖拽的时候可滚动距离会变大是因为弹簧效果的存在,这里指的是松手后的可滚动距离 )
-
UIScrollView不能滚动的原因
- self.scrollView.scrollEnabled = NO (仅仅是不能够滚动,scrollView上的点击事件可以响应)
- self.scrollView.userInteractionEnabled = NO (不能响应所有操作)
- contentSize小于等于scrollView的size
-
UIScrollView没用过的属性
alwaysBounceHorizontal :不论有没有设置contentSize,垂直方向总是有弹簧效果
subViews:不要通过subviews索引拿到scrollView子控件,因为数组排序不是固定的(滚动条的存在)
contentOffset:(scrollView左上角减去内容左上角)假如我们设置scrollView的水平可滚动范围为50,那么通过代码设置contentOffset可以实现水平滚动100 但是当用户手轻轻拖拽scrollView时,内容就会回到正常的偏移量。
contentInset: 设置内边距,可以设置上左下右的内边距,内边距是不能设置内容的。偏移量的计算是不受影响的(只是偏移量会有一个初始值,不影响计算差值),只是增加了额外的滚动区域。
监听scrollView滚动停止:
结合两个方法:
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
if (decelerate == NO) {
NSLog(@"不会执行下面的方法,scrollView已经停止滚动");
}else {
NSLog(@"会执行下面的方法");
}
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
}
缩放
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
//放回要缩放的视图
}
scrollv分页标准:以scrollView的尺寸为一页
代理
代理属性为weak弱引用,避免循环引用
一般谁管理,就设置谁为代理
遍历数组删除数据
下面的案例说明我们在自定义控件时要考虑多次赋值时是否会有问题。
一种常见的写法:
for (UIImageView *imageView in self.scrollView.subviews) {
[imageView removeFromSuperview];
}
在开发中有一个原则:在遍历数组时要保证数组的长度不变
所以我们可以使用下面的方法
[self.scrollView.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
屏幕适配
1、Autoresizing (只能相对父控件布局)
代码设置:
redView.autoresizingMask = ....
2、Autolayout
在storyboard中,选择下图给控件添加约束,如果要修改约束,不能再点击这个按钮进行修改,因为这个只能添加新的约束
这里上下左右默认是离子控件最近的控件,这里是SafeArea这个区域不包含上面的状态栏,所以默认是子控件和这个区域的距离。
当我们想布局绿色的View距离红色的View时,需要先将绿色的View往下平移到红色View的左边,这样在做约束时,才能找到红色的View,而且由于红色的View距离绿色的View最近,所以默认就是相对红色View布局的。
布局两个空间等宽等高的操作方法
1、用command选中两个控件,然后点击
选择equalWidth 和 equalHeight
2、从一个控件摁住control建拖向另一个控件
设置一个View为另一个View宽度的一半
首先设置两个宽度相等,然后修改比例即可,如下图
还可以先设置两个控件左对齐,然后点击这个约束进入右边的设置页面,让红色的leading = 蓝色的水平中心,也可以实现(通用公式为A的属性 = B的属性 X 系数 + 一个值)(注意上面可以选择属性,下面还可以选择具体的控件)
其他的约束
最下面两个是指当前选中视图的中心点和父控件中心点的距离
其余的需要选择至少两个控件进行布局
UILabel 与 AutoLayout :
我们只需要确定UILabel的位置就可以完成对UILabel的约束,因为尺寸会根据内容来确定,但是如果我们需要换行的话,可以设置一个最大宽度,我们可以选择Less Than or equal 来保证当内容过小时也可以包裹内容
让父控件的高度随着子控件的高度伸缩: 父控件不设置高度,设置label与父控件底部的距离即可
通过代码实现AutoLayout
这里体现了万能公式 objct1.property1 = (obj2.property2 * multiplier) + constant value
//设置红色View的宽度为100
NSLayoutConstraint *wlcs = [NSLayoutConstraint constraintWithItem:redView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:0.0 constant:100];
//约束要添加到最近的父控件上去,多个控件要找公共的父控件
[redView addConstraint:wlcs];
VFL语言实现AutoLayout
NSString *hvcs = @"H:|-20-[redView]-20-|";
NSDictionary *views = @{@"redView":redView};
NSArray *hlcs = [NSLayoutConstraint constraintsWithVisualFormat:hvcs options:kNilOptions metrics:nil views:views];
[self.view addConstraints:hlcs];
约束的优先级 (默认1000)
使用场景:
A B C三个控件,C距离B设置了距离约束,假如B被删除,想让C距离A一定的约束,这个时候就要设置C距离A的约束优先级低一些。
约束动画
1、将要修改的约束以连线的方式拖到控件中(一个约束也是一个对象)
2、约束动画不能通过UIView动画实现
[UIView animateWithDuration:2 animations:^{
self.weight.constant = 20;
}];
要通过
self.weight.constant = 20;
[UIView animateWithDuration:2 animations:^{
//强制刷新,需要拿到父控件
[self.view layoutIfNeeded];
}];
Masonry
发现:下载下来的Masonry里面有一个info.plist,如果不删除,引用需要#import"Masonry/Masonry.h",删除后直接#import"Masonry.h"
完整的万能公式写法为
make.top.mas_equalTo(self.view.mas_top).multipliedBy(1.0).offset(20);
下面两种写法是一样的(距离父控件的顶部为20)
make.top.mas_equalTo(20);
make.top.offset(20);
使用makeConstrains方法是添加新的约束
使用updateConstrains方法是更新约束(这个方法有可能也会添加新的约束,如果之前没有设置一个约束时就会添加)
remakeConstrains方法是删除之前的所有约束,添加新的约束(不常用,比较危险)
Baseline的使用方法
如果让一个多行显示的lable和一个按钮底部对齐,会发现文字内容并不是对齐的,如果想文字内容底部对齐,就需要用到这个。
UITableView
分组数据的模型构建:每组用一个模型表示(包括内容数组(数组中放内容模型)、组头信息等等),最后将这些模型放到数组中。
代理方法解释:
//取消选中某行,比如先选中了第一行,当我们再点击第二行时会先调用取消选中第一行的方法,再调用选中第二行的方法
- (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath {
}
UITableviewController
控制器的View就是tableView (self.view 和 self.tableView一样)
UITableViewCell优化思路
由于每当一个cell将要显示到页面上都会调用一次创建cell的代理方法(如果不复用,移除屏幕的cell会被销毁,这样频繁的创建、销毁会消耗性能),所以可以将划出屏幕的cell放到缓存池中,每次将要创建cell时先去缓存池中找,如果找到了就取出这个cell重新赋值就可以了,这样可以避免重复创建cell。由于缓存池中可能不仅有一种cell,所以需要给cell创建一个标识。每次根据标识去缓存池中取cell。
标准写法:
//写static的原因:由于ID是局部变量,方法频繁调用,这样每次ID都会被销毁再创建,所以添加static。static不改变变量的作用域,只改变变量的生命周期,此时生命周期为整个程序
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
//定义重用标识
static NSString *ID = @"wine";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:ID];
}
return cell;
}
简便写法:(一般使用在自定义cell上,因为系统的cell无法设置cell的style)
1、在创建tableView的时候注册cell (写在viewdidLoad里能保证注册一次,还能保证在调用返回cell之前注册)
self.tableView registerClass:[UITableView class] forCellReuseIdentifier:@"ID"];
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
//定义重用标识
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
return cell;
}
UITableView设置索引条
- (NSArray<NSString *> *)sectionIndexTitlesForTableView:(UITableView *)tableView {
//利用KVC取出模型数组中的每一个元素(模型)的title属性,作为新的数组返回
return [self.carGroups valueForkeyPath:@"title"];
}
自定义UITableViewCell
自定义控件要重写这个方法(通过注册、或者手动创建、使用复用都会调用下面的方法)
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if ( self) {
}
return self;
}
自定义等高Cell
1、frame方式
- 与自定义控件不同,自定义cell需要重写- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier方法(因为需要绑定重用标识)在这个方法中创建并添加子控件
与之前变成习惯不同:控件的属性用weak,因为这个控件要被添加到cell中。
UILabel *label = [[UILabel alloc] init];
[self.contentView addSubView:label];//将子控件添加到contentView上,此时已经被强引用
self.label = label;//要通过这种方式给弱引用赋值
- 在layoutSubview里面布局子控件,在这个方法里,就可以使用contentView的frame进行布局了(一定要调用super laoutSubviews)
2、Autolayout方式
- 使用Masonry,约束可以直接在- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier方法中添加。
3、xib方式
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
//定义重用标识
static NSString *ID = @"wine";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
if (cell == nil) {
//这里要注意,xib的重用标识要在xib中设置(Identifier)
cell = [[[NSBundle mainBundle] loadNIbNamed:NSStringFromClass([XMGTgCell class]) owner:nil options:nil] lastObject];
}
return cell;
}
或者通过注册的方式
//通过注册方式xib中可以不设置Identifier
[self.tableView registerNib:[UINib nibWithNibName:NSStringFromClass([XMGTgcell class]) bundle:nil[ forCellReuseIdentifier:ID];
4、storyboard方式
无需代码注册,在storyboard中写标识,直接
[tableView dequeueReusableVellWithIdentifier:ID]即可
自定义不等高Cell
1、frame
在initWithStyle中定义子控件
在LayoutSubviews中设置frame布局子控件
给cell赋值模型属性时,有if必有else,防止cell复用
cell的高度在表格代理方法中动态返回,高度存储在模型中返回即可
性能优化:上面的方法中,由于layoutSubviews和返回高度的代理方法都是频繁调用的,所以不能在这两个方法中计算frame。解决方法是把计算的方法封装在模型中。内部要做判断如果有值了就直接返回,避免频繁计算。
- 计算label的宽高
//只能计算一行
- (CGSize)sizeWithAttributes:(nullable NSDictionary<NSAttributedStringKey, id> *)attrs NS_AVAILABLE(10_0, 7_0);
//根据宽度返回计算的高度
- (CGRect)boundingRectWithSize:(CGSize)size options:(NSStringDrawingOptions)options context:(nullable NSStringDrawingContext *)context NS_AVAILABLE(10_11, 6_0);
2、通过storyboard方式
不需要计算cell的高度,通过设置约束的方式计算(通过最后一个子控件与uiTableViewCell的间距约束),但是代码中需要写
//self-sizing技术(iOS8以后支持,虽然下面两个API一个是iOS7一个是iOS5就支持了)
self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedRowHeight = 44;
对于动态显示的元素,我们可以拿到控件的高度约束,通过设置值来控制。
性能优化:estimateRowHeight
tableView的代理方法heightforRowHeight初始会调用很多次,有多少行就会调用多少次,如果我们设置了估算高度,就能避免开始所有行都调用。还能改变代理方法的调用顺序:即先调用返回cell的方法,后调用heightforRow的方法。
UITableView刷新
全局刷新:[self.tableView reloadData];
局部刷新:
1、self.tableView reloadRowsAtIndexPaths:<#(nonnull NSArray<NSIndexPath *> *)#> withRowAnimation:<#(UITableViewRowAnimation)#> (这个方法要保证数据源长度不变,所以更新模型的时候调用这个方法)
2、self.tableView insertRowsAtIndexPaths:<#(nonnull NSArray<NSIndexPath *> *)#> withRowAnimation:<#(UITableViewRowAnimation)#> (添加,对应数据源也在对应位置添加模型)
3、self.tableView deleteRowsAtIndexPaths:<#(nonnull NSArray<NSIndexPath *> *)#> withRowAnimation:<#(UITableViewRowAnimation)#> (删除,对应数据源也在对应位置删除模型)
通知
这里要何监听方法一一对应,可以指定哪个对象发的哪个通知。
[NSNotificationCenter defaultCenter] postNotificationName:<#(nonnull NSNotificationName)#> object:<#(nullable id)#> userInfo:<#(nullable NSDictionary *)#>]
对于通知的传值,我们可以传入一个字典,如果想拿到对象,我们可以通过note.object的方法拿到发通知的对象,但是发送通知时候要将这个对象传入object参数中
图片缓存思路
1、内存缓存:用字典,以url作为key,image作为value
2、磁盘缓存(Documents(不允许存储内容?)、Library(Caches、Preferences)、tmp(随时会被删除))
综上,放到Caches中(不会被备份),图片名称命名不能使用url,但可以使用url中最后一部分作为图片的名称([imageV.icon lastPathComponent])
3、读取图片的思路:先从内存中查找、再从磁盘中查找、如果都没有,再去看当前图片是否正在下载,如果正在下载什么都不做,如果没有下载任务,再去下载任务(需要对任务缓存,避免重复添加下载操作)
NSOperation
我们在使用NSOperation的时候要使用其子类,如果不把操作放到队列里,需要手动执行start方法,由于队列是管理操作的,所以加入到队列的操作会自动执行start,当我们自定义操作时,通常把任务放到main方法中,其实main 方法是在start方法里面调用的。
类似的NSThread我们也可以定义子类,重写main方法
SDWebImage框架需要注意的方法
[SDWebImageDownloador shareDownloader] downloadImageWithURL: options: progress: complete这个方法是不做任何缓存的,而且complete方法的回调是在子线程中的。
类似的还有[SDWebImageManager shareManager] lownloadImageWithURL:optinos:progress:complete 这个方法就是带内存和磁盘缓存,并且complete回调是在主线程中的。
内存警告的处理:
其他的
NSCache
可以设置总成本 和每次缓存消耗的成本,当超过缓存消耗的成本就会释放之前缓存的对象。
由于NSCache的key是对对象的strong引用,所以当多个key对应一个对象时,成本为一个对象的成本。
KVO
缺点:
若果一个类用了KVO监听,苹果会默认为这个类创建一个子类NSKVONotifing_类名
代理delegate
代理可以理解为:假如A遇到的问题让B解决,A就若引用B,在B中实现同样的方法,当A遇到的问题时,让B调用B的方法。(感觉就是方法传递)。代理的类型设置为id是为了解耦,避免委托方要引入对应类的头文件。
协议的理解:规范哪些对象可以成为代理。所以引用的代理对象遵循协议,同时定义这个协议即可。
在定义协议的时候,最好写上@optional 否则如果代理不实现方法会有警告
代理在调用方法时,最好要先判断一下是否实现了对应的代理方法。
遍历模型数组的一些优化
对于一些商品列表,我们将我们选中模型再存入另一个数组中,这样在操作时,就遍历这个子数组就可以了。
RunLoop
子线程的RunLoop需要手动创建
RunLoop启动之后只能在一种模式下运行
RunLoop中至少有一个Source 或者 一个Timer保证RunLoop不退出
GCD定时器
1、GCD的定时器不受RunLoop影响,不用考虑RunLoop的运行模式
2、绝对精准(控制台的时间是打印时间,可能略有不同,但内部执行是绝对精准的)
3、可以指定线程执行
CFRunLoopSourceRef
(根据函数调用栈分为:)
1、Source1:基于端口的
2、Source0:非基于端口的(用户主动触发
CFRunLoopObserver
添加观察者:
RunLoop的自动释放池
PCH文件
#ifdef __OBJC__
//所有OC定义的写到这里就可以了,这样c文件就不会引入这些OC的头文件了
#endif
单例
实现方法1:模仿苹果的写法
#import "Person.h"
@implementation Person
//1.程序一运行,就创建对象
static Person *_instance;
//什么时候调用:当类被加载到内存当中就会调用。这个方法比main函数调用还要早
+(void)load{
NSLog(@"%s",__func__);
_instance = [[Person alloc] init];
}
//2.创建的对象,只有一个
+ (instancetype)sharedPerson{
return _instance;
}
//3.只要调用alloc,程序就崩溃
//Terminating app due to uncaught exception 'NSInternalInconsistencyException',
//reason: 'There can only be one UIApplication instance.'
+(instancetype)alloc{
if (_instance) {
//程序就崩溃
NSException *exc = [NSException exceptionWithName:@"NSInternalInconsistencyException"
reason:@"There can only be one Person instance."
userInfo:nil];
//抛出异常
[exc raise];
}
return [super alloc];
}
@end
UIApplication
1、程序启动创建的第一个对象就是UIApplication单例
2、应用级别的对象
程序启动原理
UIWindow
一些特殊的窗口
1、状态栏
2、键盘
UIWindow的层级关系
级别越高越靠外(可见)
层级可以做加减,比如UIWindowLevelAlert + 1
通过xib加载控制器
1、在xib中创建一个View
2、在File's Owner中选择控制器的类
3、右击File's Owner连接View
4、通过nib加载控制器 ViewController *VC =[ [UIViewController alloc] initWithNibName:@"VC" bundle:nil];
控制器的View的创建
在loadView方法中创建,如果想自定义控制器的view 也在这个方法中自定义。
控制器的init的方法 底层会调用initWithNIbName
一般的控件的init的方法 底层会调用initWithFrame
如果满足了注意点,那么loadView底层会做下面四件事情。
默认控制器的颜色
clearColor 这个颜色是不能穿透的,即View下面的东西不可点击。
alpha <= 0.01 可以穿透
控制器View的懒加载
当第一次加载时,伪代码如下:
删除字符串中从后往前的第一个指定字符
分类
如果在分类中重写的是系统的方法,使用时就不用包含头文件,而且可以删掉.h文件
UINavigationController
1、导航控制器除了自身的view,还有两个子控件,一个是导航条,还有一个是存放子控制器的view
2、导航条的y值为20,高度为44
3、以栈的形式保存自控制器
4、通过查看层级结构可以知道,当有一个新的控制器压入到栈,之前的控制器view会从父控件上移除,并不是销毁,只是不显示(动画结束后)。
5、导航控制器永远显示栈顶控制器的view
6、导航条的内容由栈顶控制器的navigationItem设置
7、导航条上的按钮位置由系统决定,尺寸可以有自己决定,一般宽高都是35
8、在iOS7之后,苹果会自动给导航控制器里面的所有UIScrollView顶部都会添加额外的滚动区域64 (在viewDidAppear打印可以验证),如果不需要添加额外的滚动区域,通过设置
self.automaticallyAdjustScrollViewInsets=NO;
9、
大文件下载 && NSURLConnection
实现方法1:
下载大文件时,避免内存飙升,需要一边下载一边写入沙盒,需要用到NSFileManager 和 句柄
实现方法2:
输出流
断点下载
结合上面的大文件下载,只需要在每次开启任务的时候,通过设置请求头来实现。
文件上传
1、在请求头中告知服务器
2、按照固定格式拼接请求体
多线程下载思路
1、通过设置请求头来实现一个子线程下载某一部分
2、在往沙盒写文件的时候,要做到哪一部分就写到文件的哪个位置。
这里就涉及指定文件写入位置,通过文件句柄来实现。
文件的压缩和解压
可以使用SSZipArchive 等第三方库,不仅可以压缩、解压还可以设置密码。
KVC底层赋值过程
id特性
只要引入类的头文件,则id类型可以调用这个类的对象方法。
NSURLConnection补充
当需要在子线程中使用NSURLConnection发送网络请求时,需要在子线程中开启RunLoop
NSURLSession
1、Post请求
其中,completionHandler是在子线程中回调的
NSURL *url = [NSURL URLWithString:@"www.baidu.com"];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
request.HTTPMethod = @"POST";
request.HTTPBody = [@"canshu" dataUsingEncoding:NSUTF8StringEncoding];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
}];
[task resume];
2、代理方法
- (void)viewDidLoad {
[super viewDidLoad];
NSURL *url = [NSURL URLWithString:@"www.baidu.com"];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
request.HTTPMethod = @"POST";
request.HTTPBody = [@"canshu" dataUsingEncoding:NSUTF8StringEncoding];
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request];
[task resume];
}
#pragma -mark NSURLSessionDataDelegate
//接收到服务器的响应 会默认取消请求 需要通过回调来告知系统 不取消请求
-(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {
// typedef NS_ENUM(NSInteger, NSURLSessionResponseDisposition) {
// NSURLSessionResponseCancel = 0, /* Cancel the load, this is the same as -[task cancel] */
// NSURLSessionResponseAllow = 1, /* Allow the load to continue */
// NSURLSessionResponseBecomeDownload = 2, /* Turn this request into a download */
// NSURLSessionResponseBecomeStream API_AVAILABLE(macos(10.11), ios(9.0), watchos(2.0), tvos(9.0)) = 3, /* Turn this task into a stream task */
// } NS_ENUM_AVAILABLE(NSURLSESSION_AVAILABLE, 7_0);
//需要调用允许
completionHandler(NSURLSessionResponseAllow);
}
//接收到服务器返回的数据
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
}
- (void)URLSession:(NSURLSession *)session task:(nonnull NSURLSessionTask *)task didCompleteWithError:(nullable NSError *)error {
}
大文件下载
方法1:
由于这个方法内部已经实现了边下载边写入文件,所以可以下载大文件,无需担心内存,但是缺点是不能监听下载过程
NSURL *url = [NSURL URLWithString:@"www.baidu.com"];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
NSURLSession *session = [NSURLSession sharedSession];
//该方法内部已经实现了边接收数据边写入沙盒的操作,写入到tmp路径下面(通过打印location可以查看)
NSURLSessionDownloadTask *downTask = [session downloadTaskWithRequest:request completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
//由于该方法默认将下载的内容存放到tmp文件夹中,所以我们要执行剪切操作,
//拼接文件路径
//respose.suggestedFilename是文件的推荐名称,就是url的最后一个节点([url lastComponent])
NSString *fullPath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:response.suggestedFilename];
[[NSFileManager defaultManager] moveItemAtURL:location toURL:[NSURL fileURLWithPath:fullPath] error:nil];
}];
[downTask resume];
方法二:通过代理方法
- (void)viewDidLoad {
[super viewDidLoad];
NSURL *url = [NSURL URLWithString:@"www.baidu.com"];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
//该方法内部已经实现了边接收数据边写入沙盒的操作,写入到tmp路径下面(通过打印location可以查看)
NSURLSessionDownloadTask *downTask = [session downloadTaskWithRequest:request];
[downTask resume];
}
#pragma -mark NSURLSessionDownloadDelegate
//后面三个参数依次为:本次写入的数据大小、下载的数据的总大小、文件的总大小
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
}
//当恢复下载的时候调用该方法
//fileOffset:从什么地方开始下载
//expectedTotalBytes:文件的总大小
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes {
}
//下载完成时调用 location:文件的临时存储路径(也是边下载边写入沙盒) 所以在这个方法中也要执行剪切操作
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
}
//请求结束时调用
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
}
断点续传
方案一:
首先取消方法中的resumeData不是指已经下载的数据,只是保存了断点信息
局限性:假如用户不点击取消直接退出,再回来就无法实现继续下载
方案二:(推荐使用)
使用NSURLSessionDataTask
实现思路跟NSURLConnection一样
离线断点下载
思路:当用户退出程序再次进入时,我们可以根据沙盒找到下载了一般的文件,读取文件大小,从而修改请求头实现离线断点下载!
关于文件句柄的移动,在创建的时候 移动一下。
Session的清理工作
如果设置了代理,就要执行
使用NSURLSession实现文件上传
1、使用NSURLSessionUpload
2、要想监听上传进度,就要使用代理,由下面的关系可知,遵守NSRULDataDelegate
Base64
缺点:经过base64后文件数据会变大,因为原来的8位经过base64后每6位就取值。
散列函数
https && 原生请求
微博个人中心分析:
首先头部视图在往下拖拽的时候,并没有跟顶部分离,所以这个头部视图不是表格的表头视图,由于表视图的内容可以滚到顶部,所以tableView的frame是从顶部开始的,所以要给表格设置边距(contentInset)然后头部视图单独添加在控制器的view上面。当表格移动时,根据偏移量的差值,移动头部视图。
如果要做视觉差,思路是根据偏移量缩小头不是图的高度来实现。
向下拉的时候图片放大是因为头不是图变大,同时设置了图片的contentMode,就自然实现了。
导航栏背景图片的虚实变化是根据颜色生成图片的方法来实现的
沙盒
Documents文件夹如果存储文件有可能被拒,所以一般不往该文件夹中存储数据
沙盒与包(mainBundle)
沙盒指的是文件夹,包指的是压缩包,区分这两个概念。
归档
归档自定义对象,首先要遵守NSCoding协议,在解档时,需要调用下面的方法:
其中并不是调用super initWithCoder ,联想到从nib文件加载文件时,是要调用super initWithCoder的,原因是 只要父类遵循了NSCoding协议,就可以调用父类的这个方法,如果父类没有遵守,就不调用。
下面是nib文件关联的view
transform
在做平移动画的时候,我们使用transform就相当于下面注释的代码
如果需要在上一次的基础上平移,使用下面的方法:
清空形变的方法:
在storyboard中使用xib的方法,使用一个占位的view 然后在控制器中给这个指针赋值就可以了。
UIStoryboardSegue
自动型:在storyboard直接从按钮拖线,如果不需要逻辑判断条件,可以使用这种方式
手动型:
传值
performSegue底层实现如下:
在segue对象中,可以拿到目标控制器。
只有来源控制器才能拿到segue对象。所以反向传值的时候,目标控制器不能通过segue拿到源控制器。可以在源控制器跳转的时候,让目标控制器引用源控制器,这里为了解耦,可以使用代理。
在storyboard中点击cell进行push操作时,假如通过连线的方式进行跳转,如果失败,可能原因是cell不是storyboard创建的,这个时候在storyboard中给cell设置id,然后删除代码创建的部分即可。
UITabBarController
UITabBarController作为控制器,肯定有自己的view,这个view有两个子控件,一个是UITabBar,另一个是子控制器的View(这里是一个占位的View) ,与UINavigatinoController一样。
泛型
Modal控制器的底层实现
被model的控制器是需要被强引用的,它的主人是主动model的那个控制器。
触摸事件传递
1、由父控件传递给子控件
2、如果父控件不能接受事件,子控件也不会接收事件
Runtime
runtime一般用来修改系统类
1、可以调用私有方法
2、修改系统的方法实现(方法交换),在这个类的分类中的load方法中实现方法交换。
3、动态添加方法(消息转发的第一步)
4、动态添加属性
在分类中,如下
hittest方法
场景1:一个按钮,和一个view同时添加到控制器的view上,view盖在按钮上,现在要求当触碰的点在按钮范围内就响应按钮的方法。
场景2:超出父控件的子控件响应事件
由事件的传递知道,父控件无法响应,子控件无法响应,所以需要使用hittest方法。
事件的响应
如果在一个View中的touchesBegan方法中调用super,则上一个响应者也会响应事件。可以实现一个事件多个响应者响应。
由上面我们知道,如果一个控制器的所有view都不实现toucheBegan方法(不能是按钮),(默认就是不处理,那么会向上传递)控制器如果实现了touchBegan方法就会由控制器来处理。
手势
代理方法的作用,可以拿到touch对象,从而可以设置点击区域。
2、长按手势一般在方法中添加判断,否则长按时移动也会频繁调用
3、轻扫手势
Quarz2D
1、绘图
只有在drawRect方法中才能获取view的上下文
drawRect手动调用是不会生成跟view相关联的上下文的,当我们需要根据外部变量的改变连续绘图时,需要调用重绘的方法
[self setNeedsDisplay]
CADisplayLink
所以做绘制动画的时候,应该选择CADisplayLink
图形上下文状态栈
为了避免每次绘制都重新设置上下文的状态(线宽、颜色等)我们可以先把它保存到图形上下文栈中,然后恢复即可。
上下文的矩阵操作
图片水印
view的上下文需要在drawRect中获取,而图片的上下文需要自己手动创建
图片裁剪
字典转模型框架的核心
利用runtime取出对象的属性,再根据这个属性到字典里取值。
super
子类如果调用父类的方法,在父类方法中的self 仍然是子类。
常见Bug
下面如果不勾选,添加的文件就不会参与编译。
宏与const
const
static 与 extern
view与viewController
view存在不一定viewController存在,因为view没有强引用其控制器。
父子控制器
开发规范:
push:
假如A是一个导航控制器的根视图控制器,这个根视图控制器上有一个控制器的view,如果不设置父子关系,那么这个被添加的控制器就不能执行pushViewController方法。但是push方法会把这个根视图控制器的view全部移除(连同这个被添加的控制器view)。
modal
综上,也就是一个控制器执行push 或者 modal的时候,如果自己不行,会找父控制器可不可以实现。
判断一个控制器的view是否加载(ios9.0之后)
ios9.0之前 网易新闻页判断
UIScrollView的自动布局
通知与多线程
assign 和 weak
ARC中才有weak ,安全性高。当对象销毁 在访问不会造成野指针。
UIScrollView底层实现
通过改变其bonus来实现内容的滚动。
Block定义成属性的写法
block是OC对象
Block内存管理
首先,用__block修饰的变量仍然是局部变量,加上static就变成全局变量,所以下面的结论:
栈管理block,函数结束,就会销毁。
Block循环引用
当我们在执行延时操作时,有的时候不用strongSelf,会导致self被释放掉,所以在block中在生命一个strongSelf,比如下面的例子,在延时block中,因为使用了strongSelf才确保了self在执行完延时block后才被释放(执行_block这个block时,里面的strongSelf已经被释放,但是下面block中的strongSelf仍然强引用这控制器,所以等到延时block执行完毕后整个控制器才被释放)。
block变量传递
block作为函数的返回值
应用:Masonry框架、链式编程思想
首先了解一点,一个block后面加上括号,就相当于调用了这个block。
UICollectionview
当UICollectionView是水平滚动时,行间距属性设置的就是水平方向的。(minimumLineSpacing)
代码封装的另一种写法
自定义流水布局
需要了解下面五种方法
UIImageView的Layer
CALayer的两个属性
这两个属性是描述父子层位置的两个属性
非跟层的layer与隐式动画
也可以取消隐式动画
UIView动画与核心动画的区别
CoreAnimation
transform
关于导航控制器的一些记录 ###2020/02/04
1,一个很常见的app情景是:个人中心和登录页面的管理问题
如果这两个页面的导航控制器的设置不太一样,当我们切换跟视图控制器时 被切换的页面显示可能会存在问题
self.navigationController.navigationBar.translucent = YES; 这个属性会影响切换时页面的显示问题