71、iOS常用控件详解

iOS常用控件详解

1. UISwitch相关说明

在iOS 6中开发者呼吁并经过一些“破解”后添加的UISwitch的 onImage offImage 属性,在iOS 7及以后版本中已不起作用。

2. UIStepper

2.1 基本介绍

UIStepper允许用户增加或减少一个数值。它看起来像两个并排的按钮,一个(默认)标有减号,另一个标有加号。用户可以点击或按住按钮,还可以在两个按钮间滑动手指进行交互。它只有一种尺寸,任何设置其大小的尝试都会被忽略。它维护一个数值,即 value 属性。每次用户增加或减少数值时,数值会按 stepValue 改变。如果达到 minimumValue maximumValue ,相应的按钮会被禁用,除非 wraps 属性为 true ,此时数值会从最小值重新开始超过最大值,反之亦然。当用户改变步进器的值时,会报告一个 Value Changed 控制事件。

以下是一个使用UIStepper更新进度视图的示例代码:

@IBAction func doStep(_ sender: Any) {
    let step = sender as! UIStepper
    self.prog.setProgress(
        Float(step.value / (step.maximumValue - step.minimumValue)),
        animated:true)
}

2.2 特性设置

  • 连续更新 :如果 isContinuous true (默认值),长按其中一个按钮会反复更新值,更新开始较慢,然后变快。
  • 自动重复 :如果 autorepeat false ,直到与步进器的整个交互结束,更新的值才会作为 Value Changed 控制事件报告,默认值为 true

2.3 外观定制

步进器的外观可以定制。其轮廓和按钮标题的颜色由 tintColor 决定,该颜色可能从视图层次结构的上级继承。还可以使用以下方法指定构成步进器结构的图像:
- setDecrementImage(_:for:)
- setIncrementImage(_:for:)
- setDividerImage(_:forLeftSegmentState:rightSegmentState:)
- setBackgroundImage(_:for:)

这些图像的工作方式与搜索栏的范围栏类似。背景图像应该是可调整大小的,它们会拉伸到两个按钮后面,图像的一半作为每个按钮的背景。如果按钮被禁用,它会显示 .disabled 背景图像;否则,显示 .normal 背景图像,用户点击时显示 .highlighted 背景图像。如果要提供背景图像,可能需要提供所有三种状态的图像;如果某个状态的背景图像为 nil ,则使用默认图像。同样,可能也需要提供三种分隔图像,以覆盖一个或两个分段被高亮显示的三种组合。增量和减量图像会替换默认的减号和加号,它们会合成在背景图像之上,除非明确提供 .alwaysOriginal 图像,否则它们会被视为模板图像,由 tintColor 着色。如果只提供 .normal 图像,它会自动调整以适应其他两种状态。

3. UIPageControl

3.1 基本介绍

UIPageControl是一排点,每个点称为一个页面,它通常与其他类似页面的界面(如 isPagingEnabled 设置为 true UIScrollView )结合使用。通常需要自己协调页面控制与其他界面的交互。

3.2 常用属性和方法

  • 页面数量 :点的数量由 numberOfPages 属性决定。可以调用 size(forNumberOfPages:) 方法来了解容纳给定数量点所需的最小边界大小。可以使页面控制比点更宽,以增加用户可点击的目标区域。
  • 点击交互 :用户可以点击当前页面点的一侧来增加或减少当前页面,此时页面控制会报告一个 Value Changed 控制事件。
  • 颜色定制 :点的颜色可以区分当前页面( currentPage )和其他页面。默认情况下,当前页面显示为实心点,其他点稍有透明度。可以自定义 pageIndicatorTintColor (一般点的颜色)和 currentPageIndicatorTintColor (当前页面点的颜色),因为默认的点颜色是白色,在正常情况下可能很难看到。
  • 背景颜色 :可以设置 backgroundColor ,以显示用户可点击的区域,或通过对比使点更清晰可见。
  • 单页隐藏 :如果 hidesForSinglePage true ,当 numberOfPages 变为1时,页面控制会变得不可见。
  • 延迟显示 :如果 defersCurrentPageDisplay true ,当用户点击增加或减少页面控制的值时,当前页面的显示不会改变。会报告一个 Value Changed 控制事件,但需要自己的代码处理此操作并调用 updateCurrentPageDisplay

