[译]自定义控件教程: 可复用的 Slider

栏目: IOS · 发布时间: 5年前

内容简介:更新说明:此教程由 Lea Marolt Sonnecnschein 升级至 iOS 12、Xcode 10 和 Swift 4.2。原文作者是 Colin Eberhardt。UI 控件是 app 最重要的组成部分。它们是让用户查看 app 并与之交互的图形化组件。苹果提供了一系列控件,比如 UITextField,UIButton 和 UISwitch。通过这些内置控件,你可以创建出各种各样的用户界面。但是,有时候我们需要做一些定制化,内置控件不一定能够满足我们的需要。

更新说明:此教程由 Lea Marolt Sonnecnschein 升级至 iOS 12、Xcode 10 和 Swift 4.2。原文作者是 Colin Eberhardt。

UI 控件是 app 最重要的组成部分。它们是让用户查看 app 并与之交互的图形化组件。苹果提供了一系列控件,比如 UITextField,UIButton 和 UISwitch。通过这些内置控件,你可以创建出各种各样的用户界面。

但是,有时候我们需要做一些定制化,内置控件不一定能够满足我们的需要。

iOS 自定义控件在你自己创建的控件。自定义控件也和标准控件一样,应该具有通用性和广泛性。你会发现有一个充满活力的开发者社区,他们喜欢分享自己的iOS自定义控件创作,这些控件都同时具备这两种特性。

在本教程中,你将编写一个 RangSlide 自定义控件。它是一个具有两个滑块的 slider,允许你同时设定下限值和上限值。你将学习如何扩展已有控件、设计和实现控件的 API,以及如何将你的新控件分享到开发者社区。

让我们开始定制它!

使用 Download Materials 按钮下载开始项目。

假设你要开发一个搜索在售房产的 app。这个 app 允许用户指定一个价格区间来过滤搜索结果。

你可以向用户展示两个 UISlider 控件,一个指定最大价格,一个指定最小价格。但是,这种 UI 不利于让用户直观地看出这是一个价格区间。如果在一个 slider 中显示两个滑块,一个用于表示最高价格一个用于表示最低价格会更好。

[译]自定义控件教程: 可复用的 Slider

好的设计 vs 坏的设计

你可以继承 UIView,创建一个自定义 view,用于展现价格区间。这对于你的 app 是一种不错的选择 —— 但将它迁移到其它 app 时会比较麻烦。

更好的做法是尽量以通用的方式创建一个新组件,以便复用。对于自定义控件来说这非常重要。

创建 iOS 定制控件时,首先面对的第一个问题就是,要通过继承/扩展哪一个类来实现新控件。

你的 class 应该是 UIView 子类,只有这样才能在 app UI 中使用它。

如果你查看苹果的 UIKit 手册,你会看到框架中有许多控件,比如 UILable、UIWebView 都直接继承于 UIView。但是,UIButton 和 UISwitch 则是继承于 UIControl,它们的关系如下图所示:

[译]自定义控件教程: 可复用的 Slider

注:UI 组件的完整类图,请参考 UIKit 框架参考

在本教程中,你将继承 UIControl。

在 Xcode 中打开开始项目。你的 slider 控件代码位于 RangeSlider.swift。在编写代码之前,请将它添加到 view controller 中,以便你能直观地看到它的变化。

打开 ViewController.swift 将内容修改为:

import UIKit

class ViewController: UIViewController {
  let rangeSlider = RangeSlider(frame: .zero)
  
  override func viewDidLoad() {
    super.viewDidLoad()
    
    rangeSlider.backgroundColor = .red
    view.addSubview(rangeSlider)
  }
  
  override func viewDidLayoutSubviews() {
    let margin: CGFloat = 20
    let width = view.bounds.width - 2 * margin
    let height: CGFloat = 30
    
    rangeSlider.frame = CGRect(x: 0, y: 0,
                               width: width, height: height)
    rangeSlider.center = view.center
  }
}

