70、iOS 搜索栏与控件的深入解析

iOS 搜索栏与控件的深入解析

1. UISearchBar 概述

UISearchBar 本质上是文本字段的包装器,它将文本字段作为其子视图之一,但无法直接访问该文本字段。默认情况下,它显示为带有放大镜图标的圆角矩形,用户可在此输入文本。不过,它本身并不执行搜索操作,也不显示搜索结果,常见的做法是在表格视图中显示搜索结果,而 UISearchController 类能轻松实现这一点。

2. UISearchBar 的基本属性
  • 文本与占位符 :搜索栏的当前文本通过 text 属性获取,还可以设置占位符,当没有输入文本时占位符会显示。
  • 提示信息 :可在搜索栏上方显示提示信息,解释其用途。
  • 代理方法 :通过 UISearchBarDelegate 代理方法可监听编辑事件,具体如下:
  • searchBarShouldBeginEditing(_:)
  • searchBarTextDidBeginEditing(_:)
  • searchBar(_:textDidChange:)
  • searchBar(_:shouldChangeTextIn:replacementText:)
  • searchBarShouldEndEditing(_:)
  • searchBarTextDidEndEditing(_:)
3. UISearchBar 的样式设置
  • barStyle :有两种样式可选:
  • .default :扁平浅灰色背景和白色搜索字段。
  • .black :黑色背景和黑色搜索字段。
  • searchBarStyle :有三种样式:
  • .default
  • .prominent :与 .default 相同。
  • .minimal :透明背景和深色透明搜索字段。
  • 背景颜色与图像 :可通过 barTintColor 更改背景颜色,若 barStyle .black ,该颜色也会影响搜索字段本身。还能设置自定义背景图像,使用 setBackgroundImage(_:for:barMetrics:) 方法,此图像会覆盖其他确定背景的方式。
4. 搜索字段的调整
  • 位置调整 :可使用 searchFieldBackgroundPositionAdjustment 属性调整搜索字段相对于背景的位置,使用 searchTextPositionAdjustment 属性调整文本在搜索字段内的位置。
  • 替换搜索字段图像 :调用 setSearchFieldBackgroundImage(_:for:) 方法替换搜索字段的图像,第二个参数为 UIControl.State ,可能的值为 .normal .disabled ,但 API 未提供禁用搜索字段的方法,可通过以下代码实现:
for v in self.sb.subviews[0].subviews {
    if let tf = v as? UITextField {
        tf.isEnabled = false
        break
    }
}
5. 搜索栏的按钮设置
  • 内部按钮 :搜索字段有文本时会自动显示内部取消按钮(通常是圆圈中的 X)。右侧可能显示搜索结果按钮( showsSearchResultsButton )或书签按钮( showsBookmarkButton ),若同时要求显示两者,将显示搜索结果按钮。输入文本时,这些按钮会消失以显示取消按钮。
  • 外部取消按钮 :可通过 showsCancelButton 属性或 setShowsCancelButton(_:animated:) 方法显示外部取消按钮。
  • 按钮点击代理方法
  • searchBarResultsListButtonClicked(_:)
  • searchBarBookmarkButtonClicked(_:)
  • searchBarCancelButtonClicked(_:)
  • 自定义图标 :使用 setImage(_:for:state:) 方法自定义搜索图标和内部右侧图标的图像,图标类型通过 UISearchBar.Icon 指定:
  • .search
  • .clear (内部取消按钮)
  • .bookmark
  • .resultsList
  • 状态参数可选 .normal .highlighted ,若未提供正常图像,将使用默认图像;若提供正常图像但未提供高亮图像,两者都将使用正常图像。设置 isSearchResultsButtonSelected true 可反转搜索结果按钮的行为。
6. 范围按钮

