UISegmentedControl最佳实践

22
我正在尝试找出在 iPhone 应用程序中使用 UISegmentedControl 的“最佳”方法。我已经阅读了一些关于此的帖子,也看到了一些人的想法,但我无法确定最佳方法。我所指的帖子是: 从 UISegmentedControl 更改视图 以及 如何使用 UISegmentedControl 切换视图? 似乎有以下几种选项:
  • 在 IB 中添加每个视图,并将它们叠放在一起然后显示/隐藏它们
  • 在 IB 中单独创建每个子视图,然后在主视图中创建一个容器来填充所需的子视图
  • 设置一个非常高或非常宽的 UIView 并根据所选段动画向左/右或向上/下移动
  • 使用 UITabBarController 来交换子视图 - 看起来很傻
  • 对于表格,重新加载表格并在 cellForRowAtIndex 中根据选择的分段选项从不同的数据源或部分填充表格(不适用于我的应用)
那么哪种方法最适合子视图/非表格方法?哪个实现起来最容易?您能分享一些样例代码以进行此方法吗?
谢谢!
3个回答

19

我在iPad应用程序中也遇到了这个要求。

我想到的解决方案是为每种视图创建专门的视图控制器来处理与这些视图相关的业务逻辑(即与每个段相关的逻辑),并在选择段索引更改时以编程方式将它们添加/删除为子视图到一个“管理”控制器中。

要做到这一点,必须创建一个额外的UIViewController子类来管理UISegmentedControl更改,并添加/删除子视图。

下面的代码执行了所有这些操作,还考虑了一些注意事项/附加功能:

  • 子视图不会自动调用viewWillAppear / viewWillDisappear等方法,需要通过“管理”控制器告诉它们
  • 当“管理”控制器位于导航控制器内部时,它的viewWillAppear / viewWillDisappear等方法不会自动调用,因此需要导航控制器委托
  • 如果您想从一个段的子视图推送到导航栈中,您需要回调到“管理”视图来完成操作,因为子视图是在导航层次结构之外创建的,不会有对导航控制器的引用。
  • 如果在导航控制器场景中使用,则后退按钮自动设置为段名称。

界面:

@interface SegmentManagingViewController : UIViewController <UINavigationControllerDelegate> {
    UISegmentedControl    * segmentedControl;
    UIViewController      * activeViewController;
    NSArray               * segmentedViewControllers;
}

@property (nonatomic, retain) IBOutlet UISegmentedControl * segmentedControl;
@property (nonatomic, retain) UIViewController            * activeViewController;
@property (nonatomic, retain) NSArray                     * segmentedViewControllers;

@end

实现:

@interface SegmentManagingViewController ()
- (void)didChangeSegmentControl:(UISegmentedControl *)control;
@end

@implementation SegmentManagingViewController

@synthesize segmentedControl, activeViewController, segmentedViewControllers;

- (void)viewDidLoad {
    [super viewDidLoad];

    UIViewController * controller1 = [[MyViewController1 alloc] initWithParentViewController:self];
    UIViewController * controller2 = [[MyViewController2 alloc] initWithParentViewController:self];
    UIViewController * controller3 = [[MyViewController3 alloc] initWithParentViewController:self];

    self.segmentedViewControllers = [NSArray arrayWithObjects:controller1, controller2, controller3, nil];
    [controller1 release];
    [controller2 release];
    [controller3 release];

    self.navigationItem.titleView = self.segmentedControl =
    [[UISegmentedControl alloc] initWithItems:[NSArray arrayWithObjects:@"Seg 1", @"Seg 2", @"Seg 3", nil]];
    self.segmentedControl.selectedSegmentIndex = 0;
    self.segmentedControl.segmentedControlStyle = UISegmentedControlStyleBar;

    [self.segmentedControl addTarget:self action:@selector(didChangeSegmentControl:) forControlEvents:UIControlEventValueChanged];

    [self didChangeSegmentControl:self.segmentedControl]; // kick everything off
}

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    [self.activeViewController viewWillAppear:animated];
}

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    [self.activeViewController viewDidAppear:animated];
}

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    [self.activeViewController viewWillDisappear:animated];
}

- (void)viewDidDisappear:(BOOL)animated {
    [super viewDidDisappear:animated];
    [self.activeViewController viewDidDisappear:animated];
}

#pragma mark -
#pragma mark UINavigationControllerDelegate control

// Required to ensure we call viewDidAppear/viewWillAppear on ourselves (and the active view controller)
// inside of a navigation stack, since viewDidAppear/willAppear insn't invoked automatically. Without this
// selected table views don't know when to de-highlight the selected row.

- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
    [viewController viewDidAppear:animated];
}

- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
    [viewController viewWillAppear:animated];
}

#pragma mark -
#pragma mark Segment control

- (void)didChangeSegmentControl:(UISegmentedControl *)control {
    if (self.activeViewController) {
        [self.activeViewController viewWillDisappear:NO];
        [self.activeViewController.view removeFromSuperview];
        [self.activeViewController viewDidDisappear:NO];
    }

    self.activeViewController = [self.segmentedViewControllers objectAtIndex:control.selectedSegmentIndex];

    [self.activeViewController viewWillAppear:NO];
    [self.view addSubview:self.activeViewController.view];
    [self.activeViewController viewDidAppear:NO];

    NSString * segmentTitle = [control titleForSegmentAtIndex:control.selectedSegmentIndex];
    self.navigationItem.backBarButtonItem  = [[UIBarButtonItem alloc] initWithTitle:segmentTitle style:UIBarButtonItemStylePlain target:nil action:nil];
}

#pragma mark -
#pragma mark Memory management

- (void)dealloc {
    self.segmentedControl = nil;
    self.segmentedViewControllers = nil;
    self.activeViewController = nil;
    [super dealloc];
}

@end

希望这可以帮到你。

1
投票支持!-didChangeSegmentedControl:方法中存在一个小的内存泄漏。最后一条语句应该是:self.navigationItem.backBarButtonItem = [[[UIBarButtonItem alloc] initWithTitle:segmentTitle style:UIBarButtonItemStylePlain target:nil action:nil] autorelease]; viewDidLoad中也有类似的内存泄漏:self.navigationItem.titleView = self.segmentedControl = [[[UISegmentedControl alloc] initWithItems:[NSArray arrayWithObjects:@"Seg 1", @"Seg 2", @"Seg 3", nil]] autorelease]; - Mustafa
在这个模式的进一步更新中,我添加了一个链接 - 利用 UINavigationController 而不是“容器”视图控制器。http://redartisan.com/2010/6/27/uisegmented-control-view-switching-revisited - crafterm
由于某种原因,当我在iPad上使用它时,详细部分无法正确调整大小,就好像视图是以完整的iPad纵向绘制的。 - Convolution
@Convolution,你解决了这个问题吗?我在iPhone上实现时遇到了类似的问题。第一个选定的片段视图可以正确调整大小,但其他的不能。 - bencallis
@bencallis 抱歉,这件事发生得太久了,我完全忘记了我是如何解决这个问题的。 - Convolution

11
我会选择你提到的第二个选项,使用IB创建子视图并将其在主视图中进行切换。这是一个很好的机会来使用UIViewController(未经子类化):在初始设置中,使用-initWithNibName:bundle:创建控制器(其中第一个参数是包含单个子视图的NIB的名称,第二个参数是nil),根据需要将它的view添加为主视图的子视图。这样可以帮助保持内存占用低:当收到内存警告时,UIViewController的默认行为是释放其视图(如果没有父视图)。只要从视图层次结构中移除隐藏的视图,就可以将控制器保留在内存中,而不必担心释放任何内容。
(针对评论进行编辑:)
您不需要子类化UIViewController,但您需要为每个视图准备单独的XIB文件。您也不需要在IB中向容器视图添加任何内容。
在处理所有这些内容的类的接口中使用实例变量:
 UIViewController *controllerOne;
 UIViewController *controllerTwo;

 UIViewController *currentController;

 IBOutlet UIView *theContainerView;

在你的设置中 (-applicationDidFinishLaunching: 或者其他)

 controllerOne = [[UIViewController alloc] initWithNibName:@"MyFirstView" bundle:nil];
 controllerTwo = [[UIViewController alloc] initWithNibName:@"MySecondView" bundle:nil];

切换到一个控制器:

 - (void)switchToController:(UIViewController *)newCtl
 {
      if(newCtl == currentController)
           return;
      if([currentController isViewLoaded])
           [currentController.view removeFromSuperview];

      if(newCtl != nil)
           [theContainerView addSubview:newCtl.view];

      currentController = newCtl;
 }
然后只需使用例如以下方式调用:
 [self switchToController:controllerOne];

只是为了澄清一下...你的意思是要为每个子视图创建一个单独的UIViewController .h、.m和.xib文件,在IB中实例化它们中的一个,并将其添加到“包含”视图的viewDidLoad中,然后在IB中向包含视图添加一个UIView,然后做什么? - Neal L

3

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接