如何拆分冗长的uiviewcontroller类?

时间:2014-07-30 00:40:31

标签: ios objective-c uiviewcontroller

我有一个很长的UIViewController,有很多部分和行。尽管对于这些部分中的每一部分都有单独的方法,但仍然变得麻烦地跳到不同的方法。有没有最好的方法来设计这样的课程?我想为这些部分中的每个部分设一个类别?这是一个好主意吗?

4 个答案:

答案 0 :(得分:1)

我发现与委托方法相关的代码成为我的视图控制器的很大一部分。 this article at objc.io中描述的一种策略是将数据源委托方法移动到它们自己的类中。

答案 1 :(得分:1)

在去年,我开始从视图控制器中移出尽可能多的代码,尝试创建“更轻的视图控制器”。因为你可以使用实现一个 - 而且只有一个 - 视图控制器将具有的功能的某些方面的对象。你可以称之为Sub Controller,但是我和其他人使用名称«Intentions»来表达这个事实,即每个对象都有一个意图。

我尝试了不同种类,从目标/动作到基于块,我最终发现它是最有用的。

真实代码的一个例子:

我是一个收银机应用程序,需要通过wifi与热敏打印机进行通信 要添加打印机,我的AddPrinterViewController包含用于IP地址,端口和名称/位置的文本字段。

而不是将视图控制器实现为我在类上创建的所有文本字段的委托,该类将作为一个文本字段的委托,并具有基于块的验证。

@interface TextfieldDelegateIntention : NSObject
@property(nonatomic, weak, readonly) UITextField *textField;

@property (nonatomic, copy) BOOL (^validationBlock)(UITextField *textField);

-(instancetype)initWithTextField:(UITextField *)textField
                 validationBlock: (BOOL (^)(UITextField *textField)) validationBlock;

-(BOOL)validate;
@end

#import "TextfieldDelegateIntention.h"

@interface TextfieldDelegateIntention ()<UITextFieldDelegate>
@property(nonatomic, weak) UITextField *textField;
@end


@implementation TextfieldDelegateIntention
-(instancetype)initWithTextField:(UITextField *)textField
                 validationBlock: (BOOL (^)(UITextField *textField)) validationBlock
{
    self = [super init];
    if(self){
        self.validationBlock = validationBlock;
        self.textField = textField;
        self.textField.delegate = self;
    }
    return self;
}

-(BOOL)validate
{
    if (self.validationBlock) {
        return self.validationBlock(self.textField);
    }
    return NO;
}

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

-(void)textFieldDidEndEditing:(UITextField *)textField
{
    [textField resignFirstResponder];
}
@end

我使用故事板中的三个文本字段设置视图控制器。

IP(仅限第4版)地址'文本字段可能如下所示:

TextfieldDelegateIntention *ipAddresIntention = [[TextfieldDelegateIntention alloc] initWithTextField:self.ipTextField validationBlock:^BOOL(UITextField *textField) {
    NSArray *components = [textField.text componentsSeparatedByString:@"."];
    if ([components count] == 4) {
        __block BOOL compsAreValidNumbers = YES;
        [components enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
            if ([obj integerValue] > -1 && [obj integerValue] < 256 ) {
            } else {
                compsAreValidNumbers = NO;
                *stop = YES;
            }
        }];
        return compsAreValidNumbers;
    }
    return NO;
}];

过于简单的验证,但有助于用户正确输入。

完整视图控制器的代码如下所示

#import <UIKit/UIKit.h>
#import "BaseModalViewController.h"
@class NamendPOSNetworkPrinter;
@class PrinterProvider;

@interface AddPrinterViewController : BaseModalViewController
@property (nonatomic, strong) PrinterProvider *printerProvider;

@property (nonatomic, copy) void(^printerPreferecesEntered)(NSDictionary *printerDict);
@end

#import "AddPrinterViewController.h"
#import "TextfieldDelegateIntention.h"
#import "ButtonIntention.h"
#import "PrinterProvider.h"

