使用Segue在视图控制器之间传递数据

24

我是iOS新手。我在ViewControllers之间传递数据时遇到了问题。

我的设置如下:

  • 选择view_1
  • 推出view_2
  • 推出view_3

我想把'view_1'的ViewController引用(id)发送到'view_3'。所以我在'view_1'中包括include "view_3"并将值设置到'view_3'的变量中(使用view_3 *v3=[[view_3 alloc] init ]; v3.reference=self;)。在控制台中,它显示:

视图控制器:-; <ViewController:0x89e9540>

但是在'view_3'中,控制台中显示

视图控制器(null)

但是当我使用'view_2'传递此数据时,它可以工作。但是原因是什么?我想知道这种行为,并且是否有任何解决方法?

请帮忙解答。


Max,我强烈建议你遵循@CouchDeveloper提出的指导意见。如果你是iOS新手,应该努力遵守苹果公司建议的规范。偏离这些准则会给你带来麻烦,并导致你的代码难以管理。 - physics90
6个回答

46
当触发segue时,“将数据传递给目标控制器”将通过重写方法prepareForSegue:sender:来实现。
通常情况下,您将数据而不是源视图控制器传递给目标视图控制器。 “数据”可能是您的应用程序“模型”的某个方面。它是像“用户”这样的对象,或者可能是包含“用户”等内容的数组。 目标视图控制器不应该了解视图控制器。这意味着,目标视图控制器不需要导入源视图控制器的头文件。
另一方面,视图控制器可以了解目标视图控制器的具体类或目标视图控制器的基本类,因此将导入目标视图控制器的头文件。
请参阅:在触发Segue时配置目标控制器 如果您需要在源和目标之间建立某种“通信协议”,您可以使用委托与其他视图控制器进行通信。这涉及到定义一个@protocol(例如,有一个方法doneButton),以及在目标视图控制器中定义的一个属性delegate。如果该协议是特定于目标视图控制器的,则应在目标视图控制器的头文件中定义该协议。通常,您应该从目标控制器的角度定义协议,而不是从源控制器的要求出发。
然后,源视图控制器创建一个代理(除非它本身已经是代理),并设置目标视图控制器的delegate。目标视图控制器将把委托方法发送给代理,由代理处理。
现在,从VC_A传递“数据”到VC_B应该很简单。您应该阅读一些使用prepareForSegue:sender:的示例。例如,目标视图控制器可能具有一个表示其应显示的内容的属性data。源视图控制器必须在prepareForSegue:sender:中设置此属性。
Passing data from VC_A over VC_B to VC_C should be straight forward as well.
注意:每个视图控制器都可以定制其数据,以使其成为下一个视图控制器的合适数据。
如果VC_C需要其源视图控制器VC_B中没有的数据,则有几种方法可以解决此问题。但是,这通常是设计不良的标志。
您可以拥有一个应用程序模型,它是全局的。假设您的“应用程序模型”是类型为Document的对象。假设在任何时候只有一个该应用程序模型的实例。那么,该模型是一个“单例”,可以像这样从应用程序的任何地方访问:
Document* document = [Document sharedDocument];

然而,获取一个模型实例的首选方法是在需要访问它的第一个视图控制器中,这种情况下是 VC_A。

接着,VC_A将一个Document实例传递给下一个视图控制器 VC_B。而VC_B又将文档对象传递给VC_C。

您应该阅读官方文档 "View Controller Programming Guide for iOS"。


示例1

假设您有一个名为“用户”的列表。该列表应在表视图控制器中显示,并且还应该有一个详细视图,显示一个用户的详细信息。

表视图控制器将具有一个“data”属性users:

UsersTableViewController.h中:

@interface UsersTableViewController : UIViewController
@property (nonatomic, readonly) NSArray* users;
@end

严格来说,这个user属性不需要公开。例如,如果表视图内部自己获取用户列表,则没有必要从外部访问它。

"users"数组是表视图的数据,应该以行显示。每一行显示一个用户的“摘要”。

用户的更多详细信息应该在详细视图控制器中显示。详细视图控制器的数据是单个类型为User的用户。

