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 对应 print。
  • 颜色、字体等可使用类型推断简写:.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. 几种连线方式

  1. 从控制器代码往 Storyboard 控件连线。
  2. 从 Storyboard 控件往控制器代码连线。
  3. 在 Storyboard 中右键控件,直接拖到界面顶部的控制器图标上。
  4. 在 Storyboard 中右键控件,拖到工具条(First Responder / Exit 等)上。
  5. 不预先定义方法,直接从 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 类似:

  1. 从控制器代码往 Storyboard 控件连线。
  2. 从 Storyboard 控件往控制器代码连线。
  3. 在 Storyboard 中右键控件,拖到界面顶部的控制器图标上。
  4. 在 Storyboard 中右键控件,拖到工具条上。
  5. 不预先定义属性,直接从 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?
posted @ 2015-07-14 01:39  Mr.陳  阅读(4718)  评论(0)    收藏  举报