一、自定义View需要继承UIView
@interface ZSUIView : UIView
注意的几点问题:
1.initWithFrame
init和initWithFrame都是UIView初始化方法,初始化UIView对象的时候可以通过这两种方式。(使用代码创建UIView)
//第一种方式
ZSUIView *view=[[ZSUIView alloc]init];
view.frame=CGRectMake(100, 100, 100, 100);
//第二种方式
ZSUIView *view=[[ZSUIView alloc]initWithFrame:CGRectMake(100, 100, 100, 100)];
init内部会调用initWithFrame,也就是无论第一种还是第二种方式都会调用initWithFrame方法,所以重写initWithFrame才能避免外部初始化UIView的局限性。
自定义初始化方法以init开头,才会调用initWithFrame。
-(instancetype)initWithData:(NSString *)data
{
if(self=[super init])
{
}
return self;
}
如果用xib创建View,会先initWithCoder方法(加载时),再调用awakeFromNib方法(加载完)。
2.layoutSubviews
用来布局子View的Frame
相比在初始化方法initWithFrame中设置子View的Frame有几点优势
a.分离布局代码,初始化方法中只会在初始化设置一次,如果想再改变需要分别设置,麻烦。
b.子View的布局可以跟随父View布局改变而改变。
其中涉及到layoutSubviews的调用机制,
addsubview的时候
ZSUIView*view=[[ZSUIView alloc]init];
[self.view addSubview:view];//ZSUIView中layoutSubviews被调用。
@implementation ZSUIView
-(void)layoutSubviews
{
NSLog(@"%@:%@",NSStringFromClass(self.class),NSStringFromSelector(_cmd));
[super layoutSubviews];
self.bgview.frame=CGRectMake(10, 10, self.frame.size.width/3, self.frame.size.height/3);
}
@end
父View尺寸(不包括位置)改变的时候
- (IBAction)clickTest:(id)sender {
CGRect viewFrame=view.frame;
//viewFrame.origin.x+=10; //ZSUIView中layoutSubviews不会被调用。
viewFrame.size.width+=10; //ZSUIView中layoutSubviews被调用。
view.frame=viewFrame;
}
如果子View尺寸改变,父View的layoutSubviews同样也会被调用
Xib创建的自定义View同样适用。
所以,给我们布局提供的思路就是,子View的布局跟随父View布局而布局,如果父View布局改变,子View也不会太丑;做一些动态尺寸控件时候子View变化的布局写在layoutSubviews中。
3.UITextField
InPutView:默认键盘
AccessoryInputView:默认无
一个思路是继承UITextField,初始化的时候重写InPutView属性,比如改成PickerView,点击就弹出PickerView,也是自定义键盘的思路。
AccessoryInputView用来做评论回复之类的功能。
4.initWithFrame、initWithCoder、awakeFromNib
通过存代码创建View,initWithFrame被调用
通过xib,sb创建View,先调用initWithCoder,再调用awakeFromNib
此时,initWithCoder中子控件还没有创建出来(解析文件开始调用),在awakeFromNib才创建出来。
二、UIButton
1.背景图片拉伸
-(UIImage*)resizeImage
{
UIImage *image = self;
// 设置端盖的值
CGFloat top = image.size.height * 0.5;
CGFloat left = image.size.width * 0.5;
CGFloat bottom = image.size.height * 0.5;
CGFloat right = image.size.width * 0.5;
// 设置端盖的值
UIEdgeInsets edgeInsets = UIEdgeInsetsMake(top, left, bottom, right);
// 设置拉伸的模式
UIImageResizingMode mode = UIImageResizingModeStretch;
// 拉伸图片
UIImage *newImage = [image resizableImageWithCapInsets:edgeInsets resizingMode:mode];
return newImage;
}
@end
2.调整按钮内部图片和文字的位置
//默认图片文字左右结构,改变为右左结构
CGRect iFrame=self.testBtn.imageView.frame;
CGRect tFrame=self.testBtn.titleLabel.frame;
iFrame.origin.x=CGRectGetMaxX(tFrame);
self.testBtn.imageView.frame=iFrame;
self.testBtn.titleLabel.frame=tFrame;
三、UIScrollView
1.contentOffset,contentInset
contentOffset是内容相对于tableview-frame起始点的偏移量;header属于tableview内容的一部分;而contentInset不属于tableview内容的一部分。
contentInset是指tableview内容的边距,除内容外还能额外看到的区域。
(做自定义下拉加载悬停就是控制这个属性值)
所以如果给tableview加一个header,默认contentOffset.y从0开始;
如果给tableview加一个contentInset.top,默认contentOffset.y从-top开始,但是从top开始显示;
有导航条的tableview,默认contentOffset.y=-navBarHeight,除非添加以下属性,保证从contentOffset.y从0开始。
if (@available(iOS 11.0, *)) {
tv.contentInsetAdjustmentBehavior=UIScrollViewContentInsetAdjustmentNever;
} else {
self.automaticallyAdjustsScrollViewInsets=NO;
}
有一些UIScrollView的contentOffset(0,0)但是还有外边框的样式UI,这样做比较符合逻辑。
2.delegate方法
-(void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
//1->
NSLog(@"scrollViewWillBeginDragging");
}
-(void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
{
//2->
NSLog(@"scrollViewWillEndDragging");
}
-(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
//3->decelerate为YES 4才会被调用
//从交互上感受就是 如果用户拖拽之后scrollview有惯性滑动才会调用减速
//如果拖拽之后就不动了那么就不产生减速效果,也不调用减速代理方法
NSLog(@"scrollViewDidEndDragging:%d-%f",decelerate,scrollView.contentOffset.x);
}
-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
//4.
NSLog(@"scrollViewDidEndDecelerating:%f",scrollView.contentOffset.x);
}
-(UIView*)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
//指定scrollview哪个控件进行缩放,同时指定缩放比例就可以完成缩放功能
//self.scrollview.maximumZoomScale=2;
//self.scrollview.minimumZoomScale=0.5;
return nil;
}
3.scrollsToTop
.h文件是这么描述的:
当用户点击状态栏,滑动到顶部,也可以通过scrollViewShouldScrollToTop代理设置;
默认YES;
当在屏幕上有多个UIScrollView,scrollsToTop失效,所以tableview嵌套tableview的情况,就没有scrollsToTop功能,但是如果内部tableview.scrollsToTop=NO;外部tableview.scrollsToTop=YES;就可以生效。
// When the user taps the status bar, the scroll view beneath the touch which is closest to the status bar will be scrolled to top, but only if its `scrollsToTop` property is YES, its delegate does not return NO from `-scrollViewShouldScrollToTop:`, and it is not already at the top.
// On iPhone, we execute this gesture only if there's one on-screen scroll view with `scrollsToTop` == YES. If more than one is found, none will be scrolled.
@property(nonatomic) BOOL scrollsToTop __TVOS_PROHIBITED; // default is YES.
四、AutoLayout
1.在xib或者sb中添加控件,默认contrain to margins是选中的,所以这就是为什么设置左边距0会有间距,取消该选中就可以了。
2.UILabel包裹内容
- 设置UILabel的位置
- 不设置高度约束,宽度约束设置为<=
3.约束优先级
使用场景:
三个连续相隔20pt的View,如果做到去掉中间的View,右侧的View靠近左侧View20pt。
对右侧的View进行左侧View和中间View20pt的两个约束(当然会报错),只需要把左侧View20pt的约束优先级调整低就不会出错,而且实现该需求。
4.修改约束
把约束拖拽到控制器进行修改。
//约束的改变本质也是改变view的frame
self.grayviewheight.constant=100;
//给改变约束添加动画的方式->
[UIView animateWithDuration:2.0 animations:^{
//强制self.view的子控件布局刷新
[self.view layoutIfNeeded];
}];
效果相当于
CGRect grayFrame=self.grayview.frame;
grayFrame.size.width=100;
[UIView animateWithDuration:2.0 animations:^{
self.grayview.frame=grayFrame;
}];
5.Masonry
如果你的控件是代码创建的,还需要添加约束,那么->masonry。
- 给一个UIView添加约束,先添加该UIView,再添加约束。
UIView * mview=[[UIView alloc]init];
mview.backgroundColor=[UIColor orangeColor];
[self.view addSubview:mview];
[mview mas_makeConstraints:^(MASConstraintMaker *make) {}
- 基本思路就是相对于x控件的偏移约束是x
[mview mas_makeConstraints:^(MASConstraintMaker *make) {
//1.0 mview左边相对于self.view的左边距离20pt,右边底边距离20,高度100
make.left.equalTo(self.view.mas_left).offset(20);
make.right.equalTo(self.view.mas_right).offset(-20);
make.bottom.equalTo(self.view.mas_bottom).offset(-20);
make.height.mas_equalTo(100);
//1.1 左边相对于左边则可以省略不写,mas_equalTo包含equalTo
make.left.mas_equalTo(self.view).offset(20);
make.right.mas_equalTo(self.view).offset(-20);
make.bottom.mas_equalTo(self.view).offset(-20);
make.height.mas_equalTo(100);
//1.2 相对于父控件可以省略不写
make.left.offset(20);
make.right.offset(-20);
make.bottom.offset(-20);
make.height.mas_equalTo(100);
//1.3 距离相等可以并列写
make.left.offset(20);
make.right.bottom.offset(-20);
make.height.mas_equalTo(100);
//1.3 距离相等可以并列写
make.left.offset(20);
make.right.bottom.offset(-20);
make.height.mas_equalTo(100);
}];
MASConstraintMaker定义的其他属性可以查看MasonrySDK(反正也是开源的)
- 更新约束并且添加动画
[mview mas_updateConstraints:^(MASConstraintMaker *make) {
make.height.mas_equalTo(200);
}];
[UIView animateWithDuration:1.0 animations:^{
[self.view layoutIfNeeded];
}];
6.UITableView
- cell中的contentView作用是更容易做侧滑删除,收藏。
- cell真实的frame
cell:
- (void)awakeFromNib {
[super awakeFromNib];
//cell在xib本身定义宽度
//NSLog(@"布局cell.width:%f-contentView.width%f",self.frame.size.width,self.contentView.frame.size.width);
}
//父控件尺寸确定好才会调用
-(void)layoutSubviews
{
//tableview的宽度
//NSLog(@"布局cell.width:%f-contentView.width%f",self.frame.size.width,self.contentView.frame.size.width);
}
vc:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//开始是cell在xib中定义的宽度,滑动到第二次加载该view的时候才是tableview的宽度
ZSTableViewCell * cell=[tableView dequeueReusableCellWithIdentifier:@"cell"];
NSLog(@"代理方法cell.width:%f-contentView.width%f",cell.frame.size.width,cell.contentView.frame.size.width);
return cell;
}
所以在cell的layoutSubviews方法中才能拿到实际的frame
- 静态Cell
a.创建storyborad,拖一个uitableviewcontroller到sb中。
b.改变该uitableview类型为static cells,并指定uitableviewcontroller标识。
c.创建控制器和该uitableviewcontroller关联。
d.代码跳转
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"sb名称" bundle:nil];
StaticCellTableViewController *vc = [storyboard instantiateViewControllerWithIdentifier:@"tvc标识"];
[self presentViewController:vc animated:YES completion:nil];
- 调用顺序
tableview第一次创建出来:
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(@"heightForRowAtIndexPath");
return 50;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(@"cellForRowAtIndexPath");
ZSTableViewCell * cell=[tableView dequeueReusableCellWithIdentifier:@"cell"];
cell.name=@"";
return cell;
}
- (void)awakeFromNib {
[super awakeFromNib];
NSLog(@"awakeFromNib");
}
-(void)layoutSubviews
{
NSLog(@"layoutSubviews");
}
-(void)setName:(NSString *)name
{
NSLog(@"setName");
_name=name;
}
tableview:heightForRowAtIndexPath(可见范围内的)->
tableview:cellForRowAtIndexPath->
cell:awakeFromNib->
tableview:setData->
cell:layoutSubviews
cellForRowAtIndexPath优先于awakeFromNib,是因为[tableView dequeueReusableCellWithIdentifier:@"cell"]的方法内部让cell初始化;
setData方法优先于layoutSubviews,是因为在每个cellForRowAtIndexPath方法执行完毕才添加该cell到tableview中;
cell:awakeFromNib只会在第一次创建时候调用,原因是tableview复用机制。
所以在layoutSubviews使用模型不会有问题,也能解释为什么layoutSubviews中才能获取真正cell的frame。
- 不定行高的cell的警告可以通过修改约束优先级擦除。
- tableview局部刷新的前提是要保证数组数据长度不变,除非使用
self.tableview insertSections:<#(nonnull NSIndexSet *)#> withRowAnimation:<#(UITableViewRowAnimation)#>
self.tableview deleteSections:<#(nonnull NSIndexSet *)#> withRowAnimation:<#(UITableViewRowAnimation)#>
- cell侧滑按钮设置
a.只实现单一按钮功能,比如删除
//实现这两个代理
-(void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(@"删除改行");
}
-(NSString *)tableView:(UITableView *)tableView titleForDeleteConfirmationButtonForRowAtIndexPath:(NSIndexPath *)indexPath
{
return @"删除";
}
b.实现侧滑多按钮功能
-(NSArray<UITableViewRowAction*>*)tableView:(UITableView *)tableView editActionsForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewRowAction * row1=[UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleDefault title:@"编辑" handler:^(UITableViewRowAction * _Nonnull action, NSIndexPath * _Nonnull indexPath) {
NSLog(@"编辑");
}];
UITableViewRowAction * row2=[UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleDefault title:@"删除" handler:^(UITableViewRowAction * _Nonnull action, NSIndexPath * _Nonnull indexPath) {
NSLog(@"删除");
}];
row2.backgroundColor=[UIColor blueColor];
return @[row1,row2];
}
- 多cell选中功能
//1.设置属性
self.tableView.allowsMultipleSelectionDuringEditing=YES;
//2.进入tableview编辑模式
[self.tableView setEditing:YES animated:YES];
//3.获得选中cell的indexPath
NSArray<NSIndexPath*> * selectedRows=self.tableView.indexPathsForSelectedRows;
//注意点:
//批量删除注意点,删除数组中的模型,而不是直接删除对应index,因为删除一个index的元素时,整体数组index会改变
- 分割线通栏
cell.layoutMargins=UIEdgeInsetsZero;//iOS(8.0)
- 解决xib初始化控件尺寸的拉伸问题
- (void)awakeFromNib {
[super awakeFromNib];
self.autoresizingMask=UIViewAutoresizingNone;
}
- 解决UITableView刷新跳动问题
if (@available(iOS 11.0, *)) {
UITableView.appearance.estimatedRowHeight = 0;
UITableView.appearance.estimatedSectionFooterHeight = 0;
UITableView.appearance.estimatedSectionHeaderHeight = 0;
}
7.使用技巧
a.StackView很好用,但是只能是IOS9.0以后。
b.比例控件
xib中控件可以通过向父控件拖拽equal width或者height,然后调节比率完成按照比例实现控件宽高。也可以向自己拖拽aspect ratio属性,控制控件自己的宽高比。
c.居中偏差
控件先horizontally or verticality in container,然后对约束Edit,调整constant的值,让控件按居中位置偏移。
d.实现多个View平分的效果,可以使用辅助View,设置各个View间距为0,然后隐藏掉辅助控件。
e.A B 垂直或者水平中心对齐,可以拖拽B到A身上,也可以多选A,B选择horizontal/vertical centers。
五、CALayer
1.CATransform3D是layer比UIView拓展的属性,支持3D形变。
2.position和anchorPoint
-
概念:
position->用来设置CALayer在父层中的位置
anchorPoint->决定CALayer身上哪个点会在position所指的位置,默认(0.5,0.5)范围0-1 -
修改anchorPoint的值,position不改变。
比如:在控制器View上添加一个View,frame是(100,100,100,100)
View的中心点center=layer.position是(150,150),锚点是(0.5,0.5)
如果修改锚点值为(0,0),position(150,150)-(当前View左上角),frame变成(150,150,100,100)
修改anchorPoint为(0,0)->position不变,(150,150)点固定,如果想要保证position所指的位置在修改后的锚点在左上角(0,0),那么自然UIView要向右下方向移动。
如果修改锚点值为(1,1),position(150,150)-(当前View右下角),frame变成(50,50,100,100) -
作用:
使用旋转动画的话,锚点是旋转点中心点位置,默认是中心旋转,如果设置为(0,0),以左上角为中心点旋转。
3.隐式动画
每一个UIView都关联一个CALayer,默认是RootLayer。
所有非RootLayer,手动创建的CALayer,都有隐式动画。
4.核心动画CAAnimation
CABasicAnimation
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
animation.delegate=self;
//动画选项的设定
animation.duration = 2.5; // 持续时间
animation.repeatCount = 1; // 重复次数
// 起始帧和终止帧的设定
animation.fromValue = [NSValue valueWithCGPoint:self.phoneBtn.layer.position]; // 起始帧
animation.toValue = [NSValue valueWithCGPoint:CGPointMake(320, 480)]; // 终止帧
//动画结束停在终止帧位置
animation.removedOnCompletion = NO;
animation.fillMode=kCAFillModeForwards;
// 添加动画
[self.phoneBtn.layer addAnimation:animation forKey:@"move-layer"];
CAKeyframeAnimation
CAKeyframeAnimation * key=[CAKeyframeAnimation animation];
UIBezierPath * path=[UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(0, 0)];
[path addLineToPoint:CGPointMake(1, 1)];
key.path=path.CGPath;
key.rotationMode=@"autoReverse";
[self.phoneBtn.layer addAnimation:key forKey:@"move-layer"];
CATransition
页面翻页各种效果
UIView动画和CA核心动画区别
- UIView动画是对该UIView的属性进行改变的延时效果
常用属性比如改变frame,transform,alpha。
改变属性的相互响应:
1.当一个view的frame被更改时
a.当更改size时,它的bounds的width和height会被更改为与frame的size一致,但是bounds的origin不会被更改。view的center,layer的position可能会被更改。
b.当更改origin时,对bounds属性无影响。view的center,layer的position可能会被更改。
2.当一个view的bounds被更改时
a.当更改size时,frame的width和size会改为同bounds的size一致,frame的origin有可能更改(取决于layer的anchorPoint)。view的center,layer的position可能会被更改不会更改。
b.当更改origin时,frame无影响,view的center,layer的position不会更改。
3.当view的center更改时
frame的origin会更改,layer的position会更改。
4.当一个view的transform被更改了,即不为CGAffineTransformIdentity。
frame属性可能会更改,view的bounds,center不会变,layer的position不会变。这个很重要,这样保持了在transform后,view的frame虽然改变了,但是内部参考系是不变的,可以继续进行其他变换,只要不更改frame或center或layer的position。
CA核心动画对该UIView的属性不改变(比如一个位移动画让UIView改变位置只是看起来改变了,其实位置还在初始点位置,点击原始位置才会有响应,点击看到UIView的终止帧位置没响应,是一种动画假象)
- UIView是直接作用在该UIView属性上的,CA核心动画作用在CALayer上
- 一般有交互事件的UIView,尤其是位移改变还做交互的UIView不做CA核心动画,单纯的帧动画或者转场效果可以做CA核心动画。
UIView物理动画
[UIViewanimateWithDuration:1.0delay:2.0usingSpringWithDamping:0.5initialSpringVelocity:0.1options:UIViewAnimationOptionTransitionFlipFromTopanimations:^{
CGRectviewFrame =_view3.frame;
viewFrame.origin.x+=transweight;
_view3.frame=viewFrame;
}completion:nil];
六、UIImageView
1.显示部分图片(截取图片指定区域显示到当前UIImageView上)
//截取图片上半部分填充到testImg中
self.testImg.layer.contentsRect=CGRectMake(0, 0, 1, 0.5);
2.最近项目中有关于自定义相机以及对拍完照片裁剪的一些问题,总结出来和大家分享一下。
http://www.cocoachina.com/ios/20150605/12021.html
3.UIImageView的contentMode
typedef NS_ENUM(NSInteger, UIViewContentMode) {
//如果包含Scale,按照ImageView尺寸进行填充
UIViewContentModeScaleToFill, //完全按照ImageView尺寸填充(不会超出ImageView尺寸)
UIViewContentModeScaleAspectFit, //按照原始图像宽高比在ImageView尺寸内填充(不会超 出ImageView尺寸)
UIViewContentModeScaleAspectFill, //按照原始图像宽高比填充,使得宽度或者长度等于 ImageView尺寸的宽度或者长度(可能超出ImageView尺寸)
//如果不包含Scale,按照图像原始比例进行填充,结合clipsToBounds属性,保证不超出UIImageView尺寸
UIViewContentModeRedraw,
UIViewContentModeCenter,
UIViewContentModeTop,
UIViewContentModeBottom,
UIViewContentModeLeft,
UIViewContentModeRight,
UIViewContentModeTopLeft,
UIViewContentModeTopRight,
UIViewContentModeBottomLeft,
UIViewContentModeBottomRight,
};
七、UICollectionView
1.使用注意
UICollectionView初始化必须指定该UICollectionView的布局样式Layout。
Layout可以控制该UICollectionView显示样式。
UICollectionViewFlowLayout * layout=[[UICollectionViewFlowLayout alloc]init];
/*
中间间隔
如果UICollectionViewScrollDirectionVertical:
定义最小间隔的原因是因为CollectionView横向布局放不下会自动换行,所以此时间隔就不是定义的间隔了
*/
layout.minimumInteritemSpacing=1;
/*
行间距
如果UICollectionViewScrollDirectionHorizontal:
定义最小行间距的原因是因为CollectionView纵向布局放不下会自动换行,所以此时间隔就不是定义的间隔了
*/
layout.minimumLineSpacing=1;
//itemSize
layout.itemSize=CGSizeMake(100, 100);
/*
滑动方向UICollectionViewScrollDirectionVertical
0246
1357
滑动方向UICollectionViewScrollDirectionHorizontal
01
23
45
67
*/
layout.scrollDirection=UICollectionViewScrollDirectionHorizontal;
UICollectionView * collectionView=[[UICollectionView alloc]initWithFrame:CGRectMake(0, 100, 300, 300) collectionViewLayout:layout];
[collectionView registerNib:[UINib nibWithNibName:@"TestCollectionViewCell" bundle:nil] forCellWithReuseIdentifier:@"testCollectionViewCell"];
collectionView.delegate=self;
collectionView.dataSource=self;
[self.view addSubview:collectionView];
结论:间距和itemSize设置要合理才不会让布局错乱;Bannel和引导页都可以使用横向滑动的CollectionView做。
网友评论