72、iOS 控件与视图开发全解析

iOS 控件与视图开发全面解析

iOS 控件与视图开发全解析

1. 分段控件定制

分段控件(Segmented Control)的定制无需针对每个状态进行,对于未指定的状态,它会使用 .normal 状态的设置。设置背景图像会改变分段控件的高度。

setContentPositionAdjustment 方法中, segmentType: 参数是必需的,因为默认情况下,两端的分段有圆角(如果只有一个分段,其两端都是圆角)。该参数( UISegmentedControl.Segment )可让你区分以下几种情况:
- .any
- .left
- .center
- .right
- .alone

2. UIButton 详解

2.1 按钮类型

按钮(UIButton)是基本的可点击控件,可能包含标题、图像和背景图像,也可能有背景颜色。按钮有不同的类型,通过 init(type:) 初始化,类型( UIButton.ButtonType )如下:
| 按钮类型 | 描述 |
| ---- | ---- |
| .system | 标题文本以按钮的 tintColor 显示,该颜色可能从视图层次结构继承。点击按钮时,标题文本颜色会暂时变为背后颜色(可能是按钮背景色)。图像被视为模板图像,由 tintColor 着色,除非明确提供 .alwaysOriginal 图像。点击时,图像(即使不是模板图像)也会暂时着色。 |
| .detailDisclosure .infoLight .infoDark .contactAdd | 这些本质上都是 .system 按钮,图像会自动设置为标准图像。前三种是圆圈中的 “i”,最后一种是圆圈中的加号。两种信息类型相同,与 .detailDisclosure 的区别在于,它们默认 showsTouchWhenHighlighted true 。 |
| .custom | 标题和图像没有自动着色,图像默认是普通图像。 |

2.2 按钮特性设置

按钮有标题、标题颜色、标题阴影颜色,也可以提供属性标题,通过 NSAttributedString 一次性指定这些特性。需要区分按钮的内部图像和背景图像,背景图像必要时会拉伸以填充按钮边界,内部图像若小于按钮则不调整大小。

按钮的六个特性(标题、标题颜色、标题阴影颜色、属性标题、图像和背景图像)可以根据按钮的当前状态( .highlighted .selected .disabled .normal )而变化。设置这些特性的方法都需要指定相应的状态或使用位掩码指定多个状态:
- setTitle(_:for:)
- setTitleColor(_:for:)
- setTitleShadowColor(_:for:)
- setAttributedTitle(_:for:)
- setImage(_:for:)
- setBackgroundImage(_:for:)

获取这些特性时,需要指定感兴趣的单个状态或询问当前显示的特性:
- title(for:) currentTitle
- titleColor(for:) currentTitleColor
- titleShadowColor(for:) currentTitleShadowColor
- attributedTitle(for:) currentAttributedTitle
- image(for:) currentImage
- backgroundImage(for:) currentBackgroundImage

2.3 特性显示规则

在使用 set 方法配置这些特性时,如果未为特定状态指定特性,或按钮同时处于多个状态,会使用内部启发式方法确定显示内容,一般规则如下:
- 如果为特定状态(高亮、选中或禁用)指定了特性,且按钮仅处于该状态,则使用该特性。
- 如果未为特定状态指定特性,且按钮仅处于该状态,则使用该特性的正常版本作为后备。
- 状态组合通常会使按钮回退到正常状态的特性。例如,按钮同时处于高亮和选中状态时,会显示正常标题。

2.4 按钮绘制属性

UIButton 还有一些属性决定其在不同状态下的绘制方式:
- showsTouchWhenHighlighted :若为 true ,按钮高亮时会投射圆形白色光晕。若有内部图像,光晕在其后方居中;若无内部图像,光晕在按钮中心居中。光晕绘制在背景图像或颜色之上。
- adjustsImageWhenHighlighted :在 .custom 按钮中,若该属性为 true (默认),且没有单独的高亮图像(且 showsTouchWhenHighlighted false ),按钮高亮时正常图像会变暗,适用于内部图像和背景图像。 .system 按钮已对高亮图像着色,此属性不适用。
- adjustsImageWhenDisabled :若为 true ,且没有单独的禁用图像,按钮禁用时正常图像会变灰,适用于内部图像和背景图像。 .custom 按钮默认 true .system 按钮默认 false

2.5 按钮大小与布局

按钮有与内容相关的自然大小。使用自动布局时,按钮可自动采用该大小作为 intrinsicContentSize ,也可在子类中重写 intrinsicContentSize 或应用显式约束来修改。不使用自动布局时,在代码中创建按钮后,需调用 sizeToFit 或指定显式大小,否则按钮可能大小为 .zero 而不可见。

