重新定义目标操作方法的最安全的方法是什么,以便`UIButtons`将消息中继到我的`UIViewController`中的方法?

我通过创建自定义UIView来重新组织旧代码,以将属于View UI元素与属于ViewController. UI元素分开ViewController. 以前包含在几个ViewControllers中的几个按钮现在位于单个自定义UIView ,我需要它们将消息发送到当前viewController内的方法,以便用户可以导航到其他viewControllers.

关于SO的答案建议使用委托,而对同一问题的答案表明不使用委托。 对不同问题的这个答案也是有道理的。 但我不知道哪一个最适合我的需要。 我不愿意做任何可能会破坏管理MultiviewViewController的当前appDelegate (它在ViewControllers).之间切换ViewControllers).任何事情ViewControllers).

所以我的问题是:

什么是重新定义目标操作方法的最安全的方法,以便UIButtons消息中继到我的UIViewController方法?

我的代码的“瘦”版本显示了我想要做的事情

安全 – 澄清

通过安全 ,我只是意味着可靠,即不太可能在运行时引入意想不到的后果。 这个问题的根本动机是将UI元素保留在它们所属的位置 – 即在UIViewUIViewController – 而不会破坏现有的应用程序。

ViewController.h

 #import  @interface ViewController : UIViewController @end 

ViewController.m

 #import "ViewController.h" #import "CustomView.h" @interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; CGRect rect = [UIScreen mainScreen].bounds; float statusBarHeight = [[UIApplication sharedApplication] statusBarFrame].size.height; CGRect screenFrame = CGRectMake(0, statusBarHeight, rect.size.width, rect.size.height - statusBarHeight); self.view = [[UIView alloc] initWithFrame: screenFrame]; self.view.backgroundColor = [UIColor lightGrayColor]; CustomView *cv = [[CustomView alloc]initWithFrame:screenFrame]; //create an instance of custom view [self.view addSubview:cv]; // add to your main view } - (void)goTo1 { NSLog(@"switch to Family 1"); // * commented out from the original app // MultiviewAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate]; // [parent setSelectedZone:1]; // [appDelegate displayView:1]; } - (void)goTo2 { NSLog(@"switch to Family 2"); // * commented out from the original app // MultiviewAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate]; // [parent setSelectedZone:2]; // [appDelegate displayView:1]; } - (void)goTo3 { NSLog(@"switch to Family 3"); // * commented out from the original app // MultiviewAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate]; // [parent setSelectedZone:3]; // [appDelegate displayView:1]; } @end 

CustomView.h

 #import  #import  @interface CustomView : UIView { } @end 

CustomView.m

 #import  #import "CustomView.h" @interface CustomView () - (UIViewController *)viewController; @end @implementation CustomView : UIView - (UIViewController *)viewController { if ([self.nextResponder isKindOfClass:UIViewController.class]) return (UIViewController *)self.nextResponder; else return nil; } - (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:[UIScreen mainScreen].bounds]; if (self) { [self threeButtons]; } return self; } - (void)buttonPicked:(UIButton*)button { NSLog(@"Button %ld : - send message to UIViewController instead.”, (long int)[button tag]); switch (button.tag) { case 1: // [self goTo1]; break; case 2: // [self goTo2]; break; case 3: // [self goTo3]; break; default: break; } } - (void)threeButtons { int count = 3; int space = 5; float size = 60; for (int i = 1; i <= count; i++) { CGFloat x = (i * (size + space)) + 40; CGFloat y = 100; CGFloat wide = size; CGFloat high = size; UIButton *buttonInView = [[UIButton alloc] initWithFrame:CGRectMake(x, y, wide, high)]; [buttonInView setTag:i]; [buttonInView addTarget:self action:@selector(buttonPicked:) forControlEvents:UIControlEventTouchUpInside]; buttonInView.layer.borderWidth = 0.25f; buttonInView.layer.cornerRadius = size/2; [buttonInView setTitle:[NSString stringWithFormat:@"%i", i] forState:UIControlStateNormal]; [buttonInView setTitleColor: [UIColor blackColor] forState:UIControlStateNormal]; buttonInView.layer.borderColor = [UIColor blackColor].CGColor; buttonInView.backgroundColor = UIColor.whiteColor; [self addSubview:buttonInView]; } } @end 

您链接的第3个答案的开头说明:

我认为在关于父结构的视图中知道它并不是一个好主意。 它打破了封装,导致难以维护,错误和额外的关系。

我认为这只是一半的权利。 我要说的是,自定义视图不仅不知道控制器可能使用它的任何内容,使用自定义视图的控制器也不应该知道有关自定义视图的详细信息。

UITableView为例。 表视图对使用表视图的任何类都一无所知。 通过使用其委托和数据源协议实现这一点。 同时,使用表视图的控制器没有任何直接知识或挂钩到表视图的视图结构(超出与表视图单元关联的特定API)。

您的自定义视图的用户不应该知道构成视图的UI组件。 它应该只显示指示某些高级事件发生的事件,而不是UIButton特定的UIButton (或其他)。