@interface AddPrinterViewController ()
@property (weak, nonatomic) IBOutlet UITextField *nameTextField;
@property (weak, nonatomic) IBOutlet UITextField *ipTextField;
@property (weak, nonatomic) IBOutlet UITextField *portTextField;
@property (nonatomic, strong) NSArray *textFieldIntentions;
@property (nonatomic, strong) ButtonIntention *okIntention;
@property (weak, nonatomic) IBOutlet UIButton *okButton;
@end

@implementation AddPrinterViewController

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Custom initialization
    }
    return self;
}

- (void)viewDidLoad
{
    [super viewDidLoad];

    TextfieldDelegateIntention *nameIntention = [[TextfieldDelegateIntention alloc] initWithTextField:self.nameTextField validationBlock:^BOOL(UITextField *textField) {
        if ([textField.text length] > 0) {
            return YES;
        }
        return NO;
    }];

    TextfieldDelegateIntention *ipAddresIntention = [[TextfieldDelegateIntention alloc] initWithTextField:self.ipTextField validationBlock:^BOOL(UITextField *textField) {
        NSArray *components = [textField.text componentsSeparatedByString:@"."];
        if ([components count] == 4) {
            __block BOOL compsAreValidNumbers = YES;
            [components enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
                if ([obj integerValue] > -1 && [obj integerValue] < 256 ) {
                } else {
                    compsAreValidNumbers = NO;
                    *stop = YES;
                }
            }];
            return compsAreValidNumbers;
        }
        return NO;
    }];

    TextfieldDelegateIntention *portIntention = [[TextfieldDelegateIntention alloc] initWithTextField:self.portTextField validationBlock:^BOOL(UITextField *textField) {
        if ([textField.text integerValue] > 1023 && [textField.text integerValue]< 65536) {
            return YES;
        }
        return NO;
    }];

    self.textFieldIntentions = @[nameIntention, ipAddresIntention, portIntention];

    __block typeof(self) weakSelf = self;
    ButtonIntention *okIntention = [[ButtonIntention alloc] initWithButton:self.okButton actionBlock:^(UIButton *button) {
        typeof(weakSelf) strongSelf = weakSelf;
        if (strongSelf) {
            __block BOOL formIsValid = YES;
            [strongSelf.textFieldIntentions enumerateObjectsUsingBlock:^(TextfieldDelegateIntention *intention, NSUInteger idx, BOOL *stop) {
                BOOL isValid = [intention validate];
                if (!isValid) {
                    intention.textField.backgroundColor = [UIColor redColor];
                    formIsValid = NO;
                } else {
                    intention.textField.backgroundColor = [UIColor greenColor];

                }
            }];

            if (formIsValid) {
                if (self.printerPreferecesEntered) {
                    self.printerPreferecesEntered(@{
                                                        @"name": strongSelf.nameTextField.text,
                                                        @"ipAddress": strongSelf.ipTextField.text,
                                                        @"port": @([strongSelf.portTextField.text integerValue])
                                                    });
                }

                dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                    [strongSelf dismissViewControllerAnimated:YES completion:NULL];
                });
            }
        }
    }];
    self.okIntention = okIntention;
}


@end

如您所见,我可以使用简单的块为三个文本字段添加不同的行为,只覆盖一个方法 - 没有其他方法添加。

我的博客验证电子邮件的另一个例子:Lighter ViewControllers with Block-based Intentions


tableview的数据源也很容易实现为Intention。

#import <UIKit/UIKit.h>

@class PrinterProvider;

@interface PrinterDataSource : NSObject <UITableViewDataSource>
@property (nonatomic, strong) PrinterProvider *printerProvider;
@end

#import "PrinterDataSource.h"
#import "VSPOSNetworkPrinter.h"
#import "PrinterProvider.h"


@interface PrinterDataSource ()
@end


@implementation PrinterDataSource

-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 1;
}