这里我们创建了一个自定义控件,指定它的 frame,将它添加到 view 中。还设置了红色的背景色,以便它能在屏幕上显示出来。

Build & run 。你会看到:

[译]自定义控件教程: 可复用的 Slider

在向控件中添加可视化元素之前,你需要定义几个属性用于保持控件的状态。这就开始构成了控件 API 的一部分。

注:控件的 API 定义了要暴露给控件使用者的方法和属性。

设置控件的默认属性

打开 RangeSlider.swift 将代码修改为:

import UIKit

class RangeSlider: UIControl {
  var minimumValue: CGFloat = 0
  var maximumValue: CGFloat = 1
  var lowerValue: CGFloat = 0.2
  var upperValue: CGFloat = 0.8
}

这 4 个属性描述了控件的所有状态。它们分别指定了某个区间的最大值、最小值,以及用户设置的上限值和下限值。

设计良好的控件需要指定默认属性,否则控件在屏幕上的显示将不正常。

接下来是控件上的互动元素:即负责表示区间的上限值和下限值的两个滑块,以及滑块所属的轨道。

CoreGraphics vs 位图

在屏幕上显示控件有两种主要方式:

  1. CoreGraphics: 用 CALayer 和 CoreGraphics 来显示控件。
  2. 位图:以图片的方式表示控件的各个元素。

每种方式都各有长短,大概罗列如下:

  • Core Graphics: 用 Core Graphics 编写控件意味着你必须自己编写图形绘制代码,这需要你做更多的工作。但是,这种技术允许你创建更加灵活的 API。

    用 Core Graphics,你可以对控件的每个属性进行参数化,比如颜色、边框线宽度、弧线 —— 甚至包括绘制控件时的每一个可视化元素。

  • 位图:用位图编写控件是自定义控件中最简单的方式。如果想让其它开发者能够修改控件的外观,你只需要将这些图片暴露成 UIImage 属性即可。

    使用图片对使用控件的开发者来说是是最灵活的方式。开发者可以修改控件外观的每个像素和细节,但需要熟练的图形设计技巧——但要用代码来修改控件则比较难。

在本教程中,你将两种都尝试一下。我们会用图片来渲染滑块,而用 core graphics 绘制滑轨。

注:有趣的是,苹果在自己的控件中更喜欢用位图。最大的原因是它们知道每个控件的大小,同时不想让你进行过多的定制化。总之,他们想让所有的 app 都拥有类似的外观。

添加滑块

打开 RangeSlider.swift 添加下列属性,就在你之前定义的属性下面:

var thumbImage = #imageLiteral(resourceName: "Oval")

private let trackLayer = CALayer()
private let lowerThumbImageView = UIImageView()
private let upperThumbImageView = UIImageView()

trackLayer、lowerThumbImageView 和 upperThumbImageView 用于绘制控件的各个部分。

仍然在 RangeSlider, 添加构造函数:

override init(frame: CGRect) {
  super.init(frame: frame)
  
  trackLayer.backgroundColor = UIColor.blue.cgColor
  layer.addSublayer(trackLayer)
  
  lowerThumbImageView.image = thumbImage
  addSubview(lowerThumbImageView)
  
  upperThumbImageView.image = thumbImage
  addSubview(upperThumbImageView)
}

required init?(coder aDecoder: NSCoder) {
  fatalError("init(coder:) has not been implemented")
}

改构造函数将 layer 和 view 添加到控件中。

要看见所添加的元素,你必须指定它们的 frame。在构造函数后面加入:

// 1
private func updateLayerFrames() {
  trackLayer.frame = bounds.insetBy(dx: 0.0, dy: bounds.height / 3)
  trackLayer.setNeedsDisplay()
  lowerThumbImageView.frame = CGRect(origin: thumbOriginForValue(lowerValue),
                                     size: thumbImage.size)
  upperThumbImageView.frame = CGRect(origin: thumbOriginForValue(upperValue),
                                     size: thumbImage.size)
}
// 2
func positionForValue(_ value: CGFloat) -> CGFloat {
  return bounds.width * value
}
// 3
private func thumbOriginForValue(_ value: CGFloat) -> CGPoint {
  let x = positionForValue(value) - thumbImage.size.width / 2.0
  return CGPoint(x: x, y: (bounds.height - thumbImage.size.height) / 2.0)
}

在这些方法中分别进行了:

  1. 第一个方法,让 trackerLayer 居中对齐,通过 thumbOriginForValue 方法计算出滑块的位置。
  2. 这个方法中,根据给定的值计算和bound 计算出位置。
  3. 最后一个方法,thumbOriginForValue 返回一个位置,让滑块中心正好位于计算出的位置。

在 init(frame:) 方法中调用新方法:

updateLayerFrames()

然后,覆盖 frame 属性,实现属性观察器:

override var frame: CGRect {
  didSet {
    updateLayerFrames()
  }
}

当 frame 改变时,属性观察器会重新计算 layer 的 frame。当控件通过非默认的 frame 来进行初始化时,这是必须的,比如像 ViewController.swift 中那样。

Build & run ,你的 slider 显示出来了!

[译]自定义控件教程: 可复用的 Slider

红色是控件的背景色,蓝色是滑轨颜色,两个蓝色的圆分别两个滑块。

你的控件显示出来了,但它们还不能相应事件!

对于这个控件而言,用户应该能够通过拖动每个滑块来设置一个区间值。你应该响应这些事件,更新 UI 和外部属性。

响应触摸

打开 RangeSlider.swift 添加属性:

private var previousLocation = CGPoint()

这个属性用于跟踪触摸位置。

要怎样跟踪控件的各种触摸和释放事件?

UIControl 提供了几个跟踪触摸的方法。UIControl 的子类可以覆盖这些方法,添加自己的事件处理逻辑。

在你的自定义控件中,覆盖 3 个 UIControl 方法:beginTracking(_:with:)、continueTracking(_:with:) 和 endTracking(_:with:)。

在 RangeSlider.swift 文件最后添加下列代码:

extension RangeSlider {
  override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
    // 1
    previousLocation = touch.location(in: self)
    
    // 2
    if lowerThumbImageView.frame.contains(previousLocation) {
      lowerThumbImageView.isHighlighted = true
    } else if upperThumbImageView.frame.contains(previousLocation) {
      upperThumbImageView.isHighlighted = true
    }
    
    // 3
    return lowerThumbImageView.isHighlighted || upperThumbImageView.isHighlighted
  }
}

当用户第一次触摸控件时,iOS 会调用这个方法。在这个方法中:

  1. 首先,将触摸事件转换到控件的坐标空间。
  2. 接下来,检查两个滑块是否被触摸。
  3. 通知 UIControl 父类后续触摸是否应该被跟踪。如果某个滑块被高亮的话,那么我们就继续跟踪触摸事件。

有了基本的触摸事件之后,你需要在用户手指滑过屏幕时处理这些事件。

在 beginTracking 方法之后添加:

override func continueTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
  let location = touch.location(in: self)
  
  // 1
  let deltaLocation = location.x - previousLocation.x
  let deltaValue = (maximumValue - minimumValue) * deltaLocation / bounds.width
  
  previousLocation = location
  
  // 2
  if lowerThumbImageView.isHighlighted {
    lowerValue += deltaValue
    lowerValue = boundValue(lowerValue, toLowerValue: minimumValue,
                            upperValue: upperValue)
  } else if upperThumbImageView.isHighlighted {
    upperValue += deltaValue
    upperValue = boundValue(upperValue, toLowerValue: lowerValue,
                            upperValue: maximumValue)
  }
  
  // 3
  CATransaction.begin()
  CATransaction.setDisableActions(true)
  
  updateLayerFrames()
  
  CATransaction.commit()
  
  return true
}

