将数据从委托 Swift 类传递到 SwiftUI 结构中的 EnvironmentObject

Posted

技术标签:

【中文标题】将数据从委托 Swift 类传递到 SwiftUI 结构中的 EnvironmentObject【英文标题】:Passing Data from Delegate Swift Class to EnvironmentObject in SwiftUI Struct 【发布时间】:2020-06-01 16:58:49 【问题描述】:

如何将传入的数据从 Swift 类中的委托触发的方法传递到 EnvironmentObject?

我知道,要使其正常工作,我的 Swift 类需要从 SwiftUI 结构(作为父 SwiftUI 结构的子级)调用/初始化。但是,我在 Apple Watch 应用程序的 ExtensionDelegate 中初始化了我的 Swift 类。我希望在名称更新时看到 UI 文本元素发生变化。

以下代码在 Apple Watch 上运行:

class User: ObservableObject 
    
    @Published var id: UUID?
    @Published var name: String?

//SwiftUI struct
struct UI: View 

@EnvironmentObject var userEnv: User

var body: some View 
   Text(userEnv.name)
 

// Swift class
class WatchConnectivityProvider: NSObject, WCSessionDelegate 

 static let shared = WatchConnectivityProvider()
 private let session: WCSession
    
    init(session: WCSession = .default) 
        self.session = session
        super.init()
    
    
    func activateSession() 
        if WCSession.isSupported() 
            session.delegate = self
            session.activate()
        
    

    //This func gets triggered when data is sent from the iPhone
    func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: @escaping ([String : Any]) -> Void) 
        
        let list = message["list"]
        
        let jsonDecoder = JSONDecoder()
        if let data = try? jsonDecoder.decode(User.self, from: list as! Data) 
            
        // !!! This is where I would like to update the EnvironmentObject userEnv !!!   
        // What is the best way to do this? Remember, this class has already been initialised within the ExtensionDelegate.
        
    

//ExtensionDelegate of Apple Watch app, initialising the WatchConnectivityProvider
class ExtensionDelegate: NSObject, WKExtensionDelegate 

    func applicationDidFinishLaunching() 
        // Perform any final initialization of your application.
        WatchConnectivityProvider.shared.activateSession()
    

【问题讨论】:

【参考方案1】:

依赖注入

其中一个解决方案可能是全局存储对您的@EnvironmentObject 的引用,例如。在一些依赖容器中。

enum Dependencies 
    struct Name: Equatable 
        let rawValue: String
        static let `default` = Name(rawValue: "__default__")
        static func == (lhs: Name, rhs: Name) -> Bool  lhs.rawValue == rhs.rawValue 
    

    final class Container 
        private var dependencies: [(key: Dependencies.Name, value: Any)] = []

        static let `default` = Container()

        func register(_ dependency: Any, for key: Dependencies.Name = .default) 
            dependencies.append((key: key, value: dependency))
        

        func resolve<T>(_ key: Dependencies.Name = .default) -> T 
            return (dependencies
                .filter  (dependencyTuple) -> Bool in
                    return dependencyTuple.key == key
                        && dependencyTuple.value is T
                
                .first)?.value as! T
        
    

然后你像这样创建你的对象:

Dependencies.Container.default.register(User())

您可以从代码中的任何位置访问它:

let user: User = Dependencies.Container.default.resolve()
user.modify()

Dependency Injection 用于 Swift 的更详细解释可以找到here。

单例

或者,您可以使用标准的Singleton 模式使您的用户数据在全球范围内可用。更详细的解释可以找到here。

最后的想法

Clean Architecture for SwiftUI 是一个很好的例子(至少在我看来)如何以 clean 的方式编写 ios 应用程序。这有点复杂,但你可以拿起一些部分。

【讨论】:

这看起来非常复杂。特别是当自定义对象很复杂时。我对 EnvironmentObject 的理解是允许在应用程序范围内共享数据及其更新。这不应该需要依赖关系。虽然我确信这是一种方法,但感觉不像 SwiftUI。 确实,这很复杂。 EnvironmentObject 的问题在于它是一种单例,但仅适用于 SwiftUI Views。如果您想要一个简单的解决方案,请尝试使用普通的单例。类似的问题是here。 所以在 SwiftUI 中使用 Swift 类的唯一方法是从 SwiftUI 中调用类和方法,例如在 .onAppear self.userEnv.name = WatchConnectivityProvider.shared.getData() ?如果我想获得定期更新,我必须在 Timer 间隔上调用该方法。这感觉像是一个不成熟的设计。我不希望我的应用程序逻辑驻留在 Swift 类中吗? 如果你想遵循MVVM 模式,你可以有一个ViewModel 类,你的业务逻辑驻留在那里,并在你的View 中作为ObservedObject。您可以像.onAppear self.viewModel.load() 一样使用它。对于访问ViewModel 中的env 对象的问题,我推荐使用Dependency Injection。否则我所知道的只剩下一个Singleton。请记住,这仍然是 SwiftUI 的早期阶段。未来可能会有更好的解决方案。 Here 是一篇关于如何以 clean 方式编写 iOS 应用的好文章。【参考方案2】:

这里是错误的巨型代码:

class ExtensionDelegate: ObservableObject, NSObject, WCSessionDelegate, WKExtensionDelegate 
var session: WCSession?
@Published var model = Model

func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: @escaping ([String : Any]) -> Void) 
    print(#function)
    var replyValues = Dictionary<String, Any>()
    replyValues["status"] = "failed"
    
    // 2442 Bytes
    if let data = message["data"] as? Data 
        // push that work back to the main thread
        DispatchQueue.main.async 
            self.model = try? JSONDecoder().decode(Model.self, from: data)
        
        if let vm = vm 
            replyValues["status"] = "ok"
            replyValues["id"] = vm.id.uuidString
        
    
    replyHandler(replyValues)

...

【讨论】:

以上是关于将数据从委托 Swift 类传递到 SwiftUI 结构中的 EnvironmentObject的主要内容,如果未能解决你的问题,请参考以下文章

如何将数据从父模型类传递到 SwiftUI 中的子视图

如何在swift 3中从一个视图控制器传递到上一个视图控制器时设置委托?

为啥我在实现委托以将值从子 swift 类传递给父目标 C 类时出错?

SwiftUI如何从视图调用或通知scenedelegate

Swift - 如何将数据从 UITableViewCell 类传递到 UIViewController

如何使用通知将字符串数据从 swift 传递到目标 c 类