当用户在表视图中点击某一行时,将显示详细视图控制器。在显示之前,表视图控制器必须配置详细视图控制器:表视图控制器将当前选定的用户分配给详细视图控制器的“数据属性”。因此,详细视图控制器应该有一个公共属性user

@interface UserViewController : UIViewController
@property (nonatomic) User* user;
@end

"The table view controller在prepareForSegue:sender:中配置详细视图控制器:"
"UsersTableViewController.m中:"
- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if ([[segue identifier] isEqualToString:@"ShowUserDetails"]) {
        UserViewController* userViewController = [segue destinationViewController];
        userViewController.user = [self.users objectInListAtIndex:[self.tableView indexPathForSelectedRow].row];
    }
}

例子2

第二个例子更加复杂,使用“委托”作为一种在控制器之间建立通信的手段。

注意:

这不是一个完整的例子。本例目的在于演示如何使用“委托”。实现像例子中所示的数据任务的全功能实现将需要更多的努力。在这种情况下,“委托”将是最常用的方法来完成这项任务(依我看)。

假设我们想要:

  • 显示用户
  • 修改(编辑)用户
  • 创建新用户,并且
  • 删除用户

从详细视图中进行这些“数据任务”不应该由详细视图控制器自己执行,而是由代理负责这些数据任务。

这些数据操作应该由代理处理:

@protocol UserDataSourceDelegateProtocol <NSObject>
- (User*) viewControllerUser:(UserViewControllerBase*)viewController;
- (void) viewController:(UserViewControllerBase*)viewController dismissWithUpdatedUser:(User*)user;
- (void) viewController:(UserViewControllerBase*)viewController dismissWithDeletedUser:(User*)user;
- (void) viewController:(UserViewControllerBase*)viewController dismissWithCreatedUser:(User*)user;
@end

这个协议反映了基本的CRUD方法(创建、读取、更新、删除)。
再次强调,我们不希望详细视图控制器“自己”执行这些数据方法,而是由实现UserDataSourceDelegateProtocol的实例执行。详细视图控制器具有此委托的属性,并将这些“数据任务”发送到委托。
可能会有几个详细视图控制器,都是抽象类UserViewControllerBase的子类,处理显示、编辑和创建任务。用户删除可以在表视图和“显示用户”视图控制器中执行:
- ShowUserViewController - EditUserViewController - NewUserViewController 例如,当用户点击“返回”按钮并且用户修改了用户对象时,EditUserViewController将发送viewController:dismissWithUpdatedUser:消息。现在,委托可能允许或不允许关闭详细视图。例如,在存在验证错误时,它可能会禁止关闭。

UserDataSourceDelegateProtocol 协议可以在根视图控制器中实现,例如表视图控制器。然而,一个专门负责处理数据任务的独立类可能更合适。在下面的示例中,表视图控制器也将成为此数据处理程序。

UserDataSourceDelegateProtocol 可以在额外的头文件中定义。

UsersTableViewController.m 中:

#import "UserDataSourceDelegateProtocol.h"
#import "ShowUserViewController.h"


@interface UsersTableViewController () <UserDataSourceDelegateProtocol> 
@property (nonatomic, readonly) NSArray* users;
@end


// This delegate will be called when the detail view controller request 
// the user object which shall be displayed.
- (User*) viewControllerUser:(UserViewControllerBase*)viewController {
    return [self.users objectInListAtIndex:[self.tableView indexPathForSelectedRow].row];
}

在这里,表视图控制器配置显示用户详细信息视图控制器:

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if ([segue.identifier isEqualToString:UserShowSegueID])
    {
        ShowUserViewController* showViewController = segue.destinationViewController;
        showViewController.delegate = self; // Data Source Handler is self
    }
}

“编辑用户”视图控制器通常是“显示用户”视图控制器的目标视图控制器,当用户点击“编辑”按钮时会查看该控制器。
“显示用户”视图控制器将为“编辑用户”视图控制器设置委托,并获取相同的委托:
ShowUserViewController.m 文件中。
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if ([segue.identifier isEqualToString:UserEditSegueID])
    {
        EditUserViewController* editViewController = segue.destinationViewController;
        editViewController.delegate = self.delegate; // pass through the data source hanlder
    }
}