// 4
private func boundValue(_ value: CGFloat, toLowerValue lowerValue: CGFloat, 
                        upperValue: CGFloat) -> CGFloat {
  return min(max(value, lowerValue), upperValue)
}

代码解释如下:

  1. 首先,计算偏移坐标,即用户手指划过的像素点。然后根据控件的上限值和下限值将它乘以一个相对系数。
  2. 根据用户拖动的位置调整上限值或下限值。
  3. 设置 CATransaction 的 disabledActions 属性。这会使每个 layer 的 frame 改变立即被应用,而不是等待动画完成。最后,调用 updateLayerFrames,将滑块移动到正确的位置。
  4. boundValue(_:toLowerValue:upperValue:) 将传入的值限定在某个范围。这个助手方法比起使用 min/max 函数要简单易读。

现在你就实现了 slider 的拖动,但还需要处理触摸和拖拽事件的结束。

在 continueTracking(_:with:) 后添加:

override func endTracking(_ touch: UITouch?, with event: UIEvent?) {
  lowerThumbImageView.isHighlighted = false
  upperThumbImageView.isHighlighted = false
}

这个方法重置了两个滑块的非高亮状态。

Build & run,测试一下你的新 slider!你现在可以拖动滑块了。

[译]自定义控件教程: 可复用的 Slider

注意,当 slider 在跟踪触摸时,你可以将手指拖出控件的范围之外,然后又拖回控件,跟踪并不会失效。对于低分辨率小屏设备来说,这是一个相当有用的特性 —— 因为它们对于手指来说触摸的空间更小!

控件已经可以交互了,用户可以通过它设定上限值和下限值了。但如何将这种变化通知给 app 以便 app 知晓控件当前的变化呢?

变化通知的方式有许多种:NSNotification、键值观察(KVO),委托模式、目标-动作模式等等。选择太多了。

那么,该怎么做呢?

如果你观察 UIKit 的控件,你会发现它们没有用 NSNotification,也不主张使用 KVO,为了保持一致性,你可以排除这两种选择。在 UIKit 中大量的是使用另外两种 —— 委托模型和目标-动作模型。

我们来细说一下这两种方式:

** 委托模式 **: 定义一个协议,声明一系列通知要用到的方法。这种控件通常会有一个名为 delegate 的属性,它接收一个实现该协议的对象。例如 UITableView,它声明了 UITableViewDelegate 协议。注意,这类控件只能接收当个的 delegate 对象。一个委托方法可以接受任意数量的参数,因此无论你有多少信息都可以传递给委托方法。

** 目标-动作模式 **:UIControl 基类提供了目标-动作模式。当控件状态发生改变,事件会被通知给目标对象,这个事件是一个 UIControlEvents 的枚举值。你可以为控件的 action 提供多个目标对象,也可以创建自定义事件(参考 UIControlEventApplicationReserved),限制是最多可以有 4 个自定义事件。控件的动作不会发送任何数据,因此当事件触发时,不能通过动作来传递额外的数据。

两种方式的区别在于:

  • 多播: 目标-动作模型的变化通知是多播的,而委托模型只能通知当个委托对象。
  • 灵活性:如果用委托模型自定义协议,你可以控制要传递的信息的多少。目标-动作模型无法传递额外的信息,客户端接收事件时必须自己获取这些信息。

你的 range slider 控件并没有太多需要通知的状态变化和交互。唯一的变化就是控件的上限值和下限值。

这样,用目标-动作模式就好了。这也是本教程一开始就继承 UIControl 的原因之一。

好,现在都明白了吗?:]

slider 的值在 continueTracking 方法修改,因此在这里添加通知代码。

打开 RangeSlider.swift,找到 continueTracking(_:with:) 在 return 之前添加:

sendActions(for: .valueChanged)

