原创
最近更新: 2022/04/22 06:09

objective-C入门 之 UI基础(二)

iOS开发文档 - UIKit

视觉显示与绘图工具

ios核心动画高级技巧

图层 CALayer

CALayer 一般是作为 view 的属性出现,用于管理 view 的具体视觉显示效果。一般来说,简单的显示效果,如背景颜色等,可以直接在 view 上设置,而图层操作可以实现视觉效果的精细控制。

//创建图层
+ (instancetype)layer;
- (instancetype)init;
- (instancetype)initWithLayer:(id)layer;
//获取当前显示的图层
- (instancetype)presentationLayer;

//内容属性,默认为 nil ,用于存放图层显示的内容
//虽然是id类型,但只有 CGImage 和 NSImage 对象生效,其他情况显示白板
//如果存在复杂的 view 或图层操作,则应避免设置这个属性
@property(strong) id contents;
@property CGRect contentsRect;
@property CGRect contentsCenter;

//管理图层的生命周期
@property(weak) id<CALayerDelegate> delegate;
- (void)drawInContext:(CGContextRef)ctx;//绘制时调用
- (void)display;//加载图层内容,会调用delegate的displayLayer:方法,没实现则调用自己的drawInContext:方法

//显示属性
@property float opacity;
@property(getter=isHidden) BOOL hidden;
@property CGColorRef backgroundColor;
@property BOOL masksToBounds;					//超出范围的子图层是否隐藏
@property(strong) __kindof CALayer *mask;	//该属性指向的图层与原图层的重叠部分不显示,受透明度影响,用于图层镂空
@property(getter=isDoubleSided) BOOL doubleSided;//图层旋转时背面是否显示
@property CGFloat cornerRadius;					//圆角半径,可以和masksToBounds属性配合使用
@property CACornerMask maskedCorners;		//选择哪几个角是圆角
//描边属性
@property CGFloat borderWidth;
@property CGColorRef borderColor;
//阴影属性
@property float shadowOpacity;
@property CGFloat shadowRadius;
@property CGSize shadowOffset;
@property CGColorRef shadowColor;
@property CGPathRef shadowPath;		//阴影形状

//位置属性
@property CGRect frame;
@property CGRect bounds;
@property CGPoint position;	//frame的origin部分
@property CGFloat zPosition;//z轴位置,影响图层的层叠顺序

//变换
@property CATransform3D transform;
@property CATransform3D sublayerTransform;
//获取和设置仿射变换的(平移、旋转、缩放)的值
- (CGAffineTransform)affineTransform;
- (void)setAffineTransform:(CGAffineTransform)m;

//子图层相关
@property(copy) NSArray<__kindof CALayer *> *sublayers;
@property(readonly) CALayer *superlayer;
- (void)addSublayer:(CALayer *)layer;
- (void)removeFromSuperlayer;
- (void)insertSublayer:(CALayer *)layer atIndex:(unsigned int)idx;
- (void)insertSublayer:(CALayer *)layer below:(CALayer *)sibling;
- (void)insertSublayer:(CALayer *)layer above:(CALayer *)sibling;
- (void)replaceSublayer:(CALayer *)oldLayer with:(CALayer *)newLayer;
//在图层数组中操作建议配合名字属性
@property(copy) NSString *name;

//更新图层
- (void)setNeedsDisplay;		//将图层设定为需要更新
- (void)setNeedsDisplayInRect:(CGRect)r;			//将图层的指定部位设定为需要更新
@property BOOL needsDisplayOnBoundsChange;	//当bounds改变时,系统自动调用setNeedsDisplay方法
- (void)displayIfNeeded;		//强制更新,不推荐
- (BOOL)needsDisplay;			//返回图层是否需要更新

