NSUserDefaults 不适用于 Watch OS2 的 Xcode beta

Posted

技术标签:

【中文标题】NSUserDefaults 不适用于 Watch OS2 的 Xcode beta【英文标题】:NSUserDefaults not working on Xcode beta with Watch OS2 【发布时间】:2015-08-31 08:03:46 【问题描述】:

我刚刚安装了最新的 Xcode 测试版来试用 Swift 2 以及对 Apple Watch 开发部分所做的改进。

我实际上很难弄清楚为什么这种在 iOSWatch OS2 之间共享信息的基本 NSUserDefaults 方法不起作用。

我关注this step-by-step tutorial 以检查我是否在此过程中遗漏了某些内容,例如为电话应用程序和分机打开同一个组,但这是我得到的:NOTHING。 p>

这是我在 iPhone 应用程序中为 ViewController 编写的内容:

import UIKit

class ViewController: UIViewController 
    @IBOutlet weak var lb_testo: UITextField!
    let shared_defaults:NSUserDefaults = NSUserDefaults(suiteName: "group.saracanducci.test")!
    var name_data:NSString? = ""

    override func viewDidLoad() 
        super.viewDidLoad()

        name_data = shared_defaults.stringForKey("shared")
        lb_testo.text = name_data as? String
    

    override func didReceiveMemoryWarning() 
        super.didReceiveMemoryWarning()
    

    @IBAction func upgrade_name(sender: AnyObject) 
        name_data = lb_testo.text
        shared_defaults.setObject(name_data, forKey: "shared")

        lb_testo.resignFirstResponder()
        shared_defaults.synchronize()
    

这就是我在 WatchKit 的 InterfaceController 中的内容:

import WatchKit
import Foundation

class InterfaceController: WKInterfaceController 
    @IBOutlet var lb_nome: WKInterfaceLabel!
    let shared_defaults:NSUserDefaults = NSUserDefaults(suiteName: "group.saracanducci.test")!
    var name_data:NSString? = ""

    override func awakeWithContext(context: AnyObject?) 
        super.awakeWithContext(context)
    

    override func willActivate() 
        super.willActivate()

        if (shared_defaults.stringForKey("shared") != "")
            name_data = shared_defaults.stringForKey("shared")
            lb_nome.setText(name_data as? String)
        else
            lb_nome.setText("No Value")
        
    

    override func didDeactivate() 
        super.didDeactivate()
    

我做了一些测试,似乎 ios 应用和 Watch OS 使用了不同的组......他们不共享信息,他们将信息存储在本地。

有人遇到同样的问题吗?知道如何解决吗?

【问题讨论】:

请在问题中包含您的代码,以免损坏的链接不会使该问题对未来的读者毫无用处。 .stringForKey("shared") 已经返回了一个不需要转换的字符串。您应该使用 nil 合并运算符来解开它 name_data = NSUSerDefaults().stringForKey("shared") ?? “没有价值” 问题肯定出在其他地方。您拥有的代码是正确的;我在我的 3 个手表应用程序中多次使用这种模式。我认为这不会有帮助,但请尝试将 NSUserDefaults 调用移动到您的数据源,以便您从一个地方获取 NSUserDefaults 引用,而不是初始化它的 2 个不同副本。 我会在标题中添加 xcode 7 / watch OS2。 对于那些感兴趣的人,即使在 watchOS 2 中,设置仍会从 iOS 推送到 Apple Watch。请参阅我的回答 ***.com/a/32707727/630614 【参考方案1】:

使用 watch OS2,您不能再使用共享组容器。 Apple Docs:

观看使用共享组与其 iOS 应用共享数据的应用 必须重新设计容器以不同方式处理数据。在 watchOS 2 中, 每个进程必须在本地管理自己的任何共享数据的副本 容器目录。对于实际共享和更新的数据 这两个应用程序,这需要使用 Watch Connectivity 框架来 在它们之间移动数据。

【讨论】:

谢谢伙计,这段代码我快疯了!我会想出另一种使用 Watch Connectivity 使其工作的方法 :) 我也会这样做;) OMG - 最近几天真是浪费时间。 谢谢你救了我的命。 卫生署! - 今晚我想“嘿,我为什么不重新设计我的应用程序以使用共享应用程序组”,然后我花了几个小时试图找出它为什么不起作用。最糟糕的部分?显然我两周前看到并接受了这个答案!【参考方案2】:

NSUserDefaults(即使有应用组)不会在 watchOS 2 中的 iPhone 和 Watch 之间同步。如果你想从你的 iPhone 应用或 Settings-Watch.bundle 同步设置,你必须处理同步自己。