搜索栏还可显示范围按钮,用于让用户改变搜索的含义。通过 showsScopeBar 属性使范围按钮显示,按钮标题通过 scopeButtonTitles 属性设置,当前选中的范围按钮通过 selectedScopeButtonIndex 属性获取。用户点击不同范围按钮时,代理会收到通知: searchBar(_:selectedScopeButtonIndexDidChange:)

范围栏的外观可高度自定义:
- 背景图像 :通过 scopeBarBackgroundImage 设置范围栏的背景图像,会根据需要拉伸或平铺。
- 按钮背景图像 :使用 setScopeBarButtonBackgroundImage(_:for:) 方法设置按钮的背景图像,状态可选 .normal .selected
- 分隔线图像 :调用 setScopeBarButtonDividerImage(_:forLeftSegmentState:rightSegmentState:) 方法自定义按钮之间的分隔线图像。
- 文本属性 :使用 setScopeBarButtonTitleTextAttributes(_:for:) 方法自定义范围按钮标题的文本属性。

7. 其他设置
  • 外部取消按钮 :虽然无法直接通过搜索栏访问外部取消按钮,但它是 UIBarButtonItem ,可使用 UIBarButtonItem 外观代理进行自定义。
  • 键盘处理 :搜索栏的键盘显示问题与文本字段相同,其文本输入属性也与文本字段类似。用户点击键盘上的搜索键时,代理会收到通知,此时需调用 resignFirstResponder 方法关闭键盘并执行搜索操作: searchBarSearchButtonClicked(_:)
  • 嵌入方式 :搜索栏可作为条形按钮项的自定义视图嵌入工具栏或导航栏,也可作为标题视图嵌入导航栏。此外, UISearchBar 本身也可作为顶部栏使用。
8. UIControl 概述

UIControl 是 UIView 的子类,主要作为多个内置控件类的超类,赋予它们共同的行为。控件的重要共性是能自动跟踪和分析触摸事件,并通过动作消息将其作为重要的控制事件报告给代码。每个控件实现了部分可能的控制事件,控制事件如下:
| 控制事件 | 描述 |
| ---- | ---- |
| .touchDown | 触摸按下 |
| .touchDownRepeat | 重复触摸按下 |
| .touchDragInside | 在控件内拖动 |
| .touchDragOutside | 在控件外拖动 |
| .touchDragEnter | 从控件外拖入 |
| .touchDragExit | 从控件内拖出 |
| .touchUpInside | 在控件内抬起触摸 |
| .touchUpOutside | 在控件外抬起触摸 |
| .touchCancel | 触摸取消 |
| .valueChanged | 值改变 |
| .editingDidBegin | 编辑开始 |
| .editingChanged | 编辑内容改变 |
| .editingDidEnd | 编辑结束 |
| .editingDidEndOnExit | 编辑在退出时结束 |
| .allTouchEvents | 所有触摸事件 |
| .allEditingEvents | 所有编辑事件 |
| .allEvents | 所有事件 |

控制事件大致分为三组:
- 用户触摸屏幕(如 Touch Down Touch Drag Inside 等)。
- 用户编辑文本(如 Editing Did Begin Editing Changed 等)。
- 用户改变控件的值( Value Changed )。

不同控件通常发出的控制事件如下:
| 控件 | 控制事件 |
| ---- | ---- |
| UIButton | 所有触摸事件 |
| UIDatePicker | 值改变 |
| UIPageControl | 所有触摸事件,值改变 |
| UIRefreshControl | 值改变 |
| UISegmentedControl | 值改变 |
| UISlider | 所有触摸事件,值改变 |
| UISwitch | 所有触摸事件,值改变 |
| UIStepper | 所有触摸事件,值改变 |
| UITextField | 除抬起事件外的所有触摸事件,所有编辑事件 |
| UIControl(通用) | 所有触摸事件 |

每个控件还有一个主要控制事件 primaryActionTriggered
- 对于 UIButton ,主要控制事件是 Touch Up Inside
- 对于 UITextField ,主要控制事件是 Did End On Exit
- 其他控件的主要控制事件是 Value Changed