4. UIDatePicker

4.1 基本介绍

UIDatePicker看起来像 UIPickerView ,但它不是 UIPickerView 的子类,它使用 UIPickerView 来绘制自身,但不提供对该选择器视图的官方访问。其目的是表达日期和时间的概念,处理日历和数字的复杂性,让开发者无需处理这些细节。当用户更改其设置时,日期选择器会报告一个 Value Changed 控制事件。

4.2 模式设置

UIDatePicker有四种模式( datePickerMode ),决定其绘制方式:
| 模式 | 显示内容 |
| ---- | ---- |
| .time | 显示时间,例如有小时和分钟组件 |
| .date | 显示日期,例如有月、日和年组件 |
| .dateAndTime | 显示日期和时间,例如显示星期几、月、日,以及小时和分钟组件 |
| .countDownTimer | 显示小时和分钟数,例如有小时和分钟组件 |

4.3 显示设置

  • 区域设置影响 :日期选择器显示的具体组件和值默认取决于用户在设置应用(通用 → 语言与地区 → 地区)中的偏好。例如,美国时间显示1到12的小时数加上分钟和AM或PM,而英国时间显示1到24的小时数加上分钟。如果用户在设置应用中更改区域格式,日期选择器的显示会立即改变。
  • 日历和时区 :日期选择器有 calendar timeZone 属性,分别为 Calendar TimeZone ,默认值为 nil ,这意味着日期选择器会响应用户的系统级设置。也可以手动更改这些值,但不要更改 .countDownTimer 模式的日期选择器的 timeZone ,否则显示的值会偏移,会让自己和用户感到困惑。
  • 分钟间隔 :如果有分钟组件,默认会显示每分钟,但可以通过 minuteInterval 属性更改。最大值为30,此时分钟组件的值为0和30。任何将 minuteInterval 设置为不能被60整除的值的尝试都会被默默忽略。

4.4 日期设置

日期选择器表示的日期(除非其模式为 .countDownTimer )是 date 属性,类型为 Date 。默认日期是日期选择器实例化时的当前时间。对于 .date 模式的日期选择器,默认时间是当地时间的午夜12点;对于 .time 模式的日期选择器,默认日期是今天。内部值以当地时区计算,因此如果更改了日期选择器的 timeZone ,它可能与显示的值不同。

4.5 最大最小日期

日期选择器中启用的最大和最小日期由 maximumDate minimumDate 属性决定。超出此范围的值可能会显示为禁用状态。日期选择器可以显示的范围实际上没有实际限制,因为表示其组件的“滚筒”不是物理的,并且值会在用户旋转时动态添加。以下是一个设置日期选择器初始最小和最大日期的示例代码:

dp.datePickerMode = .date
var dc = DateComponents(year:1954, month:1, day:1)
let c = Calendar(identifier:.gregorian)
let d1 = c.date(from: dc)!
dp.minimumDate = d1
dp.date = d1
dc.year = 1955
let d2 = c.date(from: dc)!
dp.maximumDate = d2

4.6 倒计时模式

.countDownTimer 模式的日期选择器显示的是 countDownDuration ,这是一个 TimeInterval ,是一个表示秒数的双精度浮点数,尽管显示的最小间隔是分钟。 .countDownTimer 日期选择器实际上不会进行倒计时,需要以其他方式进行倒计时,并使用其他界面来显示倒计时。苹果时钟应用的定时器标签显示了一个典型的界面,用户最初配置选择器视图来设置 countDownDuration ,但一旦开始倒计时,选择器视图会隐藏,一个标签会显示剩余时间。需要注意的是, .countDownTimer 日期选择器的 Value Changed 事件不可靠(尤其是在应用启动后不久,以及用户尝试将定时器设置为零时)。解决方法是不要依赖 Value Changed 事件,例如在界面中提供一个按钮,让用户点击以让代码读取日期选择器的 countDownDuration

