• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

[iOS开发]UIKit

武飞扬头像
复杂化
帮助1

CALayer和UIView

区别

UIView继承自UIResponder,主要负责事件传递、事件响应,属于基于UIKit框架
CALayer继承自NSObject,负责图像渲染,动画和视图的显示,属于QuartzCore框架

提到这两个 就不得不提一下单一职责原则。通俗地讲就是一个类只做一件事。这两大内容就符合单一职责原则

虽然CALayer没有事件响应的能力,但是我们可以通过

  • hitTest
  • convert
    两个方法来判断事件是不是在layer上,从而来给事件添加点击事件

关系

  • 所有的界面元素都继承自UIView。它真正的绘图部分是由CALayer的类来管理的。UIView本身更像是一个CALayer的管理器,访问其跟绘图和坐标有关的属性,例如framebounds等等,实质上内部都是在访问它所包含的CALayer的相关属性。
  • UIView有个layer属性,可以返回它的主CALayer实例。UIView有一个layerClass方法,返回主layer所使用的类(默认返回就是[CALayer class]),UIView的子类可以通过重载这个方法来时UIView使用不同的CALayer来显示。
  • UIView的CALayer类似于UIView的子view树状结构,也可以向它的layer上添加子layer,来完成某些特殊的表示
    CALayer *grayCover = [[CALayer alloc] init];
    grayCover.backgroundColor = [[UIColor greenColor] CGColor];
    grayCover.position = CGPointMake(200,200);
    grayCover.bounds = CGRectMake(100,100,80,80);
    [self.view.layer addSublayer:grayCover];

会在目标view上敷上一层绿色的透明膜

学新通
并没有添加新图层,而是在原本view上添加的新图层,这和addsubview并不一样。

  • CALayer视图结构类似UIView的子view树形结构,可以向它的layer上添加子layer,类似于向View上添加View,来完成某些特殊的表示。
  • UIVIew的layer树形在系统内部,被系统维护三份copy
    • 第一份,逻辑树,代码可以在里面操作,例如通过代码更改layer的属性【比如frame\bounds】就在这一份进行操作
    • 第二份,动画树,这是一个中间层,系统在这一层更改属性,进行各种渲染操作
    • 第三份,显示树,这棵树的内容就是当前正被显示在屏幕上的内容

这三棵树的逻辑结构都是一样的,区别只是有各自的属性

动画加载上的区别?

tableview

tableView遵循的两个delegate

对于tableView我们已经十分熟悉

为了复习 我们看一下tableView需要完成的操作

  • 第一肯定是正常的创建tableView,tableView需要遵循两个delegate,<UITableViewDelegate, UITableViewDataSource> ,顾名思义,dataSource 意思为数据源,delegate 意为代理,其内部包含很多方法。
    • UITableView需要一个数据源(dataSource)来显示数据,UITableView会向数据源查询一共有多少行数据以及每一行显示什么数据等等。没有设置数据源的UITableView只是个空壳。凡是遵守UITableViewDataSource协议的OC对象,都可以是UITableView的数据源。
    • 我们也需要为UITableView设置代理对象(delegate),以便在UITableView触发某些事件时做出相应的处理,比如选中了某一行。凡是遵守了UITableViewDelegate协议的OC对象。都可以是UITableView的代理对象,一般会让控制器充当UITableView的dataSource和delegate,通过我们手动实现协议中的某些方法来完成tableView的实现

具体到方法

这三个方法都是dataSource协议中的方法,我们往往通过这三个方法实现数据的显示

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 

这两个常用的方法是delegate中的方法,提供某些交互方法

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath

tableView复用机制

tableView复用机制

tableView自适应高度

关于自适应高度,主要思路有两个

  • 手动计算高度,进行存储,然后通过heightForRow来调用存在数组中的缓存进行实现。
  • iOS7之后UITableView默认的Self-Sizing技术,不需要实现heightForRow方法,系统通过AutoLayout约束自动计算cell高度

下面我们就进行一下这两个实现思路的讲解
先说第二个吧,我认为这个方法既然是新技术,那么就一定能帮我们更好的使用tableView

AutoLayout自适应高度

主要思路就是两步

  1. 使用AutoLayout方法
  2. 不要实现heightForRowAt方法

(说着简单,实际上这两点的实现浪费了我很多时间去解决Masonry在layouSubviews不起作用的问题)

如何使用AutoLayout方法?

AutoLayout是一种自动布局技术,可以帮我们自动实现布局,苹果官方也推荐开发者尽量使用AutoLayout来布局UI界面。

其实就是两个核心概念,参考、约束

听起来很像我们的Masonry的使用,确实,使用Masonry来完成自适应高度是很舒服的,直接使用其约束条件,不用我们自己手动实现各种约束条件。