考虑到这些想法,你联系的第一个答案是最好的。 使用适当的界面设计自定义视图,而不是隐藏其内部详细信息。 这可以使用委托协议,通知(如在NSNotificationCenter )或事件块属性来完成。

通过采用这种方法,您的自定义视图的实现可以完全更改,而无需更改事件接口。 您现在可以用其他自定义视图替换UIButton (例如)。 您只需调整代码以调用相同的委托方法(或发布一些适当的通知)。 现在无需担心自定义视图的每个客户端都需要从调用addTarget...更改为其他一些适当的代码。 所有客户端只是继续实现自定义视图的相同旧委托方法。

在您的特定情况下,不要将自定义视图视为具有某些控制器需要处理的三个按钮。 将您的自定义视图视为可能发生3种不同的事件。 自定义视图的客户端只需要知道其中一个事件发生了,哪个事件发生了。 但是,发送给自定义视图的客户端的信息都不应包含任何有关UIButton 。 将3个事件分类为特定于这些事件所代表的更抽象的级别,而不是如何实现它们。

委托协议方法似乎解决了我的问题。 为此,有必要将action方法从CustomView类移动到ViewController类,其中的角色是导航到其他viewcontrollers。 最初有一条与该线相关的警告

  cv.delegate = self; Assigning to 'id' from incompatible type 'ViewController *const __strong'. 

此链接描述了通过将ViewController实例强制转换为委托来解决的类似警告 。 在这里,它涉及将违规声明更改为

  cv.delegate = (id )self; 

我不确定这个解决方案在哪里与rmaddy的有用答案的以下声明一致

您的自定义视图的用户不应该知道构成视图的UI组件。 它应该只显示指示某些高级事件发生的事件,而不是触发特定的UIButton(或其他)。

但是我已经可以看到它将来如何加速现有应用的维护。 除非有人提供更好的东西,否则我会继续使用它。 谢谢rmaddy 。

CustomView.h

 #import  @protocol CustomViewDelegate  -(void)buttonPressed:(UIButton*)button; @end @interface CustomView : UIView @property (assign) id delegate; @end 

CustomView.m

 #import "CustomView.h" @implementation CustomView - (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:[UIScreen mainScreen].bounds]; if (self) { self.backgroundColor = [UIColor lightGrayColor]; [self threeButtons]; } return self; } - (void)threeButtons { int buttonCount = 3; int space = 5; float buttonSize = 60; int initialOffset = 60; CGFloat horizontallyCentred = ([UIScreen mainScreen].bounds.size.width - buttonSize) / 2; for (int i = 1; i <= buttonCount; i++) { CGFloat x = horizontallyCentred; CGFloat y = initialOffset + i * (buttonSize + space); CGFloat wide = buttonSize; CGFloat high = buttonSize; UIButton *buttonInView = [[UIButton alloc] initWithFrame:CGRectMake(x, y, wide, high)]; [buttonInView setTag:i]; [buttonInView addTarget:self.delegate action:@selector(buttonPressed:) forControlEvents:UIControlEventTouchUpInside]; buttonInView.layer.borderWidth = 0.25f; buttonInView.layer.cornerRadius = buttonSize/2; [buttonInView setTitle:[NSString stringWithFormat:@"%i", i] forState:UIControlStateNormal]; [buttonInView setTitleColor: [UIColor blackColor] forState:UIControlStateNormal]; buttonInView.layer.borderColor = [UIColor blackColor].CGColor; buttonInView.backgroundColor = UIColor.whiteColor; [self addSubview:buttonInView]; } } @end 

ViewController.h

 #import  @interface ViewController : UIViewController @end 

ViewController.m

 #import "ViewController.h" #import "CustomView.h" @interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; CGRect rect = [UIScreen mainScreen].bounds; float statusBarHeight = [[UIApplication sharedApplication] statusBarFrame].size.height; CGRect screenFrame = CGRectMake(0, statusBarHeight, rect.size.width, rect.size.height - statusBarHeight); self.view = [[UIView alloc] initWithFrame: screenFrame]; CustomView *cv = [[CustomView alloc]initWithFrame:screenFrame]; //create an instance of custom view // cv.delegate = self; cv.delegate = (id )self; [self.view addSubview:cv]; } - (void)buttonPressed:(UIButton*)button { NSLog(@"Button %ld : - message sent from UIView.", (long int)[button tag]); switch (button.tag) { case 1: [self goTo1]; break; case 2: [self goTo2]; break; case 3: [self goTo3]; break; default: break; } } - (void)goTo1 { NSLog(@"switched to Family 1"); // MultiviewAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate]; // [parent setSelectedZone:1]; // [appDelegate displayView:1]; } - (void)goTo2 { NSLog(@"switched to Family 2"); // MultiviewAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate]; // [parent setSelectedZone:2]; // [appDelegate displayView:1]; } - (void)goTo3 { NSLog(@"switched to Family 3"); // MultiviewAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate]; // [parent setSelectedZone:3]; // [appDelegate displayView:1]; } @end