4.7 日期转换

要在 Date 和字符串之间进行转换,需要使用 DateFormatter

@IBAction func dateChanged(_ sender: Any) {
    let dp = sender as! UIDatePicker
    let d = dp.date
    let df = DateFormatter()
    df.timeStyle = .full
    df.dateStyle = .full
    print(df.string(from: d))
    // Tuesday, August 10, 1954 at 3:16:00 AM GMT-07:00
}

5. UISlider

5.1 基本介绍

UISlider表示一个可连续设置的值( value ,类型为 Float ),介于某个最小值和最大值( minimumValue maximumValue ,默认值分别为0和1)之间。它表现为一个沿着轨道定位的对象,即滑块。当用户更改滑块的位置时,滑块会报告一个 Value Changed 控制事件。如果 isContinuous true (默认值),用户按下并拖动滑块时会连续报告事件;如果为 false ,则仅在用户释放滑块时报告事件。当用户按下滑块时,滑块处于 .highlighted 状态。要通过动画更改滑块的值,可以在动画函数中调用 setValue(_:animated:)

5.2 点击轨道响应

通常希望修改滑块的行为,使用户点击其轨道时,滑块移动到用户点击的位置。但滑块本身不会响应轨道上的点击,不会报告控制事件。不过,可以使用手势识别器实现此功能,以下是一个附加到UISlider的 UITapGestureRecognizer 的操作方法:

@objc func tapped(_ g:UIGestureRecognizer) {
    let s = g.view as! UISlider
    if s.isHighlighted {
        return // tap on thumb, let slider deal with it
    }
    let pt = g.location(in:s)
    let track = s.trackRect(forBounds: s.bounds)
    if !track.insetBy(dx: 0, dy: -10).contains(pt) {
        return // not on track, forget it
    }
    let percentage = pt.x / s.bounds.size.width
    let delta = Float(percentage) * (s.maximumValue - s.minimumValue)
    let value = s.minimumValue + delta
    delay(0.1) {
        UIView.animate(withDuration: 0.15) {
            s.setValue(value, animated:true) // animate sliding the thumb
        }
    }
}

5.3 外观定制

  • 颜色设置 :滑块的 tintColor (可能从视图层次结构的上级继承)决定了滑块左侧轨道的颜色。可以使用 minimumTrackTintColor maximumTrackTintColor 属性更改轨道两部分的颜色,使用 thumbTintColor 属性更改滑块的颜色。
  • 轨道图像 :轨道两端的图像是 minimumValueImage maximumValueImage ,默认值为 nil 。如果将它们设置为实际图像,滑块会尝试在其自身边界内定位它们,并缩小轨道的绘制以进行补偿。可以在子类中重写以下方法来更改此行为:
  • minimumValueImageRect(forBounds:)
  • maximumValueImageRect(forBounds:)
  • trackRect(forBounds:)

以下是一个扩展轨道宽度到滑块全宽,并将图像绘制在滑块边界外的示例:

override func maximumValueImageRect(forBounds bounds: CGRect) -> CGRect {
    return super.maximumValueImageRect(
        forBounds:bounds).offsetBy(dx: 31, dy: 0)
}
override func minimumValueImageRect(forBounds bounds: CGRect) -> CGRect {
    return super.minimumValueImageRect(
        forBounds: bounds).offsetBy(dx: -31, dy: 0)
}
override func trackRect(forBounds bounds: CGRect) -> CGRect {
    var result = super.trackRect(forBounds: bounds)
    result.origin.x = 0
    result.size.width = bounds.size.width
    return result
}
  • 滑块图像 :滑块也是一个图像,可以使用 setThumbImage(_:for:) 方法设置。主要有两种相关状态, .normal .highlighted 。如果为这两种状态都提供图像,用户拖动滑块时滑块会自动更改。默认情况下,图像会在轨道中以滑块当前值表示的点为中心;可以在子类中重写 thumbRect(forBounds:trackRect:value:) 方法来移动此位置。例如:
