【iOS】—— UIKit相关问题

文章目录

UIKit

objectivec 复制代码
#import <UIKit/UIKit.h> 

UIKit 框架提供了 iOS 或 Apple tvOS App 所需的基础架构。它提供了用于实施界面的窗口和视图架构,用于向 App 提供多点触控和其他类型输入的事件处理基础架构,以及管理用户、系统和 App 之间互动所需的主运行循环。该框架提供的其他功能包括动画支持、文档支持、绘图和打印支持、当前设备的相关信息、文本管理和显示、搜索支持、辅助功能支持、App 扩展支持和资源管理。

常用的UIKit组件

  • UIView及子类,包括UIScrollView(UITableView、UICollectionView、UITextView)、UILabel、UIControl(UIButton、UITextField)、UIPickerView等;
  • 与控件相关但不能被人所直观看到的图形、绘图、打印、文本等的配置与控制,包括UIViewController、UIImage等。

懒加载的优势

懒加载(延迟加载),把对象的实例化尽量延迟,即启动应用程序时不加载这个资源,只有在运行时用到才加载(按需加载)。

例如,如果启动APP后一次性加载大量数据、图片和音视频等资源,就有可能会耗尽移动设备内存。这时就可以使用懒加载技术。具体来说,重写属性的getter方法,先判断该属性是否为nil,如果为空再进行实例化,否则直接返回属性。

懒加载的好处有:不必将创建对象的代码全部写在viewDidLoad方法中,代码的可读性更强;每个控件分别负责各自的实例化处理,代码的耦合程度低;不必在初始化阶段加载所有数据,节省内存,也就是系统的内存占用率会减少;减少服务器端压力。

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来显示。
  • UIViewCALayer类似于UIView的子view树状结构,也可以向它的layer上添加子layer,来完成某些特殊的表示
objectivec 复制代码
    UIView *firstView = [[UIView alloc] init];
    firstView.frame = CGRectMake(200, 200, 200, 200);
    firstView.backgroundColor = [UIColor redColor];
    [self.view addSubview:firstView];
    
    CALayer *layer = [[CALayer alloc] init];
    layer.backgroundColor = [[UIColor greenColor] CGColor];
    layer.position = CGPointMake(100,100);  //中心点
    layer.bounds = CGRectMake(100,100,80,80);
    [firstView.layer addSublayer:layer];

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

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

UITableView

UITableView遵循的两个delegate以及必须实现的方法

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

dataSource

objectivec 复制代码
//返回组数
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView

//返回每组里的行数
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 

//cell的实现
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 

delegate

objectivec 复制代码
//返回每行高度
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath

//不必须实现,cell点击事件
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath

上述四个必须实现方法执行顺序

objectivec 复制代码
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    NSLog(@"numberOfSectionsInTableView");
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    NSLog(@"numberOfRowsInSection");
    return 1;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)
indexPath {
    UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];
    NSLog(@"cellForRowAtIndexPath");
    return cell;
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    NSLog(@"heightForRowAtIndexPath");
    return 20;
}

结果:

  • numberOfSectionsInTableView
  • numberOfRowsInSection
  • cellForRowAtIndexPath
  • heightForRowAtIndexPath

其他方法的执行顺序:

第一轮:

1、numberOfSectionsInTableView :假如section=2,此函数只执行一次,假如section=0,函数不执行,默认为1

2、heightForHeaderInSection ,执行两次,此函数执行次数为section数目

3、heightForFooterInSection ,函数属性同上,执行两次

4、numberOfRowsInSection ,此方法执行一次

5、heightForHeaderInSection ,此方法执行了两次,我其实有点困惑为什么这里还要调用这个方法

6、heightForFooterInSection ,此方法执行两次,

7、numberOfRowsInSection,执行一次

8、heightForRowAtIndexPath ,行高,先执行section=0,对应的row次数
第二轮:

1、numberOfSectionsInTableView ,一次

2、heightForHeaderInSection ,section次数

3、heightForFooterInSection ,section次数

4、numberOfRowsInSection ,一次