数据代理可以按照以下方式处理更新的用户:
在UsersTableViewController.m中:
- (void) viewController:(UserViewControllerBase*)viewController
 dismissWithUpdatedUser:(User*)user {
    if (/* is valid user and can be saved */) {
        [viewController.presentingViewController dismissViewControllerAnimated:YES
                                                                     completion:nil];
    }
}

13

这个Swift项目在YouTube上帮助我最终理解如何做到这一点。

我设置了一个简单的示例,类似地进行了操作。在文本字段中写入一些文本,按下按钮,它会将文本放入下一个视图控制器中的标签中。

设置Storyboard

enter image description here

这并不是非常困难。在Interface Builder中创建Storyboard布局。要使segue工作,您只需要按住control键点击按钮并拖动到第二个视图控制器。

第一个视图控制器

第一个视图控制器的代码为

import UIKit

class FirstViewController: UIViewController {

    @IBOutlet weak var textField: UITextField!

    // This function is called before the segue
    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {

        // get a reference to the second view controller
        let secondViewController = segue.destinationViewController as! SecondViewController

        // set a variable in the second view controller with the String to pass
        secondViewController.receivedString = textField.text!
    }

}

第二个视图控制器

第二个视图控制器的代码如下:

import UIKit

class SecondViewController: UIViewController {

    @IBOutlet weak var label: UILabel!

    // This variable will hold the data being passed from the First View Controller
    var receivedString = ""

    override func viewDidLoad() {
        super.viewDidLoad()

        // Used the text from the First View Controller to set the label
        label.text = receivedString
    }

}

不要忘记

  • UITextFieldUILabel 的插座连接起来。
  • 在IB中将第一个和第二个视图控制器设置为适当的Swift文件。

将数据传递给第三个视图控制器的过程是相同的。


2

嗯,有一个解决方案,但不是最合适的方式。

ViewController3.h

-(void)getUser:(NSString *)strPassedUser;

前往您的ViewController3.m文件,在@interface上方添加一个变量,如下所示

NSString *recievingVariable ;

然后在 ViewController3.m 文件的某个位置

-(void)getUser:(NSString *)strPassedUser
{
    recievingVariable = strPassedUser;
}

导入ViewController1和ViewController3,然后像这样操作...

ViewController3 * vc3 = [ViewController3 alloc]getUser :@"me"];

在这种情况下,您的函数getUser将被调用,您将获得receivingVariable = me

这个可以工作。由于某种原因,仅将属性放在头文件中并使其可分配并不起作用,而不必放置setter。出于样式的原因,我会称其为setUser而不是getUser。此外,这是在segue中完成的,因此您无需分配viewcontroller。 - James O'Brien

1
更好更简单的解决方案。
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
UIViewController *vc = [storyboard instantiateViewControllerWithIdentifier:@"webView"];
webView = (WebViewController *)vc;
webView.strWebLink = @"http://www.Google.com/";
[self.navigationController showViewController:vc sender:self];

1
你显然没有仔细阅读问题。OP正在询问有关segue的内容,而你只是在代码中实例化VC,然后将其推送。 - Daniel Galasko

1
在Swift 4.0中。
 // In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    let vc = segue.destination as! SecondViewController
    vc.username = self.username
}

请确保segue.destination在这里是真正的不同之处。


1

使用单例模式:

在软件工程中,单例模式是一种设计模式,确保一个类只有一个实例,并提供全局访问点。

由于它具有唯一的实例,类变量和方法在整个应用程序/命名空间中共享。

示例:

class Singleton {
    static let sharedInstance = Singleton()
    var studentId = 1281
}

你可以像这样在你的应用程序中任何地方使用它:

var studentId = Singleton.sharedInstance.studentId
print("Student Id: \(studentId)")

我认为这是以编程方式执行转场时保持代码清洁的好方法。我想知道为什么这没有赞成票。 - Oliver Zhang

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