9. 控制事件的处理

对于每个想要监听的控制事件,可将一个或多个目标 - 动作对附加到控件上,可在 nib 编辑器或代码中完成。每个控件的控制事件及其目标 - 动作对构成一个调度表,可通过以下方法和属性操作和查询该调度表:
- addTarget(_:action:for:)
- removeTarget(_:action:for:)
- actions(forTarget:forControlEvent:)
- allTargets
- allControlEvents (至少有一个目标 - 动作对的控制事件的位掩码)

动作方法可以采用以下三种签名之一:
- 控件和 UIEvent
- 仅控件
- 无参数

其中,仅包含控件的签名最为常见。

当控制事件发生时,控件会查询其调度表,找到与该控制事件关联的所有目标 - 动作对,并将每个动作消息发送给相应的目标。实际上, UIControl 并不直接发送动作消息,而是通知共享应用程序发送。若要让控件立即发出与特定控制事件对应的动作消息,可调用 sendActions(for:) 方法。

10. 控件状态

控件有 isEnabled isSelected isHighlighted 属性,它们相互独立,共同对应控件的状态,状态是三种可能值的位掩码( UIControl.State ):
- .highlighted isHighlighted true
- .disabled isEnabled false
- .selected isSelected true
- .normal :对应零状态位掩码,即 isEnabled true isSelected isHighlighted 都为 false

未启用的控件不会响应用户交互,控件是否以不同方式显示以提示用户这一事实取决于具体控件。例如,禁用的 UISwitch 会变暗淡,而默认的圆角矩形文本字段不会给用户明显的禁用提示。控件的选择和高亮显示的视觉效果也因控件而异。

11. 控件内容对齐

控件有 contentHorizontalAlignment contentVerticalAlignment 属性,仅当控件有可对齐的内容时才起作用,常用于 UIButton 定位其标题和内部图像。

12. UISwitch 控件

UISwitch 用于表示布尔值,外观类似滑动开关,其 isOn 属性为 true false 。用户可通过滑动或点击来切换开关状态,状态改变时会报告 Value Changed 控制事件。可使用 setOn(_:animated:) 方法改变 isOn 属性的值并伴有动画效果。

UISwitch 的大小是固定的,任何设置其大小的尝试都会被忽略。可通过以下属性自定义其外观:
- onTintColor :开关处于开启状态时轨道的颜色。
- thumbTintColor :可滑动按钮的颜色。
- tintColor :开关处于关闭状态时轮廓的颜色。

需要注意的是,没有 offTintColor 属性,开关处于关闭状态时轨道是透明的,无法自定义其颜色。一种变通方法是在开关后面放置一个彩色的开关形状图像,代码如下:

func putColor(_ color: UIColor, behindSwitch sw: UISwitch) {
    guard sw.superview != nil else {return}
    let onswitch = UISwitch()
    onswitch.isOn = true
    let r = UIGraphicsImageRenderer(bounds:sw.bounds)
    let im = r.image { ctx in
        // 图像绘制代码
    }
}

综上所述,UISearchBar 和 UIControl 及其子类在 iOS 开发中有着丰富的功能和多样的自定义选项,开发者可以根据具体需求灵活运用这些特性,打造出符合用户体验的界面。

iOS 搜索栏与控件的深入解析

13. 触摸事件的特殊情况

在触摸事件中,没有明确的 Touch Down Inside 事件,因为任何触摸事件序列都始于 Touch Down ,且该事件必须在控件内部发生,否则这一系列触摸事件就不属于该控件,也就不会产生控制事件。

对于控件“外部”的定义存在一定的虚拟范围。当用户在控件内点击并开始拖动时,即使拖动超出了控件的边界, Inside 事件仍会触发。但当拖动距离控件超过一定距离时,会越过一个无形的边界,触发 Touch Drag Exit 事件,此后将报告 Outside 事件,直到拖动回到无形边界内,此时触发 Touch Drag Enter 事件,再次报告 Inside 事件。

