服务器之家:专注于服务器技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - IOS - iOS自定义控件开发梳理总结

iOS自定义控件开发梳理总结

2021-02-07 19:02潇潇潇潇潇潇潇 IOS

这篇文章主要介绍了iOS自定义控件开发梳理总结,自定义控件能让我们完全控制视图的展示内容以及交互操作。具有一定的参考价值,感兴趣的小伙伴们可以参考一下。

在日常ios开发中,系统提供的控件常常无法满足业务功能,这个时候需要我们实现一些自定义控件。自定义控件能让我们完全控制视图的展示内容以及交互操作。本篇将介绍一些自定义控件的相关概念,探讨自定义控件开发的基本过程及技巧。

uiview

在开始之前我们先介绍一个类uivew,它在ios app中占有绝对重要的地位,因为几乎所有的控件都是继承自uiview类。
uiview表示屏幕上的一个矩形区域,负责渲染区域内的内容,并且响应区域内发生的触摸事件。

在uiview的内部有一个calayer,提供内容的绘制和显示,包括uiview的尺寸样式。uiview的frame实际上返回的calayer的frame。

uiview继承自uiresponder类,它能接收并处理从系统传来的事件,calayer继承自nsobject,它无法响应事件。所以uiview与calayer的最大区别在于:uiview能响应事件,而calayer不能。
更详细的资料:https://developer.apple.com/reference/uikit/uiview

两种实现方式
在创建自定义控件时,主要有两种实现方式,分别是纯代码以及xib。接下来我们用这两种方式分别演示一下创建自定义控件的步骤。
我们实现一个简单的demo ,效果如下,封装一个圆形的imageview。
iOS自定义控件开发梳理总结
使用代码创建自定义控件
使用代码创建自定义控件,首先创建一个继承自uiview的类iOS自定义控件开发梳理总结

实现initwithframe:方法。在该方法中,设置自定义控件的属性,并创建、添加子视图:

?
1
2
3
4
5
6
7
8
9
10
11
12
-(instancetype)initwithframe:(cgrect)frame {
 self = [super initwithframe:frame];
 if (self) {
  _imageview = [[uiimageview alloc] initwithframe:cgrectmake(0, 0, frame.size.width, frame.size.height)];
  _imageview.contentmode = uiviewcontentmodescaleaspectfill;
  _imageview.layer.maskstobounds = yes;
  _imageview.layer.cornerradius = frame.size.width/2;
  [self addsubview:_imageview];
 
 }
 return self;
}

如果需要对子视图重新布局,需要调用layoutsubviews方法:

?
1
2
3
4
5
6
-(void)layoutsubviews {
 [super layoutsubviews];
 _imageview.frame = self.frame;
 _imageview.layer.cornerradius = self.frame.size.width/2;
 
}

layoutsubviews是调整子视图布局的方法,官方文档如下:

you should override this method only if the autoresizing and constraint-based behaviors of the subviews do not offer the behavior you want.

意思是当你需要调整subview的大小的时候,重写layoutsubviews方法。

layoutsubviews在以下情况下会被调用:

  • init初始化不会触发layoutsubviews

  • addsubview会触发layoutsubviews

  • 设置view的frame会触发layoutsubviews,当然前提是frame的值设置前后发生了变化

  • 滚动一个uiscrollview会触发layoutsubviews

  • 旋转screen会触发父uiview上的layoutsubviews事件

  • 改变一个uiview大小的时候也会触发父uiview上的layoutsubviews事件

这个自定义控件提供对外接口方法,为自定义的控件赋值

?
1
2
3
- (void)configewithimage:(uiimage *)image {
 _imageview.image = image;
}

最后,添加自定义控件到页面上

?
1
2
3
_circleimageview = [[circleimageview alloc] initwithframe:cgrectmake(0, 80, 150, 150)];
[_circleimageview configewithimage:[uiimage imagenamed:@"tree"]];
[self.view addsubview:_circleimageview];

通过xib创建自定义控件
首先创建一个自定义控件xibcircleimageview,继承自uiviewiOS自定义控件开发梳理总结
创建xib文件,与xibcircleimageview类同名iOS自定义控件开发梳理总结
配置xib中imageview的属性,并将xibcircleimageview 类与对应的xib文件进行绑定iOS自定义控件开发梳理总结
代码如下

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- (void)awakefromnib {
 [super awakefromnib];
 
 _imageview.layer.maskstobounds = yes;
 _imageview.layer.cornerradius = self.frame.size.width/2;
 // [self addsubview:_imageview];
}
 
- (void)configewithimage:(uiimage *)image {
 _imageview.image = image;
}
 
-(void)layoutsubviews {
 [super layoutsubviews];
 _imageview.layer.cornerradius = self.frame.size.width/2;
 
}

在页面中调用方式有点不同,通过loadnibnamed方法创建xib对象

 

