iOS UI

999 阅读12分钟

UI界面学习路线及重难点(UIKit)

为了便于开发者打造各式各样的优秀App,UIKit 框架提供了非常多功能强大又易用的UI控件,如下只列出易忘的部分属性。

  • 基础控件:UIViewUILabelUIButtonUIImageView
  • 高级控件:UITableViewUICollectionView
  • 屏幕适配:AutolayoutSizeclassMasonry
  • 多控制器:UINavicationControllerUITabBarController、自定义控制器管理
  • Quartz2D、核心动画、事件处理、手势、UIDynamiC

基础控件

以下列举一些在开发中可能用得上的UI控件:
UIPageControl、UITextView、UIActivityIndicator、UISwitch、UIDatePicker、UIToolbar、UIProgressView、UISlider、UISegmentControl、UIPickerView、UITextField、WKWebView、UINavigationBar、UITabBar

小知识Tip:
UIBarButtonItem: 描述按钮的具体内容
UINavigationItem: 设置导航条上内容(左边,右边,中间)
UITabBarItem: 设置tabBar上按钮内容(UITabBarButton)

如下包含知识点:
UIView、UILabel、UIButton、UIImageView、图片的加载方式、设置毛玻璃效果、背景图片的拉伸、播放音效

UIView

@property(nonatomic) CGRect frame; // 在父控件中的位置尺寸(以父控件的左上角为坐标原点)
@property(nonatomic) CGRect bounds; // 该控件的位置尺寸(以自己左上角为坐标原点bounds的x,y一般为0)
@property(nonatomic) CGPoint center; // 控件中心点位置(以父控件的左上角为坐标原点)

UILabel

@property(nonatomic) NSLineBreakMode lineBreakMode; // 换行模式
@property(nonatomic) UIColor shadowColor; // 设置阴影所需
@property(nonatomic) CGSize shadowOffset; // 设置阴影所需

UIButton

@property(nonatomic) UIEdgeInsets contentEdgeInsets; // 设置内容 内边距
@property(nonatomic) UIEdgeInsets imageEdgeInsets; // 设置图片 内边距
@property(nonatomic) UIEdgeInsets titleEdgeInsets; // 设置标题 内边距
@property(nonatomic) UIControlContentVerticalAlignment contentVerticalAlignment; // 控制按钮内部的子控件对齐,不是用contentMode,用该属性及contentHorizontalAlignment

UIImageView

@property(nonatomic,copy) NSArray *animationImages; // 显示的动画图片
@property(nonatomic) NSTimeInterval animationDuration; // 动画图片的持续时间
@property(nonatomic) NSInteger animationRepeatCount; // 动画的播放次数。默认0,代表无限播放

- (void)startAnimating; // 开始动画
- (BOOL)isAnimating; // 是否正在执行动画

图片的加载方式

  • imageNamed:
    • 就算指向它的指针被销毁,该资源也不会从内存中干掉
    • 放到Assets.xcassets的图片,默认就有缓存
    • 如果图片较小,并且频繁使用的图片,建议使用
  • imageWithContentOfFile:
    • 指向它的指针被销毁,该资源会被从内存中干掉
    • 如果图片较大,并且使用次数较少,建议使用

设置毛玻璃效果

UIImageView *imageView = [[UIImageView alloc] initWithFrame:self.view.bounds];
imageView.image = [UIImage imageNamed:@"image_name"];
imageView.contentMode = UIViewContentModeScaleAspectFill;
// 加毛玻璃效果
UIToolBar *toolBar = [[UIToolBar alloc] initWithFrame:imageView.bounds];
// 设置毛玻璃样式
toolBar.barStyle = UIBarStyleBlack;
[imageView addSubview:toolBar];

背景图片的拉伸

UIImage *image = [UIImage imageNamed:@"chat_send"]; // 创建UIImage对象
CGFloat imageWidth = image.size.width;      // 获取image宽度
CGFloat imageHeight = image.size.height;    // 获取image高度
/** 
 * 返回一张受保护且拉伸的图片 ---> CapInsets: 哪些地方要保护
 * UIImageResizingModeTile: 平铺
 * UIImageResizingModeStretch: 拉伸(伸缩)
 */
