苹果框架
UIKit -> UI
QuartzCore(绘图)
Core Animation
MapKit(地图)
CoreLocation(定位)
AVFoundation(音视频)
...
UIKit常用的总结:
UIButton内部细节
#import "XLShopViewButton.h"
@implementation XLShopViewButton
# 内部会调用的指定初始化器
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
self.titleLabel.textAlignment = NSTextAlignmentCenter;
// 禁止点击
self.enabled = NO;
// 高亮状态下不要调整按钮显示的图片
self.adjustsImageWhenHighlighted = NO;
// disabled状态下不要调整按钮显示的图片
self.adjustsImageWhenDisabled = NO;
}
return self;
}
+ (instancetype)shopView {
return [[self alloc] init];
}
+ (instancetype)shopWithModel:(id)model {
XLShopViewButton *button = [self shopView];
return button;
}
# 布局子控件方式一
// 返回文字的位置和尺寸
//- (CGRect)titleRectForContentRect:(CGRect)contentRect {
// return CGRectMake(0, 0, contentRect.size.width, contentRect.size.width);
//}
// 返回图片的位置和尺寸
//- (CGRect)imageRectForContentRect:(CGRect)contentRect {
// return CGRectMake(0, 70, contentRect.size.width, contentRect.size.height - 70);
//}
- (void)configationLabelAndImageValues {
[self setImage:[UIImage imageNamed:@"tab1"] forState:UIControlStateNormal];
[self setTitle:@"我是文字" forState:UIControlStateNormal];
[self setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
}
# 布局子控件方式二
- (void)layoutSubviews {
[super layoutSubviews];
// 苹果内部会在这个方法布局子控件,它布局好后,在改回需求的位置尺寸和位置
CGFloat imageWidth = self.frame.size.width;
CGFloat height = self.frame.size.height;
self.imageView.frame = CGRectMake(0, 0, imageWidth, imageWidth);
self.titleLabel.frame = CGRectMake(0, imageWidth, imageWidth, height - imageWidth);
}
@end
按钮调整内部控件的eg.
// 去调整按钮内部的titleLabel的大小
// 返回按钮titleLabel内部的尺寸
// contentRect:按钮当前的位置尺寸
- (CGRect)titleRectForContentRect:(CGRect)contentRect {
return CGRectMake(0, 0, contentRect.size.width, contentRect.size.width);
}
// 去调整按钮内部的imageView的大小
// 返回按钮imageView内部的尺寸
// contentRect:按钮当前的位置尺寸
- (CGRect)imageRectForContentRect:(CGRect)contentRect {
CGFloat w = 40;
CGFloat h = 45;
CGFloat x = (contentRect.size.width - w) * 0.5;
CGFloat y = 20;
return CGRectMake(x, y, w,h);
}
ScrollView常用属性
#import "XLShopScrollView.h"
@interface XLShopScrollView ()
@property (nonatomic, strong) UIActivityIndicatorView *activityIndicatorView;
@end
@implementation XLShopScrollView
#pragma mark - life cycle
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
self.backgroundColor = [UIColor redColor];
self.contentSize = CGSizeZero;
self.scrollEnabled = YES;
self.scrollsToTop = YES;
// 是否开启分页功能
// 以scrollView的尺寸为标准分页
self.pagingEnabled = YES;
// 是否展示指示器
self.showsVerticalScrollIndicator = NO;
self.showsHorizontalScrollIndicator = NO;
// 是否有弹簧效果
self.bounces = YES;
// 即使没有设置contentSize,也是有弹簧效果,用该属性做下拉刷新。
self.alwaysBounceVertical = YES;
self.alwaysBounceHorizontal = NO;
// 设置内容的偏移量,得知内容的偏移量
// 手机中的坐标系:水平方向右边是正方向,竖直方向下边是正方向
// scrollView的偏移量是scrollView的原点位置减去内容的原点位置
// 向左滑动偏移量是正的;向上滑动偏移量是正的
self.contentOffset = CGPointMake(0, 100);
// 是否动画设置偏移量
[self setContentOffset:CGPointZero animated:YES];
// 设置内边距,偏移量还是之前的计算方式,只是可以多滚动出内边距的距离
self.contentInset = UIEdgeInsetsMake(0, 0, 0, 0);
// 设置代理监听控件的内部事件
self.delegate = self;
// 设置最大和最小缩放比例并实现缩放的代理方法,返回缩放对象
// option + shift 可以移动模拟器的缩放手势,松开shift间然后再缩放
self.minimumZoomScale = 0.5;
self.maximumZoomScale = 2;
[self addSubview:self.activityIndicatorView];
}
return self;
}
//MARK:
// 系统的短的指定初始化器会去调用长的指定初始化器,
// 利用系统的指定初始化器来初始化自定义控件,把要自定义的控件放到长的初始化方法内部, 不管调用短的初始化还是长的初始化方法都会得到自定义控件的创建和初始化
- (instancetype)init {
if (self = [super init]) {}
return self;
}
- (void)layoutSubviews {
[super layoutSubviews];
self.activityIndicatorView.center = CGPointMake(self.center.x, -30);
}
#pragma mark - UIScrollViewDelegate
// scrollView正在滚动的时候就会自动调用这个方法
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {}
// 用户即将拖拽scrollview的时候会自动调用这个方法
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {}
// 用户即将停止拖拽scrollview的时候会自动调用这个方法
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset API_AVAILABLE(ios(5.0)) {
}
// 用户已经停止拖拽scrollview的时候会自动调用这个方法
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
if (!decelerate) {
NSLog(@"用户已经停止拖拽scrollView,scrollView停止滚动");
} else {
NSLog(@"用户已经停止拖拽scrollView,但是scrollview由于惯性会继续滚动,并减速");
}
}
// 停止滚动
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
NSLog(@"scrollView减速完毕,停止滚动");
}
// 将要开始减速
- (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView {}
// scrollView已经结束带有动画的滑动
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView {}
// 返回带缩放的控件
- (nullable UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {return nil;}
// scrollView将要开始缩放
- (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(nullable UIView *)view {}
// scrollView已经结束缩放
- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(nullable UIView *)view atScale:(CGFloat)scale {}
// scrollview已经结束缩放
- (void)scrollViewDidZoom:(UIScrollView *)scrollView API_AVAILABLE(ios(3.2)){}
// scrollView是否可以滑动到顶部
- (BOOL)scrollViewShouldScrollToTop:(UIScrollView *)scrollView {return YES;}
// scrollView已经滑动到顶部
- (void)scrollViewDidScrollToTop:(UIScrollView *)scrollView {}
// iOS11 新增方法
- (void)scrollViewDidChangeAdjustedContentInset:(UIScrollView *)scrollView API_AVAILABLE(ios(11.0), tvos(11.0)) {
}
#pragma mark - getters and setters
- (UIActivityIndicatorView *)activityIndicatorView {
if (_activityIndicatorView == nil) {
_activityIndicatorView = [[UIActivityIndicatorView alloc] init];
[_activityIndicatorView startAnimating];
}
return _activityIndicatorView;
}
@end
scrollView分页实现
# 方式一
// scrollView正在滚动的时候就会自动调用这个方法
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
// 在滚动的时候来判断是几页,四舍五入
NSInteger pageNum = scrollView.contentOffset.x / scrollView.frame.size.width + 0.5;
}
# 方式二
// 用户已经停止拖拽scrollview的时候会自动调用这个方法
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
if (!decelerate) {
NSLog(@"用户已经停止拖拽scrollView,scrollView停止滚动");
NSInteger pageNum = scrollView.contentOffset.x / scrollView.frame.size.width;
}
}
// 停止滚动
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
NSLog(@"scrollView减速完毕,停止滚动");
NSInteger pageNum = scrollView.contentOffset.x / scrollView.frame.size.width;
}
- (UIPageControl *)pageControl {
if (_pageControl == nil) {
_pageControl = [[UIPageControl alloc] init];
// 当页数隐藏控件
_pageControl.hidesForSinglePage = YES;
}
return _pageControl;
}
UITableView&collectionView
# tableview对多个插入,删除,重新加载和移动操作进行动画处理
- (void)beginUpdates;
- (void)endUpdates;
// UITableView中该方法在是iOS11才有的方法
- (void)performBatchUpdates:(void (NS_NOESCAPE ^ _Nullable)(void))updates
completion:(void (^ _Nullable)(BOOL finished))completion API_AVAILABLE(ios(11.0),
# collectionView对多个插入,删除,重新加载和移动操作进行动画处理
// 很早之前就有了,可以直接使用
- (void)performBatchUpdates:(void (NS_NOESCAPE ^ _Nullable)(void))updates
completion:(void (^ _Nullable)(BOOL finished))completion
对tableView的性能优化?
1. 缓存池
2. Cell行高问题,如果不是等高的cell,必须的提前计算好行高,在heightForRowAtIndexPath:直接返回,此方法会调用很多次,在此方法当中尽量少做大量计算操作。可以提前估一个行高,estimatedRowHeight(200-250)之间(目的是减少heightForRowAtIndexPath方法调用次数)
3. Cell内部空间最好一次给添加完,不要动态去添加子控件
4. 如果说cell内容子控件比较多,可以考虑把不需要于用户交互的控件通过drawRect生成图片的方式进行绘制
5. 如果cell当中使用圆角图片时,图片最好通过quartz2D裁剪成图形.,如果使用layer.cornerRadius+layer.masksToBounds 会造成离屏渲染,会耗性能
6. imageView宽高出现小数点,会造成锯齿,会造成离屏渲染耗性能。
7. 如果cell里面有图片时,imageView的尺寸要与图片保持一样大,服务器提供两套图片,开始加载的是小图,点击时在去加载大图。如果图片的尺寸不一样,要对图片进行压缩操作(形变操作transform,进行大量的计算会耗性能)
UITextFiled
@interface XLShopTextField ()
@end
@implementation XLShopTextField
#pragma mark - life cycle
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
self.delegate = self;
// 指定键盘
self.inputView = [UIView new];
}
return self;
}
// 通过xib创建
- (instancetype)initWithCoder:(NSCoder *)coder {
if (self = [super initWithCoder:coder]) {
// 指定键盘
self.inputView = [UIView new];
}
return self;
}
- (instancetype)init {
if (self = [super init]) {
}
return self;
}
#pragma mark - UITextFieldDelegate
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField {
return YES;
}
- (void)textFieldDidBeginEditing:(UITextField *)textField {
}
- (BOOL)textFieldShouldEndEditing:(UITextField *)textField {
return YES;
}
- (void)textFieldDidEndEditing:(UITextField *)textField {
}
- (void)textFieldDidEndEditing:(UITextField *)textField reason:(UITextFieldDidEndEditingReason)reason API_AVAILABLE(ios(10.0)); {
}
// 是否允许改变字符窜
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
return NO;
}
- (void)textFieldDidChangeSelection:(UITextField *)textField API_AVAILABLE(ios(13.0), tvos(13.0)) {
}
- (BOOL)textFieldShouldClear:(UITextField *)textField {
return NO;
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
return NO;
}
@end
UITableViewCell
@interface XLShopTableViewCell ()
@property (nonatomic, strong) UIView *customBackgroundView;
@property (nonatomic, strong) UIView *customSelectedBackgroundView;
@end
@implementation XLShopTableViewCell
#pragma mark - life cycle
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
// accessoryView的优先级大于accessoryType
// 设置cell的右边的指示控件
self.accessoryView = [UIView new];
// 设置cell的右边的指示样式
self.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
// 设置cell的选中的样式
self.selectionStyle = UITableViewCellSelectionStyleNone;
// 设置cell的背景颜色,backgroundView 优先级大于backgroundColor
self.backgroundColor = [UIColor redColor];
// UITableViewCell的背景控件可以用图片
self.backgroundView = self.customBackgroundView;
// 设置选中的背景控件
self.selectedBackgroundView = self.customSelectedBackgroundView;
}
return self;
}
- (void)awakeFromNib {
[super awakeFromNib];
// Initialization code
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
// Configure the view for the selected state
}
#pragma mark - getters and setters
- (UIView *)customBackgroundView {
if (_customBackgroundView == nil) {
_customBackgroundView = [UIView new];
_customBackgroundView.backgroundColor = [UIColor greenColor];
}
return _customBackgroundView;
}
- (UIView *)customSelectedBackgroundView {
if (_customSelectedBackgroundView == nil) {
_customSelectedBackgroundView = [UIView new];
_customSelectedBackgroundView.backgroundColor = [UIColor purpleColor];
}
return _customSelectedBackgroundView;
}
@end
UIViewContentMode
typedef NS_ENUM(NSInteger, UIViewContentMode) {
# 拉伸到填充整个imageView
UIViewContentModeScaleToFill,
# 等比拉伸到刚好看到整个图片
UIViewContentModeScaleAspectFit,
# 等比拉伸到图片的宽度或者高度刚好等于imageView,宽先达到ImageView的宽或者高先达到,之后居中显示。搭配`clipsToBounds`超过图片的内容裁剪掉
UIViewContentModeScaleAspectFill,
// redraw on bounds change (calls -setNeedsDisplay)
UIViewContentModeRedraw,
# 按图片原大小显示 搭配`clipsToBounds`超过图片的内容裁剪掉
UIViewContentModeCenter,
UIViewContentModeTop,
UIViewContentModeBottom,
UIViewContentModeLeft,
UIViewContentModeRight,
UIViewContentModeTopLeft,
UIViewContentModeTopRight,
UIViewContentModeBottomLeft,
UIViewContentModeBottomRight,
};
获取软件安装包资源路径
# 导入播放器框架
#import
// 获取软件的安装包
NSBundle *bundle = [NSBundle mainBundle];
// 获取安装包某一个资源的路径
NSURL *url = [bundle URLForResource:@"fileName" withExtension:@"mp3"];
// 创建 AVPlayer
AVPlayer *player = [AVPlayer playerWithURL:url];
// 播放
[player play];
图片内存问题
# 缓存图片只能访问`Assets.xcassets`
UIImage *cachedImage = [UIImage imageNamed:@"fileName"];
# 不缓存图片,不放在`Assets.xcassets`下,放在项目的其他位置,会打包到`NSBundle`里
//MARK:OfFile:代表资源的全路径
NSString *imagePath = [[NSBundle mainBundle] pathForResource:@"fileName" ofType:@"mp3"];
UIImage *noCachedImage = [UIImage imageWithContentsOfFile:imagePath];
xib
.c -> 编译 -> .o
.xib -> 编译 -> nib
# nib里可以有很多个view
# 加载xib方式一
NSArray * viewArray1 = [[NSBundle mainBundle] loadNibNamed:@"contentView" owner:nil options:nil];
UIView *view = array.firstObject;
# 加载xib方式二
UINib *nib = [UINib nibWithNibName:@"contentView" bundle:[NSBundle mainBundle]];
NSArray *viewArry2 = [nib instantiateWithOwner:nil options:nil];
UIView *view = viewArry2.firstObject;
# MARK: 如果一个控件是从xib或者storyboard创建出来的,初始化的时候一定调用initWithCoder:这个方法,但是拿到的控件可能会空的不能再这里做初始化操作。
# MARK: 通过该方式创建非xib的自定义控件,可以被其他的xib文件中引用该控件
- (instancetype)initWithCoder:(NSCoder *)coder {
if ([super initWithCoder:coder]) {
}
return self;
}
# MARK: 如果一个控件是从xib或者storyboard创建出来的,加载完毕的时候一定调用awakeFromNib这个方法
# MARK:在这里做一些属性的初始化操作
- (void)awakeFromNib {
[super awakeFromNib];
}
performSegueWithIdentifier:内部实现
1. 根据标志去storyBoard中查找有没有标志,如果有
2. 会创建segue对象UIStoryboardSegue *segue
3. 给segue对象的属性赋值 segue.sourceViewController = self
4. 给segue对象设置目的地vc segue.destinationViewController = vc;
5. 会调用当前控制器的方法
// 告诉segue已经准备好开始跳转,在方法里面可以拿到要跳转的控制器,可以在这里给目标控制器传递数据
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
6. 真正跳转的方法是[segue perform];方法,我们不需要手动实现,上边这个方法内部已经处理了
iOS 应用数据存储的常用方式
1. XML 属性列表(plist)归档
2. Preference(偏好设置,存放一些用户比较小的数据)本质也是plist文件,内部做了一层封装
3. NSKeyedArchiver 归档(NSCoding) 存放自定义对象
4. SQLite
5. CoreData
iOS 每个应用都有一个独立的沙盒,不允许访问别的沙盒中的数据
Documents:`保存应用运行时生成的需要持久化的数据, itunes同步设备时会 备份该目录例
如,游戏应用可将游戏存档保存在该目录`
Library
Caches:`保存应用运行时生成的需要持久化的数据, iTunes同步设备时不会备份该目录。一般存储体
积大、不需要备份的非重要数据`
Preference:`保存应用的所有偏好设置,iOS的 Settings(设置) 应用会在该目录中査找应用的设置信息。 iTunesi同步设备时会备份该目录`
tmp: `保存应用运行时所需的临时数据,使用完毕后再将相应的文件从该目录删除。应用没有运行时,系
统也可能会清除该目录下的文件。 itunes同步设备时 不会备份该目录`
ViewController注意点
// 是否隐藏状态栏
- (BOOL)prefersStatusBarHidden {
}
导航控制器注意点
#1. 凡是在导航控制器下的 scrollview,会自动设置一个上面的内边距64
self, tableview, contentinset = (64, 0, 0, 0)
// 如果不让它自动设置内边距,可以通过以下方法取消
self. automaticallyadjustsscrollviewinsets = NO;
// iOS 11 新增的方法
UIScrollView.appearance.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever
# 2. 隐导航控制器的导航条
self.navigationController.navigationbar.hidden = YES
#3. 设置导航条的透明度
// 导航条以及它里面的子控件设置透明度是没有效果的
self.navigationcontroller.navigationbar.alpha = 0;
# 4. 设置导航条的背景图片
// 设置设置导航条的背景图片必须得要使用 UIBarMetricsDefault
[self.navigationController.navigationBar setBackgroundImage:[UIImage imageNamed:@"barBg"] forBarMetrics:(UIBarMetricsDefault)];
//如果说导航条的背景图片设置的是ni1的话,会自动帮你创建一个白色的半透明图片,设置导航条的背景图片
[self.navigationController.navigationBar setBackgroundImage:nil forBarMetrics:(UIBarMetricsDefault)];
// 设置导航条的背景图片为透明
[self.navigationController.navigationBar setBackgroundImage:[UIImage new] forBarMetrics:(UIBarMetricsDefault)];
// 隐藏导航条下面的阴影图片
[self.navigationController.navigationBar setShadowImage:[UIImage new]];
# 设置导航条的字体大小,颜色等:
setTitleTextAttributes:
# 配置全局的导航条样式 (UIView遵守了这个协议UIAppearance)
UINavigationBar *navigationBar = [UINavigationBar appearance];
# 配置指定类的导航条样式,防止把系统的导航条样式给修改掉
[[UINavigationBar appearanceWhenContainedInInstancesOfClasses:@[[UISplitViewController class]]] setBarTintColor:myColor];
[[UINavigationBar appearanceWhenContainedInInstancesOfClasses:@[[UITabBarController class], [UISplitViewController class]]] setBarTintColor:myTabbedNavBarColor];
TableView添加拉伸控件注意点
坐标系正方向,水平向右、竖直向下正方向。
contentSize:内容,滚动范围,所有cell的集合+头部,尾部视图
contentInset:额外滚动区域,超出内容,还可以滚动多少
contentOffset:移量, tableview顶点与内容顶点差值
tableview 默认会滚动到最上面
额外滚动区域,不属于内容
导航控制器和tabBarController会影响contentInset。
设置tableView指示器的内间距
tableView.scrollIndicatorInsets = UIEdgeInsetsMake(64, 0, 49,0);
1. 通过上面的方法设置导航条透明
2. 不让导航条自动设置内边距,方法同上
3. 设置TableView的内边距,目的在当前试图控制器view上添加一个可拉伸的自定义控件
tableView.contentInset = UIEdgeInsetsMake(kCustomHeaderViewHeight, 0, 0, 0);
设置后会造成tableView内容的偏移量发生变化 kTableViewOriginOffsetY =【tableView原点位置减去内容原点位置】
4. 计算scrollView的偏移量
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
#define kCustomHeaderViewOriginHeight 200
#define kTableViewOriginOffsetY -244
//1. 计算偏移量
// scrollView.contentInset = UIEdgeInsetsMake(244, 0, 0, 0);
// 当前scrollView偏移量减去原始的偏移量
CGFloat offsetY = scrollView.contentOffset.y - kTableViewOriginOffsetY;
//2.计算高度 h = 原始高度 - 偏移量
CGFloat height = kCustomHeaderViewOriginHeight - offsetY;
if (height <= 64) {
height = 64;
}
//3. 更新高度
self.customHeaderViewHeight = height;
//4. 动态求出alpha
// 求变化的值
// 最大值的方法公式
// 1.最大值为多少(最大值为1)
// 2.什么情况下最大(当offset 为136.0 最大)
// 当offset等于136的时候 alpha = 1
// 当变化的值等于固定的值的时候为最大
// 导航条内部会做一次判断 当设置的背景图片的alpha == 1,这张图片会被设置半透明
CGFloat alpha = offsetY * 1 / 136.0;
if (alpha >= 1) {
alpha = 0.99;
}
// 根据颜色,生成一张图片
UIColor *color = [[UIColor redColor] colorWithAlphaComponent:alpha];
UIImage *image = [UIImage imageWithColor:color];
[self.navigationController.navigationBar setBackgroundImage:image forBarMetrics:(UIBarMetricsDefault)];
// 直接设置self.navigationItem.titleView.alpha变化是不起效果的
// 设置titleLabel的textColor跟随alpha变化
UILabel *titlLabel = (UILabel *)self.navigationItem.titleView;
titlLabel.textColor = [UIColor colorWithWhite:0 alpha:alpha];
}
我的实现表头拉伸 eg:
#define kZommedContentViewHeith 244
#define kzommedHeaderViewHeight 200
#define kTableViewOriginOffsetY -244
#define kSepViewHeight 44
@interface PersonDetailViewController ()
@property (nonatomic, strong) UITableView *tableView;
@property (nonatomic, strong) UIView *zoomedHeaderView;
@property (nonatomic, strong) UIView *sepView;
@property (nonatomic, strong) UIView *zommedContentView;
@property (nonatomic, assign) CGFloat navigationHegiht;
@end
@implementation PersonDetailViewController
#pragma mark - life cycle
- (void)viewDidLoad {
[super viewDidLoad];
[self.view addSubview:self.tableView];
[self.view addSubview:self.zommedContentView];
[self setupNavigationDefaultConfi];
[self setupLayoutConstraintOfTableView];
}
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
self.navigationHegiht = self.navigationController.navigationBar.frame.size.height + [[UIApplication sharedApplication] statusBarFrame].size.height;
}
#pragma mark - UITableViewDataSource
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellIndentifer = @"cellIndentifer";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIndentifer forIndexPath:indexPath];
cell.textLabel.text = @"哈哈哈哈";
return cell;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 30;
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
// 计算偏移量
CGFloat offsetY = scrollView.contentOffset.y - kTableViewOriginOffsetY;
// 计算高度 原始高度 - 偏移量
CGFloat height = kzommedHeaderViewHeight - offsetY;
if (height <= self.navigationHegiht) {
height = self.navigationHegiht;
}
[self.zommedContentView setNeedsLayout];
[self.zommedContentView layoutIfNeeded];
[self.zoomedHeaderView mas_updateConstraints:^(MASConstraintMaker *make) {
make.height.mas_equalTo(height);
}];
// 计算alpha
CGFloat alpha = 1 * offsetY/(kzommedHeaderViewHeight - self.navigationHegiht);
if (alpha >= 1) {
alpha = 0.99;
}
// 生成带alpha的颜色
UIColor *color = [[UIColor whiteColor] colorWithAlphaComponent:alpha];
// 根据颜色生成一张图片
UIImage *image = [UIImage imageWithColor:color];
[self.navigationController.navigationBar setBackgroundImage:image forBarMetrics:(UIBarMetricsDefault)];
UILabel *label = (UILabel *)self.navigationItem.titleView;
label.textColor = [UIColor colorWithWhite:0 alpha:alpha];
}
#pragma mark - private methods
- (void)setupNavigationDefaultConfi {
if (@available(iOS 11.0, *)) {
self.tableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
} else {
// Fallback on earlier versions
self.automaticallyAdjustsScrollViewInsets = NO;
}
UILabel *titleLabel = [UILabel new];
titleLabel.text = @"个人详情";
self.navigationItem.titleView = titleLabel;
[self.navigationController.navigationBar setBackgroundImage:[UIImage new] forBarMetrics:(UIBarMetricsDefault)];
[self.navigationController.navigationBar setShadowImage:[UIImage new]];
UIBarButtonItem *leftBarbutton = [[UIBarButtonItem alloc] initWithTitle:@"点我" style:(UIBarButtonItemStylePlain) target:self action:@selector(clickAction:)];
self.navigationItem.leftBarButtonItem = leftBarbutton;
}
- (void)clickAction:(UIBarButtonItem *)barBtton {
}
- (void)setupLayoutConstraintOfTableView {
[self.tableView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.view.mas_safeAreaLayoutGuideLeft);
make.right.equalTo(self.view.mas_safeAreaLayoutGuideRight);
make.top.equalTo(self.view);
make.bottom.equalTo(self.view.mas_safeAreaLayoutGuideBottom);
}];
[self.zommedContentView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.view.mas_safeAreaLayoutGuideLeft);
make.right.equalTo(self.view.mas_safeAreaLayoutGuideRight);
make.top.equalTo(self.view);
}];
}
#pragma mark getters and setters
- (UITableView *)tableView {
if (_tableView == nil) {
_tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:(UITableViewStylePlain)];
[_tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"cellIndentifer"];
_tableView.delegate = self;
_tableView.dataSource = self;
_tableView.rowHeight = 80;
_tableView.estimatedRowHeight = 200;
_tableView.estimatedSectionFooterHeight = 0;
_tableView.estimatedSectionHeaderHeight = 0;
_tableView.contentInset = UIEdgeInsetsMake(244, 0, 0, 0);
}
return _tableView;
}
- (UIView *)zoomedHeaderView {
if (_zoomedHeaderView == nil) {
_zoomedHeaderView = [UIView new];
_zoomedHeaderView.backgroundColor = [UIColor greenColor];
_zoomedHeaderView.frame = CGRectMake(0, 0, self.view.bounds.size.width, kzommedHeaderViewHeight);
}
return _zoomedHeaderView;
}
- (UIView *)sepView {
if (_sepView == nil) {
_sepView = [UIView new];
_sepView.frame = CGRectMake(0, kzommedHeaderViewHeight, self.view.bounds.size.width, kSepViewHeight);
_sepView.backgroundColor = [UIColor yellowColor];
}
return _sepView;
}
- (UIView *)zommedContentView {
if (_zommedContentView == nil) {
_zommedContentView = [UIView new];
_zommedContentView.frame = CGRectMake(0, 0, self.view.frame.size.width, kZommedContentViewHeith);
_zommedContentView.backgroundColor = [UIColor blackColor];
[_zommedContentView addSubview:self.zoomedHeaderView];
[_zommedContentView addSubview:self.sepView];
[self.zoomedHeaderView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.left.right.equalTo(_zommedContentView);
make.height.mas_equalTo(kzommedHeaderViewHeight);
}];
[self.sepView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.zoomedHeaderView.mas_bottom);
make.left.right.bottom.equalTo(_zommedContentView);
make.height.mas_equalTo(kSepViewHeight);
}];
}
return _zommedContentView;
}
@end
设置半透明指示器
# 1. 设置控件本身完全不透明
# 2. 设置控件背景色半透明
UILabel *hubLabel = [[UILabel alloc] init];
hubLabel.text = @"showHubTipStr";
hubLabel.alpha = 1.0;
hubLabel.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.5];
AutoLayout本质是转换成Frame布局
万能公式:
obj1.property1 = (obj2.property2 * multiplier) + constant value
事件是怎样产生于传递的?
触摸事件的传递是才父控件传递到子控件
如果父控件不能接收触摸事件,那么子控件就不能接收到触摸事件
一个控件什么情况下不能够接受事件?
1. 不能接收用户交互时不能够处理事件 userInteractionEnabled = NO
2. 当一个控件隐藏的时候不能接收事件 Hidden = YES的时候
3. 当一个控件为透明时候也不能接收事件
4. UIImageView,UILabel 默认用户交互是NO
如何寻找最透合的View?
1. 先判断自己是否能能够接收触摸事件,如果能再继续往下判断
2. 在判断触摸的当前的点在不在自己身上
3. 如果在自己身上,他会从后往前遍历子控件,遍历出每个子控件后,重复前面的两个步骤
4. 如果没有符合条件的子控件,那么它自己即使最适合的view
hitTest方法
hitTest方法
作用:寻找最合适的view
参数:当前是手指所在的点,产生的事件
返回值:返回谁,谁就是最合适的view
什么时候调用:只要一个事件,传递给一个控件时,就会调用这个控件的hitTest方法
-(UIVIew*)hitTest:(CGPoint)point withEvent:(UIEvent *)event
PointInside方法
PointInside方法
判断触摸点是否在控件上
以控件坐上角为坐标原点
UIResponder
@implementation XLShopResponder
// 触摸事件
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {}
- (void)touchesCancelled:(NSSet *)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)remoteControlReceivedWithEvent:(UIEvent *)event {}
- (instancetype)init {
if (self = [super init]) {
}
return self;
}
@end
UITouch
@implementation XLShopTouch
// 返回值表示触摸在view上的位置
// 这里返回的位置是针对view的坐标系的(以view的左上角为原点(0,O) 调用时传入的view参数为nil的话,返回的是触摸点在 UIWindoow的位置
- (CGPoint)locationInView:(UIView *)view {
return [super locationInView:view];
}
//该方法记录了当前试图,前一次触摸点的位置。
- (CGPoint)preciseLocationInView:(UIView *)view {
return [super preciseLocationInView:view];
}
@end
@implementation XLResponderView
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint currentPoint = [touch locationInView:self];
CGPoint prePoint = [touch preciseLocationInView:self];
CGFloat offsetx = currentPoint.x - prePoint.x;
CGFloat offsety = currentPoint.y - prePoint.y;
self.transform = CGAffineTransformTranslate(self.transform, offsetx, offsety);
}
@end
UIEvent
@implementation XLShopEvent
- (instancetype)init {
if (self = [super init]) {
// UIEvent称为事件对象,记录事件产生的类型和时刻
// 事件的类型
// @property(nonatomic,readonly) UIEventType type API_AVAILABLE(ios(3.0));
// @property(nonatomic,readonly) UIEventSubtype subtype API_AVAILABLE(ios(3.0));
// 事件的时间戳
// @property(nonatomic,readonly) NSTimeInterval timestamp;
}
return self;
}
@end
@implementation XLResponderView
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint currentPoint = [touch locationInView:self];
CGPoint prePoint = [touch preciseLocationInView:self];
CGFloat offsetx = currentPoint.x - prePoint.x;
CGFloat offsety = currentPoint.y - prePoint.y;
self.transform = CGAffineTransformTranslate(self.transform, offsetx, offsety);
}
@end
手势
@interface XLShopGestureRecognizer ()
@end
@implementation XLShopGestureRecognizer
#pragma mark life cycle
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
// 点击手势
UITapGestureRecognizer *tapGes = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapGes:)];
[self addGestureRecognizer:tapGes];
// 平移手势
UIPanGestureRecognizer *panGes = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panGes:)];
panGes.delegate = self;
[self addGestureRecognizer:panGes];
// 相对于坐标系,坐标系有大小有方向
// - (CGPoint)translationInView:(nullable UIView *)view;
// 转换后清零操作
// - (void)setTranslation:(CGPoint)translation inView:(nullable UIView *)view
// 旋转手势
UIRotationGestureRecognizer *rotationGes = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:@selector(rotationGes:)];
rotationGes.delegate = self;
[self addGestureRecognizer:rotationGes];
// 捏合手势
UIPinchGestureRecognizer *pinGes = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinGes:)];
pinGes.delegate = self;
[self addGestureRecognizer:pinGes];
// 手势默认是不能同时支持多个手势的,可以通过代理设置shouldRecognizeSimultaneouslyWithGestureRecognizer 返回YES,返回多个手势
}
return self;
}
#pragma mark - event response
- (void)tapGes:(UITapGestureRecognizer *)tap {
}
// 获取的偏移量是相对于最原始的点
- (void)panGes:(UIPanGestureRecognizer *)pan {
// 相对于控件原点的位置的偏移量有大小
// CGPoint point = [pan locationInView:self];
// 相对于控件坐标系的偏移量(有大小方向)
CGPoint point = [pan translationInView:self];
// 方式一:
// 使用带make松开手第二次再次平移的时候会,控件的位置又会回到原点的位置。
// self.transform = CGAffineTransformMakeTranslation(point.x, point.y);
// 方式二:
self.transform = CGAffineTransformTranslate(self.transform, point.x, point.y);
// 清零操作
[pan setTranslation:CGPointMake(0, 0) inView:self];
}
- (void)rotationGes:(UIRotationGestureRecognizer *)rorationGes {
// 获取选装角度(已经是弧度)
// 相对于最原始的弧度
CGFloat roration = rorationGes.rotation;
self.transform = CGAffineTransformRotate(self.transform, roration);
// 清零操作
[rorationGes setRotation:0];
}
- (void)pinGes:(UIPinchGestureRecognizer *)pin {
// 放大缩小
// 获取缩放比例相对于最原始的比例
CGFloat scale = pin.scale;
self.transform = CGAffineTransformScale(self.transform, scale, scale);
// 清零操作
[pin setScale:1];
}
#pragma mark - UIGestureRecognizerDelegate
// 是否允许接收手势事件
// 询问delegate是否允许手势接收者接收一个touch对象
// 返回YES,则允许对这个touch对象审核,NO,则不允许。
// 这个方法在touchesBegan:withEvent:之前调用,为一个新的touch对象进行调用
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
// 相对于控件的原点的位置(有大小)
CGPoint point = [touch locationInView:self];
// 判断点在控件的左边还是右边
if (point.x > self.frame.size.width * 0.5) {
// 在控件的右边允许Target接收事件
return YES;
}
// 在控件的左边不允许Target接收事件
return NO;
}
// 是否允许同时支持多个手势,系统默认返回NO
// 这个函数一般在一个手势接收者要阻止另外一个手势接收自己的消息的时候调用
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
// 询问一个手势接收者是否应该开始解析执行一个触摸的接收事件
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
return YES;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceivePress:(UIPress *)press {
return YES;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveEvent:(UIEvent *)event API_AVAILABLE(ios(13.4), tvos(13.4)) API_UNAVAILABLE(watchos) {
return YES;
}
//下面这个两个方法也是用来控制手势的互斥执行的
//这个方法返回YES,第一个手势和第二个互斥时,第一个会失效
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
//这个方法返回YES,第一个和第二个互斥时,第二个会失效
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
@end
抽屉效果
#define kXLScreenWidth [UIScreen mainScreen].bounds.size.width
#define TargetMaxX (kXLScreenWidth * 0.8)
@interface XLShopDrawViewController ()
@property (nonatomic, strong) UIView *leftView;
@property (nonatomic, strong) UIView *mainView;
@end
@implementation XLShopDrawViewController
#pragma mark - life cycle
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.view.backgroundColor = [UIColor whiteColor];
[self.view addSubview:self.leftView];
[self.view addSubview:self.mainView];
[self openDraw];
}
#pragma mark - event response
- (void)panGes:(UIPanGestureRecognizer *)pan {
// 结束偏移操作
if (pan.state == UIGestureRecognizerStateEnded) {
// 控件的相对于父控件中心点左边,关闭抽屉
if (self.mainView.frame.origin.x < kXLScreenWidth * 0.5) {
[self closeDraw];
return;
} else {
// 控件的相对于父控件中心点右边,打开抽屉
[self openDraw];
return;
}
}
// 相对于坐标系的偏移量
CGPoint point = [pan translationInView:self.mainView];
[self positionWithOffsetX:point.x];
[pan setTranslation:CGPointZero inView:self.mainView];
}
#pragma mark - publickMethod
- (void)closeDraw {
[UIView animateWithDuration:0.25 animations:^{
// 清空形变
self.mainView.transform = CGAffineTransformIdentity;
// 位置复位
self.mainView.frame = self.view.bounds;
}];
}
- (void)openDraw {
[UIView animateWithDuration:0.25 animations:^{
CGFloat offset = TargetMaxX - self.mainView.frame.origin.x;
[self positionWithOffsetX:offset];
CGFloat maxX = TargetMaxX;
CGRect targetFrame = self.mainView.frame;
targetFrame.origin.x = maxX;
self.mainView.frame = targetFrame;
}];
}
#pragma mark - private methods
// 根据原始偏移量,计算当前位置大小
- (void)positionWithOffsetX:(CGFloat)offsetX {
void(^animatonBlock)(void) = ^ {
// 1. 控件偏移量修改
// 方式一:修改frame
// 方式二:做transform形变
// self.mainView.transform = CGAffineTransformTranslate(self.mainView.transform, offsetX, 0);
// 修改mainView的frame
CGRect fram = self.mainView.frame;
fram.origin.x+= offsetX;
// 防止偏移量导致控件的位置偏移到屏幕的左边
if (fram.origin.x <= 0) {
self.mainView.frame = self.view.bounds;
return;
}
self.mainView.frame = fram;
//2. 控件位置做缩放
// 原始比例:1
// 0.9 0.8 0.7
// 1- 0.1 = 0.9
// 1- 0.2 = 0.8
// 1- 0.3 = 0.7
// 缩放
// 找最大值 最大值是0.3
CGFloat scale = self.mainView.frame.origin.x * 0.3 / kXLScreenWidth;
scale = 1 - scale;
// 做缩放操作
self.mainView.transform = CGAffineTransformMakeScale(scale, scale);
};
[UIView animateWithDuration:0.25 animations:animatonBlock];
}
#pragma mark - getters and setters
- (UIView *)leftView {
if (_leftView == nil) {
_leftView = [UIView new];
_leftView.backgroundColor = [UIColor redColor];
_leftView.frame = self.view.bounds;
}
return _leftView;
}
- (UIView *)mainView {
if (_mainView == nil) {
_mainView = [UIView new];
_mainView.backgroundColor = [UIColor blueColor];
UIPanGestureRecognizer *panGes = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panGes:)];
[_mainView addGestureRecognizer:panGes];
_mainView.frame = self.view.bounds;
}
return _mainView;
}
@end