swift视图容器_如何使用IBDesignable在Swift中创建漂亮的,可重复使用的渐变视图...

swift视图容器

by Lee Dowthwaite

通过李道思韦特

如何使用IBDesignable在Swift中创建漂亮的,可重复使用的渐变视图 (How to create a beautiful, reusable gradient view in Swift with IBDesignable)

This tutorial will demonstrate how to create a versatile, @IBDesignable gradient view class in Swift 4. You can drop the CAGradientView into storyboards and preview at design time. Or add it programmatically. You can set the colors for two gradient stops (start and end) and gradient direction (in degrees). These properties are completely controllable from the IB inspector.

本教程将演示如何在Swift 4中创建通用的@IBDesignable渐变视图类。您可以将CAGradientView放到情节提要中并在设计时进行预览。 或以编程方式添加它。 您可以设置两个渐变色标(开始和结束)和渐变方向(以度为单位)的颜色。 这些属性完全可以从IB检查器控制。

为什么需要这个 (Why You Need This)

Designers just love gradients. Admittedly, like blurred backgrounds and drop shadows, they go in and out of fashion with the changing wind. They do tend to be more subtle nowadays, so they need a fair amount of fiddling around to get them just right.

设计师只喜欢渐变。 诚然,就像背景模糊和阴影一样,它们随着风的变化而进出时尚。 如今,它们确实确实更加微妙,因此他们需要大量的摆弄才能使它们正确。

Creating a gradient involves a reasonable amount of work. Tweaking it until your designer is happy can be a time-consuming process. This tutorial shows you how to build a gradient view component you can drop into storyboards and preview right from within Interface Builder.

创建渐变涉及大量的工作。 对其进行调整直到设计师满意为止可能是一个耗时的过程。 本教程向您展示如何构建渐变视图组件,您可以将其放到情节提要中并在Interface Builder中直接预览。

Your designers will love you for it, and you’ll save yourself a lot of time.

您的设计师会为此而爱,您会节省很多时间。

你会建立什么 (What You Will Build)

It’s easy to say you’re going to build a gradient view, but what are the exact requirements? Let’s define them:

说要建立一个渐变视图很容易,但是确切的要求是什么? 让我们定义它们:

  • It must be a UIView subclass

    它必须是UIView子类

  • It must be @IBDesignable so it can be previewed in Xcode/Interface Builder

    它必须是@IBDesignable以便可以在Xcode / Interface Builder中预览

  • It must be completely configurable either in code or in Interface Builder

    必须在代码或Interface Builder中完全可配置

获取示例项目 (Get the Example Project)

To follow through this tutorial properly you’ll need the example project which you can grab from GitHub.

为了正确地完成本教程,您需要一个示例项目,您可以从GitHub获取该项目。

When you load the project into Xcode and open the example ViewController scene in the storyboard, you will be able to select the gradient view and edit it in the Attributes Inspector, as shown in the image below.

当您将项目加载到Xcode中并在情节提要中打开示例ViewController场景时,将能够选择渐变视图并在Attributes Inspector中对其进行编辑,如下图所示。

关于渐变层 (About Gradient Layers)

NOTE: This is not intended to be an introduction to CAGradientLayer. If you need a more basic introduction, please read my Mastering CAGradientLayer in Swift tutorial which explains all the background you need.

注意:这并不旨在作为CAGradientLayer的介绍。 如果您需要更基础的介绍,请阅读我的Swift Mastering CAGradientLayer教程,其中介绍了您需要的所有背景。

There are several ways to achieve a gradient effect in iOS but in this tutorial we will be using CAGradientLayer. This is a subclass of CALayer, a Core Animation object that is part of the view’s layer hierarchy. In iOS, UIViews are described as layer-backed views because their appearance is controlled by their layer property. Every view has a layer, and just like every UIView can have multiple subviews, every layer can have multiple sublayers.

有几种方法可以在iOS中实现渐变效果,但是在本教程中,我们将使用CAGradientLayer 。 这是CALayer的子类, CALayer是核心动画对象,属于视图层层次结构。 在iOS中, UIView被描述为层支持的视图,因为其外观由其layer属性控制。 每个视图都有一个层,就像每个UIView可以有多个子视图一样,每个层也可以有多个子层。

