首页 iOS开发:MVC架构
文章
取消

iOS开发:MVC架构

在iOS开发中,MVC(Model-View-Controller)是一种常用的设计模式,它将应用程序分为三个组成部分:模型(Model)、视图(View)和控制器(Controller)。这种分离结构有助于更好地管理应用程序,并使代码更具可读性和可维护性。

MVC的工作原理

MVC模式的基本思想是将应用程序分为三个部分:

  1. 模型:负责处理数据逻辑。它通常包含应用程序中使用的所有数据对象以及对这些对象进行操作的方法。
  2. 视图:提供了一个用户界面,使用户可以与应用程序交互并获取信息。
  3. 控制器:协调视图和模型之间的交互,并根据用户输入执行相应的操作。

MVC模式的核心理念是分离关注点。通过将应用程序分成独立的部分,我们可以更好地管理和修改代码,并降低出现错误的风险。例如,如果你需要更改某个功能或修复某个问题,你只需查找与该功能或问题相关的模块,并进行更改或修复。这样做比搜索整个代码库要容易得多。

数据模型(Model)

模型通常是应用程序中最重要的部分之一,因为它存储并管理数据。在MVC模式中,模型不仅是数据的容器,还负责定义数据的行为。这意味着,如果你想执行任何与数据相关的操作,你都需要访问模型对象。在MVC架构中,数据模型通常由以下几个部分组成:

  • 模型对象(Model Object):用于表示应用程序的数据结构及其操作的接口。
  • 数据库(Database):用于存储数据模型。
  • 网络服务(Network Service):用于获取和更新远程数据。

在MVC模式中,模型可以看作是一个黑盒子。它接受请求(如更新、添加或删除数据),并根据请求进行操作。一旦操作完成,它将通知任何关注该数据的视图和控制器对象。

视图(View)

视图是用户与应用程序交互的主要方法。它们通常是屏幕上显示的内容。视图负责显示数据,并且能够响应用户事件,如点击、拖动和滑动等。当视图中的数据发生变化时,视图会自动刷新显示新数据。在MVC架构中,视图通常由以下几个部分组成:

  • 视图控件(View Control):用于显示UI元素,例如按钮、标签、文本框等。
  • 视图容器(View Container):用于组织多个视图控件,例如表格视图、集合视图、滚动视图等。

在MVC模式中,视图是被动的。它们不能直接访问或更改模型数据。相反,视图通过控制器向模型发送请求,并从模型接收更新。这种分离允许我们更好地组织代码,并降低错误的风险。

控制器(Controller)

控制器是MVC模式中的“指挥官”,负责协调模型和视图之间的交互。控制器接收并处理用户事件,比如触摸屏幕或按下按钮。然后,它决定应该如何更新视图并更改模型数据。在MVC架构中,控制器通常由以下几个部分组成:

  • 视图控制器(View Controller):用于管理一个或多个视图控件和它们之间的交互。
  • 数据源(Data Source):提供数据模型和视图之间的连接,以便显示正确的数据。
  • 委托(Delegate):处理用户输入事件和其他系统事件,并根据需要更新数据模型和视图。

在MVC模式中,控制器是视图和模型之间的桥梁。当视图需要更新数据时,它会向控制器发送请求。控制器会将请求转发给模型,并将更新的数据返回给视图。

mvc

示例

下面演示如何使用MVC模式开发一个简单的通讯录。

架构设计:

  1. Model(模型):联系人数据模型,包含属性姓名、电话号码和地址。
  2. View(视图):联系人管理界面,包含表格视图用于展示联系人列表,并提供增加、修改和删除联系人的按钮。
  3. Controller(控制器):联系人管理控制器,用来处理用户与视图之间的交互,同时负责将用户输入反映到数据模型上。

需要建立的类:

  1. Contact:联系人数据,数据模型类。
  2. ContactCell:联系人单元格视图,视图类。
  3. ContactDetailsView:联系人详情页视图,视图类。
  4. ContactListViewController:联系人列表页面,控制器类。
  5. ContactDetailsViewController:联系人详情页面,控制器类。

代码:

Contact.h

1
2
3
4
5
6
7
8
9
#import <Foundation/Foundation.h>

@interface Contact : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *phone;
@property (nonatomic, copy) NSString *address;

@end

Contact.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#import "Contact.h"

@implementation Contact

- (instancetype)init {
    self = [super init];
    if (self) {
        _name = @"";
        _phone = @"";
        _address = @"";
    }
    return self;
}

@end

ContactCell.h

1
2
3
4
5
6
7
8
9
#import <UIKit/UIKit.h>

@interface ContactCell : UITableViewCell

@property (nonatomic, strong) UILabel *nameLabel;
@property (nonatomic, strong) UILabel *phoneLabel;
@property (nonatomic, strong) UILabel *addressLabel;

@end

ContactCell.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#import "ContactCell.h"

@implementation ContactCell

- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
    if (self) {
        _nameLabel = [[UILabel alloc] initWithFrame:CGRectZero];
        _nameLabel.translatesAutoresizingMaskIntoConstraints = NO;
        [self.contentView addSubview:_nameLabel];
        
        _phoneLabel = [[UILabel alloc] initWithFrame:CGRectZero];
        _phoneLabel.translatesAutoresizingMaskIntoConstraints = NO;
        [self.contentView addSubview:_phoneLabel];
        
        _addressLabel = [[UILabel alloc] initWithFrame:CGRectZero];
        _addressLabel.translatesAutoresizingMaskIntoConstraints = NO;
        [self.contentView addSubview:_addressLabel];
        
        NSDictionary *views = @{@"name": _nameLabel, @"phone": _phoneLabel, @"address": _addressLabel};
        [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-10-[name]-[phone(==name)]-10-[address]-10-|" options:0 metrics:nil views:views]];
        [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-10-[name]-10-|" options:0 metrics:nil views:views]];
        [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-10-[phone]-10-|" options:0 metrics:nil views:views]];
        [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-10-[address]-10-|" options:0 metrics:nil views:views]];
    }
    return self;
}
@end

ContactDetailsView.h

1
2
3
4
5
6
7
8
9
10
11
12
#import <UIKit/UIKit.h>

@interface ContactDetailsView : UIView

@property (nonatomic, strong) UILabel *nameLabel;
@property (nonatomic, strong) UILabel *phoneLabel;
@property (nonatomic, strong) UILabel *addressLabel;
@property (nonatomic, strong) UITextField *nameField;
@property (nonatomic, strong) UITextField *phoneField;
@property (nonatomic, strong) UITextField *addressField;

@end

ContactDetailsView.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#import "ContactDetailsView.h"

@implementation ContactDetailsView

- (instancetype)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        self.backgroundColor = [UIColor whiteColor];

        CGFloat margin = 10.0;
        CGFloat labelWidth = 80.0;
        CGFloat fieldHeight = 30.0;

        self.nameLabel = [[UILabel alloc] initWithFrame:CGRectMake(margin, 100, labelWidth, fieldHeight)];
        self.nameLabel.text = @"Name:";
        [self addSubview:self.nameLabel];

        self.nameField = [[UITextField alloc] initWithFrame:CGRectMake(labelWidth + 2 * margin, 100, CGRectGetWidth(frame) - labelWidth - 3 * margin, fieldHeight)];
        self.nameField.borderStyle = UITextBorderStyleRoundedRect;
        [self addSubview:self.nameField];

        self.phoneLabel = [[UILabel alloc] initWithFrame:CGRectMake(margin, 150, labelWidth, fieldHeight)];
        self.phoneLabel.text = @"Phone:";
        [self addSubview:self.phoneLabel];

        self.phoneField = [[UITextField alloc] initWithFrame:CGRectMake(labelWidth + 2 * margin, 150, CGRectGetWidth(frame) - labelWidth - 3 * margin, fieldHeight)];
        self.phoneField.borderStyle = UITextBorderStyleRoundedRect;
        [self addSubview:self.phoneField];

        self.addressLabel = [[UILabel alloc] initWithFrame:CGRectMake(margin, 200, labelWidth, fieldHeight)];
        self.addressLabel.text = @"Address:";
        [self addSubview:self.addressLabel];

        self.addressField = [[UITextField alloc] initWithFrame:CGRectMake(labelWidth + 2 * margin, 200, CGRectGetWidth(frame) - labelWidth - 3 * margin, fieldHeight)];
        self.addressField.borderStyle = UITextBorderStyleRoundedRect;
        [self addSubview:self.addressField];
    }
    return self;
}

@end

ContactListViewController.h

1
2
3
4
5
6
7
8
#import <UIKit/UIKit.h>
#import "Contact.h"
#import "ContactCell.h"
#import "ContactDetailsViewController.h"

@interface ContactListViewController : UITableViewController

@end

ContactListViewController.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
#import "ContactListViewController.h"

@interface ContactListViewController () <ContactDetailsViewControllerDelegate>

@property (nonatomic, strong) NSMutableArray<Contact *> *contacts;

@end

@implementation ContactListViewController

- (instancetype)init {
    self = [super initWithStyle:UITableViewStylePlain];
    if (self) {
        _contacts = [[NSMutableArray alloc] init];
        for (int i = 1; i <= 20; i++) {
            Contact *contact = [[Contact alloc] init];
            contact.name = [NSString stringWithFormat:@"联系人%d", i];
            contact.phone = [NSString stringWithFormat:@"1301212560%d", i];
            contact.address = [NSString stringWithFormat:@"地址%d", i];
            [_contacts addObject:contact];
        }
    }
    return self;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.navigationItem.title = @"通讯录";
    self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(addContact)];
    
    [self.tableView registerClass:[ContactCell class] forCellReuseIdentifier:@"ContactCell"];
}

- (void)addContact {
    ContactDetailsViewController *addContactVC = [[ContactDetailsViewController alloc] init];
    addContactVC.delegate = self;
    UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:addContactVC];
    [self presentViewController:navController animated:YES completion:nil];
}

#pragma mark - Table view data source

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.contacts.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    ContactCell *cell = [tableView dequeueReusableCellWithIdentifier:@"ContactCell" forIndexPath:indexPath];
    
    Contact *contact = self.contacts[indexPath.row];
    cell.nameLabel.text = contact.name;
    cell.phoneLabel.text = contact.phone;
    cell.addressLabel.text = contact.address;
    
    return cell;
}