2.6 按钮元素访问与定位

标题通过 UILabel 显示,可通过按钮的 titleLabel 访问标题的标签特性。内部图像由 UIImageView 绘制,可通过按钮的 imageView 访问其特性。

图像和标题的整体内部位置由按钮的 contentVerticalAlignment contentHorizontalAlignment 控制,也可通过设置 contentEdgeInsets titleEdgeInsets imageEdgeInsets 微调图像和标题的位置。

以下四个方法可访问按钮元素的定位:
- titleRect(forContentRect:)
- imageRect(forContentRect:)
- contentRect(forBounds:)
- backgroundRect(forBounds:)

这些方法在按钮重绘时调用,包括每次状态改变时。默认情况下,内容矩形和背景矩形相同,可在子类中重写这些方法改变按钮元素的定位。

2.7 自定义按钮示例

class CustomButton: UIButton {
    override func backgroundRect(forBounds bounds: CGRect) -> CGRect {
        var result = super.backgroundRect(forBounds: bounds)
        if self.isHighlighted {
            result = result.insetBy(dx: 3, dy: 3)
        }
        return result
    }

    override var intrinsicContentSize: CGSize {
        return super.intrinsicContentSize.sizeByDelta(dw: 25, dh: 20)
    }
}

这个自定义按钮子类增加了按钮的 intrinsicContentSize 以提供更大的内容边距,并在高亮时缩小按钮以提供反馈。

3. 自定义控件

3.1 自定义 UIControl 子类

创建自定义 UIControl 子类可自动获得内置的触摸事件,还可重写以下方法自定义触摸跟踪,同时有属性指示触摸跟踪是否正在进行:
- beginTracking(_:with:)
- continueTracking(_:with:)
- endTracking(_:with:)
- cancelTracking(with:)
- isTracking
- isTouchInside

使用自定义 UIControl 子类的主要原因可能是获得控制事件的便利性,触摸跟踪方法比 UIResponder 触摸方法更高级,它们跟踪单个触摸, beginTracking continueTracking 返回 Bool ,可停止当前触摸跟踪。

3.2 自定义旋钮控件示例

class MyKnob: UIControl {
    var angle: CGFloat = 0 {
        didSet {
            self.angle = min(max(self.angle, 0), 5) // 限制角度范围
            self.transform = CGAffineTransform(rotationAngle: self.angle)
        }
    }
    private var initialAngle: CGFloat = 0

    override func draw(_ rect: CGRect) {
        UIImage(named: "knob")!.draw(in: rect)
    }

    func pToA(_ t: UITouch) -> CGFloat {
        let loc = t.location(in: self)
        let c = CGPoint(x: self.bounds.midX, y: self.bounds.midY)
        return atan2(loc.y - c.y, loc.x - c.x)
    }

    override func beginTracking(_ t: UITouch, with _: UIEvent?) -> Bool {
        self.initialAngle = pToA(t)
        return true
    }

    override func continueTracking(_ t: UITouch, with _: UIEvent?) -> Bool {
        let ang = pToA(t) - self.initialAngle
        let absoluteAngle = self.angle + ang
        switch absoluteAngle {
        case -CGFloat.greatestFiniteMagnitude...0:
            self.angle = 0
            self.sendActions(for: .valueChanged)
            return false
        case 5...CGFloat.greatestFiniteMagnitude:
            self.angle = 5
            self.sendActions(for: .valueChanged)
            return false
        default:
            self.angle = absoluteAngle
            if self.isContinuous {
                self.sendActions(for: .valueChanged)
            }
            return true
        }
    }

    override func endTracking(_: UITouch?, with _: UIEvent?) {
        self.sendActions(for: .valueChanged)
    }
}

这个自定义旋钮控件从最小位置开始,内部角度值为 0,可通过单指顺时针旋转到最大位置,内部角度值为 5 弧度。通过重写触摸跟踪方法实现旋转功能,并在角度超出范围时进行限制。

graph TD;
    A[开始] --> B[触摸开始];
    B --> C[记录初始角度];
    C --> D[触摸移动];
    D --> E[计算角度变化];
    E --> F{角度是否超出范围};
    F -- 是 --> G[限制角度并发送事件];
    F -- 否 --> H[更新角度并发送事件];
    G --> I[结束触摸];
    H --> I;
    I --> J[发送值改变事件];

4. 导航栏、工具栏和标签栏

4.1 栏的类型和使用场景