//子图层管理
@property(strong) id<CALayoutManager> layoutManager;
- (void)setNeedsLayout;		//图层的bounds或者子图层改变时自动调用,会在下一个循环中调用layoutSublayers方法
- (void)layoutSublayers;	//会调用layoutManager的layoutSublayersOfLayer:方法
- (void)layoutIfNeeded;		//该方法调用后,会回溯祖先直到不需要重绘的图层,然后进行重绘
- (BOOL)needsLayout;

//动画相关方法
- (void)addAnimation:(CAAnimation *)anim forKey:(NSString *)key;
- (__kindof CAAnimation *)animationForKey:(NSString *)key;
- (void)removeAllAnimations;
- (void)removeAnimationForKey:(NSString *)key;
- (NSArray<NSString *> *)animationKeys;

贝塞尔曲线 UIBezierPath

用于绘制直线、曲线、圆、矩形等简单图形,也可以定义包含直线段和曲线段的复杂多边形。

本质上是绘制点在屏幕上的运动轨迹,同时可以附以填充等效果。

//初始化
+ (instancetype)bezierPath;
+ (instancetype)bezierPathWithRect:(CGRect)rect;
+ (instancetype)bezierPathWithOvalInRect:(CGRect)rect;
//圆角矩形
+ (instancetype)bezierPathWithRoundedRect:(CGRect)rect 
                             cornerRadius:(CGFloat)cornerRadius;
//部分圆角的矩形
+ (instancetype)bezierPathWithRoundedRect:(CGRect)rect 
                        byRoundingCorners:(UIRectCorner)corners 
                              cornerRadii:(CGSize)cornerRadii;
//圆弧
+ (instancetype)bezierPathWithArcCenter:(CGPoint)center 
                                 radius:(CGFloat)radius 
                             startAngle:(CGFloat)startAngle 
                               endAngle:(CGFloat)endAngle 
                              clockwise:(BOOL)clockwise;
+ (instancetype)bezierPathWithCGPath:(CGPathRef)CGPath;
//创建一个现存Path的反向Path
- (UIBezierPath *)bezierPathByReversingPath;

//绘图控制
- (void)moveToPoint:(CGPoint)point;		//移动当前点
- (void)addLineToPoint:(CGPoint)point;	//连接当前点与目标点
//添加圆弧
- (void)addArcWithCenter:(CGPoint)center
                  radius:(CGFloat)radius
              startAngle:(CGFloat)startAngle
                endAngle:(CGFloat)endAngle
               clockwise:(BOOL)clockwise;
//添加曲线,效果参考开发手册
- (void)addCurveToPoint:(CGPoint)endPoint 
          controlPoint1:(CGPoint)controlPoint1 
          controlPoint2:(CGPoint)controlPoint2;
//关闭路径,即连接路径的首尾
- (void)closePath;
- (void)removeAllPoints;
//添加多条路径
- (void)appendPath:(UIBezierPath *)bezierPath;

//获取当前状态
@property(nonatomic) CGPathRef CGPath;
- (CGPathRef)CGPath;
@property(nonatomic, readonly) CGPoint currentPoint;

//视觉效果
@property(nonatomic) CGFloat lineWidth;
@property(nonatomic) CGFloat flatness;	//曲线的平滑度
//虚线效果
- (void)setLineDash:(const CGFloat *)pattern
              count:(NSInteger)count
              phase:(CGFloat)phase;
- (void)getLineDash:(CGFloat *)pattern
              count:(NSInteger *)count
              phase:(CGFloat *)phase;

//渲染方法
- (void)fill;		//填充,默认会关闭路径
- (void)stroke;	//描边

- (void)applyTransform:(CGAffineTransform)transform;

文本处理相关控件

文本 UILabel

用于呈现简单文本。

