将iOS项目重构为不使用故事板

时间:2014-05-20 00:40:10

标签: ios objective-c xcode

随着我的项目越来越大&更复杂的是,我发现使用故事板变得无法管理。我的故事板文件变得越来越大,我在应用程序中重用组件/整个视图的需求正在增长,并且需要从屏幕跳转到完全不同的屏幕变得困难,而不会在我的故事板上出现纠结的混乱。

我已经开始在这样的代码中执行诸如push / pop / present模态视图控制器之类的简单操作:

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

但我正在寻求有关如何进一步采取行动的建议。

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

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

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

由于

1 个答案:

答案 0 :(得分:1)

为我最复杂的UI呈现故事板......

Marked up Storyboard image

这就是我需要的(目前)六个独立的UI标签,具有无限的能力,可以潜入"多个视图控制器深度,使用户能够找到他们正在寻找的数据。

警告 警告 警告 我担心用户可能会失去"在这个迷宫中。请注意您的UI设计,以确保用户知道如何深入"深入"他们正在进行他们的数据搜索游览,或者至少为他们提供一个简单的方法来“爆炸”#34;他们退路了。

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

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

我会忽略"特殊功能"这个练习的控制器。

所以让我们关注:

  1. "标准清单"键入TVC,
  2. "详情内容"键入TVC,&
  3. "从"中选择键入TVC。
  4. 当用户选择标签时,"标准列表"类型TVC提供UI以向用户显示项目列表。添加按钮(导航栏项目)提供了通过"详细内容"手动将新项目添加到列表的机会。键入TVC。

    IB来自"标准列表"到"详细内容"键入TVC

    故事板(IB创建)从"标准列表"中的项目行开始。触发“细节内容”#seg;键入TVC,以便用户可以查看和/或编辑项目数据。

    故事板(IB创建)从"标准列表"中的添加按钮(导航栏项目)开始。触发一个空白或新的" "详细内容"中的项目键入TVC,以便用户可以添加新项目和相关数据。

    IB来自"详情内容"到"从"中选择键入TVC

    故事板(IB创建)从"详细内容"中的项目行开始。触发一个segue到"选择"键入TVC,具体取决于该内容所需的数据类型。 (例如,"选择"类型TVC之一是嵌入在标准视图控制器中的UIDatePicker,使用户可以轻松选择日期和/或时间。)

    我到达了我想要开始跳跃的地步"选择来自"键入TVC回到"详细内容"键入TVC。

    我甚至没有尝试手动执行此操作。我的强迫症需要我的故事板保持原始和尽可能整洁 - 疯狂的segues"倒退"以便于重用控制器。

    所以最后回答 - 是的,谢谢你坚持我 - 我认为序言是必要的。

    对于与&#34相关的每个班级;从"中选择键入TVC,我创建了一个自定义segue,嵌入在TVC实现文件的代码中。

    随着我越来越熟悉自定义segues,我可能会将这些删除到一个单独的帮助"类。

    但是在那之前,每个类实现文件顶部的代码"从"中选择键入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
    

    简单,对吧!

    下一步?使用"故事板ID"提供您想要重复使用的控制器。

    返回Interface Builder / Storyboard文件,选择要在代码中重复使用的控制器。

    在Identity Inspector中,在副标题" Identity"下,输入" Storyboard ID"。我喜欢使用" id _"作为故事板ID的前缀,如下面的示例图所示。我将在下面的示例代码中使用此示例,因此请密切注意。

    Identity Inspector

    下一步?如何触发自定义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?
    
        }
    

    希望这有帮助。