?
1
2
3
4
5
//使用xib创建自定义控件
_xibcircleimageview = [[[nsbundle mainbundle] loadnibnamed:@"xibcircleimageview" owner:nil options:nil] lastobject];
_xibcircleimageview.frame = cgrectmake(0, 500, 100, 100);
[_xibcircleimageview configewithimage:image];
[self.view addsubview:_xibcircleimageview];

当使用xib创建自定义控件时,初始化不会调用initwithframe:方法,只会调用initwithcoder:方法,初始化完毕后才调用awakefromnib方法,注意要在awakefromnib中初始化子控件。因为initwithcoder:方法表示对象是从文件解析来的,就会调用,而awakefromnib方法是从xib或者storyboard加载完毕后才会调用。

小结

这两种创建自定义控件的方式各有优劣,纯代码方式比较灵活,维护和扩展都比较方便,但写起来比较麻烦。xib方式开发效率高,但不易扩展和维护,适合功能样式比较稳定的自定义控件。

事件传递机制

在自定义控件中,可能需要动态响应事件,如按钮太小,不易点击,需要扩大按钮的点击范围,接下来我们谈谈ios的事件传递机制。

事件响应链

uiresponder类能够响应触摸、手势以及远程控制等事件。它是所有可响应事件的基类,其中包括很常见的uiview、uiviewcontroller以及uiapplication。

uiresponder的属性和方法如下图,其中nextresponder表示指向一个uiresponder对象。iOS自定义控件开发梳理总结

那么事件响应链与uiresponder有什么关系呢?应用内的视图按一定的结构组织起来,即树状层次结构,一个视图可以有多个子视图,而子视图只能有一个父视图。当一个视图被添加到父视图上时。每一个视图的nextresponder属性就指向它的父视图,这样,整个应用就通过nextresponder串成了一条链,即响应链。响应链是一个虚拟链,并不是真实存在的,它借助uiresponder的nextresponder串连起来。如下图iOS自定义控件开发梳理总结

hit-test view

有了事件响应链,接下来就是寻找具体响应对象了,我们称之为:hit-testing view,寻找这个view的过程称为hit-test。
什么是hit-test?我们可以把它理解为一个探测器,通过这个探测器,我们可以找到并判断手指是否触摸在某个视图上。
hit-test是如何工作的?hit-test采用递归方式从视图的根节点开始遍历,直到找到某个点击的视图。

首先从uiwindow发送hittest:withevent:消息开始,判断该视图是否能响应触摸事件,如果不能响应返回nil,表示该视图不能响应触摸事件。然后再调用pointinside:withevent:方法,该方法用于判断触摸事件点击的位置是否处理该视图范围内,如果pointinside:withevent:返回no,那么hittest:withevent:也直接返回nil。

如果pointinside:withevent: 方法返回yes,那么该视图向所有子视图发送hittest:withevent:消息,所有子视图的调用顺序是从最顶层视图一直到最底层视图,即从subviews的数组的末尾向前遍历。直到有子视图返回非空对象或全部遍历完毕。若有子视图返回非空对象,则hittest:withevent:方法返回该对象,处理结束;若所有子视图都返回nil,则hittest:withevent:方法返回该视图自身。

事件传递机制的应用

举几个例子,说明一下事件传递机制在自定义控件中的应用。

一、扩大view的点击区域。假设一个button的大小为20px 20px,太小难以点击。我们通过重写这个button子类的hittest:withevent:方法,判断点击处point是否在button周围20px以内,如果是则返回自身,实现扩大点击范围的功能,代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
-(uiview *)hittest:(cgpoint)point withevent:(uievent *)event {
 if (!self.isuserinteractionenabled || self.hidden || self.alpha<=0.01) {
  return nil;
 }
 cgrect touchrect = cgrectinset(self.bounds, -20, -20);
 if (cgrectcontainspoint(touchrect, point)) {
  for (uiview *subview in [self.subviews reverseobjectenumerator]) {
   cgpoint convertedpoint = [subview convertpoint:point toview:self];
   uiview *hittestview = [subview hittest:convertedpoint withevent:event];
   if (hittestview) {
    return hittestview;
   }
  }
  return self;
 }
 return nil;
}

二、穿透传递事件。

假设有两个view,viewa和viewb,viewb完全覆盖viewa,我们希望点击viewb时能响应viewa的事件。我们重写这个viewa的hittest:withevent:方法,不继续遍历它的子视图,直接返回自身。代码如下:

?
1
2
3
4
5
6
7
8
9
10
-(uiview *)hittest:(cgpoint)point withevent:(uievent *)event {
 if (!self.isuserinteractionenabled || self.hidden || self.alpha<=0.01) {
  return nil;
 }
 if ([self pointinside:point withevent:event]) {
  nslog(@"in view a");
  return self;
 }
 return nil;
}

回调机制

在自定义控件开发中,需要向它的父类回传返回值。比如一个存放按钮的自定义控件,需要在上层接收按钮点击事件。我们可以使用多种方式回调消息,比如target action模式、代理、block、通知等。

target-action