这就是当值变化发生时,通知所有订阅者的代码了。

有了通知代码之后,你需要把它用到 app 中。

打开 ViewController.swift 在类底部添加方法:

@objc func rangeSliderValueChanged(_ rangeSlider: RangeSlider) {
  let values = "(\(rangeSlider.lowerValue) \(rangeSlider.upperValue))"
  print("Range slider value changed: \(values)")
}

这个方法向控制台输出了 slider 的值,以此证明控件已经发送了通知。

现在,在 viewDidLoad() 中添加代码:

rangeSlider.addTarget(self, action: #selector(rangeSliderValueChanged(_:)),
                  for: .valueChanged)

每当 slider 发送 valueChanged 事件时就调用 rangeSliderValueChanged(_:smiley: 方法。

Build & run,左右拖动滑块。你会看到控制台中输出了控件的值:

Range slider value changed: (0.117670682730924 0.390361445783134)
Range slider value changed: (0.117670682730924 0.38835341365462)
Range slider value changed: (0.117670682730924 0.382329317269078)

你可能对 slider 的彩色 UI 不太感冒。它看起来就像是水果沙拉!让我们来给它动个小小的整容手术。

用 Core Graphics 修改控件

首先,来修改滑轨的图片。

在 RangeSliderTrackLayer.swift 中,将代码修改为:

import UIKit

class RangeSliderTrackLayer: CALayer {
  weak var rangeSlider: RangeSlider?
}

这段代码添加了一个对 RangeSlider 的引用。因为 slider 中包含了滑轨,为了避免循环持有,我们使用了弱引用。

打开 RangeSlider.swift,找到 trackLayer 属性,将它修改为 RangeSliderTrackLayer 类型:

private let trackLayer = RangeSliderTrackLayer()

然后,将 init(frame:)方法修改为:

override init(frame: CGRect) {
  super.init(frame: frame)
  
  trackLayer.rangeSlider = self
  trackLayer.contentsScale = UIScreen.main.scale
  layer.addSublayer(trackLayer)
  
  lowerThumbImageView.image = thumbImage
  addSubview(lowerThumbImageView)
  
  upperThumbImageView.image = thumbImage
  addSubview(upperThumbImageView)    
}

这里确保 trackLayer 能引用 range slider,并移除默认的背景色。设置 contentsScale 属性,让它等于设备屏幕的 scale,以更好地适配视网膜屏。

另外还需要移除控件的红色背景。

打开 ViewController.swift,在 viweDidLoad 方法中找到这一句并删除它:

rangeSlider.backgroundColor = .red

Build & run。你会看到:

[译]自定义控件教程: 可复用的 Slider

滑块飘起来了?就是这样!

别急 —— 你已经删掉了花哨的颜色。你的控件并没有消失,但现在背景变成了空白,你需要稍微修饰它一下。

因为大部分开发者喜欢在编写 app 时定制控件让它和所编写的 app 保持一致的外观,因此你需要为 slider 增加一些属性,以便允许对控件外观进行一定的定制。

打开 RangeSlider.swift 添加下列属性:

var trackTintColor = UIColor(white: 0.9, alpha: 1)
var trackHighlightTintColor = UIColor(red: 0, green: 0.45, blue: 0.94, alpha: 1)

然后打开 RangeSliderTrackLayer.swift。

这个 layer 负责渲染滑轨。它继承了 CALayer,现在它只会绘制固定的颜色。

要绘制滑轨,你必须实现 draw(in:) 方法,用 Core Graphics API 进行绘制。

注:关于更多 Core Graphics 的内容,建议阅读本站的 Core Graphics 101 教程系列 ,因为 Core Graphics 已经超出了本教程的范畴。

在 RangeSliderTrackLayer 中添加方法:

override func draw(in ctx: CGContext) {
  guard let slider = rangeSlider else {
    return
  }
  
  let path = UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius)
  ctx.addPath(path.cgPath)
  
  ctx.setFillColor(slider.trackTintColor.cgColor)
  ctx.fillPath()
  
  ctx.setFillColor(slider.trackHighlightTintColor.cgColor)
  let lowerValuePosition = slider.positionForValue(slider.lowerValue)
  let upperValuePosition = slider.positionForValue(slider.upperValue)
  let rect = CGRect(x: lowerValuePosition, y: 0,
                    width: upperValuePosition - lowerValuePosition,
                    height: bounds.height)
  ctx.fill(rect)
}

