在Swift中如何在标签页视图控制器之间传递数据?

27

它的作用

  1. 当我的选项卡控制器中第一个页面加载时,我从一个JSON文件中获取数据
  2. 我将其存储在数组中(在第一个视图控制器中)
  3. 获取到的数据将显示在第二个视图控制器中。这些数据已经被加载并存储在第一个视图控制器的数组中。

问题:

我无法找到一种方法来在两个视图控制器之间传递数据,因为不能基于Segue标识符来传递数据,因为这是一个选项卡控制器

请帮忙解决!


使用 unwind segue 的示例。但是,仅当您从一个选项卡的后代导航到另一个选项卡的根时,这才有用。 - Suragch
https://stackoverflow.com/a/65499693/14414215详细说明了我在项目中的实现方式。 - app4g
7个回答

38

如果您需要在视图控制器之间传递数据,则:

var secondTab = self.tabBarController?.viewControllers[1] as SecondViewController
secondTab.array = firstArray 

它说 [AnyObject] 没有名为 subscript 的成员。 - 0to1000
2
如果有超过2个选项卡怎么办? - user5306470
2
@DaniSpringer 这是一种非常老的处理方式。最好使用依赖注入和Presenter来处理您的代码。 当然,如果您想要额外使用这个: var thirdTab = self.tabBarController?.viewControllers[2] as ThirdViewController thirdTab.array = firstArray viewControllers[]数组中存储了所有viewController的信息,因此您可以从tabController内部访问它们。 - H. Serdar Çınar
很好。我应该查找哪种更新的方法来完成这个任务? - user5306470
请查看 RxSwift 以了解更好的方式和响应式编程。 - H. Serdar Çınar

22

如Woodster在上面的回答中建议的那样,我最终使用了单例模式。

在Swift 3中

创建一个新的Swift文件并创建一个类:

class Items {
    static let sharedInstance = Items()
    var array = [String]() 
}

在任何一个你的视图控制器中,你都可以像这样访问你的数组:

Items.sharedInstance.array.append("New String")
print(Items.sharedInstance.array)

3
在Stack和其他地方讨论了半打次之后,有很多不同的答案来解决这个问题,但是这篇回答/解决方案是最好的明确而简洁的。这是一个很好的干净的架构,符合Model View Presenter等设计模式。如果可能的话,我想给这个答案点赞5次,以帮助关注那些问如何解决这个问题的人们。非常感谢您分享看起来像最佳实践,也就是解决这个需求的正确方式。 - Gene Bo
我很高兴能够帮助 :) 更多信息请参见此处。https://krakendev.io/blog/the-right-way-to-write-a-singleton - Jeff
4
我认为这并不是一种“良好的清晰架构”。在代码库中任意访问全局状态听起来可能会带来潜在的灾难。甚至链接文章的作者也表示:“请注意,这篇文章并不代表我赞成使用单例来共享状态”。 - NeverwinterMoon

10

H. Serdar的代码示例是正确的,这是访问另一个选项卡的视图控制器并向其提供数据的方法。

请记住,在Swift中传递数组时,您是按值传递它的 ,而不像Objective-C一样按引用传递。这意味着您的第二个视图控制器所做的更改不会反映在第一个视图控制器中,因为第二个视图控制器正在使用数组的副本,而不是相同的数组。如果您想让两个视图控制器修改同一个数组,请将数组放在类中,并传递该类的单个实例。

还有其他一些注意事项: 您可以对TabBarController进行子类化,以给它一个存储数据的属性,并且该属性可供所有选项卡使用:

if let tbc = tabBarController as? YourCustomTabBarSubclass {
  println("here's my data \(tbc.array)")
 }

在这种情况下,您将从多个标签页访问同一数组,因此一个标签页中的更改会反映在其他地方。

我建议不要使用您的应用程序委托作为存储数据的集中位置。这不是应用程序委托的目的。它的目的是处理应用程序对象的委托调用。

视图控制器应该拥有所有需要完成其工作的封装在其中的数据。它们与其模型数据(例如您的数组、数据库引用或托管对象上下文引用)保持连接,而不是通过遍历视图控制器图或进入委托甚至使用全局变量来访问另一个对象。这种模块化、自包含的视图控制器结构使您可以重新组织您的应用程序以适应不同设备的相似但唯一的设计,例如在一个设备上(如iPad)中以弹出视图控制器的形式呈现,在另一个设备上(如iPhone)则以全屏形式呈现。


8

SWIFT 3

在您的第一个视图控制器中,像平常一样声明变量(在您的情况下是一个数组)。

在您的第二个视图控制器中,请执行以下操作:

var yourVariable: YourVariableClass {
    get {
        return (self.tabBarController!.viewControllers![0] as! FirstViewControllerClass).yourVariable
    }
    set {
        (self.tabBarController!.viewControllers![0] as! FirstViewControllerClass).yourVariable = newValue
    }
}

这是因为在标签栏控制器中,所有选项卡项目后面的视图控制器都会被初始化。在第二个视图控制器中这样做,实际上是从/向第一个视图控制器获取/设置变量。

2

Xcode 11 & Swift 5 + Storyboard + 依赖注入方法

假设您正在使用Storyboard,这是我设计的一种方法。

步骤1:

像下面图片中所示,在tabBarController上放置一个标识符。

