【问题标题】:Refactoring iOS project to NOT use storyboards重构 iOS 项目以不使用情节提要
【发布时间】:2014-05-20 00:40:09
【问题描述】:

随着我的项目变得越来越大和越来越复杂,我发现故事板的使用变得难以管理。我的故事板文件越来越大,我在应用程序中重用组件/整个视图的需求越来越大,并且从屏幕上跳转到完全不同的屏幕的需求变得越来越困难,而我的故事板上没有乱七八糟的segues。

我已经开始用这样的代码做一些简单的事情,比如推送/弹出/呈现模式视图控制器:

UIViewController *vc = [[UIViewController alloc] init]; // usually a custom subclass of uiviewcontroller
[vc setModalPresentationStyle:UIModalPresentationFullScreen];
[self presentViewController:vc animated:YES completion:nil];

但我正在寻找有关如何更进一步的建议。

例如:我希望能够从一个选项卡切换到导航控制器堆栈中另一个选项卡的第三个视图控制器。在故事板中,我可以简单地在两个视图控制器之间拖动一个转场。如果没有情节提要,我可以在我的 tabcontroller 上设置选定的索引,然后将控制器推送到新选项卡的选定索引中,直到我到达我想要的那个?比如:

[self.tabBarController setSelectedIndex:2]; // then how do I get deep inside the new tab controller's view heirarchy?

真的,我只是在寻找有关如何将使用情节提要的大型项目重构为不使用情节提要的项目的文章/建议。我已经发现该项目更易于管理,更易于重用组件,并且在没有情节提要的情况下更易于控制。只需要多一点建议/实践来完成这个过渡。

谢谢

【问题讨论】:

  • 我在一个大型项目中遇到了与您类似的情况。我很高兴继续在我的基本 UI 框架中使用故事板,并使用代码 segues 来维护“干净”的故事板界面。我在自定义 segues 中经常使用视图控制器故事板 ID。这种妥协的解决方案听起来对您来说是否有趣,或者您肯定想完全消除 SB?
  • 你描述的状态和我现在差不多。我的故事板用于基本的应用程序工作流程布局。然而,随着更多屏幕的添加,这开始变得不可维护,并且需要多个路由到一个屏幕作为业务需求。 (故事板开始有很多难以跟踪的转场)。我想我想尝试一起删除故事板,并想知道是否其他人也这样做了。不过,我很想听听您对“混合”方法的体验。
  • 您喜欢聊天还是回答?
  • 继续回答。我认为这将使大多数人受益。 -- 谢谢!

标签: ios objective-c xcode


【解决方案1】:

为我最复杂的 UI 展示故事板...

这就是(目前)六个单独的 UI 选项卡所需要的全部,它们具有无限“深入”视图控制器的多个深度的能力,使用户能够找到他们正在寻找的数据。

警告 警告 警告 我担心用户可能会在这个迷宫中“迷路”。请注意您的 UI 设计,以确保用户了解他们对数据查找 safari 的“深入”程度,或者至少为他们提供一种简单的方法来“弹出”他们的出路。

我将我的 VC/TVC 排列成列以便于使用 - 事实证明 - 便于描述功能。

标签栏控制器和导航控制器是不言自明的。

我将忽略本练习的“特殊功能”控制器。

所以让我们专注于:

  1. “标准列表”类型的 TVC,
  2. “详细内容”类型的 TVC,&
  3. “选择”类型的 TVC。

当用户选择一个选项卡时,“标准列表”类型的 TVC 会提供 UI 以向用户显示项目列表。添加按钮(导航栏项目)提供了通过“详细内容”类型的 TVC 手动将新项目添加到列表的机会。

IB 从“标准列表”转为“详细内容”类型的 TVC

“标准列表”中项目行的故事板(IB 创建)转场触发“详细内容”类型 TVC 的转场,因此用户可以查看和/或编辑项目数据。

“标准列表”中添加按钮(导航栏项)的故事板(IB 创建)转场触发转场到“详细内容”类型 TVC 中的空白或“新”项,因此用户可以添加一个新项目和相关数据。

IB 从“详细内容”转为“选择”类型的 TVC

“详细内容”中项目行的情节提要(IB 创建)转场会触发转场到“选择自”类型 TVC,具体取决于该内容所需的数据类型。 (例如,“Select from”类型的 TVC 之一是嵌入在标准视图控制器中的 UIDatePicker,它使用户能够轻松选择日期和/或时间。)

我想开始从“Select from”类型的 TVC 跳回到“Detail Content”类型的 TVC。