有三种栏类型:导航栏( UINavigationBar )、工具栏( UIToolbar )和标签栏( UITabBar ),它们可以独立使用,但通常与内置视图控制器结合使用:
| 栏类型 | 位置 | 常用结合控制器 |
| ---- | ---- | ---- |
| UINavigationBar | 屏幕顶部 | UINavigationController |
| UIToolbar | 屏幕底部或顶部,底部更常见 | UINavigationController |
| UITabBar | 屏幕底部 | UITabBarController |

4.2 栏的位置和度量

如果栏要占据屏幕顶部,其表观高度应增加以覆盖透明状态栏。 UINavigationController 拥有的 UINavigationBar 会自动处理,否则需要手动设置。iOS 提供了栏位置的概念, UIBarPositioning 协议(由 UINavigationBar UIToolbar UISearchBar 采用)定义了 barPosition 属性,可能的值( UIBarPosition )如下:
- .any
- .bottom
- .top
- .topAttached

barPosition 是只读的,可通过栏的委托设置。委托协议 UINavigationBarDelegate UIToolbarDelegate UISearchBarDelegate 都遵循 UIBarPositioningDelegate ,定义了 position(for:) 方法,可让栏的委托指定栏的 barPosition

class ViewController: UIViewController, UINavigationBarDelegate {
    @IBOutlet weak var navbar: UINavigationBar!
    override func viewDidLoad() {
        super.viewDidLoad()
        self.navbar.delegate = self
    }

    func position(for bar: UIBarPositioning) -> UIBarPosition {
        return .topAttached
    }
}

如果栏的委托在 position(for:) 实现中返回 .topAttached ,栏的表观高度将向上扩展以覆盖状态栏。为确保最终位置正确,栏的顶部应与安全区域布局指南的顶部有零常量约束。同样,底部与安全区域布局指南底部有零常量约束的工具栏或标签栏,其表观高度将在 iPhone X 的主屏幕指示器后方向下扩展。

栏的高度也反映在其栏度量中,这指的是应用方向改变时栏的标准高度变化。

4.3 注意事项

栏的实际高度不变,只是绘制时扩展,由于 clipsToBounds false 才可见,因此不应将栏的 clipsToBounds 设置为 true

综上所述,在 iOS 开发中,合理运用各种控件和栏的特性,能够创建出功能丰富、用户体验良好的应用界面。通过自定义控件和栏的位置、外观等,可以满足不同的设计需求。

5. UISearchBar 概述

虽然前文未详细提及,但 UISearchBar 也是一个重要的控件,它可以独立作为顶部栏使用。UISearchBar 常用于在应用中提供搜索功能,用户可以在其中输入搜索关键字来查找相关内容。

5.1 UISearchBar 的委托设置

和导航栏、工具栏类似,UISearchBar 也可以通过委托来设置其位置。由于 UISearchBar 采用了 UIBarPositioning 协议,其 barPosition 属性同样可以通过委托的 position(for:) 方法来指定。示例代码如下:

class ViewController: UIViewController, UISearchBarDelegate {
    @IBOutlet weak var searchBar: UISearchBar!
    override func viewDidLoad() {
        super.viewDidLoad()
        self.searchBar.delegate = self
    }

    func position(for bar: UIBarPositioning) -> UIBarPosition {
        return .topAttached
    }
}

通过上述代码,我们可以将 UISearchBar 的位置设置为 .topAttached ,使其表观高度向上扩展以覆盖状态栏。

5.2 UISearchBar 的常用属性和方法

UISearchBar 有许多常用的属性和方法,以下是一些常见的:
| 属性/方法 | 描述 |
| ---- | ---- |
| text | 获取或设置搜索框中的文本内容。 |
| placeholder | 设置搜索框的占位文本,提示用户输入内容。 |
| showsCancelButton | 控制是否显示取消按钮。 |
| delegate | 设置搜索栏的委托,用于处理搜索相关的事件。 |
| searchBarSearchButtonClicked(_:) | 委托方法,当用户点击搜索按钮时调用。 |
| searchBarCancelButtonClicked(_:) | 委托方法,当用户点击取消按钮时调用。 |

以下是一个简单的使用示例:

class ViewController: UIViewController, UISearchBarDelegate {
    @IBOutlet weak var searchBar: UISearchBar!

    override func viewDidLoad() {
        super.viewDidLoad()
        searchBar.delegate = self
        searchBar.placeholder = "请输入搜索内容"
        searchBar.showsCancelButton = true
    }