Tab Bar Controller

步骤2:

scenedelegate.swift文件(不是appDelegate.swift)中,将以下代码添加到适当的func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 方法中。

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
        // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
        // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).

        self.window = self.window ?? UIWindow()//@JA- If this scene's self.window is nil then set a new UIWindow object to it.

        //@Grab the storyboard and ensure that the tab bar controller is reinstantiated with the details below.
        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        let tabBarController = storyboard.instantiateViewController(withIdentifier: "tabBarController") as! UITabBarController

        for child in tabBarController.viewControllers ?? [] {
            if let top = child as? StateControllerProtocol {
                print("State Controller Passed To:")
                print(child.title!)
                top.setState(state: stateController)
            }
        }

        self.window!.rootViewController = tabBarController //Set the rootViewController to our modified version with the StateController instances
        self.window!.makeKeyAndVisible()

        print("Finished scene setting code")
        guard let _ = (scene as? UIWindowScene) else { return }
    }

您会注意到,scenedelgate.swift文件有一个成员变量; var window: UIWindow?。在xCode 11和Swift 5中,这个变量从appDelegate中移动了出来,所以很多类似的答案和教程都已经过时了。
在代码中的部分storyboard.instantiateViewController(withIdentifier:您需要添加您用的名称作为参数。在我的截图中,您会看到我将其称为tabBarController。
为了使此函数适用于任何类型的viewController,而不必在索引上单独实例化每个viewController,我使用了称为StateControllerProtocol的协议策略。我们下一步将创建这个协议以及保存全局变量的StateController。
第三步:
stateController.swift或您希望将其命名的文件中,添加以下代码,并删除对您的项目无用的方面。
import Foundation

struct tdfvars{
    var lateBED:Double = 0.0
    var acuteBED:Double = 0.0
    var rbe:Double = 1.4
    var t1half:Double = 1.5
    var alphaBetaLate:Double = 3.0
    var alphaBetaAcute:Double = 10.0
    var totalDose:Double = 6000.00
    var dosePerFraction:Double = 200.0
    var numOfFractions:Double = 30
    var totalTime:Double = 168
    var ldrDose:Double = 8500.0
}

//@JA - Protocol that view controllers should have that defines that it should have a function to setState
protocol StateControllerProtocol {
  func setState(state: StateController)
}

class StateController {
    var tdfvariables:tdfvars = tdfvars()
}

您想在视图之间共享的变量,我建议将其添加到结构体中。 我把我的命名为tdfvariables,但您需要根据项目命名。请注意此处定义的协议。 这是一个协议,将作为扩展添加到每个viewController中,该扩展定义应有一个函数来设置其stateController成员变量(我们尚未定义,但稍后会进行)。 第四步: 在我的情况下,我有两个由tabBarController控制的视图。 StandardRegimenViewController和settingsViewController。这是您要为自己的viewControllers添加的代码。
import UIKit

class SettingsViewController: UIViewController {

    var stateController: StateController?

    override func viewDidLoad() {
        super.viewDidLoad()

    }


}

//@JA - This adds the stateController variable to the viewController
extension SettingsViewController: StateControllerProtocol {
  func setState(state: StateController) {
    self.stateController = state
  }
}

这里的扩展功能向您的类添加协议,并添加了我们在stateController.swift文件中定义的所需函数。最终,这将使stateController及其结构值进入您的viewController。

第5步:

使用stateController访问您的变量!完成了!

以下是我在一个控制器中实现此操作的示例。

stateController?.tdfvariables.lateBED = 100

你可以以同样的方式读取变量!这种方法的优点是你不使用单例,而是使用依赖注入来为你的视图控制器和其他可能需要访问你的变量的内容提供支持。阅读更多关于依赖注入的文章以了解其与单例相比的优势。

0
你可以重写 tabBar(didSelect:) 方法,然后在 UITabViewController 上索引 ViewControllers 数组,并将 ViewController 强制转换为所需的自定义 ViewController。不需要共享可变状态和所有相关问题。
class SecondViewController: UIViewController {
    
    var array: [Int] = []
    override func viewDidLoad() {
        super.viewDidLoad()
    }
}


class TabViewController: UITabBarController {

   override func tabBar(
        _ tabBar: UITabBar, 
        didSelect item: UITabBarItem
    ) {
        super.tabBar(tabBar, didSelect: item)
        var secondTab = viewControllers?[1] as? SecondViewController
        secondTab?.array = [1, 2, 3]
    }
}

0
我在我的应用程序中有一个选项卡视图控制器,我在多个选项卡视图中使用相同的数组。 我通过在任何类之外(在import UIKit和类声明之间的行中)声明该数组来实现此目的,以便它基本上是每个视图都可以访问的全局变量。 你试过这样做吗?

你是如何从另一个类中访问它的? - 0to1000
为什么这个答案没有点赞?是因为它是不好的做法,还是因为它看起来是在这里最好/最容易的解决方案? - Lightsout
我尝试了这个方法,一开始似乎有效,但是我在一个控制器内对数组所做的任何更改似乎都没有修改其他控制器看到的全局数组。 - Lightsout
6
不要这样做,LOL。 - Jeff
2
全局变量是不良实践。这对于架构、维护和安全性都是不利的,此外还有其他原因。 - CRey
显示剩余2条评论

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