UIButton 为例,越过这个无形边界时,按钮会自动取消高亮显示。因此,若要捕获合法的按钮点击,可能只需考虑 Touch Up Inside 事件。

对于其他控件,情况可能会稍有复杂。例如, UISwitch 在拖动到一定距离时会取消高亮显示,但触摸仍被视为合法,仍可改变其值。所以,当用户手指离开屏幕时, UISwitch 会报告 Touch Up Inside 事件,即使同时报告 Touch Drag Outside 事件。

下面是一个简单的 mermaid 流程图,展示触摸事件的流程:

graph LR
    A[Touch Down] --> B{Inside Control?}
    B -- Yes --> C[Inside Events]
    B -- No --> D[No Control Events]
    C --> E{Drag Outside Boundary?}
    E -- Yes --> F[Touch Drag Exit]
    E -- No --> C
    F --> G[Outside Events]
    G --> H{Drag Back Inside?}
    H -- Yes --> I[Touch Drag Enter]
    H -- No --> G
    I --> C
14. 自定义控件示例

接下来通过一个简单的示例,展示如何编写自定义控件。假设我们要创建一个自定义的圆形按钮,它在被点击时会改变颜色。

import UIKit

class CustomCircleButton: UIControl {
    private var circleLayer: CAShapeLayer!

    override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        setup()
    }

    private func setup() {
        circleLayer = CAShapeLayer()
        circleLayer.path = UIBezierPath(ovalIn: bounds).cgPath
        circleLayer.fillColor = UIColor.blue.cgColor
        layer.addSublayer(circleLayer)

        addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
    }

    @objc private func buttonTapped() {
        circleLayer.fillColor = UIColor.red.cgColor
        sendActions(for: .valueChanged)
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        circleLayer.path = UIBezierPath(ovalIn: bounds).cgPath
    }
}

在上述代码中,我们创建了一个名为 CustomCircleButton 的自定义控件,它继承自 UIControl 。在 setup 方法中,我们创建了一个圆形的 CAShapeLayer 并添加到控件的图层上。同时,我们为控件添加了一个 Touch Up Inside 事件的目标 - 动作对,当按钮被点击时,会调用 buttonTapped 方法,该方法会改变圆形的填充颜色并发送 Value Changed 控制事件。

15. 总结与应用建议

通过对 UISearchBar、UIControl 及其子类的详细介绍,我们了解到这些控件在 iOS 开发中具有丰富的功能和强大的自定义能力。以下是一些应用建议:
- UISearchBar
- 合理使用代理方法,监听用户的编辑和操作事件,实现搜索功能。
- 充分利用样式设置和自定义选项,打造符合应用风格的搜索栏。
- 注意处理键盘显示和搜索结果展示的问题,提升用户体验。
- UIControl
- 明确不同控件的主要控制事件,根据需求添加目标 - 动作对。
- 利用控件状态属性,实现不同状态下的视觉效果。
- 对于复杂的交互需求,可以考虑自定义控件,满足特定的业务逻辑。

下面是一个总结表格,对比不同控件的主要特点:
| 控件 | 主要功能 | 主要控制事件 | 自定义选项 |
| ---- | ---- | ---- | ---- |
| UISearchBar | 提供搜索输入框 | 编辑事件、按钮点击事件 | 样式、背景、图标等 |
| UIButton | 触发操作 | Touch Up Inside | 标题、图像、颜色等 |
| UISwitch | 表示布尔值 | Value Changed | 颜色 |
| CustomCircleButton(自定义) | 自定义交互 | Value Changed | 形状、颜色 |

总之,开发者在实际开发中应根据具体需求,灵活运用这些控件的特性,不断优化用户界面和交互体验。通过合理的设计和编码,能够打造出功能强大、美观易用的 iOS 应用。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值