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

Posted

技术标签:

【中文标题】什么是重新定义目标操作方法的最安全方法,以便“UIButtons”将消息中继到我的“UIViewController”中的方法?【英文标题】:what is the safest way to redefine the target-action method so `UIButtons` relay messages to methods in my `UIViewController`? 【发布时间】:2017-03-23 01:32:19 【问题描述】:

我正在通过创建自定义 UIView 来重新组织旧代码,以将属于 View 的 UI 元素与属于 ViewController. 的 UI 元素分开987654327@ 我需要他们向当前viewController 中的方法发送消息,以便用户可以导航到其他viewControllers.

This answer 在 SO 上建议使用委托,而 this answer 在同一问题上建议不使用委托。 This answer 到另一个问题也很有意义。但我不知道哪一个最适合我的需要。我不愿意做任何可能破坏当前管理 MultiviewViewControllerappDelegate 的事情(这反过来又在 ViewControllers). 之间切换

所以我的问题是:

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

我的代码的“瘦”版本下面显示了我正在尝试做的事情

安全 - 澄清

安全,我只是指可靠,即不太可能引入 运行时的意外后果。这个问题的根本动机是保持 UI 它们所属的元素 - 即在 UIViewUIViewController 中 - 不破坏现有应用程序。

ViewController.h

#import <UIKit/UIKit.h>

@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 <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>

@interface CustomView : UIView 


@end

CustomView.m

#import <Foundation/Foundation.h>
#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

【问题讨论】:

“最安全”是什么意思? 【参考方案1】:

3rd answer you linked 的开头声明:

我认为在视图内部了解父结构并不是一个好主意。它破坏了封装并导致难以维护、错误和额外的关系。

我认为这只是对了一半。我想说的是,自定义视图不仅不应该知道任何控制器可能使用它的任何信息,使用自定义视图的控制器也不应该知道任何有关自定义视图细节的信息。

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

您的自定义视图的任何用户都不应该知道哪些 UI 组件构成了该视图。它应该只公开表明某些高级事件发生的事件,而不是特定的UIButton(或其他)被窃听。

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

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

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

【讨论】:

rmaddy,不错的主角。谢谢。我将使用委托和协议,但需要时间更深入地研究这些。我喜欢“自定义视图的实现可以完全改变而无需更改其事件接口”的想法。在我更好地理解之前,我会在将其标记为已接受的答案之前依靠赞成票。 很好地回答了@rmaddy :)。 @Greg您链接的第一个答案几乎就是您想要的。我要做的唯一改变是,代替通用的buttonPressed 委托方法,为每个单独的“事件”创建一个单独的方法,以便您的视图控制器确切地知道当该事件发生时该做什么并且不必有了解按钮标签值的含义。它只知道如果事件 X 发生,它需要采取 Y 行动。 Allan,我也想知道这一点,特别是因为我的一些视图将有 16 个按钮,其他视图将有 10 个,等等,如果我正确理解 rmaddy,通用委托方法不会删除需要为每个单独的事件使用单独的方法 我想这取决于您应用程序其余部分的设计。这 16 个按钮中的每一个是否都呈现完全不同的视图控制器?根据按下的按钮显示带有变量的单个视图控制器是否足够? 艾伦,不完全是。 16 个按钮中的每一个都代表该视图控制器的不同变量,另一个视图控制器选择 5 个变量,依此类推。这是一个音乐应用程序,但不是通常的应用程序,我正在为下一场音乐会做好准备此链接可能有助于解释 youtube.com/watch?v=gfaZly6dhQA 如果我从今天开始,我会在一个视图控制器中完成所有这些。【参考方案2】:

委托协议方法似乎可以解决我的问题。为此,有必要将操作方法​​从CustomView 类移动到ViewController 类,它的作用是导航到其他视图控制器。最初有一个与该行相关的警告

    cv.delegate                 = self;

Assigning to 'id<CustomViewDelegate>' from incompatible type 'ViewController *const __strong’.

此链接描述a similar warning 通过将 ViewController 实例转换为委托来解决。在这里,它涉及将违规语句更改为

    cv.delegate                 = (id <CustomViewDelegate>)self;

我不确定这个解决方案在哪里,来自 rmaddy 的有用答案的以下陈述

您的自定义视图的任何用户都不应该知道哪些 UI 组件构成了 看法。它应该只公开表明某些高级别的事件 事件发生了,而不是点击了特定的 UIButton(或其他)。

但我已经看到它将如何在未来加速现有应用程序的维护。除非有人提供更好的东西,否则我会选择它。谢谢rmaddy。

解决方案

CustomView.h

#import <UIKit/UIKit.h>

@protocol CustomViewDelegate <NSObject>

-(void)buttonPressed:(UIButton*)button;

@end


@interface CustomView : UIView

@property (assign) id<CustomViewDelegate> 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 <UIKit/UIKit.h>

@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 <CustomViewDelegate>)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

【讨论】:

以上是关于什么是重新定义目标操作方法的最安全方法,以便“UIButtons”将消息中继到我的“UIViewController”中的方法?的主要内容,如果未能解决你的问题,请参考以下文章

通过多步骤表格传递信用卡号的最安全方法?

为 C/C++ 编写检测分析器的最简单方法是啥?

在pygame中,重置每个子画面位置的最有效方法是什么?

处理信用卡的最安全方法[关闭]

获得最近目标的最有效方法是啥

什么是线程安全?