我发现在这种情况下,使用 WatchConnectivity 的用户信息传输非常有效。 您将在下面找到如何实现此功能的示例。该代码仅处理从手机到 Watch 的单向同步,但另一种方式的工作方式相同。

iPhone 应用程序中: 1) 准备需要同步的设置字典

- (NSDictionary *)exportedSettingsForWatchApp  
  
    NSUserDefaults *userDefaults = [self userDefaults]; // the user defaults to sync  

    NSSet *keys = [self userDefaultKeysForWatchApp]; // set of keys that need to be synced  
    NSMutableDictionary *exportedSettings = [[NSMutableDictionary alloc] initWithCapacity:keys.count];  

    for (NSString *key in keys)   
        id object = [userDefaults objectForKey:key];  

        if (object != nil)   
            [exportedSettings setObject:object forKey:key];  
          
      

    return [exportedSettings copy];  
  

2) 确定何时需要将设置推送到 Watch (此处未显示)

3) 将设置推送到手表

- (void)pushSettingsToWatchApp  
  
    // Cancel current transfer  
    [self.outstandingSettingsTransfer cancel];  
    self.outstandingSettingsTransfer = nil;  

    // Cancel outstanding transfers that might have been started before the app was launched  
    for (WCSessionUserInfoTransfer *userInfoTransfer in self.session.outstandingUserInfoTransfers)   
        BOOL isSettingsTransfer = ([userInfoTransfer.userInfo objectForKey:@"settings"] != nil);  
        if (isSettingsTransfer)   
            [userInfoTransfer cancel];  
          
      

    // Mark the Watch as requiring an update  
    self.watchAppHasSettings = NO;  

    // Only start a transfer when the watch app is installed  
    if (self.session.isWatchAppInstalled)   
        NSDictionary *exportedSettings = [self exportedSettingsForWatchApp];  
        if (exportedSettings == nil)   
            exportedSettings = @ ;  
          

        NSDictionary *userInfo = @ @"settings": exportedSettings ;  
        self.outstandingSettingsTransfer = [self.session transferUserInfo:userInfo];  
       
  

观看扩展中: 4) 接收用户信息传输

- (void)session:(WCSession *)session didReceiveUserInfo:(NSDictionary<NSString *, id> *)userInfo  
  
    NSDictionary *settings = [userInfo objectForKey:@"settings"];  
    if (settings != nil)   
        // Import the settings  
        [self importSettingsFromCompanionApp:settings];  
       
 

5) 将收到的设置保存到手表上的用户默认设置

- (void)importSettingsFromCompanionApp:(NSDictionary *)settings  
  
    NSUserDefaults *userDefaults = [self userDefaults]; // the user defaults to sync  

    NSSet *keys = [self userDefaultKeysForWatchApp]; // set of keys that need to be synced  
    for (NSString *key in keys)   
        id object = [settings objectForKey:key];  
        if (object != nil)   
            [userDefaults setObject:object forKey:key];  
         else   
            [userDefaults removeObjectForKey:key];  
          
      

    [userDefaults synchronize];  
  

【讨论】:

有机会发布 swift 版本吗? 不,对不起。但是自己移植应该不难。 见这里快速连接代码developer.apple.com/videos/play/wwdc2015-713 我看到 [self userdefaults];我可以在这里使用 [NSUserDefaults standardUserDefaults] 吗? 是的。就我而言,我使用的是组用户默认值,这就是存在 [self userDefaults] 的原因。【参考方案3】:

有一种重现旧功能的简单方法,我将旧组用户默认值导出到字典中,将其发送到 WatchConnectivity 框架,然后将它们重新导入另一端的用户默认值:

在电话和手表应用中:

    添加 WatchConnectivty 框架 #import &lt;WatchConnectivity/WatchConnectivity.h&gt; 并声明为 WCSessionDelegate

    在应用启动后添加代码以启动会话:

    if ([WCSession isSupported]) 
            WCSession* session = [WCSession defaultSession];
            session.delegate = self;
            [session activateSession];
        
    

    使用它来将更新的默认值发送到其他设备(在您当前的 [defaults synchronize] 之后调用):