What this means in practical terms is that each view can have an arbitrarily complex tree of layers to add visual complexity to the view. When working heavily with Core Animation, at some point the developer has to draw the distinction between adding complexity at the CALayer level and simply adding a new UIView to achieve the same effect. Usually, the delineation between views and layers is quite obvious. Often because some property of a view is required for the functionality of the app (for instance, a UILabel or UIButton is required).

实际上,这意味着每个视图都可以具有任意复杂的层树,以增加视图的视觉复杂性。 在与Core Animation进行大量工作时,开发人员有时必须在CALayer级别增加复杂性和仅添加新UIView来实现相同效果之间做出区分。 通常,视图和图层之间的轮廓非常明显。 通常是因为视图的某些属性是应用程序功能所必需的(例如,需要UILabelUIButton )。

When we create rich UIs with lots of subtle graphics, it can become all too easy to increase the complexity of the layer hierarchy. In general, you should avoid this because layers can only be managed in code, not in storyboards. The logic for managing the layer hierarchy can become quite unwieldy.

当我们创建带有许多微妙图形的丰富UI时,增加层层次结构的复杂性变得非常容易。 通常,应避免这种情况,因为图层只能在代码中进行管理,而不能在情节提要中进行管理。 管理层层次结构的逻辑可能变得非常笨拙。

For the purposes of this tutorial, you will add a single CAGradientLayer as a sublayer on the view’s layer property. This gives a one-to-one mapping between views and layers. It nicely encapsulates each gradient layer inside a UIView so it can be laid out in a storyboard.

就本教程而言,您将在视图的layer属性上添加一个CAGradientLayer作为子layer 。 这给出了视图和图层之间的一对一映射。 它很好地将每个渐变层封装在UIView中,因此可以将其放置在情节提要中。

定义视图子类 (Defining the View Subclass)

The core of this tutorial is a gradient view called LDGradientView. It is a subclass of UIView and is defined as follows:

本教程的核心是一个名为LDGradientView的渐变视图。 它是UIView的子类,定义如下:

@IBDesignable class LDGradientView: UIView {   // ... }

The class is marked as @IBDesignable which means it can be previewed in Interface Builder (Xcode’s storyboard editor).

该类被标记为@IBDesignable ,这意味着可以在Interface Builder(Xcode的故事板编辑器)中对其进行预览。

The gradient itself is defined as a private property of the class:

渐变本身定义为该类的私有属性:

// the gradient layer private var gradient: CAGradientLayer?

This property is created by the function below. It sets the gradient’s frame property to the view’s bounds, thereby taking up the entire view. This is in keeping with the one-to-one mapping between view and layer.

此属性由下面的函数创建。 它将渐变的frame属性设置为视图的边界,从而占据整个视图。 这与视图和图层之间的一对一映射保持一致。

// create gradient layer private func createGradient() -> CAGradientLayer {   let gradient = CAGradientLayer()   gradient.frame = self.bounds  return gradient }

Then it is added as a subview of the view’s layer as shown:

然后将其添加为视图图层的子视图,如下所示:

// Create a gradient and install it on the layer private func installGradient() {   // if there's already a gradient installed on the layer, remove it  if let gradient = self.gradient {    gradient.removeFromSuperlayer()  }   let gradient = createGradient()  self.layer.addSublayer(gradient)  self.gradient = gradient}

These are both private functions because a view’s layer hierarchy should be its own business.

这两个都是私有功能,因为视图的层层次结构应该是其自己的业务。

If you are installing the gradient view in a complex hierarchy, or any superview that uses constraints, then every time the frame is set the view must update itself. You can do that by adding these methods:

如果要在复杂的层次结构中安装渐变视图,或使用约束的任何超级视图,则每次设置框架时,视图都必须更新。 您可以通过添加以下方法来做到这一点:

override var frame: CGRect {  didSet {    updateGradient()  }}
override func layoutSubviews() {  super.layoutSubviews()  // this is crucial when constraints are used in superviews  updateGradient()}
// Update an existing gradient    private func updateGradient() {        if let gradient = self.gradient {            let startColor = self.startColor ?? UIColor.clear            let endColor = self.endColor ?? UIColor.clear            gradient.colors = [startColor.cgColor, endColor.cgColor]            let (start, end) = gradientPointsForAngle(self.angle)            gradient.startPoint = start            gradient.endPoint = end            gradient.frame = self.bounds        }    }

Finally, we also need some way of instantiating the view and calling the installGradient function. We do this from one of two initializers, the first to initialize from Interface Builder, and the second for programmatic instantiation:

最后,我们还需要某种方法来实例化视图并调用installGradient函数。 我们使用两个初始化程序之一进行此操作,第一个初始化程序通过Interface Builder进行初始化,第二个初始化程序以编程方式进行实例化:

// initializers required init?(coder aDecoder: NSCoder) {  super.init(coder: aDecoder)  installGradient() }
override init(frame: CGRect) {  super.init(frame: frame)  installGradient() }

定义渐变 (Defining a Gradient)

Now you have a UIView subclass that can install a CAGradientLayer, but that doesn’t achieve a whole lot. Let’s make the gradient view work for us…

现在,您有了一个UIView子类,该子类可以安装CAGradientLayer ,但是并不能实现很多功能。 让我们为我们制作渐变视图…

There are two main properties of CAGradientLayer that your custom view will be manipulating. These are:

自定义视图将操作CAGradientLayer两个主要属性。 这些是:

  • The gradient’s colors

    渐变的颜色
  • The gradient’s direction

    渐变的方向

定义颜色 (Defining the Colors)

The colors are defined as a property on CAGradientLayer:

颜色定义为CAGradientLayer的属性:

// An array of CGColorRef objects defining the color of each gradient stop. Animatable.var colors: [Any]?

关于渐变停止的注释 (A Note on Gradient Stops)

The points at which the color changes in a gradient are called gradient stops. Gradients do support fairly complex behavior and can have unlimited stops. Programming this behavior is straightforward. Creating an @IBInspectable interface for it, however, is more challenging.

颜色在渐变中发生变化的点称为渐变停止点 。 渐变确实支持相当复杂的行为,并且可以有无限次停止。 对这种行为进行编程很简单。 但是,为其创建@IBInspectable接口更具挑战性。

It would be relatively trivial to add another gradient stop or two. Solving the general problem of an arbitrary number of stops is more difficult. The solution would likely be less usable than doing the same job directly in code.

添加另一个或两个渐变停止点将是相对琐碎的。 解决任意数量的停止的一般问题更加困难。 与直接在代码中完成相同的工作相比,该解决方案的可用性可能更低。

For that reason, this project deals only with “simple” gradients. Ones that start with a color at one edge of the view and fade to another color at the opposite edge.

因此,该项目仅处理“简单”渐变。 在视图的一个边缘以一种颜色开始,然后在相对的边缘以一种颜色逐渐消失的颜色。

If you need to add another stop, you should be able to modify the code easily enough, but it would make the tutorial overly complicated.

如果您需要添加另一个停靠点,则应该能够轻松地修改代码,但是这会使教程过于复杂。

So our implementation of the color stops is simply:

因此,我们对色标的实现很简单:

// the gradient start colour @IBInspectable var startColor: UIColor?
// the gradient end colour @IBInspectable var endColor: UIColor?

These are surfaced in Interface Builder as nice color controls.

这些在Interface Builder中浮出水面,是不错的颜色控件。

定义方向 (Defining the Direction)

The gradient’s direction is defined by two properties on CAGradientLayer:

渐变的方向由CAGradientLayer上的两个属性定义:

// The end point of the gradient when drawn in the layer’s coordinate space. Animatable.var endPoint: CGPoint
// The start point of the gradient when drawn in the layer’s coordinate space. Animatable.var startPoint: CGPoint

A gradient’s start and end points are defined in the unit gradient space, which simply means that whatever the dimensions of a given CAGradientLayer, in the unit gradient space we consider the top left to be position (0, 0) and the bottom right to be position (1, 1), as illustrated below:

渐变的起点和终点是在单位渐变空间中定义的,这仅意味着无论给定CAGradientLayer的尺寸CAGradientLayer ,在单位渐变空间中,我们都将左上角视为位置(0,0),将右下角视为位置位置(1,1),如下图所示:

This is the CAGradientLayer coordinate system.

这是CAGradientLayer坐标系。

The direction is the most challenging part of making a gradient @IBDesignable. Because of the need for a start and end point, the fact that @IBInspectable attributes don’t support the CGPoint data type, not to mention the complete lack of data validation in the UI, our options are a bit limited.

方向是制作@IBDesignable渐变中最具挑战性的部分。 由于需要起点和终点, @IBInspectable属性不支持CGPoint数据类型,更不用说UI中完全缺乏数据验证这一事实,我们的选择有些局限。

When trying to work out the simplest way to define common gradient directions, a string seemed a potentially useful data type and perhaps compass points, e.g. “N”, “S”, “E”, “W” might be useful. But for intermediate directions should it support “NW”? What about “NNW” or “WNW”? And beyond that? That would immediately get confusing. And this way of thinking was clearly a long way round to realizing that the best way to describe any angle on the compass was using degrees!

当尝试找出定义共同梯度方向的最简单方法时,字符串似乎是潜在有用的数据类型,也许罗盘点(例如“ N”,“ S”,“ E”,“ W”)可能有用。 但是对于中间方向,它应该支持“西北”吗? 那“ NNW”或“ WNW”呢? 除此之外呢? 那将立即变得混乱。 而且,这种思维方式显然对于实现描述罗盘上任意角度的最佳方法是使用度数还有很长的路要走!

The user can forget about unit gradient spaces. All the complexity is reduced to a single property exposed to Interface Builder:

用户可以忘记单位梯度空间。 所有复杂性都降低为Interface Builder公开的单个属性:

// the gradient angle, in degrees anticlockwise from 0 (east/right)@IBInspectable var angle: CGFloat = 270

Its default value (270 degrees) points south simply to match the CAGradientLayer default direction. For a horizontal gradient, set it to 0 or 180.

其默认值(270度) CAGradientLayer向南以匹配CAGradientLayer默认方向。 对于水平渐变,请将其设置为0或180。

将角度转换为渐变空间 (Converting the Angle to Gradient Space)

This is the hardest bit. I’m including the code and a description of how it works. You can skip this if you’re just interested in using the class.

这是最难的一点。 我包括了代码及其工作方式的描述。 如果您只对使用该类感兴趣,可以跳过此步骤。

The top-level function to convert the angle to start and end points gradient space looks like this:

将角度转换为起点和终点梯度空间的顶级函数如下所示:

// create vector pointing in direction of angle private func gradientPointsForAngle(_ angle: CGFloat) -> (CGPoint, CGPoint) {  // get vector start and end points  let end = pointForAngle(angle)  let start = oppositePoint(end)  // convert to gradient space  let p0 = transformToGradientSpace(start)  let p1 = transformToGradientSpace(end)  return (p0, p1) }

This takes the angle that the user specified and uses it to create a vector pointing in that direction, as illustrated below. The angle specifies the rotation of the vector from 0 degrees. By convention points east in Core Animation, and increases anti-clockwise (counter-clockwise).

如下图所示,这将采用用户指定的角度并将其用于创建指向该方向的矢量。 角度指定矢量从0度开始的旋转。 按照惯例,在Core Animation中指向东方,并逆时针(逆时针)增加。

The end point is found by calling pointForAngle(), defined thus:

通过调用pointForAngle()找到pointForAngle() ,定义如下:

private func pointForAngle(_ angle: CGFloat) -> CGPoint {  // convert degrees to radians  let radians = angle * .pi / 180.0  var x = cos(radians)  var y = sin(radians)  // (x,y) is in terms unit circle. Extrapolate to unit square to get full vector length  if (fabs(x) > fabs(y)) {    // extrapolate x to unit length    x = x > 0 ? 1 : -1 y = x * tan(radians)  } else {    // extrapolate y to unit length    y = y > 0 ? 1 : -1    x = y / tan(radians)  }   return CGPoint(x: x, y: y) }

This function looks more complicated than it is. At its core, it simply takes the sine and cosine of the angle to determine the end point on a unit circle. Because Swift’s trigonometry functions (in common with most other languages) require angles to be specified in radians rather than degrees, we have to do that conversion first. Then the x value is calculated by x = cos(radians), and the y value by y = sin(radians).

此功能看起来比实际要复杂。 从本质上讲,它仅需使用角度的正弦和余弦来确定单位圆上的终点。 由于Swift的三角函数(与其他大多数语言相同)要求以弧度而非角度来指定角度,因此我们必须首先进行转换。 然后,通过x = cos(radians)计算x = cos(radians)值,通过y = sin(radians)计算y值。

The rest of the function is concerned with the fact that the resulting point is on the unit circle. The points we need, however, are in a unit square. Angles along the compass points (i.e. 0, 90, 180 and 270 degrees) will yield the correct result, at the edge of the square. For intermediate angles, the point will be inset from the edge of the square. The vector must be extrapolated to the edge of the square to give the correct visual appearance. This is illustrated below.

函数的其余部分与结果点在单位圆上有关。 但是,我们需要的点在单位平方中。 沿着罗盘点的角度(即0、90、180和270度)将在正方形的边缘产生正确的结果。 对于中间角度,该点将从正方形的边缘开始插入。 必须将向量外推到正方形的边缘以提供正确的视觉外观。 如下所示。

Now we have the end point in a signed unit square, the start point of the vector is found by the simple function below. Because the point is in a signed unit space, it is trivial to find the start point by simply reversing the sign of the components of the end point.

现在我们有一个签署单位正方形终点 ,向量的起点由下面的简单功能找到。 由于该点位于带符号的单位空间中,因此只需简单地反转终点组成部分的正负号即可轻松找到起点。

private func oppositePoint(_ point: CGPoint) -> CGPoint {  return CGPoint(x: -point.x, y: -point.y) }

Note that another way to achieve this would have been to add 180 degrees to the original angle and call pointForAngle() again. But the sign reversal method is so simple that it is slightly more efficient to do it that way.

请注意,实现此目的的另一种方法是将原始角度增加180度,然后再次调用pointForAngle() 。 但是符号反转方法是如此简单,以至于这样做的效率更高。

Now we have the start and end points in the signed unit space, all that remains is to translate them to the unsigned gradient space. The unit space has a y axis that increases northwards. Whereas in the Core Animation space y increases southwards. So the y component must be flipped as part of this translation. The location (0, 0) in our signed unit space becomes (0.5, 0.5) in the gradient space. The function is very straightforward:

现在我们在有符号单位空间中有起点和终点,剩下的就是将它们转换为无符号渐变空间。 单位空间的y轴向北增加。 而在“核心动画”空间中,y向南增加。 因此,在转换过程中必须翻转y分量。 我们的有符号单位空间中的位置(0,0)在渐变空间中变为(0.5,0.5)。 该函数非常简单:

private func transformToGradientSpace(_ point: CGPoint) -> CGPoint {  // input point is in signed unit space: (-1,-1) to (1,1)  // convert to gradient space: (0,0) to (1,1), with flipped Y axis   return CGPoint(x: (point.x + 1) * 0.5, y: 1.0 - (point.y + 1) * 0.5) }

恭喜你! (Congratulations!)

And that is all the hard work done — phew! Congratulations for getting this far — go get yourself a coffee to celebrate…

这就是所有辛勤的工作-! 恭喜您取得了如此远的成就–快来为自己喝杯咖啡庆祝一下…

界面生成器支持 (Interface Builder Support)

All that remains of the gradient view class is the prepareForInterfaceBuilder() function. This function is only run from with Interface Builder when it needs to render a view. A properly-designed @IBDesignable view can actually work quite well without it. There will be times – for instance when adding a new view to a storyboard – when it will not render properly until this function is present. You can force it to run by selecting the view in the storyboard and choosing Editor|Debug Selected Views from the menu.

渐变视图类的所有剩余内容是prepareForInterfaceBuilder()函数。 仅当需要渲染视图时,才通过Interface Builder运行此功能。 没有正确设计的@IBDesignable视图实际上可以很好地工作。 有时(例如,向情节提要板添加新视图时),直到出现此功能之前,它无法正确呈现。 您可以通过在情节提要板上选择视图,然后从菜单中选择“ 编辑器|调试选定的视图”来强制其运行。

Our implementation of the function simply makes sure the gradient is installed and updated.

我们对该函数的实现只需确保已安装和更新渐变。

override func prepareForInterfaceBuilder() {   super.prepareForInterfaceBuilder()  installGradient()  updateGradient() }

谢谢阅读! (Thanks for Reading!)

The code for this project is freely available on GitHub.

该项目的代码可在GitHub上免费获得。

Lee Dowthwaite is a veteran iOS developer who’s developed high profile apps for many top-tier clients since 2010.

Lee Dowthwaite是一位资深的iOS开发人员 ,自2010年以来为许多顶级客户开发了高端应用程序。

Originally published at appcodelabs.com on December 10, 2017.

最初于2017年12月10日发布在appcodelabs.com上。

翻译自: https://www.freecodecamp.org/news/how-to-create-a-beautiful-reusable-gradient-view-in-swift-with-ibdesignable-981aebb43d30/

swift视图容器

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值