UIImage *resizableImage = [image resizableImageWithCapInsets:
                                 UIEdgeInsetsMake(imageHeight*0.5, imageWidth*0.5, imageHeight*0.5-1, imageWidth*0.5-1) 
                                 resizingMode:UIImageResizingModeTile];
// 方式二,简洁
// [image stretchableImageWithLeftCapWidth:imageWidth*0.5 topCapHeight:imageHeight*0.5];
[self.button setBackgroundImage:resizableImage forState:UIControlStateNormal];

播放音效

@property (nonatomic, strong) AVPlayer *player;         // 播放器

self.player = [[AVPlayer alloc] init];                  // 创建播放器
NSURL *url = [[NSBundle mainBundle] URLForResource:@"song.mp3" withExtension:nil];
AVPlayerItem *playerItem = [[AVPlayerItem alloc] initWithURL:url];
[self.player replaceCurrentItemWithPlayerItem:playerItem]; // 切换歌曲
[self.player play];         // 播放
self.player.rate = 2.0;     // 调节速率

UI控件的weak和strong,weak和assign

  1. weak
    1> OC对象
  2. assign
    1> 基本数据类型
    2> OC对象
  3. strong
    1> OC对象
  4. copy
    1> NSString *
    2> block
  5. Q:使用weakassign修饰OC对象的区别
    1> 成员变量
    1)weak生成的成员变量是用__weak修饰的,比如Cat * __weak _cat;
    2) assign生成的成员变量是用__unsafe_unretained修饰的Cat * __unsafe_unretained _cat;
    2> __weak__unsafe_unretained
    1) 都不是强指针(不是强引用),不能保住对象的命
    2) __weak:所指向的对象销毁后,会自动变成nil指针(空指针),不再指向已经销毁的对象
    3) __unsafe_unretained:所指向的对象销毁后,仍旧指向已经销毁的对象

高级控件

UIScrollView

小知识Tip:
// 设置是否允许自动修改 UIScrollView 内边距
@property(nonatomic,assign) BOOL automaticallyAdjustsScrollViewInsets; //ios(7.0,11.0)
@property(nonatomic) UIScrollViewContentInsetAdjustmentBehavior contentInsetAdjustmentBehavior //ios(11.0)
@property(nonatomic) BOOL automaticallyAdjustsScrollIndicatorInsets; //ios(13.0)

@property(nonatomic) BOOL bounces; // 设置UIScrollView是否需要弹簧效果
@property(nonatomic) BOOL alwaysBounceVertical; // 不管是否设置contentSize总是有弹簧效果(下拉刷新)
@property(nonatomic) BOOL pagingEnabled; // 开启分页功能

// UIPageControl 
pageControl.numberOfPages;  // 设置总页数
pageControl.currentPage = page; // 当前页码
pageControl.hidesForSinglePage = YES;   // 单页的时候是否隐藏