当滑轨被 clip 时,你填充背景色。然后,再填充高亮颜色。

Build & run,你会看到新的滑轨显示如下:

[译]自定义控件教程: 可复用的 Slider

控件属性改变时的处理

现在控件看起来很漂亮,它的可视化样式是可变化的,它还支持目标-动作模式。

看起来已经完成了?

试想当 slider 渲染之后,属性被代码修改后会发生什么?例如,你可能想修改 slider 的预设值,或者修改滑轨的高亮色,以标出 slider 的有效范围。

现在,属性的 setter 方法还没有观察器。你必须在控件中添加它们。你需要实现当控件 frame 改变是会绘制时的属性观察器。

打开 RangeSlider.swift 将属性定义修改为:

var minimumValue: CGFloat = 0 {
  didSet {
    updateLayerFrames()
  }
}

var maximumValue: CGFloat = 1 {
  didSet {
    updateLayerFrames()
  }
}

var lowerValue: CGFloat = 0.2 {
  didSet {
    updateLayerFrames()
  }
}

var upperValue: CGFloat = 0.8 {
  didSet {
    updateLayerFrames()
  }
}

var trackTintColor = UIColor(white: 0.9, alpha: 1) {
  didSet {
    trackLayer.setNeedsDisplay()
  }
}

var trackHighlightTintColor = UIColor(red: 0, green: 0.45, blue: 0.94, alpha: 1) {
  didSet {
    trackLayer.setNeedsDisplay()
  }
}

var thumbImage = #imageLiteral(resourceName: "Oval") {
  didSet {
    upperThumbImageView.image = thumbImage
    lowerThumbImageView.image = thumbImage
    updateLayerFrames()
  }
}

var highlightedThumbImage = #imageLiteral(resourceName: "HighlightedOval") {
  didSet {
    upperThumbImageView.highlightedImage = highlightedThumbImage
    lowerThumbImageView.highlightedImage = highlightedThumbImage
    updateLayerFrames()
  }
}

对于 trackLayer 你调用的是 setNeedsDisplay 方法,而其它属性则调用 updateLayerFrames() 方法。当你修改 thumbImage 或者 hightlightedThumbImage 时,你还需要同时改变它们的 Image View 的对应属性。

你还添加了一个新属性。hightlitedThumImage 当滑块被高亮时显示。它有助于用户对正在交互的控件有更直观的感受。

然后,找到 updateLayerFrames() 在方法的开始添加:

CATransaction.begin()
CATransaction.setDisableActions(true)

在这个方法的最后添加:

CATransaction.commit()

这些代码将 frame 的 update 方法包裹在一个事务中,让重新绘制更加平滑。它还禁用了 Layer 的隐式动画,这和你之前的做法是一样的,因此 layer 的 frame 会立即刷新。

因为现在每当上限值和下限值被改变时都会自动刷新 frame,所以就可以删除掉 continueTracking(_:with:) 中的下列代码了:

// 3
CATransaction.begin()
CATransaction.setDisableActions(true)

updateLayerFrames()

CATransaction.commit()

关于 range slider 属性变化的处理就这些了。

但是,你还需要用代码来测试一下属性观察器是否正常。

打开 ViewController.swift 在 viewDidLoad() 最后添加:

let time = DispatchTime.now() + 1
DispatchQueue.main.asyncAfter(deadline: time) {
    self.rangeSlider.trackHighlightTintColor = .red
    self.rangeSlider.thumbImage = #imageLiteral(resourceName: "RectThumb")
    self.rangeSlider.highlightedThumbImage = 
      #imageLiteral(resourceName: "HighlightedRect")
}

这段代码在 1 秒中后修改控件的某些属性。还会修改滑轨的高亮颜色未红色,滑块的图片为矩形。

Build & run。一秒中后,你会看到 slider 一开始是这样的:

[译]自定义控件教程: 可复用的 Slider

变成了这样的:

[译]自定义控件教程: 可复用的 Slider

酷吧?!

接下来去哪里?

你已经完成了 range slider,可以用到项目中去了!点击下面的 Download Materials 链接可以下载最终版的项目。

但是,编写通用控件的最大好处是可以在其它项目中——以及和其它开发者分享。

你的控件已经可以发布了吗?

并没有。在分享你控件之前,请考虑以下几点:

文档—— 每个开发者最需要的东西!虽然你觉得自己的代码非常漂亮,实现了自文档化,但其它开发者可不这样认为。好的做法是提供一个公开的 API 文档,至少要对所有公开分享的代码编写文档。也就是对所有公有类和属性编写文档。

例如,你需要在文档中解释 RangeSlider 是什么 —— 它是一个 slider,有 4 个属性:最小值、最大值、上限值、下限值 —— 以及它是干什么的 —— 允许用户以直观的方式指定一个数值范围。

健壮性—— 如果你将上限值设置为比最大值还大的数会怎样?你自己当然不会这样做,但你能保证其他人不会这样干吗?你必须确保控件状态始终是有效的——无论愚蠢的 程序员 试图对它做什么。

API 的设计—— 上一点涉及到了一个更广泛的话题 —— API 的设计。创建灵活的、符合需求的、健壮的 API 能使你的控件被广泛应用(变得十分流行)。

API 设计是一个非常深奥的主题,甚至超出了本教程的范围。如果你有兴趣,请看 Matt Gemmell 的 API 设计的 25 条原则

能够让你的控件分享到全世界的地方有许多。这里有几点建议:

  • GitHub – 分享开源项目的最常见的地方。在 GitHub 上有数不清的 iOS 自定义控件。它使得人们很容易获取你的代码,通过 fork 的方式进行合作,或者提出问题。
  • CocoaPods – 允许人们很容易将你的控件添加到他们的项目中,你可以通过 CocoaPods 分享你的控件,它是一个 iOS 和 macOS 项目的依赖管理工具。
  • Cocoa Controls – 这个网站提供了一个商业控件和开源控件的目录。上面的许多开源控件都是放在 github 上的,它也是一个激发创意的地方。

希望你享受创建这个控件的过程,也许它能激发你创建出自己的自定义控件。如果是这样,请在本贴的评论中分享它 —— 我们期望看到你的作品!

Download Material


以上所述就是小编给大家介绍的《[译]自定义控件教程: 可复用的 Slider》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

分布式服务架构:原理、设计与实战

分布式服务架构:原理、设计与实战

李艳鹏、杨彪 / 电子工业出版社 / 2017-8 / 89.00

《分布式服务架构:原理、设计与实战》全面介绍了分布式服务架构的原理与设计,并结合作者在实施微服务架构过程中的实践经验,总结了保障线上服务健康、可靠的最佳方案,是一本架构级、实战型的重量级著作。 《分布式服务架构:原理、设计与实战》以分布式服务架构的设计与实现为主线,由浅入深地介绍了分布式服务架构的方方面面,主要包括理论和实践两部分。理论上,首先介绍了服务架构的背景,以及从服务化架构到微服务架......一起来看看 《分布式服务架构:原理、设计与实战》 这本书的介绍吧!

MD5 加密
MD5 加密

MD5 加密工具

SHA 加密
SHA 加密

SHA 加密工具

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具