以自定义cell中的Label的自动展开为例

我们往往在自定义的initWithStyle中实现cell中某个控件的初始化,然后在layoutSubviews中完成某个控件的布局操作。

使用自适应高度的代码如下

- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
    if ([reuseIdentifier  isEqual: @"firstCell"]) {
        _longCommentLabel = [[UILabel alloc]init];
        _longCommentLabel.numberOfLines = 0;
        [self.contentView addSubview:_longCommentLabel];
        [self layoutIfNeeded];
    }
    return self;
}

- (void)layoutSubviews {
    [super layoutSubviews];
    [_longCommentLabel mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.equalTo(_longCommentLabel.superview.mas_top).offset(10.0);
        make.bottom.equalTo(_longCommentLabel.superview.mas_bottom).offset(-10);
        make.left.equalTo(_longCommentLabel.superview.mas_left).offset(10.0);
        make.right.equalTo(_longCommentLabel.superview.mas_right).offset(-10.0);
    }];

}
学新通

可以看到Masonry的使用和我们之前正常的使用并不相同。正常使用一个Masonry应该怎么实现呢?很简单,给top、left、size,这三个条件就足以用最简单的方式实现一个布局操作。

而自适应高度呢?我们没有设置整体的size,而是就四个边,上左下右给了界限,中间的高度自己根据自己的内容的高度自己实现,达到将cell撑开的效果。很巧妙。

但是显然,仅仅使用Masonry就足够了吗?显然不是,我们还要设置个自动换行啊,怎么个换法, numberOfLines = 0;就行了,官方文档对于numberOfLines等于0的解释:若要删除任何最大限制,并根据需要使用尽可能多的行,请将此属性的值设置为0即可。

这基本上就是AutoLayout的设置部分,当然那[self layoutIfNeeded];这个呢?为什么需要这句话,这句话其实就是困扰我好久的问题
“自动展开的Masonry在layouSubviews不起作用的问题”

经过测试,iOS10之前,layoutSubViews方法在cell初始化时就会调用两次,而iOS10之后,其只会调用一次,所以我们需要手动调用layoutSubViews多走一次,就可以得到正确的高度。当然有兄弟会问,为什么写项目时没有注意到?之前我们不止一个布局,每次调用布局时都会创建一次,相当于自动走了前面的几次,而如果最后一次是自动展开效果,那么也会出现问题,但仅仅因为我们最后的一个布局不是自动展开效果,所以刚好不会出现问题。
这下应该理解了吧。

感谢这个大佬帮我解决了这个bug
iOS10后使用Masonry进行自动布局出现的问题及处理

如何使用AutoLayout方法我们已经说完了

关于不要实现heightForRow方法
在其他早一点的博客我们可以看到
学新通
现在iOS14 之后这两个都不需要设置了,因为rowHeight的默认值为UITableViewAutomaticDimension,可以看到官方文档有写。
学新通

当然也可以参考一下这篇博客,大佬对Self-Sizing进行了一些优化
iOS Self-Sizing的一点优化

不适用Masonry的版本,给我看傻了
iOS之SelfSizing Cell-高度自动计算详细讲解

手动计算高度,进行存储

这里用老学长的博客吧,两种方法

两个核心方法

sizeToFit

    UILabel * label = [[UILabel alloc] initWithFrame:CGRectMake(10,100, 50, 40)];
    label.numberOfLines = 0;
    label.text =@"当前视图的边界和边界大小的变化123123213213213123123123123123213213123213213213123123213";
    NSLog(@"the label bounds : %@",NSStringFromCGRect(label.frame));
    [label sizeToFit];
    NSLog(@"%f",label.frame.size.height);
    [self.view addSubview:label];

我们以这个例子为例,我们通过label.numberOfLines = 0;[label sizeToFit];保证其可以换行,并且可以通过sizeToFit使其成为合适位置。在sizeToFit之后我们就可以得到其高度,将高度存入数组,在heightForRow中从数组取值。

boundingRectWithSize
学新通
这个方法写的时候我们需要注意
四个参数
第一个参数是size,我们这里只给宽度,长度给MAXFLOAT,最大
第二个参数是图像的选项

  • 如果options参数为NSStringDrawingUsesLineFragmentOrigin,那么整个文本将以每行组成的矩形为单位计算整个文本的尺寸(一般使用这个就足够了)
  • 如果为NSStringDrawingTruncatesLastVisibleLine或者NSStringDrawingUsesDeviceMetric,那么计算文本尺寸时将以每个字或字形为单位来计算。
    第三个参数是attributes字典样式,我们可以在里面给@{NSFontAttributeName:[UIFont systemFontOfSize:16]}
    第四个一般为nil
    UILabel * label = [[UILabel alloc] initWithFrame:CGRectMake(10,100, 50, 40)];
    label.numberOfLines = 0;
    label.text =@"当前视图的边界和边界大小的变化123123213213213123123123123123213213123213213213123123213";
    label.font = [UIFont systemFontOfSize:15];
    
    CGRect rect = [label.text boundingRectWithSize:CGSizeMake(50, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:15]} context:nil];
    NSLog(@"%f",rect.size.height);
    [self.view addSubview:label];