//基本属性
@property(nullable, nonatomic, copy) NSString *text;
@property(nonatomic, strong) UIFont *font;
@property(nonatomic, strong) UIColor *textColor;
@property(nonatomic) NSTextAlignment textAlignment;
@property(nonatomic) NSLineBreakMode lineBreakMode; //文本超出控件范围时的显示效果
@property(nonatomic, getter=isEnabled) BOOL enabled;//是否显示文本
@property(nonatomic) BOOL adjustsFontSizeToFitWidth;//字体大小适应宽度
@property(nonatomic) CGFloat minimumScaleFactor;    //与上面的属性配合,最小缩放大小
@property(nonatomic) NSInteger numberOfLines;       //最大显示行数
@property(nullable, nonatomic, strong) UIColor *highlightedTextColor;
@property(nonatomic, getter=isHighlighted) BOOL highlighted;
@property(nullable, nonatomic, strong) UIColor *shadowColor;
@property(nonatomic) CGSize shadowOffset;

//获取控件大小,自适应行数
- (CGRect)textRectForBounds:(CGRect)bounds limitedToNumberOfLines:(NSInteger)numberOfLines;
- (void)drawTextInRect:(CGRect)rect;
@property(nonatomic) BOOL clipsToBounds;

输入框 UITextField

//委托,用于管理输入行为的生命周期
@property(nullable, nonatomic, weak) id<UITextFieldDelegate> delegate;
//相关通知
const NSNotificationName UITextFieldTextDidBeginEditingNotification;
const NSNotificationName UITextFieldTextDidChangeNotification;
const NSNotificationName UITextFieldTextDidEndEditingNotification;

//视觉属性
@property(nullable, nonatomic, copy) NSString *text;
@property(nullable, nonatomic, copy) NSString *placeholder;//未输入任何字符时的占位符
@property(nullable, nonatomic, strong) UIFont *font;
@property(nullable, nonatomic, strong) UIColor *textColor;
@property(nonatomic) NSTextAlignment textAlignment;
@property(nonatomic) UITextBorderStyle borderStyle;
@property(nullable, nonatomic, strong) UIImage *background;
@property(nullable, nonatomic, strong) UIImage *disabledBackground;
@property(nonatomic) UITextFieldViewMode clearButtonMode;   //显示[x]清除图标的时机
@property(nullable, nonatomic, strong) UIView *leftView;
@property(nonatomic) UITextFieldViewMode leftViewMode;
@property(nullable, nonatomic, strong) UIView *rightView;
@property(nonatomic) UITextFieldViewMode rightViewMode;
//字体大小自适应
@property(nonatomic) BOOL adjustsFontSizeToFitWidth;
@property(nonatomic) CGFloat minimumFontSize;

//编辑相关属性
@property(nonatomic, readonly, getter=isEditing) BOOL editing;
@property(nonatomic) BOOL clearsOnBeginEditing;         //编辑文本前清空内容,相关委托方法可以覆盖该属性
@property(nonatomic) BOOL clearsOnInsertion;
@property(nonatomic) BOOL allowsEditingTextAttributes;  //是否允许改变文本样式

//尺寸属性方法,获取不同子控件的尺寸
- (CGRect)textRectForBounds:(CGRect)bounds;
- (void)drawTextInRect:(CGRect)rect;
- (CGRect)placeholderRectForBounds:(CGRect)bounds;
- (void)drawPlaceholderInRect:(CGRect)rect;
- (CGRect)borderRectForBounds:(CGRect)bounds;
- (CGRect)editingRectForBounds:(CGRect)bounds;
- (CGRect)clearButtonRectForBounds:(CGRect)bounds;
- (CGRect)leftViewRectForBounds:(CGRect)bounds;
- (CGRect)rightViewRectForBounds:(CGRect)bounds;

//自定义样式,大多数时候没必要
@property(nullable, readwrite, strong) UIView *inputView;
@property(nullable, readwrite, strong) UIView *inputAccessoryView;

页面路由控制器

UINavgationController

导航控制器,可以实现页面的管理

//初始化
- (instancetype)initWithRootViewController:(UIViewController *)rootViewController;
- (instancetype)initWithNavigationBarClass:(Class)navigationBarClass 
                              toolbarClass:(Class)toolbarClass;