[[WCSession defaultSession] updateApplicationContext:[[[NSUserDefaults alloc] initWithSuiteName:@"group.com.company.myapp"] dictionaryRepresentation] error:nil];

    接收设置并将其保存回默认值 - 将其添加到 WCDelegate:

    -(void)session:(WCSession *)session didReceiveApplicationContext:(NSDictionary<NSString *,id> *)applicationContext 
        NSLog(@"New Session Context: %@", applicationContext);
    
        NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:@"group.com.company.myapp"];
    
        for (NSString *key in applicationContext.allKeys) 
            [defaults setObject:[applicationContext objectForKey:key] forKey:key];
        
    
        [defaults synchronize];
    
    

注意保持对非 WC 设备的支持 - 使用 if ([WCSession isSupported]) 包装您的 updateApplicationContext 调用

【讨论】:

如果您在手表应用程序未运行时更改 iPhone 应用程序的设置会怎样(isReachable 为 false)?手表应用不会收听会话。稍后,当手表应用程序启动时,它将具有旧值。手表应用需要唤醒 iPhone 应用,然后安排一些魔法发生。 优秀的答案。请注意,applicationContext 包含很多与 Apple 相关的键。为了更安全,我建议只为您期望的已验证的键名子集(例如 NSArray)添加数据。 最佳简洁答案。【参考方案4】:

如前所述,共享的 NSUserDefaults 不再适用于 WatchOS2。

这是@RichAble 答案的快速版本,还有一些注释。

在您的 iPhone 应用中,按照以下步骤操作:

选择您要从中将数据推送到 Apple Watch 的视图控制器,并在顶部添加框架。

import WatchConnectivity

现在,与手表建立 WatchConnectivity 会话并发送一些数据。

if WCSession.isSupported()  //makes sure it's not an iPad or iPod
    let watchSession = WCSession.defaultSession()
    watchSession.delegate = self
    watchSession.activateSession()
    if watchSession.paired && watchSession.watchAppInstalled 
        do 
            try watchSession.updateApplicationContext(["foo": "bar"])
         catch let error as NSError 
            print(error.description)
        
    

请注意,如果您跳过设置代理,这将不起作用,因此即使您从不使用它,您也必须设置它并添加此扩展:

extension MyViewController: WCSessionDelegate 


现在,在您的手表应用中(此确切代码也适用于 Glances 和其他手表套件应用类型)您添加框架:

import WatchConnectivity

然后你设置连接会话:

override func awakeWithContext(context: AnyObject?) 
    super.awakeWithContext(context)
    let watchSession = WCSession.defaultSession()
    watchSession.delegate = self
    watchSession.activateSession()

您只需收听和处理来自 iOS 应用程序的消息:

extension InterfaceController: WCSessionDelegate 

    func session(session: WCSession, didReceiveApplicationContext applicationContext: [String : AnyObject]) 
        print("\(applicationContext)")
        dispatch_async(dispatch_get_main_queue(), 
            //update UI here
        )
    


仅此而已。

注意事项:

    您可以随时发送新的 applicationContext 并且它 手表是否在附近并已连接或手表是否 应用程序正在运行。这会在后台以 智能的方式,数据就在那里等待 手表应用已启动。 如果您的手表应用程序实际上处于活动状态并正在运行,它应该会收到 大多数情况下会立即发送消息。 您可以反转此代码,让手表将消息发送到 iPhone 应用程序也是如此。 您的手表应用在查看时收到的applicationContext 仅是您发送的最后一条消息。如果您在查看手表应用之前发送了 20 条消息,它将忽略前 19 条并处理第 20 条。 如需在 2 个应用程序之间建立直接/硬连接或后台文件传输或排队消息,请查看WWDC video。

【讨论】:

这是一个很好的例子,我让它在新的 watchOS 3 和 swift 3 等上工作。只是一个简短的说明,你必须改变你发送的值 - 而不仅仅是“bar” ,否则您的消息将只发送一次。我花了一个小时才弄清楚:(【参考方案5】:

我花了好几个小时才得到这个。观看这个非常有用的视频!它为您提供了如何使用 WatchConnectivity 在 iPhone 应用程序和 wacth 之间共享 NSUserDefault 的基本概念!

https://www.youtube.com/watch?v=VblFPEomUtQ

【讨论】:

以上是关于NSUserDefaults 不适用于 Watch OS2 的 Xcode beta的主要内容,如果未能解决你的问题,请参考以下文章

Apple Watch、WatchKit 和 NSUserDefaults [重复]

带有标志 --legacy-watch 的 Nodemon 不适用于 docker Ubuntu/Linux

为啥 HTML 5 视频标签不适用于将 YouTube 视频用作源?

NSUserDefaults 简介

在 NSUserDefaults 中保存用户选择

iPhone 7 NSUserDefaults 变成零