#pragma mark - Table view delegate

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    ContactDetailsViewController *detailVC = [[ContactDetailsViewController alloc] init];
    detailVC.contact = self.contacts[indexPath.row];
    detailVC.delegate = self;
    UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:detailVC];
    [self presentViewController:navController animated:YES completion:nil];
}

- (NSArray<UITableViewRowAction *> *)tableView:(UITableView *)tableView editActionsForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewRowAction *deleteAction = [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleDestructive title:@"Delete" handler:^(UITableViewRowAction *action, NSIndexPath *indexPath) {
        [self.contacts removeObjectAtIndex:indexPath.row];
        [self.tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
    }];
    return @[deleteAction];
}

#pragma mark - AddContactDelegate

- (void)addContact:(Contact *)contact {
    [self.contacts addObject:contact];
    [self.tableView reloadData];
}

#pragma mark - ContactDetailsViewControllerDelegate

- (void)contactDetailsDidFinish:(ContactDetailsViewController *)controller {
    if (![self.contacts containsObject:controller.contact]) {
        [self.contacts addObject:controller.contact];
//        [self.tableView reloadData];
    }
    else {
        NSInteger index = [self.contacts indexOfObject:controller.contact];
        [self.contacts replaceObjectAtIndex:index withObject:controller.contact];
    }
    [self.tableView reloadData];
    [self dismissViewControllerAnimated:YES completion:nil];
}

- (void)contactDetailsDidCancel:(ContactDetailsViewController *)controller {
    [self dismissViewControllerAnimated:YES completion:nil];
}

@end

ContactDetailsViewController.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#import <UIKit/UIKit.h>
#import "Contact.h"
#import "ContactDetailsView.h"

@protocol ContactDetailsViewControllerDelegate;

@interface ContactDetailsViewController : UIViewController

@property (nonatomic, strong) Contact *contact;
@property (nonatomic, weak) id<ContactDetailsViewControllerDelegate> delegate;

- (instancetype)initWithContact:(Contact *)contact;

@end

@protocol ContactDetailsViewControllerDelegate <NSObject>

- (void)contactDetailsDidFinish:(ContactDetailsViewController *)controller;
- (void)contactDetailsDidCancel:(ContactDetailsViewController *)controller;

@end

ContactDetailsViewController.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#import "ContactDetailsViewController.h"

@interface ContactDetailsViewController () <UITextFieldDelegate>

@property (nonatomic, strong) ContactDetailsView *detailsView;

@end

@implementation ContactDetailsViewController

- (instancetype)initWithContact:(Contact *)contact {
    self = [super init];
    if (self) {
        _contact = contact;
    }
    return self;
}

- (void)viewDidLoad {
    [super viewDidLoad];

    self.navigationItem.title = self.contact ? @"修改联系人" : @"添加联系人";
    self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemSave target:self action:@selector(saveContact)];
    self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:@selector(cancel)];

    self.detailsView = [[ContactDetailsView alloc] initWithFrame:self.view.bounds];
    self.detailsView.nameField.delegate = self;
    self.detailsView.phoneField.delegate = self;
    self.detailsView.addressField.delegate = self;
    [self.view addSubview:self.detailsView];

    if (self.contact) {
        self.detailsView.nameField.text = self.contact.name;
        self.detailsView.phoneField.text = self.contact.phone;
        self.detailsView.addressField.text = self.contact.address;
    }
}

- (void)saveContact {
    if (!self.contact) {
        self.contact = [[Contact alloc] init];
    }
    self.contact.name = self.detailsView.nameField.text;
    self.contact.phone = self.detailsView.phoneField.text;
    self.contact.address = self.detailsView.addressField.text;
    if ([self.delegate respondsToSelector:@selector(contactDetailsDidFinish:)]) {
        [self.delegate contactDetailsDidFinish:self];
    }
}

- (void)cancel {
    if ([self.delegate respondsToSelector:@selector(contactDetailsDidCancel:)]) {
        [self.delegate contactDetailsDidCancel:self];
    }
}

#pragma mark - UITextFieldDelegate

- (BOOL)textFieldShouldReturn:(UITextField *)textField {
    [textField resignFirstResponder];
    return YES;
}

@end

AppDelegate.h

1
2
3
4
5
6
7
#import <UIKit/UIKit.h>

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property(nonatomic,strong)UIWindow *window;

@end

AppDelegate.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#import "AppDelegate.h"
#import "ContactListViewController.h"

@interface AppDelegate ()

@end

@implementation AppDelegate


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    self.window.backgroundColor = [UIColor whiteColor];
    
    // Create and set the root view controller
    ContactListViewController *contactsViewController = [[ContactListViewController alloc] init];
    UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:contactsViewController];
    self.window.rootViewController = navigationController;
    
    [self.window makeKeyAndVisible];
    return YES;
}

@end
本文由作者按照 CC BY 4.0 进行授权

雅虎天气API

统一管理同一视图控制器中的多个UITextField键盘