//委托,用于控制导航栏的生命周期和动画属性
@property(nullable, nonatomic, weak) id<UINavigationControllerDelegate> delegate;

//viewController控制
@property(nullable, nonatomic, readonly, strong) UIViewController *topViewController;
@property(nullable, nonatomic, readonly, strong) UIViewController *visibleViewController;
@property(nonatomic, copy) NSArray<__kindof UIViewController *> *viewControllers;//所有视图
- (void)setViewControllers:(NSArray<UIViewController *> *)viewControllers 
                  animated:(BOOL)animated;  //替换视图
//页面跳转相关
- (void)pushViewController:(UIViewController *)viewController 
                  animated:(BOOL)animated;
- (UIViewController *)popViewControllerAnimated:(BOOL)animated;
- (NSArray<__kindof UIViewController *> *)popToRootViewControllerAnimated:(BOOL)animated;
- (NSArray<__kindof UIViewController *> *)popToViewController:(UIViewController *)viewController 
                                                     animated:(BOOL)animated;
@property(nullable, nonatomic, readonly) UIGestureRecognizer *interactivePopGestureRecognizer;//弹出页面的手势

//顶部导航栏工具条操作
@property(nonatomic, readonly) UINavigationBar *navigationBar;//顶部导航栏,可以设置外观和控件
@property(nonatomic, readwrite, assign) BOOL hidesBarsOnTap;
@property(nonatomic, readwrite, assign) BOOL hidesBarsOnSwipe;
@property(nonatomic, readwrite, assign) BOOL hidesBarsWhenVerticallyCompact;
@property(nonatomic, readwrite, assign) BOOL hidesBarsWhenKeyboardAppears;
@property(nonatomic, getter=isNavigationBarHidden) BOOL navigationBarHidden;
- (void)setNavigationBarHidden:(BOOL)hidden animated:(BOOL)animated;
@property(nonatomic, readonly, assign) UITapGestureRecognizer *barHideOnTapGestureRecognizer;
@property(nonatomic, readonly, strong) UIPanGestureRecognizer *barHideOnSwipeGestureRecognizer;

UITabBarController

标签控制器,可以实现页面管理

@property(nonatomic, readonly) UITabBar *tabBar;    //底部的标签栏
@property(nullable, nonatomic, weak) id<UITabBarControllerDelegate> delegate;
//页面控制
@property(nullable, nonatomic, copy) NSArray<__kindof UIViewController *> *viewControllers;
- (void)setViewControllers:(NSArray<__kindof UIViewController *> *)viewControllers
                  animated:(BOOL)animated;

//可以对VC进行重排序
@property(nullable, nonatomic, copy) NSArray<__kindof UIViewController *> *customizableViewControllers;
@property(nullable, nonatomic, assign) __kindof UIViewController *selectedViewController;
@property(nonatomic) NSUInteger selectedIndex;

UITabBarControllerDelegate

- (BOOL)tabBarController:(UITabBarController *)tabBarController
shouldSelectViewController:(UIViewController *)viewController;
- (void)tabBarController:(UITabBarController *)tabBarController
 didSelectViewController:(UIViewController *)viewController;

用户交互相关

响应链与传递链

iOS中事件的响应链和传递链

传递链

由用户交互触发,由系统向离用户最近的view传递,目的是找出第一响应者(First Responder)。

UIApplication –> window –> root view –> … –> lowest view

流程描述:

1、 用户点击屏幕产生触摸事件

2、 系统将这个事件加入到一个由 UIApplication 管理的事件队列中

3、 UIApplication 会从消息队列里取事件分发下去,首先传给 UIWindow

4、 UIWindow 调用 hitTest:withEvent: 方法返回一个最终响应的视图

- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;
  • hitTest:withEvent: 方法中会调用 pointInside:withEvent: 方法去判断当前点击的point是否在UIWindow范围内,如果是的话,就会去遍历它的子视图来查找最终响应的子视图
- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event;
  • 遍历的方式是倒序,也就是说最后添加的子视图会最先遍历,在每一个视图中都会去调用hitTest:withEvent: 方法,可以理解为是一个递归调用

5、 如果最终的返回视图有值,那么这个视图就作为响应视图,结束整个事件传递;如果没有值,那么就会将 UIWindow 作为响应者。

响应链

系统找到第一响应者后,开始处理事件,从接收响应的 view 开始一级一级向上传递,直到事件被处理。

First Responder –> super view –> … –> view controller –> window –> Application –> AppDelegate

如果到最后都没有能够处理事件的组件,则丢弃这一事件。


UIResponder

OC的一个重要基类,是 UIApplication、UIViewController、UIView的父类,定义了各种用户交互相关的方法,是用户操作交互的直接对象。

//获取响应链上的下一个responder
//对于一个控件来说,这个属性意味着获取它的viewController或者superView,是一种不用设置代理访问controller的方法。
//这个方法可以重写,自定义返回的控件
@property(nonatomic, readonly, nullable) UIResponder *nextResponder;

//设置或撤销第一响应者的相关方法,主要用于输入框获取焦点
@property(nonatomic, readonly) BOOL isFirstResponder;
@property(nonatomic, readonly) BOOL canBecomeFirstResponder;
- (BOOL)becomeFirstResponder;
@property(nonatomic, readonly) BOOL canResignFirstResponder;
- (BOOL)resignFirstResponder;

//点击事件,直接重写相关方法即可,系统会自动调用
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event;
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event;
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event;
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event;//点击事件意外取消的情况(如电话)