    func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
        if let searchText = searchBar.text {
            // 处理搜索逻辑
            print("搜索内容: \(searchText)")
        }
    }

    func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
        searchBar.text = ""
        searchBar.resignFirstResponder()
    }
}

6. 控件事件处理

在 iOS 开发中,控件事件处理是非常重要的一部分,它可以让应用对用户的操作做出响应。不同的控件有不同的事件,例如按钮的点击事件、分段控件的选择改变事件等。

6.1 按钮事件处理

对于 UIButton,常见的事件是点击事件。可以通过 addTarget(_:action:for:) 方法来为按钮添加事件处理。示例代码如下:

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        let button = UIButton(type: .system)
        button.setTitle("点击我", for: .normal)
        button.addTarget(self, action: #selector(buttonClicked), for: .touchUpInside)
        button.frame = CGRect(x: 100, y: 200, width: 100, height: 50)
        view.addSubview(button)
    }

    @objc func buttonClicked() {
        print("按钮被点击了")
    }
}

在上述代码中,我们创建了一个系统类型的按钮,并为其添加了点击事件处理方法 buttonClicked 。当按钮被点击时,会调用该方法并打印相应的信息。

6.2 分段控件事件处理

分段控件的常见事件是选择改变事件。同样可以使用 addTarget(_:action:for:) 方法来处理。示例代码如下:

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        let segmentedControl = UISegmentedControl(items: ["选项1", "选项2", "选项3"])
        segmentedControl.selectedSegmentIndex = 0
        segmentedControl.addTarget(self, action: #selector(segmentedControlValueChanged), for: .valueChanged)
        segmentedControl.frame = CGRect(x: 100, y: 300, width: 200, height: 30)
        view.addSubview(segmentedControl)
    }

    @objc func segmentedControlValueChanged(_ segmentedControl: UISegmentedControl) {
        let selectedIndex = segmentedControl.selectedSegmentIndex
        print("选择的选项索引: \(selectedIndex)")
    }
}

在这个例子中,我们创建了一个分段控件,并为其添加了值改变事件处理方法 segmentedControlValueChanged 。当分段控件的选择发生改变时,会调用该方法并打印选择的选项索引。

6.3 自定义控件事件处理

对于自定义控件,如前面提到的 MyKnob 旋钮控件,通过 sendActions(for:) 方法来发送事件。在 MyKnob 中,当旋转操作结束或角度超出范围时,会发送 .valueChanged 事件。在使用自定义控件时,可以通过 addTarget(_:action:for:) 方法来监听这些事件。示例代码如下:

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        let knob = MyKnob()
        knob.frame = CGRect(x: 100, y: 400, width: 100, height: 100)
        knob.addTarget(self, action: #selector(knobValueChanged), for: .valueChanged)
        view.addSubview(knob)
    }

    @objc func knobValueChanged(_ knob: MyKnob) {
        print("旋钮角度: \(knob.angle)")
    }
}

在上述代码中,我们创建了一个 MyKnob 旋钮控件,并为其添加了值改变事件处理方法 knobValueChanged 。当旋钮的角度发生变化时,会调用该方法并打印当前的角度值。

graph LR;
    A[用户操作] --> B{控件类型};
    B -- 按钮 --> C[点击事件];
    B -- 分段控件 --> D[选择改变事件];
    B -- 自定义控件 --> E[自定义事件];
    C --> F[调用事件处理方法];
    D --> F;
    E --> F;
    F --> G[处理相应逻辑];

7. 总结与展望

7.1 总结

在 iOS 开发中,各种控件和栏的使用为应用开发提供了丰富的功能和多样的界面设计选择。分段控件可以实现选项的切换,UIButton 能为用户提供可点击的交互元素,自定义控件则可以满足特定的业务需求。同时,导航栏、工具栏、标签栏和 UISearchBar 等栏控件在界面布局和功能实现中也起着重要的作用。通过合理设置控件的属性、处理控件事件以及调整栏的位置和外观,可以创建出功能强大、用户体验良好的应用。

7.2 展望

随着 iOS 系统的不断更新和发展,控件和栏的功能也会不断增强和完善。未来可能会出现更多新颖的控件类型和更便捷的使用方式,为开发者提供更多的创作空间。同时,在用户体验方面,也会更加注重控件的交互性和视觉效果,以满足用户日益增长的需求。开发者需要不断学习和掌握新的技术和特性,才能跟上时代的步伐,开发出更加优秀的 iOS 应用。

总之,iOS 开发中的控件和栏是构建应用界面的基础,深入理解和熟练运用它们,将有助于开发者打造出高质量的应用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值