iOS开发基础1-第一个iOS程序
第一个 iOS 程序:控制器、IBAction / IBOutlet 与 UIView
开篇示例
下面是一个最基础的交互程序:故事板上放置一个 UILabel 和三个按钮,点击不同按钮修改 Label 的文字颜色与样式。
Objective-C
ViewController.h
#import <UIKit/UIKit.h>
@interface ViewController : UIViewController
// 建议:连线属性通常放在 .m 的类扩展中以保持私有,此处仅为演示
@property (nonatomic, weak) IBOutlet UILabel *label;
@end
ViewController.m
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
#pragma mark - 按钮点击事件
/// 红色按钮
- (IBAction)redBtnClick
{
NSLog(@"%s", __func__);
// 拿到 UILabel 对象,修改对象的属性,让 label 变为红色
self.label.textColor = [UIColor redColor];
self.label.text = @"我是红色文字";
self.label.backgroundColor = [UIColor purpleColor];
self.label.font = [UIFont systemFontOfSize:30];
self.label.textAlignment = NSTextAlignmentCenter;
}
/// 绿色按钮
- (IBAction)greenBtnClick
{
NSLog(@"%s", __func__);
self.label.textColor = [UIColor greenColor];
}
/// 蓝色按钮
- (IBAction)blueBtnClick
{
NSLog(@"%s", __func__);
self.label.textColor = [UIColor blueColor];
}
@end
Swift
ViewController.swift
import UIKit
class ViewController: UIViewController {
// weak 避免循环引用;隐式解包可选类型(!)因为 IB 连线在 init 之后才完成
@IBOutlet weak var label: UILabel!
// MARK: - 按钮点击事件
/// 红色按钮
@IBAction func redBtnClick() {
print(#function)
label.textColor = .red
label.text = "我是红色文字"
label.backgroundColor = .purple
label.font = .systemFont(ofSize: 30)
label.textAlignment = .center
}
/// 绿色按钮
@IBAction func greenBtnClick() {
print(#function)
label.textColor = .green
}
/// 蓝色按钮
@IBAction func blueBtnClick() {
print(#function)
label.textColor = .blue
}
}
Swift 对照要点
IBAction/IBOutlet在 Swift 中是属性包装器,写为@IBAction/@IBOutlet,放在声明前面。__func__对应#function,NSLog对应- 颜色、字体等可使用类型推断简写:
.red等价于UIColor.red。- 连线属性使用
weak var+ 隐式解包UILabel!,因为视图加载后连线才被设置。
一、控制器(UIViewController)
1. 什么是控制器
任何继承自 UIViewController 的类,都称之为控制器。
UIKit 还提供了许多专用控制器子类,例如:
| 控制器类 | 用途 |
|---|---|
UITableViewController |
管理表格视图 |
UICollectionViewController |
管理集合视图 |
UINavigationController |
管理层级导航栈 |
UITabBarController |
管理标签页切换 |
它们最终都继承自 UIViewController。
2. 控制器的作用
管理 UI 界面——负责界面的创建、生命周期事件处理、用户交互响应以及数据与视图的协调。
3. 注意点
- UI 界面可以和与之对应的控制器进行连线,通过连线让 Storyboard / XIB 上的控件和控制器代码产生关联。
- 默认情况下,UI 控件不能直接和方法关联;要让方法支持连线,必须将返回值声明为
IBAction(OC)或标记@IBAction(Swift)。
二、IBAction 与 IBOutlet
IBAction 和 IBOutlet 是 Interface Builder(IB)识别的标记,本身不影响运行时逻辑,只用于告诉 Xcode 哪些方法和属性可以在 Storyboard / XIB 中拉线。
IBAction
1. IBAction 是什么
- 从返回值角度看,
IBAction本质就是void(宏定义展开为void)。 - 只有返回值声明为
IBAction的方法,才能跟 Storyboard 中的控件进行连线。 IBAction只能加在方法上,不能加在属性上。
OC 标准写法(推荐带 sender 参数):
- (IBAction)redBtnClick:(UIButton *)sender {
NSLog(@"点击了按钮: %@", sender);
}
Swift 标准写法:
@IBAction func redBtnClick(_ sender: UIButton) {
print("点击了按钮: \(sender)")
}
带
sender参数可以在方法内获取触发事件的控件本身,便于区分多个按钮共用一个方法的场景。无参数写法也合法,但灵活性较低。
2. 几种连线方式
- 从控制器代码往 Storyboard 控件连线。
- 从 Storyboard 控件往控制器代码连线。
- 在 Storyboard 中右键控件,直接拖到界面顶部的控制器图标上。
- 在 Storyboard 中右键控件,拖到工具条(First Responder / Exit 等)上。
- 不预先定义方法,直接从 Storyboard 控件拖线到控制器代码中,Xcode 自动生成方法骨架(最常用)。
3. 注意点
3.1 拷贝控件时的连线问题
- 在 Storyboard 中拷贝控件时,会连同已有的连线一起拷贝。
- 一个方法可以被多个控件关联(例如多个按钮共用同一个点击方法,通过
sender的tag区分)。 - 一个控件可以和多个方法连线(不同事件对应不同方法,甚至同一事件关联多个方法),但开发中一般不建议同一事件关联多个方法,逻辑容易混乱。
3.2 删除连线方法后的经典崩溃
如果按钮已经关联了某个方法,但你在代码中删除了该方法而没有断开 Storyboard 中的连线,运行时点击按钮会崩溃:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException',
reason: '-[ViewController redBtnClick]: unrecognized selector sent to instance 0x7fb4aa618e50'
原因:Storyboard 中仍记录着该方法名,运行时通过 Objective-C 消息机制发送 redBtnClick,但对象已不存在该方法,故报 unrecognized selector。
3.3 IBAction 只能作为方法的返回值,不能用于属性。
IBOutlet
1. IBOutlet 是什么
- 只有声明为
IBOutlet的属性,才能跟 Storyboard 中的控件进行连线。 - 属性要想能够连线,必须在数据类型前面加上
IBOutlet(OC)或在变量前标记@IBOutlet(Swift)。
OC:
@property (nonatomic, weak) IBOutlet UILabel *label;
Swift:
@IBOutlet weak var label: UILabel!
连线属性为什么用
weak?因为控件已经被其superview强引用,控制器对其再用weak可避免循环引用。
2. 几种连线方式
与 IBAction 类似:
- 从控制器代码往 Storyboard 控件连线。
- 从 Storyboard 控件往控制器代码连线。
- 在 Storyboard 中右键控件,拖到界面顶部的控制器图标上。
- 在 Storyboard 中右键控件,拖到工具条上。
- 不预先定义属性,直接从 Storyboard 控件拖线到控制器代码中,Xcode 自动生成属性声明(最常用)。
3. 注意点
- 一个控件可以关联多个属性(虽然少见,但语法允许)。
- 一个属性不可以关联多个控件——一个指针变量只能指向一个对象。
- Xcode 在连线时会自动做类型检测,类型不匹配无法连线(例如不能把
UIButton连到UILabel *类型的属性上)。 - 如果属性和控件连线之后又删除了属性声明,但未断开 Storyboard 连线,程序一启动就会崩溃:
*** Terminating app due to uncaught exception 'NSUnknownKeyException',
reason: '[<ViewController 0x7fe9d9f1a5d0> setValue:forUndefinedKey:]:
this class is not key value coding-compliant for the key label.'
原因:控制器加载 XIB/Storyboard 时,通过 KVC(setValue:forKey:)给连线属性赋值,属性已不存在则报 forUndefinedKey。
- 任何 UI 控件都可以和属性连线(因为所有控件都是
UIView子类),但并非任何控件都可以和方法连线——只有继承自UIControl的控件(如UIButton、UITextField、UISwitch)才具备事件机制,才能连线IBAction。像UILabel、UIImageView这类非UIControl子类只能连IBOutlet,不能直接连点击事件方法。
三、UIView
1. 什么是控件
屏幕上所有的 UI 元素都叫做控件(也叫视图、组件)。例如按钮(UIButton)、文本标签(UILabel)、图片视图(UIImageView)都是控件。
2. 控件的共同属性
所有控件都拥有一些共性:
- 尺寸
- 位置
- 背景色
- 透明度
- ……
3. UIView 继承体系
苹果将控件的共同属性抽取到父类 UIView 中:
- 所有控件最终都继承自
UIView。 UIButton、UILabel都是UIView的子类(可查阅头文件验证)。
继承关系示意:
UIResponder
└── UIView
├── UILabel
├── UIControl
│ ├── UIButton
│ ├── UITextField
│ └── UISwitch
├── UIImageView
└── UIScrollView
└── UITableView / UICollectionView
4. 控制器的 view 属性
每个控制器(UIViewController)内部都有一个默认的 UIView 属性:
// UIViewController.h 中的声明(ARC 环境下为 strong)
@property (nonatomic, strong) UIView *view;
var view: UIView! { get set }
控制器管理的其他所有控件,都是这个 view 的子控件(直接或间接)。
5. UIView 常见属性和方法
属性
| 属性 | 说明 |
|---|---|
superview |
获得自己的父控件对象(只读) |
subviews |
获得自己的所有子控件对象(只读) |
tag |
控件的 ID 标识,父控件可通过 tag 找到对应子控件 |
transform |
形变属性(旋转、缩放、平移等) |
frame |
控件矩形框在父控件中的位置和尺寸(以父控件左上角为坐标原点) |
bounds |
控件矩形框的位置和尺寸(以自己左上角为坐标原点,x/y 通常为 0) |
center |
控件中点的位置(以父控件左上角为坐标原点) |
OC 声明:
@property (nonatomic, readonly) UIView *superview;
@property (nonatomic, readonly, copy) NSArray *subviews;
@property (nonatomic) NSInteger tag;
@property (nonatomic) CGAffineTransform transform;
@property (nonatomic) CGRect frame;
@property (nonatomic) CGRect bounds;
@property (nonatomic) CGPoint center;
Swift 声明:
weak var superview: UIView? { get }
var subviews: [UIView] { get }
var tag: Int
var transform: CGAffineTransform
var frame: CGRect
var bounds: CGRect
var center: CGPoint
关于
bounds的补充:大多数视图bounds.origin为(0, 0),但UIScrollView及其子类(如UITableView)的bounds.origin会随滚动偏移量(contentOffset)变化,并非始终为 0。
方法
| 方法 | 说明 |
|---|---|
addSubview: |
添加一个子控件 |
removeFromSuperview |
从父控件中移除 |
viewWithTag: |
根据 tag 标识找出对应的控件(通常是子控件) |
OC:
- (void)addSubview:(UIView *)view;
- (void)removeFromSuperview;
- (UIView *)viewWithTag:(NSInteger)tag;
Swift:
func addSubview(_ view: UIView)
func removeFromSuperview()
func viewWithTag(_ tag: Int) -> UIView?

浙公网安备 33010602011771号