什么时候用tableview相比于scrollerView,tableview做了哪些别的操作

给一个UIScrollerViw实现UITableView

ScrollView 自定义cell 组合(实现复用)

collectionview

与UITableView有相同的API设计理念–都是基于dataSource和delegate驱动的

区别:

  • 每行可以展示多个,更多样的布局方式
  • 由于一行可以展示多个视图,row不能准确的表达,所以引入了item
  • 所有视图都只能由我们自定义实现(我们必须实现自定义cell)

对于cell的样式和组织方式,由于collectionView比tableView要复杂得多,因此没有按照类似于tableView的style的方式来定义,而是专门使用了一个类来对collectionView的布局和行为进行描述,这就是UICollectionViewLayout。

UICollectionViewLayout和我们之前学的NSOperation一样,都是抽象的类,所以我们需要需要UICollectionViewLayout的子类完成操作,我们可以通过自己继承的方式完成,当然系统也为我们创建好了一个子类,UICollectionViewFlowLayout。就是流式布局。

就自定义样式而言,Layout中有一个属性为UICollectionViewLayoutAttributes

@property (nonatomic) CGRect frame;
@property (nonatomic) CGPoint center;
@property (nonatomic) CGSize size;
@property (nonatomic) CATransform3D transform3D;
@property (nonatomic) CGRect bounds API_AVAILABLE(ios(7.0));
@property (nonatomic) CGAffineTransform transform API_AVAILABLE(ios(7.0));
@property (nonatomic) CGFloat alpha;
@property (nonatomic) NSInteger zIndex; // default is 0
@property (nonatomic, getter=isHidden) BOOL hidden; // As an optimization, UICollectionView might not create a view for items whose hidden attribute is YES
@property (nonatomic, strong) NSIndexPath *indexPath;

@property (nonatomic, readonly) UICollectionElementCategory representedElementCategory;
@property (nonatomic, readonly, nullable) NSString *representedElementKind; // nil when

可以看到UICollectionViewLayoutAttributes的实例中包含了需要诸如边框、中心点、大小、形状、透明度、层级关系、是否隐藏等等信息。

每一个cell都会对应一个Attributes。整体的逻辑,通过设置默认的layout,layout中设置每一个cell对应的Attributes,然后将整个的layout赋值给collectionView。

流式布局,每行排满后自动换行

对于流式布局而言,我们需要掌握三个重要属性interitemspace(每一行中,每一个cell之间最小的具体)、linespacing(行与行之间的空间大小)、itemSize(一个cell的大小) 只是标记最小的间距,这三个属性是会变化的

流式布局也给我们提供了几种常见的方法

@protocol UICollectionViewDelegateFlowLayout <UICollectionViewDelegate>
@optional

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath;
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section;
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section;
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section;
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section;
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section;
@end

我们可以通过重写其中的某一个方法来实现三个属性在特定的indexPath对应的cell的修改

UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init];
    flowLayout.minimumLineSpacing = 10;
    flowLayout.minimumInteritemSpacing = 10;
    flowLayout.itemSize = CGSizeMake((self.view.frame.size.width - 10) / 2, 300);
    /*
    layout用于生成cell所有的布局信息,一个属性layouAttributes,
     
     通过设置默认的laypout,layout中设置每一个cell对应的attributes,将整个的layout赋值给collectionview
     如何设置呢?
     其是抽象的类,需要继承viewlayout
    
     flowlayout流式布局interitemspace(每一行中,每一个cell之间最小的具体)、linespacing(行与行之间的空间大小)、itemSize(一个cell的大小) 只是标记最小的间距,这三个属性是会变化的
    
     流式布局,每行排满后自动换行
     */
    
        //需要大小 layout
     _collectionView = [[UICollectionView alloc] initWithFrame:self.view.bounds collectionViewLayout:flowLayout];
    [self.view addSubview:_collectionView];
    
    [_collectionView registerClass:[CollectionViewCell class] forCellWithReuseIdentifier:@"UICollectionViewCell"];
    
    _collectionView.dataSource = self;
    _collectionView.delegate = self;
    
}

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
    return 20;
}

- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    UICollectionViewCell *cell = [_collectionView dequeueReusableCellWithReuseIdentifier:@"UICollectionViewCell" forIndexPath:indexPath];
    cell.backgroundColor = [UIColor grayColor];
    return cell;
}

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
    if (indexPath.item % 3 == 0) {
        return CGSizeMake(self.view.frame.size.width ,100);
    } else {
        return CGSizeMake((self.view.frame.size.width - 10) / 2, 300);
    }
}

学新通

就是下面这样的布局
学新通

自定义时需要重写下面的这些方法

-(void)prepareLayout
prepare方法被自动调用,以保证layout实例的正确。

-(CGSize)collectionViewContentSize
返回collectionView的内容的尺寸

-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
 1. 返回rect中的所有的元素的布局属性
 2. 返回的是包含UICollectionViewLayoutAttributes的NSArray
 3. UICollectionViewLayoutAttributes可以是cell,追加视图或装饰视图的信息,通过不同的UICollectionViewLayoutAttributes初始化方法可以得到不同类型的UICollectionViewLayoutAttributes:
  1)layoutAttributesForCellWithIndexPath:
  2)layoutAttributesForSupplementaryViewOfKind:withIndexPath:
  3)layoutAttributesForDecorationViewOfKind:withIndexPath:

-(UICollectionViewLayoutAttributes )layoutAttributesForItemAtIndexPath:(NSIndexPath )indexPath
返回对应于indexPath的位置的cell的布局属性

-(UICollectionViewLayoutAttributes )layoutAttributesForSupplementaryViewOfKind:(NSString )kind atIndexPath:(NSIndexPath *)indexPath
返回对应于indexPath的位置的追加视图的布局属性,如果没有追加视图可不重载

-(UICollectionViewLayoutAttributes * )layoutAttributesForDecorationViewOfKind:(NSString)decorationViewKind atIndexPath:(NSIndexPath )indexPath
返回对应于indexPath的位置的装饰视图的布局属性,如果没有装饰视图可不重载

-(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
当边界发生改变时,是否应该刷新布局。如果YES则在边界变化(一般是scroll到其他地方)时,将重新计算需要的布局信息。
学新通

调用顺序

1)-(void)prepareLayout  
  a. collection view 只会在第一次layout的时候调用一次`-prepareLayout`,作为第一次通知layout实例对象的消息
  b. collection view 会在 layout 对象 invalidated 之后并且requerying之前再次调用
  c. 继承自UICollectionViewLayout都需要重写这个方法,一般都是在这个方法里面准备好`layoutAttributesForElements(in:)`这个方法要使用到的`UICollectionViewLayoutAttributes`数组。
  
2)  -(CGSize) collectionViewContentSize 
  a. 确定collectionView的所有内容的尺寸
  b. 每一次移动collection view时都会调用这个方法,并且这个方法会被调用多次

3)-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
  初始的layout的外观将由该方法返回的UICollectionViewLayoutAttributes来决定。

4)在需要更新layout时,需要给当前layout发送 
     1)-invalidateLayout, 该消息会立即返回,并且预约在下一个loop的时候刷新当前layout
     2)-prepareLayout,
     3)依次再调用-collectionViewContentSize和-layoutAttributesForElementsInRect来生成更新后的布局。
学新通

之前并没有接触过CollectionView,对自定义的使用不是很熟悉

自定义UICollectionViewLayout实现
iOS之简单瀑布流的实现

UIViewController的生命周期

ViewController生命周期

frame、bounds

frame是相对于父视图自己的位置,bounds是自己的起始位置。其有两个属性,size和origin,origin初始都为0(如果设置了setBounds当我没说),setBounds中前两个变量为负数是什么意思?为何(-30,-30)的偏移量,却可以让view向右下角移动呢?

这是因为setBounds的作用是:强制将自己(view1)坐标系的左上角点,改为(-30,-30)。那么view1的原点,自然就向在右下方偏移(30,30)。

这里Inset也是同样道理,-50,-50,rect 的坐标(origin)按照(dx,dy) 进行平移,然后将rect的大小(size) 宽度缩小2倍的dx,高度缩小2倍的dy;

举个例子:所以inset 值为正,缩小,值为负,放大。

UIViewController和UIResponder

我们最熟悉的UIApplication、UIView、UIViewController这几个类是直接继承自UIResponder,UIResponder类是专门用来响应用户的操作处理各种事件(UIEvent)的。
UIResponder提供了用户点击、按压检测(presses)以及手势检测(motion)的回调方法,分别对应用户开始、移动、结束以及取消,其中只有在程序强制退出或者来电时,取消事件才会调用。

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhgcbhhj
系列文章
更多 icon
同类精品
更多 icon
继续加载