// NSTimer
@property(nonatomic, weak) NSTimer *timer;
- (void)startTimer {
    // 返回一个自动执行的定时器对象
    self.timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(nextPage:) userInfo:nil repeats:YES];   
    /**
     * 目的:不管主线程在做什么操作,都会分配一定的时间处理定时器
     * NSDefaultRunLoopMode(默认):同一时间只能执行一个任务
     * NSRunLoopCommonModes(公用):可以分配一定的时间执行其他任务
     */
    [[NSRunLoop mainRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
}
- (void)stopTimer {
    [self.timer invalidate];
    self.timer = nil;
}

UITableView

1、性能优化-cell重用; 2、左滑删除; 3、编辑模式,批量删除
UITableViewCell自动高度
UITableViewCell自动高度卡顿优化

UICollectionView

UICollectionView 基本使用

  1. 创建 UICollectionViewLayout,使用系统默认流式布局,或自定义布局;
  2. 创建 UICollectionView,设置 delegatedatasource,注册 cell 类型;
  3. 选择实现 UICollectionViewDataSource 中方法,行数、cell 复⽤;
  4. 选择实现 UICollectionViewDelegate 方法(点击、滚动等);

UICollectionViewFlowLayout 流式布局,每行排满后自动换行; 关键熟悉如下:

//一个section有很多行item,这个属性表示最小行距,默认值不是0(比如第一行和第二行item的行距)
@property (nonatomic) CGFloat minimumLineSpacing;
//这个属性表示两个item之间的最小间距,默认值不是0;(⽤来计算一行可以布局多少个 item)
@property (nonatomic) CGFloat minimumInteritemSpacing;
//这个属性表示section的内边距,上下左右的留边
@property (nonatomic) UIEdgeInsets sectionInset;

屏幕适配

手机机型
(iPhone)
设备分辨率
(px)
逻辑分辨率
(pt) - (ppi)
屏幕尺寸英寸
(inch)
像素图缩放因子
(Scale Factor)
2023 ↑2023 ↑2023 ↑2023 ↑2023 ↑
iPhone14 Pro Max1290x2796430x932 - 4606.7@3x
iPhone14 Pro1179x2556393x852 - 4606.1@3x
12/13 ProMax、14Plus1284x2778428x926 - 4586.7@3x
12/13/14、12/13 Pro1170x2532390x844 - 4606.1@3x
12mini/13mini1080x2340360x780 - 4765.4@3x
Xs Max/11 ProMax1242x2688414x896 - 4586.5@3x
XR/11828x1796414x896 - 3266.1@2x
X/Xs/11Pro1125x2436375x812 - 4585.8@3x
6p/6sp/7p/8p Plus1242x2208414x736 - 4015.5@3x
6/6s/7/8/SE2/SE3750x1334375x667 - 3264.7@2x
iPhone5/5s/5c/SE1640x1136320x568 - 3264@2x
iPhone4/4s640x960320x480 - 3263.5@1x
iPhone2G/3G/3GS320x480320x480 - 1633.5@1x

iPhone设备默认指令集

CPU架构

Autolayout

核心计算公式:obj1.property1 = (obj2.property2 * multiplier) + constant value

  • 约束:通过给控件添加约束,来决定控件的位置和尺寸
  • 参照:添加约束时,依照谁来添加(可以是父控件或兄弟控件)

添加约束的规则

  • 在创建约束之后,需要将其添加到作用的view上
  • 对于两个同层级view之间的约束关系,添加到它们的父view上
  • 对于两个不同层级view之间的约束关系,添加到它们最近的共同父view上
// 例如:
tempView.translatesAutoresizingMaskIntoConstraints = NO;    // 禁止autoresizing自动转为约束
// 宽度约束-这里只列出一项
NSLayoutConstraint *wlcs = [NSLayoutConstraint constraintWithItem:tempView 
                                        attribute:NSLayoutAttributeWidth 
                                        relatedBy:NSLayoutRelationEqual 
                                        toItem:nil 
                                        attribute:NSLayoutAttributeNotAnAttribute multiplier:0.0 constant:100];
[tempView addConstraint:wlcs];

使用VFL语言,可视化格式语言

  • H:[cancelButton(72)]-12-[acceptButton(50)]
    • cancelButton宽72,acceptButton宽50,它们之间间距12
  • H:[wideView(>=60@700)]
    • wideView宽度大于等于60,该约束条件优先级为700
  • V:[redBox][yellowBox(==redBox)]
    • 竖直方向上,先有一个redBox,其下方紧接一个高度等于redBox高度的yellowBox
  • H:|-10-[Find]-[FindNext]-[FindField(>=20)]-|
    • 水平方向上,Find距离父view左边缘默认间隔宽度,之后FindNext距离Find间隔默认宽度,再之后是宽度不小于20的FindField,它和FindNext以及父view右边缘的间隔都是默认宽度。(竖线表示supperview的边缘)
// 例如:
// 水平方向
NSDictionary *views = NSDictionaryOfVariableBindings(redView, blueView);
NSDictionary *metrics = @{@"space" : @30};
NSString *hvfl = @"H:|-space-[blueView]-space-[redView(==blueView)]-space-|"
NSArray *hlcs = [NSLayoutConstraint constraintsWithVisualFormat:hvfl options:NSLayoutFormatAlignAllTop | NSLayoutFormatAlignAllBottom metrics:metrics views:views];
[self.view addConstraints:hlcs];
// 垂直方向
NSString *vvfl = @"V:[blueView(50)]-space-|";
NSArray *vlcs = [NSLayoutConstraint constraintsWithVisualFormat:vvfl options:KnilOptions metrics:metrics views:views];
[self.view addConstraints:vlcs];

Sizeclass

TODO

Masonry

https://github.com/SnapKit/Masonry

WebView

  • WebKit 框架
    • WebKit 是一个开源的 Web 浏览器引擎。
    • WebKit.framework 就是在 WebCore、底层桥接、JSCore 引擎等核心模块的基础上,针对iOS平台的项目封装。
  • 基本的加载
    • 通过 configuration 进行基本设置(例: 基本的共享 Cookie 设置、基础偏好设置、播放视频设置、默认 JS 注入)
    • 加载 URL & HTML
    • 类比之前的 UIKit 提供基础的功能,在 delegate 中处理业务逻辑
- (instancetype)initWithFrame:(CGRect)frame configuration:(WKWebViewConfiguration *)configuration;
- (nullable WKNavigation *)loadRequest:(NSURLRequest *)request;
- (nullable WKNavigation *)loadHTMLString:(NSString *)string baseURL:(nullable NSURL *)baseURL;

WKWebView Delegates

  • WKNavigationDelegate

    • decidePolicyForNavigationAction 是否加载请求 (scheme 拦截、特殊逻辑、JS 和 Native 通信)
    • didFinishNavigation webView 完成加载(业务逻辑)
    • didFailNavigation webView 加载失败 (loadingView展示,重试按钮等)
    • webViewWebContentProcessDidTerminate webView Crash 回调 (⾃自动重新加载)
  • WKUIDelegate

    • runJavaScriptAlertPanelWithMessage
    • runJavaScriptConfirmPanelWithMessage
    • runJavaScriptTextInputPanelWithPrompt 处理理 alert( ) / confirm( ) / prompt( ) 自定义样式

UIApplication

UIApplication对象是应用程序的象征,每一个应用都有自己的UIApplication对象,而且是单例的,一个iOS程序启动后创建的第一个对象就是UIApplication对象,利用UIApplication对象,能进行一些应用级的操作。如下列出常用属性:

  • @property(nonatomic) NSInteger applicationIconBadgeNumber; //设置程序图标右上角的红色提醒数字
  • @property(nonatomic) BOOL networkActivityIndicatorVisible; //设置联网指示器的可见性
  • 以及统一设置状态栏,也可由UIViewController管理,如下
    • - (UIStatusBarStyle)preferredStatusBarStyle; //状态栏样式
    • - (BOOL)prefersStatusBarHidden; //状态栏的可见性

UIApplicationDelegate
iOS程序的启动过程:

  • application:didFinishLaunchingWithOptions: 程序加载完毕
  • applicationDidBecomeActive: 程序获取焦点
  • applicationDidEnterBackground: 程序进入后台
  • applicationWillResignActive: 程序失去焦点
  • applicationWillEnterForeground: 程序从后台回到前台
  • applicationDidReceiveMemoryWarning: 内存警告,可能要终止程序
  • applicationWillTerminate: 程序即将退出

控制器管理

UINavicationController

TODO

UITabBarController

TODO

Quartz2D、核心动画、事件处理、手势、UIDynamiC

transform

// transform 形变 - 平移操作
self.imageV.transform = CGAffineTransformTranslate(self.imageV.transform, 0, 100); // 相对上一次做形变
// transform 形变 - 旋转操作
self.imageV.transform = CGAffineTransformRotate(self.imageV.transform, M_PI_4);
// transform 形变 - 缩放操作
self.imageV.transform = CGAffineTransformScale(self.imageV.transform, 0.8, 0.8);

事件

/**
 * iOS中事件可分为3大类型
 * 1.触摸事件
 * 2.加速计事件
 * 3.远程控制事件
 * 只有继承了UIResponder对象才能接收并处理事件,我们称之为“响应者对象”;UIResponder提供了如下方法:
 */
 // 触摸事件
 - (void)touchesBegin:(NSSet *)touches withEvent:(UIEvent *)event;  // 开始触摸view
 - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;  // 手指在view上移动
 - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;  // 手指离开view
 - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;  // 触摸结束前,某个系统事件(电话呼入)打断
 // 加速计事件
 - (void)motionBegin:(UIEventSubtype)motion withEvent:(UIEvent *)event;
 - (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event;
 - (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event;
 // 远程控制事件
 - (void)remoteControlReceivedWithEvent:(UIEvent *)event;

UITouch

@property (nonatomic, readonly, retain) UIWindow *window; // 触摸产生时所处的窗口
@property (nonatomic, readonly, retain) UIView *view; // 触摸产生时所处的视图
@property (nonatomic, readonly) NSUInteger tapCount; // 短时间内点按屏幕的次数,可根据tapCount判断单击、双击等
@property (nonatomic, readonly) NSTimeInterval timestamp; // 记录了触摸事件产生或变化时的时间,单位是秒

- (CGPoint)locationInView:(UIView *)view; // 返回值表示触摸在view上的位置
- (CGPoint)previousLocationView:(UIView *)view; // 该方法记录了前一个触摸点的位置

事件的产生和传递

  • 发生触摸事件后,系统会将该事件加入到一个由 UIApplicationMain 管理的事件 队列
  • UIApplicationMain 会从事件队列中取出最前面的事件,并将事件分发下去处理,通常先发送给主窗口(keyWindow)
  • 主窗口会在视图层次结构中找到一个最合适的视图来处理触摸事件,这也是整个事件处理过程的第一步
  • 找到合适的视图控件后,就会调用视图控件的touches方法来做具体的事件处理
// 作用:去寻找最适合的view; 何时调用:当一个事件传递给当前view时调用
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
}
/**
 作用:判断当前点在不在它调用view(谁调用pointInside,这个view就是谁)
 何时调用:它是在hitTest方法中调用的
 */
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
}

事件传递的完整过程

  1. 先将事件对象由上往下传递(由父控件传递给子控件),找到最合适的控件来处理这个事件
  2. 调用最合适控件的 touches... 方法
  3. 如果调用了 [supper touches...] 就会将事件顺着响应者链条往上传递,传递给上一个响应者
  4. 接着就会调用上一个响应者的 touches... 方法

手势

  • UIGestureRecognizer 是一个抽象类,定义了所有手势的基本行为,使用它的子类才能处理具体手势
    • UITapGestureRecognizer 敲击
    • UIPinchGestureRecognizer 捏合,用于缩放
    • UIPanGestureRecognizer 拖拽
    • UISwipeGestureRecognizer 轻扫
    • UIRotationGestureRecognizer 旋转
    • UILongGestureRecognizer 长按

Quartz2D

Quartz2D是一个二维绘图引擎,同时支持iOS和Mac系统,能完成如下工作:

  • 绘制图形 (线条/三角形/矩形/圆/弧)等
  • 绘制文字
  • 绘制/生成图片(图像)
  • 读取/生成PDF
  • 截图/裁剪图片
  • 自定义UI控件(涂鸦/画板)
  • ……

** 图形上下文(Graphics Context) 类型为CGContextRef **

  • 作用
    • 保存绘图信息,绘图状态
    • 决定绘制的输出目标(绘制到什么地方去?输出目标可以是PDF文件、Bitmap或者显示器的窗口上)
    • 相同的一套绘图序列,指定不同的图形上下文,就可将相同的图像绘制到不同的目标
  • Quartz2D提供了以下几种类型的Graphics Context
    • Bitmap Graphics Context
    • PDF Graphics Context
    • Window Graphics Context
    • Layer Graphics Context
    • Printer Graphics Context

案例-自定义view

  • 新建类继承UIView,实现- (void)drawRect:(CGRect)rect方法,在该方法中
    • 取得跟当前view相关联的图形上下文
    • 绘制相应的图形内容
    • 利用图形上下文将绘制的所有内容渲染显示到view上面

小知识Tip:

  • 为什么要实现drawRect:方法才能绘制到view上?
    • 因为在drawRect:方法中才能取得跟view相关联的图形上下文
  • drawRect:方法在什么时候被调用?
    • 当view第一次显示到屏幕时(被加到UIWindow上显示出来)
    • 调用view的setNeedsDisplay或者setNeedsDisplayInRect:时

高阶动画

TODO