target-action是一种设计模式,当事件触发时,它让一个对象向另一个对象发送消息。这个模式我们接触的比较多,如为按钮绑定点击事件,为view添加手势事件等。uicontrol及其子类都支持这个机制。target-action 在消息的发送者和接收者之间建立了一个松散的关系。消息的接收者不知道发送者,甚至消息的发送者也不知道消息的接收者会是什么。

基于 target-action 传递机制的一个局限是,发送的消息不能携带自定义的信息。ios 中,可以选择性的把发送者和触发 action 的事件作为参数。除此之外就没有别的控制 action 消息内容的方法了。

举个例子,我们使用target-action为控件添加一个单击手势。

?
1
2
3
4
5
6
  uitapgesturerecognizer *tapgr = [[uitapgesturerecognizer alloc] initwithtarget:self action:@selector(refresh)];
  [_imageview addgesturerecognizer:tapgr];
 
- (void)refresh{
 nslog(@"touch imageview");
}

代理

代理是一种我们常用的回调方式,也是苹果推荐的方式,在系统框架uikit中大量使用,如uitableview、uitextfield。

优点:1,代理语法清晰,可读性高,易于维护 ;2,它减少了代码耦合性,使事件监听与事件处理分离;3,一个控制器可以实现多个代理,满足自定义开发需求,灵活性较高;

缺点:1,实现代理的过程较繁琐;2,跨层传值时加大代码的耦合性,并且程序的层次结构也变得混乱;3,当多个对象同时传值时不易区分,导致代理易用性大大降低;

block

block封装一段代码,并当做变量进行传递,它十分方便地将不同地方的代码组织在一起,可读性很高。

优点:1,语法简洁,代码可读性和可维护性较高。2,配合gcd优秀的解决多线程问题。

缺点:1,block中得代码将自动进行一次retain操作,容易造成内存泄露。 2.block内默认引用为强引用,容易造成循环引用。

通知

代理是一对一的关系,通知是一对多的关系,通知相比代理可以实现更大跨度的通信机制。但接收对象多了,就难以控制,有时不希望的对象也接收处理了消息。

优点:1,使用简单,代码精简。2,支持一对多,解决了同时向多个对象监听的问题。3,传值方便快捷,context自身携带相应的内容。

缺点:1,通知使用完毕后需要注销,否则会造成意外崩溃。2,key不够安全,编译器不会检测到是否被通知中心正确处理。3,调试时难以跟踪。 4,当使用者向通知中心发送通知的时候,并不能获得任何反馈信息。 5.需要一个第三方的对象来做监听者与被监听者的中介。

总结

至此,开发自定义控件的相关知识梳理了一遍,希望能帮助大家更好地理解自定义控件开发。

延伸 · 阅读

精彩推荐
  • IOS解析iOS开发中的FirstResponder第一响应对象

    解析iOS开发中的FirstResponder第一响应对象

    这篇文章主要介绍了解析iOS开发中的FirstResponder第一响应对象,包括View的FirstResponder的释放问题,需要的朋友可以参考下...

    一片枫叶4662020-12-25
  • IOSiOS布局渲染之UIView方法的调用时机详解

    iOS布局渲染之UIView方法的调用时机详解

    在你刚开始开发 iOS 应用时,最难避免或者是调试的就是和布局相关的问题,下面这篇文章主要给大家介绍了关于iOS布局渲染之UIView方法调用时机的相关资料...

    windtersharp7642021-05-04
  • IOSIOS 屏幕适配方案实现缩放window的示例代码

    IOS 屏幕适配方案实现缩放window的示例代码

    这篇文章主要介绍了IOS 屏幕适配方案实现缩放window的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要...

    xiari5772021-06-01
  • IOSIOS开发之字典转字符串的实例详解

    IOS开发之字典转字符串的实例详解

    这篇文章主要介绍了IOS开发之字典转字符串的实例详解的相关资料,希望通过本文能帮助到大家,让大家掌握这样的方法,需要的朋友可以参考下...

    苦练内功5832021-04-01
  • IOSiOS中tableview 两级cell的展开与收回的示例代码

    iOS中tableview 两级cell的展开与收回的示例代码

    本篇文章主要介绍了iOS中tableview 两级cell的展开与收回的示例代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧...

    J_Kang3862021-04-22
  • IOSiOS通过逆向理解Block的内存模型

    iOS通过逆向理解Block的内存模型

    自从对 iOS 的逆向初窥门径后,我也经常通过它来分析一些比较大的应用,参考一下这些应用中某些功能的实现。这个探索的过程乐趣多多,不仅能满足自...

    Swiftyper12832021-03-03
  • IOSiOS 雷达效果实例详解

    iOS 雷达效果实例详解

    这篇文章主要介绍了iOS 雷达效果实例详解的相关资料,需要的朋友可以参考下...

    SimpleWorld11022021-01-28
  • IOS关于iOS自适应cell行高的那些事儿

    关于iOS自适应cell行高的那些事儿

    这篇文章主要给大家介绍了关于iOS自适应cell行高的那些事儿,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的...

    daisy6092021-05-17