override func thumbRect(forBounds bounds: CGRect,
    trackRect rect: CGRect, value: Float) -> CGRect {
        return super.thumbRect(forBounds: bounds,
            trackRect: rect, value: value).offsetBy(dx: 0, dy: -7)
}

需要注意的是,增大或偏移滑块的大小可能会误导用户对可触摸拖动区域的判断。滑块本身是可触摸的 UIControl ,只有与滑块边界相交的滑块部分才是可拖动的。用户可能会尝试拖动绘制在滑块边界外的部分,这会失败并让用户感到困惑。一种解决方案是增加滑块的高度,如果使用自动布局,可以在nib编辑器中添加明确的高度约束,或在代码中重写 intrinsicContentSize ;另一种解决方案是创建子类并使用点击测试修改。

  • 轨道图像设置 :轨道由两个图像组成,一个在滑块左侧,一个在右侧。可以使用 setMinimumTrackImage(_:for:) setMaximumTrackImage(_:for:) 方法设置。如果为 .normal .highlighted 状态都提供图像,用户拖动滑块时图像会更改。图像应该是可调整大小的,这样滑块才能巧妙地让用户感觉是在沿着单个静态轨道拖动滑块。实际上有两个图像,当用户拖动滑块时,一个图像水平增长,另一个图像水平缩小。

以下是一个使用单个15×15圆形对象(硬币)图像创建轨道的示例:

let coinEnd = UIImage(named:"coin")!.resizableImage(withCapInsets:
    UIEdgeInsets(top: 0, left: 7, bottom: 0, right: 7), resizingMode: .stretch)
self.setMinimumTrackImage(coinEnd, for:.normal)
self.setMaximumTrackImage(coinEnd, for:.normal)

6. UISegmentedControl

6.1 基本介绍

UISegmentedControl是一排可点击的分段,每个分段类似于一个按钮。用户点击分段来选择选项。默认情况下( isMomentary false ),最近点击的分段会保持选中状态。如果 isMomentary true ,点击的分段会短暂高亮显示(默认情况下,高亮显示与选中状态无区别,但可以更改),之后不会显示分段选择,但内部被点击的分段仍然是选中分段。

6.2 选中状态管理

  • 选中分段索引 :可以使用 selectedSegmentIndex 属性设置和获取选中的分段。当在代码中设置该属性时,即使是 isMomentary true 的分段控件,选中的分段也会保持可见选中状态。 selectedSegmentIndex 值为 UISegmentedControlNoSegment 表示没有分段被选中。当用户点击尚未可见选中的分段时,分段控件会报告一个 Value Changed 事件。
  • 动画选择 :分段控件的选择更改是可动画的,可以在动画函数中更改选择,例如:
UIView.animateWithDuration(0.4, animations: {
    self.seg.selectedSegmentIndex = 1
})

要在用户点击分段时更缓慢地动画更改,可以将分段控件的图层速度设置为小数。

6.3 分段状态管理

  • 启用状态 :可以使用 setEnabled(_:forSegmentAt:) 方法分别启用或禁用分段,并使用 isEnabledForSegment(at:) 方法获取其启用状态。默认情况下,禁用的分段会绘制为褪色状态,用户无法点击,但仍可以在代码中选择它。
  • 颜色设置 :分段控件的轮廓和选择的颜色由 tintColor 决定,该颜色可能从视图层次结构的上级继承。

6.4 分段内容设置

分段可以有标题或图像,设置其中一个时,另一个会变为 nil 。图像被视为模板图像,由 tintColor 着色,除非明确提供 .alwaysOriginal 图像。标题由 tintColor 着色,除非设置其属性以包含不同的颜色。设置和获取现有分段的标题和图像的方法如下:
- setTitle(_:forSegmentAt:) titleForSegment(at:)
- setImage(_:forSegmentAt:) imageForSegment(at:)

如果在代码中创建分段控件,可以使用 init(items:) 方法配置分段,该方法接受一个数组,每个项可以是字符串或图像:

let seg = UISegmentedControl(items:
    [UIImage(named:"one")!.withRenderingMode(.alwaysOriginal), "Two"])
seg.frame.origin = CGPoint(30,30)
self.view.addSubview(seg)

6.5 分段动态管理

以下是动态管理分段的方法:
- insertSegment(withTitle:at:animated:)
- insertSegment(with:at:animated:) (第一个参数是 UIImage
- removeSegment(at:animated:)
- removeAllSegments

可以使用只读的 numberOfSegments 属性获取分段的数量。

6.6 分段大小和位置设置

  • 分段大小 :如果 apportionsSegmentWidthsByContent 属性为 false ,分段大小会相等;如果为 true ,每个分段的宽度会根据其内容单独调整。也可以使用 setWidth(_:forSegmentAt:) 方法显式设置分段的宽度,并使用 widthForSegment(at:) 方法获取宽度。将分段宽度设置为0表示该分段将自动调整大小。
  • 内容偏移 :可以使用 setContentOffset(_:forSegmentAt:) 方法更改分段内内容(标题或图像)的位置,并使用 contentOffsetForSegment(at:) 方法获取偏移量。

6.7 外观定制

可以使用以下方法定制分段控件的外观,这些方法与设置步进器或搜索栏的范围栏外观的方法类似:
- setBackgroundImage(_:for:barMetrics:)
- setDividerImage(_:forLeftSegmentState:rightSegmentState:barMetrics:)
- setTitleTextAttributes(_:for:)
- setContentPositionAdjustment(_:forSegmentType:barMetrics:)

7. 控件特性总结

7.1 事件响应总结

控件名称 主要事件 说明
UIStepper Value Changed 用户改变步进器的值时触发
UIPageControl Value Changed 用户点击改变当前页面时触发
UIDatePicker Value Changed 用户更改日期选择器设置时触发,但 .countDownTimer 模式下不可靠
UISlider Value Changed 用户更改滑块位置时触发, isContinuous 决定触发时机
UISegmentedControl Value Changed 用户点击未选中的分段时触发

7.2 外观定制总结

不同控件有不同的外观定制方式,以下是一个简单的对比:
| 控件名称 | 主要外观定制属性和方法 |
| ---- | ---- |
| UIStepper | tintColor setDecrementImage setIncrementImage setDividerImage setBackgroundImage |
| UIPageControl | pageIndicatorTintColor currentPageIndicatorTintColor backgroundColor |
| UIDatePicker | 主要受系统设置影响,可设置 calendar timeZone minuteInterval 等 |
| UISlider | tintColor minimumTrackTintColor maximumTrackTintColor thumbTintColor setThumbImage setMinimumTrackImage setMaximumTrackImage 等 |
| UISegmentedControl | tintColor setTitle setImage setBackgroundImage setDividerImage setTitleTextAttributes 等 |

7.3 控件使用流程图

graph LR
    A[选择控件类型] --> B{UIStepper}
    A --> C{UIPageControl}
    A --> D{UIDatePicker}
    A --> E{UISlider}
    A --> F{UISegmentedControl}
    B --> B1[设置初始值和范围]
    B --> B2[定制外观]
    B --> B3[处理Value Changed事件]
    C --> C1[设置页面数量]
    C --> C2[定制颜色]
    C --> C3[处理Value Changed事件]
    D --> D1[选择模式]
    D --> D2[设置日期范围和间隔]
    D --> D3[处理Value Changed事件(注意.countDownTimer模式)]
    E --> E1[设置最小值和最大值]
    E --> E2[定制轨道和滑块外观]
    E --> E3[处理Value Changed事件]
    F --> F1[配置分段内容]
    F --> F2[设置选中状态和动画]
    F --> F3[处理Value Changed事件]

8. 实际应用场景示例

8.1 音乐播放应用中的控件使用

在音乐播放应用中,可以综合使用这些控件来提供更好的用户体验:
- UISlider :用于控制音量大小,用户拖动滑块可以连续调整音量。

let volumeSlider = UISlider()
volumeSlider.minimumValue = 0
volumeSlider.maximumValue = 1
volumeSlider.value = 0.5
volumeSlider.addTarget(self, action: #selector(volumeChanged), for:.valueChanged)
self.view.addSubview(volumeSlider)

@objc func volumeChanged(_ sender: UISlider) {
    // 根据滑块的值调整音量
    let volume = sender.value
    // 实现音量调整逻辑
}
  • UISegmentedControl :用于切换播放模式,如单曲循环、顺序播放、随机播放。
let playModeSegment = UISegmentedControl(items: ["单曲循环", "顺序播放", "随机播放"])
playModeSegment.selectedSegmentIndex = 1
playModeSegment.addTarget(self, action: #selector(playModeChanged), for:.valueChanged)
self.view.addSubview(playModeSegment)

@objc func playModeChanged(_ sender: UISegmentedControl) {
    let selectedIndex = sender.selectedSegmentIndex
    // 根据选中的分段设置播放模式
}
  • UIDatePicker :用于设置定时关闭播放的时间。
let timerPicker = UIDatePicker()
timerPicker.datePickerMode = .countDownTimer
timerPicker.addTarget(self, action: #selector(timerChanged), for:.valueChanged)
self.view.addSubview(timerPicker)

@objc func timerChanged(_ sender: UIDatePicker) {
    let duration = sender.countDownDuration
    // 根据时长设置定时关闭逻辑
}

8.2 图片浏览应用中的控件使用

在图片浏览应用中:
- UIPageControl :与 UIScrollView 配合使用,显示当前图片的页码。

let pageControl = UIPageControl()
pageControl.numberOfPages = imageCount
pageControl.currentPage = 0
pageControl.addTarget(self, action: #selector(pageChanged), for:.valueChanged)
self.view.addSubview(pageControl)

@objc func pageChanged(_ sender: UIPageControl) {
    let currentPage = sender.currentPage
    // 根据当前页码滚动到对应的图片
}
  • UISlider :可以用于调整图片的缩放比例。
let zoomSlider = UISlider()
zoomSlider.minimumValue = 1
zoomSlider.maximumValue = 5
zoomSlider.value = 1
zoomSlider.addTarget(self, action: #selector(zoomChanged), for:.valueChanged)
self.view.addSubview(zoomSlider)

@objc func zoomChanged(_ sender: UISlider) {
    let zoomScale = sender.value
    // 根据缩放比例调整图片的显示大小
}

9. 注意事项和常见问题

9.1 UIDatePicker的.countDownTimer模式问题

  • 问题描述 .countDownTimer 模式下 Value Changed 事件不可靠,尤其是在应用启动后不久和用户尝试将定时器设置为零时。
  • 解决方案 :不要依赖 Value Changed 事件,例如在界面中提供一个按钮,让用户点击以让代码读取日期选择器的 countDownDuration

9.2 UISlider的拇指区域问题

  • 问题描述 :增大或偏移滑块的拇指可能会误导用户对可触摸拖动区域的判断,只有与滑块边界相交的拇指部分才是可拖动的。
  • 解决方案
  • 增加滑块的高度,使用自动布局时可在nib编辑器中添加明确的高度约束,或在代码中重写 intrinsicContentSize
  • 创建子类并使用点击测试修改,重写 hitTest 方法。

9.3 UISegmentedControl的高度问题

  • 问题描述 :分段控件的高度不会自动增加以适应过高的分段图像,图像高度会被压缩。
  • 解决方案 :使用自动布局时,通过约束或重写 intrinsicContentSize 来更改高度,或设置背景图像。

10. 总结

通过对UIStepper、UIPageControl、UIDatePicker、UISlider和UISegmentedControl等常用iOS控件的详细介绍,我们了解了它们的基本功能、事件响应、外观定制以及实际应用场景。在开发iOS应用时,合理选择和使用这些控件可以大大提升用户体验。同时,要注意每个控件可能出现的问题,并采取相应的解决方案。希望本文能为iOS开发者在使用这些控件时提供有价值的参考。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值