-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return [[self.printerProvider allPrinters] count];
}

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"PrinterCell" forIndexPath:indexPath];
    cell.textLabel.text = [[self.printerProvider allPrinters][indexPath.row] ipAddress];
    cell.detailTextLabel.text = [[self.printerProvider allPrinters][indexPath.row] name];
    return cell;
}


@end

将其用作

#import "PrinterViewController.h"
#import "AddPrinterViewController.h"
#import "ButtonIntention.h"
#import "BarButtomItemIntention.h"
#import "NamendPOSNetworkPrinter.h"
#import "PrinterProvider.h"
#import "PrinterDataSource.h"

@interface PrinterViewController ()
@property (weak, nonatomic) IBOutlet UITableView *tableView;
@property (weak, nonatomic) IBOutlet UIBarButtonItem *addButton;
@property (nonatomic, strong) BarButtomItemIntention *addIntention;
@property (nonatomic, strong) AddPrinterViewController *addPrinterViewController;
@property (strong, nonatomic) IBOutlet PrinterDataSource *printerDataSource;
@property (nonatomic, strong) PrinterProvider *printerProvider;
@end

@implementation PrinterViewController

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Custom initialization
    }
    return self;
}

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.printerDataSource.printerProvider = self.printerProvider;

    self.addPrinterViewController = [self.storyboard instantiateViewControllerWithIdentifier:@"AddPrinterViewController"];

    typeof(self) weakSelf = self;
    [self.addPrinterViewController setPrinterPreferecesEntered:^(NSDictionary *printerDict) {
        NamendPOSNetworkPrinter *printer = [[NamendPOSNetworkPrinter alloc] initWithName:printerDict[@"name"]
                                                                               ipAddress:printerDict[@"ipAddress"]
                                                                                    port:printerDict[@"port"]];

        typeof(self) strongSelf = weakSelf;
        if (strongSelf) {
            [strongSelf.printerProvider addPrinter:printer];
            [strongSelf.tableView reloadData];
        }
    }];

    self.addPrinterViewController.printerProvider = self.printerProvider;
    self.addIntention = [[BarButtomItemIntention alloc] initWithButtonItem:self.addButton
                                                               actionBlock:^(UIBarButtonItem *buttonItem)
    {
        typeof(weakSelf) strongSelf = weakSelf;
        if(strongSelf){
            [strongSelf presentViewController:strongSelf.addPrinterViewController
                                     animated:YES completion:^{

            }];
        }
    }];
}



@end

这种方法的魅力在于您可以独立创建和子类视图控制器和意图,并且我可以根据需要轻松地重新排列它们。实际上,苹果在最近的wwdc视频«Advanced User Interfaces with Collection Views»

中开始宣传类似的做法

答案 2 :(得分:0)

我建议您使用#pragma mark [divider name]将您的班级分成几个部分,这样您就可以轻松地对方法进行分组。否则,创建处理包含功能的子类是唯一的选择。我已经看过8,000多个线路视图控制器类,所以有时候如果你太深入而无法将它全部分开,这是不可避免的。

答案 3 :(得分:0)

这更像是一种编程哲学。 Objective-C有冗长的方法(名称和声明),因此简化代码的最佳方法是将任何重复模式委托给外部类。

例如,我创建了一个仅保留返回数值运算的类,例如对NSNumbers,字符串等的算术运算。另一个只在输入上返回布尔值的类。 例如,如果您验证数字的有效性并将其递增,或者将其设置为1,即使只是代码中的两个位置,而不是像这样的事情:

if (variableName != nil && variableName.intValue > 0)
{
    variableName2 = [NSNumber numberWithInt:(variableName.intValue + 1)];
}
else
{
    variableName2 = [NSNumber numberWithInt:1];
}

在外面使用此功能会更容易(例如在“Arithmetics”类中),并将其称为如下:

variableName2 = [Arithmetics incrementNumber:variableName];

如果你像这样替换所有代码,通过具有良好命名的类和易于理解的方法(名称),你的代码将更具逻辑可读性和易于遵循,这是Objective-C的哲学(你也得到了能够在所有代码中重用这些方法的好处,在任何地方保持相同的逻辑。)