//晃动事件或者远程控制(线控等)事件
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event;
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event;
- (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event;

//按键事件
- (void)pressesBegan:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event;
- (void)pressesChanged:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event;
- (void)pressesEnded:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event;
- (void)pressesCancelled:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event;

//键盘,当需要自定义键盘时重写这个view
@property(nullable, nonatomic, readonly, strong) __kindof UIView *inputView;
@property(nullable, nonatomic, readonly, strong) UIInputViewController *inputViewController;
//键盘顶上的小横条
@property(nullable, nonatomic, readonly, strong) __kindof UIView *inputAccessoryView;
@property(nullable, nonatomic, readonly, strong) UIInputViewController *inputAccessoryViewController;
- (void)reloadInputViews;

//键盘通知相关的常量,配合NotificationCenter使用
//键盘通知事件
const NSNotificationName UIKeyboardWillChangeFrameNotification;
const NSNotificationName UIKeyboardDidChangeFrameNotification;
const NSNotificationName UIKeyboardWillHideNotification;
const NSNotificationName UIKeyboardDidHideNotification;
const NSNotificationName UIKeyboardWillShowNotification;
const NSNotificationName UIKeyboardDidShowNotification;
//键盘事件通知信息dict的key
NSString *const UIKeyboardAnimationCurveUserInfoKey;
NSString *const UIKeyboardAnimationDurationUserInfoKey;
NSString *const UIKeyboardFrameBeginUserInfoKey;
NSString *const UIKeyboardFrameEndUserInfoKey;
NSString *const UIKeyboardIsLocalUserInfoKey;

当以下几种情况时,UIView不接受用户交互:

  • alpha < 0.01
  • userInteractionEnabled = NO
  • hidden = YES
  • 点击位置不在控件或者父控件响应范围内

UIGestureRecognizer

手势识别器,将一系列touches识别为一个手势,并触发相应操作。 这个类一般不直接使用,而是使用其子类:

UITapGestureRecognizer		//点击,可以设置点击次数和触摸点数量
UIPinchGestureRecognizer	//双指缩放手势,可设置比例和速度
UIRotationGestureRecognizer	//双指旋转手势,可设置旋转的角度和速度
UISwipeGestureRecognizer	//滑动手势,可设置滑动方向和触摸点数量
UILongPressGestureRecognizer	//长按手势,可设置触摸点数量,触发前的点击次数、长按时间,允许手指移动的距离
UIPanGestureRecognzer		//拖拽手势,可设置位置、触摸点数量、速度等
UIScreenEdgePanGestureRecognzer	//在屏幕边缘的平移手势,可设置在哪个边缘

定义手势之后,需要调用相关 view的addGestureRecognizer 进行绑定,才能使手势识别生效。

UIGestureRecognizer拥有和UIResponder相同的点击事件与按键事件,见UIResponder类中的介绍。

@property(nullable, nonatomic, copy) NSString *name;//用于标识不同的手势识别器

//绑定方法
- (instancetype)initWithTarget:(id)target action:(SEL)action;
- (void)addTarget:(id)target action:(SEL)action;
- (void)removeTarget:(id)target action:(SEL)action;

//点击信息
- (CGPoint)locationInView:(UIView *)view;
- (CGPoint)locationOfTouch:(NSUInteger)touchIndex inView:(UIView *)view;
@property(nonatomic, readonly) NSUInteger numberOfTouches;

//手势信息
@property(nonatomic, readwrite) UIGestureRecognizerState state;
@property(nullable, nonatomic, readonly) UIView *view;	//手势绑定的view
@property(nonatomic, getter=isEnabled) BOOL enabled;	//是否启用

//手势识别结束时调用
- (void)reset;

//处理手势冲突的方法
//无视手势之外的点击
- (void)ignoreTouch:(UITouch *)touch forEvent:(UIEvent *)event;
- (void)ignorePress:(UIPress *)button forEvent:(UIPressesEvent *)event;
//设置手势优先级
- (BOOL)canBePreventedByGestureRecognizer:(UIGestureRecognizer *)preventingGestureRecognizer;
- (BOOL)canPreventGestureRecognizer:(UIGestureRecognizer *)preventedGestureRecognizer;
- (void)requireGestureRecognizerToFail:(UIGestureRecognizer *)otherGestureRecognizer;//参数手势会取消调用者手势

//当识别到该手势时,会取消绑定的view上所有的点击。该属性默认为YES,可能会引发点击事件的冲突。
//如需要点击tableview空白区域触发事件,就应当将此属性设置为NO。
@property(nonatomic) BOOL cancelsTouchesInView;

UIGestureRecognizerDelegate

	@property(nullable, nonatomic, weak) id<UIGestureRecognizerDelegate> delegate;

允许用户进行更细粒度的操作

//是否允许手势识别器进行识别对象
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer;
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
       shouldReceiveTouch:(UITouch *)touch;
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
       shouldReceivePress:(UIPress *)press;
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
       shouldReceiveEvent:(UIEvent *)event;

//多手势处理
//是否允许同时处理两个手势
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer;
//是否可以终止别的手势/被别的手势终止
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer;
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer;

其他相关类

UITouch

点击的位置、控件、次数、类型、手势、事件、尺寸、移动、力度、相位(点击开始、结束等不同阶段)等信息。

UIPress

press事件的信息,同上。

UIEvent

用户交互事件,存放事件类型、交互的窗口和控件、时间戳、触摸点的信息等。

//触摸点信息
@property(nonatomic, readonly, nullable) NSSet<UITouch *> *allTouches;
- (NSSet<UITouch *> *)touchesForView:(UIView *)view;
- (NSSet<UITouch *> *)touchesForWindow:(UIWindow *)window;
- (NSArray<UITouch *> *)coalescedTouchesForTouch:(UITouch *)touch;//按顺序获取触摸点,以实现顺畅的连线
- (NSArray<UITouch *> *)predictedTouchesForTouch:(UITouch *)touch;
- (NSSet<UITouch *> *)touchesForGestureRecognizer:(UIGestureRecognizer *)gesture;

//子类
@interface UIPressesEvent : UIEvent

评论区