5、heightForHeaderInSection ,执行section次数

6、heightForFooterInSection,执行section次数

7、numberOfRowsInSection,执行一次

8、heightForRowAtIndexPath,行高,先执行一次

9、cellForRowAtIndexPath

10、willDisplayCell

然后8、9、10依次执行直到所有的cell被描画完毕

UICollectionView和UITableView的区别

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

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

objectivec 复制代码
@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。整体的逻辑,通过设置默认的layoutlayout中设置每一个cell对应的Attributes,然后将整个的layout赋值给collectionView

UICollectionViewFlowLayout和UICollectionViewLayout区别

  • UICollectionViewLayout是一个抽象类 一般的,抽象类只定义了一些子类公有的属性和行为,不能直接使用。
  • UICollectionViewFlowLayout是流水布局,UI控件会像流水一样,一行排满了自动下一行排。

UICollectionViewFlowLayout自定义时要重写的方法

objectivec 复制代码
-(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到其他地方)时,将重新计算需要的布局信息。

调用顺序:

objectivec 复制代码
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来生成更新后的布局。

手动计算行高的两种方式

  • sizeToFit
  • boundingRectWithSize

sizeToFit和sizeThatFit区别:

  • -(CGSize)sizeThatFits:(CGSize)size; 会返回一个最适合的 Size,但是不会改变原来视图的frame 的size
  • -(void)sizeToFit; 内部会调用 - (CGSize)sizeThatFits:(CGSize)size; 方法获取一个最适合的size, 并使用这个size 来调整当前视图的frame 的size.

简单的说:- (CGSize)sizeThatFits:(CGSize)size;- (void)sizeToFit; 都或获取一个最适合的Size, 但是- (CGSize)sizeThatFits:(CGSize)size; 不会改变原始图的frame,仅仅是返回一个size, 而- (void)sizeToFit; 会为你计算一个最合适的Size的同时,并根据这个Size来调整你当前视图的frame

boundingRectWithSize

返回文本绘制所占据的矩形空间。

objectivec 复制代码
CGRect rect=[(NSString *)obj boundingRectWithSize:CGSizeMake(1000, FONTHEIGHT) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:16]} context:nil].size.width;

里面的参数如下:

  • obj 是指要计算显示的字符串
  • boundingRectWithSize 表示计算的宽高限制
  • 计算高度时,需要宽度固定:CGSizeMake(1000, CGFLOAT_MAX)
  • 这里的1000也可以用已经确定的控件的宽度替代self.label.width
  • 计算结果表示在宽度最多为1000高度不限时,显示完全字符串需要的高度
  • 计算宽度时,需要高度固定:CGSizeMake(CGFLOAT_MAX, 200)
  • 同理200也可以用已知高度替换:self.label.height
  • 计算结果表示在高度不超过200时,将给定的字符串现实完全需要的宽度
  • options是文本绘制的附加选项,NSStringDrawingUsesLineFragmentOrigin 是默认基线
  • attributes字典格式,限定字符串显示的样式,一般限制字体较多,比如:@{NSFontAttributeName:[UIFont systemFontOfSize:16]}
  • context包括一些信息,例如如何调整字间距以及缩放。最终,该对象包含的信息将用于文本绘制。一般写nil。

ViewController生命周期

【iOS】------ ViewController生命周期

  • loadView:加载view。这个方法中,要正式加载View了,控制器 view 是通过懒加载的方式进行加载的,即用到的时候再加载。在 view 加载过程中首先会调用 loadView 方法,在这个方法中主要完成一些关键 view 的初始化工作。
  • viewWillAppear: 视图将要显示
  • viewWillLayoutSubviews:控制器的view将要布局子控件
  • viewDidLayoutSubviews: 控制器的view布局子控件完成
  • viewDidAppear: 视图已经显示
  • viewWillDisappear: 视图将要消失
  • viewDidDisappear: 视图已经消失

UIViewController和UIResponder

我们最熟悉的UIApplicationUIViewUIViewController这几个类是直接继承自UIResponderUIResponder类是专门用来响应用户的操作处理各种事件(UIEvent)的。