我什至没有尝试手动执行此操作。我的强迫症要求我的情节提要尽可能保持原始和整洁 - NO 疯狂地“倒退”以促进控制器的重用。

所以最后得到一个答案 - 是的,感谢你坚持我 - 我认为序言是必要的。

对于与“Select from”类型 TVC 关联的每个类,我创建了一个自定义 segue,嵌入到 TVC 实现文件的代码中。

随着我对自定义转场越来越熟悉,我可能会将它们删除到单独的“助手”类中。

但在那之前,“Select from”类型TVC的每个类实现文件顶部的代码...

////////////////////////////////////////////////////////////
/// Subclass of UIStoryboardSegue must override -perform ///
////////////////////////////////////////////////////////////
@interface Segue_YourCustomNameHere : UIStoryboardSegue

@end

@implementation Segue_YourCustomNameHere

- (void)perform {
    ThisSelectFromClass *sourceViewController = self.sourceViewController;
    [sourceViewController.navigationController pushViewController:self.destinationViewController animated:YES];
}

@end
////////////////////////////////////////////////////////////
///         END of subclass of UIStoryboardSegue         ///
////////////////////////////////////////////////////////////


@interface ThisSelectFromClass ()

//private declarations

@end


@implementation ThisSelectFromClass

//implementation code

@end

简单,对!

下一步?使用“Storyboard ID”提供您想要重复使用的控制器。

返回您的 Interface Builder / Storyboard 文件,然后选择您想在代码中重用的控制器。

在 Identity Inspector 的“Identity”子标题下,输入“Storyboard ID”。我喜欢在我的故事板 ID 前加上“id_”,如下图示例所示。我将在下面的示例代码中使用此示例,请注意这一点。

下一步?如何触发自定义segue???

回到你的类的实现文件(上面的例子使用ThisSelectFromClass

让我们建议当用户点击特定的数据行时需要 segue。

代码...

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    NSString *mainStoryboard = nil;
    UIStoryboard *storyboard = nil;

    UIViewController *destinationVC = nil;
    Segue_YourCustomNameHere *segue = nil;

    mainStoryboard = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UIMainStoryboardFile"];
    storyboard = [UIStoryboard storyboardWithName:mainStoryboard bundle:nil];

    //  I check the appropriate path depending on the title of my TVC
    //  You may need to determine another check method.

    if ([self.title isEqualToString:<<the title string>>]) {
        destinationVC = [storyboard instantiateViewControllerWithIdentifier:@"id_DetailContentVC"];
        segue = [[Segue_YourCustomNameHere alloc] initWithIdentifier:@"segue_YourSegueIdentifierNameHere"
                                                              source:self
                                                         destination:destinationVC];
    } else if ([self.title isEqualToString:<<another title string>>]) {
        destinationVC = [storyboard instantiateViewControllerWithIdentifier:@"id_NextDetailContentVC"];
        segue = [[Segue_YourCustomNameHere alloc] initWithIdentifier:@"segue_YourNextSegueIdentifierNameHere"
                                                              source:self
                                                         destination:destinationVC];
    } else if... // as many iterations as necessary
        //  Repeat above
    } else {
        //  Do other stuff - maybe error checking?
    }

    //  And because I always incorporate a UISearchDisplayController and I'm too lazy to think about how I should remove it for this example...
    id object = nil;
    if (tableView == self.tableView) {
        object = [self.fetchedResultsController objectAtIndexPath:indexPath];
    } else {
        object = [self.searchResults objectAtIndex:indexPath.row];
    }

    //  Finally...
    [self prepareForSegue:segue sender:object];  // optional
    [segue perform];
}

并且,以防万一您需要并在上方添加 // optional 行...

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    id segueDestinationVC = segue.destinationViewController;

    if ([segue.identifier isEqualToString:@"segue_YourSegueIdentifierNameHere"]) {
        //  These two lines of code are examples only...
        [segueDestinationVC setTitle:[sender valueForKey:@"aSenderKey"]]; // example
        [segueDestinationVC setObjectID:[sender objectID]];               // example

    } else if ([segue.identifier isEqualToString:@"segue_YourNextSegueIdentifierNameHere"]) {
        //  These two lines of code are examples only...
        [segueDestinationVC setTitle:[sender valueForKey:@"aSenderKey"]]; // example
        [segueDestinationVC setObjectID:[sender objectID]];               // example

    } else {
        //  Do other stuff - again maybe error checking?

    }

希望这会有所帮助。

【讨论】:

    猜你喜欢
    • 2012-08-01
    • 2015-10-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多