UIResponder提供了用户点击、按压检测(presses)以及手势检测(motion)的回调方法,分别对应用户开始、移动、结束以及取消,其中只有在程序强制退出或者来电时,取消事件才会调用。

UIControl

UIControl 建立在视图上,增加了更多的交互支持。最重要的是,它增加了 target / action 模式。看一下具体的子类,我们可以看一下按钮,日期选择器 (Date pickers),文本框等等。创建交互控件时,你通常想要子类化一个 UIControl。一些常见的像 bar buttons (虽然也支持 target / action) 和 textView (这里需要你使用代理来获得通知) 的类其实并不是 UIControl

UIResponder

UIResponderUIView 的父类。responder 能够处理触摸、手势、远程控制等事件。之所以它是一个单独的类而没有合并到 UIView 中,是因为 UIResponder 有更多的子类,最明显的就是 UIApplicationUIViewController。通过重写 UIResponder的方法,可以决定一个类是否可以成为第一响应者 (first responder),例如当前输入焦点元素。

touches (触摸) 或 motion (指一系列运动传感器) 等交互行为发生时,它们被发送给第一响应者 (通常是一个视图)。如果第一响应者没有处理,则该行为沿着响应链到达视图控制器,如果行为仍然没有被处理,则继续传递给应用。如果想监测晃动手势,可以根据需要在这3层中的任意位置处理。

UIResponder 还允许自定义输入方法,从 inputAccessoryView 向键盘添加辅助视图到使用 inputView 提供一个完全自定义的键盘。

frame和bounds

  • Frame: 视图的位置和大小使用是父视图的坐标系,所以将视图放置在父级中这一点就很重要。
  • Bounds:视图的位置和大小,使用的是其自己的坐标系,而对于这一点而言将视图的内容或子视图放置在其自身内很重要。

我们先来初始化一个View看看:

objectivec 复制代码
    UIView *firstView = [[UIView alloc] init];
    firstView.frame = CGRectMake(200, 200, 200, 200);
    firstView.backgroundColor = [UIColor redColor];
    [self.view addSubview:firstView];
    NSLog(@"frame = %@\n", NSStringFromCGRect(firstView.frame));
    NSLog(@"bounds = %@", NSStringFromCGRect(firstView.bounds));

然后我们给这个View旋转90度看看:

objectivec 复制代码
firstView.transform = CGAffineTransformMakeRotation(M_PI * 0.25);

可以看到Bounds仍然相同,但是Frame已经发生了更改。现在更容易看出framebounds之间的区别。

何时使用Frame,何时使用Bounds

  • 由于frame关联视图在其父视图中的位置,因此您在进行向外更改时会使用它,例如更改其宽度或查找视图与其父视图顶部之间的距离。
  • 使用bounds时,你正在向内变化,就像画的东西或视图中安排子视图。如果您对它进行了一些转换,还可以使用bounds来获取视图的大小。
相关推荐
叽哥15 分钟前
Flutter Riverpod上手指南
android·flutter·ios
大熊猫侯佩1 天前
桃花岛 Xcode 构建秘籍:Swift 中的 “Feature Flags” 心法
app·xcode·swift
悄然林静1 天前
Mac终端执行`brew doctor`报`openssl@1.1`警告
mac·xcode·apple
用户091 天前
SwiftUI Charts 函数绘图完全指南
ios·swiftui·swift
YungFan1 天前
iOS26适配指南之UIColor
ios·swift
权咚2 天前
阿权的开发经验小集
git·ios·xcode
用户092 天前
TipKit与CloudKit同步完全指南
ios·swift
小溪彼岸2 天前
macOS自带截图命令ScreenCapture
macos
法的空间2 天前
Flutter JsonToDart 支持 JsonSchema
android·flutter·ios
2501_915918412 天前
iOS 上架全流程指南 iOS 应用发布步骤、App Store 上架流程、uni-app 打包上传 ipa 与审核实战经验分享
android·ios·小程序·uni-app·cocoa·iphone·webview