Apple Watch, WatchKit, and NSUserDefaults [duplicate] - ios

This question already has answers here:
WatchKit SDK not retrieving data from NSUserDefaults
(2 answers)
Closed 8 years ago.
I figured since the "Watch App" is a bundle in the same iOS application, I should be able to access NSUserDefaults within my Apple Watch App. Is this not so?
I can't seem to get NSUserDefaults values from my iOS (which I made sure has data as it loads fine in the iOS app).
When I run it in the Apple Watch app, it comes in as empty. Do I not have access to the same NSUserDefaults values as the iOS parent app?
I have a custom class which contains an NSMutalArray. This mutable array is what I want to display in a Table in the Watch App. This same class which contains the NSMutableArray also has functions to save/load data to/from NSUserDefaults. It works fine in my iOS app but just doesn't load data in the Apple Watch app.
What am I doing wrong here or what am I not aware of?
Here is the class:
Keep in mind that I use it as a singleton object too. It saves it's data into NSUserDefaults by first converting itself into NSData - which is why you see extra functions in this class that you can just ignore.
#import "Teacher.h"
#import "Course.h"
#implementation Teacher
static Teacher *sharedInstance = Nil;
+ (Teacher *)sharedInstance
{
#synchronized(self)
{
if(!sharedInstance)
{
sharedInstance = [[self alloc] init];
}
}
return sharedInstance;
}
- (id)init
{
self = [super init];
if (self)
{
_classes = [[NSMutableArray alloc] init];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder
{
[coder encodeObject:_classes forKey:#"classes"];
}
- (id)initWithCoder:(NSCoder *)coder
{
self = [super init];
if (self)
{
_classes = [coder decodeObjectForKey:#"classes"];
}
return self;
}
+ (void)saveData
{
NSMutableArray *teacherClasses = [[Teacher sharedInstance] classes];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:teacherClasses];
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
[userDefaults setObject:data forKey:#"teacherClasses"];
}
+ (void)loadData
{
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSMutableArray *loadClasses= [NSKeyedUnarchiver unarchiveObjectWithData:[userDefaults objectForKey:#"teacherClasses"]];
if(loadClasses == Nil)
{
loadClasses = [[NSMutableArray alloc] init];
}
[[Teacher sharedInstance] setClasses:loadClasses];
}
- (Teacher *) copyWithZone:(NSZone *)zone
{
Teacher *teacher = [[Teacher alloc] init];
[teacher setClasses:[self classes]];
return teacher;
}
#end
Thanks.

You need to use a shared container if you wish to share data between your iOS app and your Apple Watch app.

Related

WatchConnectivity not sending data

I'm using WatchConnectivity to send a simple dictionary from an iPhone to Apple Watch.
On the Apple Watch side, to get around the fact that contexts may not be queued when the app is opened, the last received data is saved to UserDefaults and retrieved if there is nothing in the queue when setting up my WatchKit table. I have implemented this in another WatchKit app and everything worked somewhat fine, but in this one data is never received by the Watch.
I've only tried it in the simulator because my app spins for eternity on my Watch and never loads (the loading screen looks like a WatchOs 1 screen?). The WatchConnectivity framework is included in each product (Extension and iPhone app). Thanks for your help.
Here's the iPhone code (implemented in a ViewController):
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
if ([WCSession isSupported]) {
WCSession *session = [WCSession defaultSession];
session.delegate = self;
[session activateSession];
}
}
- (void)viewDidAppear:(BOOL)animated {
NSDictionary *toPass = [[NSDictionary alloc] initWithObjectsAndKeys:AppDelegate.profiles,#"profiles", nil];
[[WCSession defaultSession] updateApplicationContext:toPass error:nil];
NSLog(#"sent data");
[self.tableView reloadData];
}
And the Apple Watch Code:
- (void)awakeWithContext:(id)context {
[super awakeWithContext:context];
// Configure interface objects here.
self.profiles = [[NSMutableArray alloc] init];
if ([WCSession isSupported]) {
WCSession *session = [WCSession defaultSession];
session.delegate = self;
[session activateSession];
}
[self setupTable];
}
- (void)session:(WCSession *)session didReceiveApplicationContext:(NSDictionary<NSString *,id> *)applicationContext {
NSDictionary *receieved = [[NSDictionary alloc] init];
receieved = applicationContext;
NSMutableArray *profiles = [[NSMutableArray alloc] init];
profiles = [receieved objectForKey:#"profiles"];
self.profiles = [[NSMutableArray alloc] init];
self.profiles = profiles;
NSData *arrayData = [NSKeyedArchiver archivedDataWithRootObject:self.profiles];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:arrayData forKey:#"bookmarks"];
[self setupTable];
NSLog(#"new");
}
- (void)setupTable {
...
After some setup code
if (self.profiles.count == 0) {
NSLog(#"Nothing in the queue, checking defaults");
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSData *got = [defaults objectForKey:#"bookmarks"];
NSLog(#"Got Defaults!");
self.profiles = [[NSMutableArray alloc] init];
self.profiles = [NSKeyedUnarchiver unarchiveObjectWithData:got];
}
...
More setup code later
}
Change this line:
[[WCSession defaultSession] updateApplicationContext:toPass error:nil];
To be:
NSError *error = nil;
If (![[WCSession defaultSession] updateApplicationContext:toPass error:&error]) {
NSLog(#"error: %#", error);
}
And I bet you'll see you are getting an error returned!
Also, what type of objects does AppDelegate.profiles contain?

Encoding and decoding custom objects

I'm having trouble encoding and saving a list of custom objects containing a MKMapItem to NSUserDefaults.
Firstly, I get the selected MKMapItem from an array of MKMapItems used for a tableView and store that in my sharedManager instance. (All the values in sharedManager will be used later to create a custom object).
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// Get the tapped MKMapItem
MKMapItem *selectedMapItem = self.searchResults[indexPath.row];
// Create a sharedManager instance
MyManager *sharedManager = [MyManager sharedManager];
// Set the workRegion and workLocation in sharedManager
NSLog(#"selectedMapItem: %#", [selectedMapItem name]);
sharedManager.workLocation = selectedMapItem;
// Post a notification to alert the PreviewMapViewController
[[NSNotificationCenter defaultCenter] postNotificationName:#"showAnnotations" object:self.searchResults];
[[NSNotificationCenter defaultCenter] postNotificationName:#"zoomToAnnotation" object:selectedMapItem];
[[NSNotificationCenter defaultCenter] postNotificationName:#"showMap" object:nil];
}
This is the code I use to take the MKMapItem from sharedManager and put it in the custom object I've created:
MyManager *sharedManager = [MyManager sharedManager];
newModel.workLocation = sharedManager.workLocation;
My custom object stores workLocation in its header file with a property as follows:
#property (nonatomic, strong) MKMapItem *workLocation;
This is the implementation file where I encode and decode the workLocation object:
#implementation WorkLocationModel
-(id)init {
// Init self
self = [super init];
if (self)
{
// Setup
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder {
[coder encodeObject:self.workLocation forKey:#"workLocation"];
}
-(instancetype)initWithCoder:(NSCoder *)coder {
self = [super init];
if (self)
self.workLocation = [coder decodeObjectForKey:#"workLocation"];
return self;
}
#end
My breakpoint set to catch all the exceptions breaks on the encodeObject line.
The error occurs when I add this custom object to a NSMutableArray and then save that array using:
[[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:_myObjects] forKey:#"myObjects"];
Exception:
-[MKMapItem encodeWithCoder:]: unrecognized selector sent to instance 0x7f9f14acf400
Can anyone help me with this?
UPDATE:
NSData *workLocationData = [NSKeyedArchiver archivedDataWithRootObject:sharedManager.workLocation];
MKMapItem does not conform to NSCoding or SSecureCoding.
You will need to encode the individual items and re-create a MKMapItem on decode.

Send NSArray Via AirDrop

I have an NSMutableArray self.certificates
This array is made up of saved strings and core data. I want to send this through AirDrop. I have checked out serialization and and im trying to send it with the folowing
- (void)send{
NSData *jsonData2 = [NSJSONSerialization dataWithJSONObject:self.certificates options:NSJSONWritingPrettyPrinted error:nil];
NSString *jsonString = [[NSString alloc] initWithData:jsonData2 encoding:NSUTF8StringEncoding];
NSLog(#"Electrical Certificates List:\n%#", jsonString);
UIActivityViewController *activityCtr = [[UIActivityViewController alloc] initWithActivityItems:#[jsonString]
applicationActivities:nil];
NSMutableArray *excludedActivities = [self iOSActivities].mutableCopy;
[excludedActivities addObject:UIActivityTypeAddToReadingList];
[excludedActivities addObject:UIActivityTypePostToFlickr];
[excludedActivities addObject:UIActivityTypePostToTencentWeibo];
[excludedActivities addObject:UIActivityTypePostToVimeo];
[activityCtr setExcludedActivityTypes:excludedActivities];
[self presentViewController:activityCtr
animated:YES
completion:nil];
}
This gives me the following error
'NSInvalidArgumentException', reason: 'Invalid type in JSON write (Certificate)'
I have converted to data to a string so not sure what im missing here
Ive researched NSInvalidArgumentException, reason: 'Invalid type in JSON write (__NSDate)' and How to send NSArray to web service
The exception is thrown by JSONSerialization, before the array is converted to data.
To share a custom data type, you'll want to implement NSCoding and UIActivityItemSource on your model object:
#interface CertificateGroup : NSObject <NSCoding, UIActivityItemSource>
#property(copy, nonatomic) NSArray *certificates;
#end
#implementation CertificateGroup
- (void)encodeWithCoder:(NSCoder *)aCoder {
// Save all your custom properties
[aCoder encodeObject:self.certificates forKey:#"certificates"]l
}
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super init];
if (self) {
// Read back properties
self.certificates = [aDecoder decodeObjectForKey:#"certificates"];
}
return self;
}
- (id)activityViewControllerPlaceholderItem:(UIActivityViewController *)activityViewController
{
//Let the activity view controller know NSData is being sent by passing this placeholder.
return [NSData data];
}
- (id)activityViewController:(UIActivityViewController *)activityViewController itemForActivityType:(NSString *)activityType
{
//Serialize this object for sending. NSCoding protocol must be implemented for the serialization to occur.
return [NSKeyedArchiver archivedDataWithRootObject:self];
}
- (NSString *)activityViewController:(UIActivityViewController *)activityViewController dataTypeIdentifierForActivityType:(NSString *)activityType {
return #"com.mycompany.myapp.certificates";
}
#end
Then, when you create your activity view controller:
CertificatesGroup *group = [CertificatesGroup new];
group.certificates = self.certificates;
UIActivityViewController *activityCtr = [[UIActivityViewController alloc] initWithActivityItems:#[group]
applicationActivities:nil];
...
You're app delegate should implement -application:openURL:sourceApplication:annotation: and decode the incoming certificates.
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
NSData *groupData = [NSData dataWithContentsOfURL:url];
CertificatesGroup *group = [NSKeyedUnarchiver unarchiveObjectWithData:groupData];
NSLog(#"%#", group.certificates);
return YES;
}
For more info, see Apple's AirDropSample project, especially APLProfile.h/.m, APLProfileViewController.h/.m and AppDelegate.m.

ObjectiveC: How to stop a music player from another view in Xcode?

I would like to stop my music playing from another view by using delegates. I already have that setup and i know there is this option...
- (void)viewWillDisappear:(BOOL)animated{
[player stop]; ///(my players name here and command to stop the player)
}
However i do not want to use that function because i have the music playing throughout. I have already added code to ensure my player does not overlap. But when i do want to stop my music i want to do so in a different view. I have tried the '[player stop];' function when the delegate is called but it does not work.
Can anyone help?
Thanks in advanced...
EDIT
HERE IS THE CODE FOR MY OPTIONS PAGE.H FILE:
#protocol onHandlerDelegate <NSObject>
- (void)off:(BOOL)success;
- (void)on:(BOOL)success;
-(void)volumeValueChange:(UISlider *)sender;
#end
#import <UIKit/UIKit.h>
#interface OptionsPage : UIViewController{
IBOutlet UISlider *VolumeSlider;
NSTimer *VolumeTimer;
IBOutlet UISwitch *onoroffmusic;
}
-(IBAction) UIBarButtonItem:(id)sender;
-(IBAction) SwitchMusic:(id)sender;
-(IBAction)changevolume:(id)sender;
#property (nonatomic, retain) UISwitch *onoroffmusic;
#property (nonatomic, strong) id<onHandlerDelegate> delegatePpty1;
#end
HERE IS MY OPTIONS PAGE .M FILE:
#import "OptionsPage.h"
#import "ViewController.h"
#interface OptionsPage ()
#end
#implementation OptionsPage
#synthesize onoroffmusic;
#synthesize delegatePpty1;
- (IBAction)changevolume:(id)sender{
int volume = VolumeSlider.value;
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:[NSNumber numberWithInt:volume] forKey:#"volume"];
[defaults synchronize];
[[self delegatePpty1]volumeValueChange:(VolumeSlider)];
}
-(IBAction) SwitchMusic:(id)sender{
if (onoroffmusic.on) {
[[self delegatePpty1] on:YES];
printf("done");
onoroffmusic.on = YES;
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults removeObjectForKey:#"off"];
[defaults setObject:[NSNumber numberWithBool:YES] forKey:#"on"];
[defaults synchronize];
}
else {
/// do nothing
[[self delegatePpty1] off:YES];
printf("off");
onoroffmusic.on = NO;
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults removeObjectForKey:#"on"];
[defaults setObject:[NSNumber numberWithBool:YES] forKey:#"off"];
[defaults synchronize];
ViewController *home = [[ViewController alloc] initWithNibName:nil bundle:nil];
[home.player stop];
}
}
-(IBAction) UIBarButtonItem:(id)sender{
ViewController *Backbutton = [[ViewController alloc] initWithNibName:nil bundle:nil];
[self presentViewController:Backbutton animated:YES completion:nil];
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad{
VolumeSlider.value = 0.3;
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
NSObject * object = [prefs valueForKey:#"volume"];
if(object == nil){
VolumeSlider.value = 0.3;
}
else{
VolumeSlider.value = [[[NSUserDefaults standardUserDefaults] objectForKey:#"volume"] intValue];
}
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
NSUserDefaults *defaults2 = [NSUserDefaults standardUserDefaults];
BOOL restoredBoolValue2 = [[defaults2 objectForKey:#"off"] boolValue];
BOOL restoredBoolValue1 = [[defaults2 objectForKey:#"on"] boolValue];
if (restoredBoolValue2) {
////donothing
onoroffmusic.on = FALSE;
}
if (restoredBoolValue1) {
////donothing
onoroffmusic.on = TRUE;
}
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
Within the two files i declare a delegate and my switches. Now just to receive that delegate in my view controller:
MY VIEWCONNTROLLER .H FILE:
#import "OptionsPage.h"
#interface ViewController : UIViewController <onHandlerDelegate>{
AVAudioPlayer *player;
}
#property (nonatomic, retain) AVAudioPlayer *player;
#end
HERE IS MY .M FILE:
Firstly I
#synthesize player;
Then check if my delegates have been called by using:
-(void)volumeValueChange:(UISlider *)sender
{
if (sender.value > 0.3) {
player.volume = sender.value;
}
else{
player.volume = 0.3;
}
}
-(void)on:(BOOL)success{
NSLog(#"Delegate Method Called");
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:[NSNumber numberWithBool:YES] forKey:#"mykey1"];
[self play];
[player play];
}
-(void)off:(BOOL)success{
NSLog(#"Delegate Method Called");
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults removeObjectForKey:#"mykey1"];
[defaults setObject:[NSNumber numberWithBool:YES] forKey:#"mykey"];
[player stop];
player = nil;
[defaults removeObjectForKey:#"music"];
}
I then load the bool value of my nsuserdefults by using this:
- (void)viewDidLoad
{
if ([[NSUserDefaults standardUserDefaults] stringForKey:#"mykey"] == nil ) {
if ([[NSUserDefaults standardUserDefaults] stringForKey:#"music"] == nil) {
[self play];
}
}
}
NSUserDefaults *defaults2 = [NSUserDefaults standardUserDefaults];
BOOL restoredBoolValue2 = [[defaults2 objectForKey:#"mykey"] boolValue];
if (restoredBoolValue2) {
////donothing
[player stop];
}
BOOL restoredBoolValue1 = [[defaults2 objectForKey:#"mykey1"] boolValue];
if (restoredBoolValue1){
[self play];
}
And i have my play void function
-(void)play{
if ([[NSUserDefaults standardUserDefaults] stringForKey:#"music"] == nil) {
NSURL *url = [NSURL fileURLWithPath:[NSString stringWithFormat:#"%#/Music.mp3", [[NSBundle mainBundle] resourcePath]]];
NSError *error;
player = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error];
player.numberOfLoops = -1;
[player play];
player.volume = 0.3;
player.currentTime = 0.0;
[[NSUserDefaults standardUserDefaults] setObject:#"-" forKey:#"music"];
}
else {
[player stop];
}
}
and lastly in my app delegate.m file i :
- (void)applicationDidEnterBackground:(UIApplication *)application
{
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
[[NSUserDefaults standardUserDefaults] setObject:nil forKey:#"music"];
}
Make player a property and stop it like this:
[anotherView.player stop];
First you need to change onHandlerDelegate from strong to weak because it is a delegate.
Secondly you need an instance of OptionsPage on the second view controller and you need to implement the delegate and do optionPage.delegate = self after it this will work.

How to add a textfield to an NSMutableArray

I'm trying to present what was entered into a textfield into a nsmutable array that will later be displayed.
I think that the problem is in the -(void)viewDidLoad method but I included all of the code just in case. The catch is that I will be leaving this page and then returning to it after another piece of information is selected. As this happens, I need to keep track of EACH thing that was entered into the textfield. Thanks for any help!
#import "EnteringCoursesViewController.h"
#import "SelectRotationController.h"
#implementation EnteringCoursesViewController
#synthesize classField;
#synthesize indicatedClass;
#synthesize labelClassTitle;
#synthesize selectRotationController;
#synthesize classesEnteredTable;
- (IBAction)chooseType {
UIActionSheet *typeSheet = [[UIActionSheet alloc]
initWithTitle:#"Class types"
delegate:self
cancelButtonTitle:nil
destructiveButtonTitle:nil
otherButtonTitles:#"Core Class", #"Elective", nil];
[typeSheet showInView:self.view];
[typeSheet release];
}
- (void)actionSheet:(UIActionSheet *)typeSheet clickedButtonAtIndex:(NSInteger)buttonIndex
{
if (buttonIndex == 0) {
self.indicatedClass = classField.text;
NSString *indicatedString = indicatedClass;
NSString *greeting = [[NSString alloc]
initWithFormat:#"%# meets 6 times per rotation", indicatedString];
labelClassTitle.text = greeting;
labelClassTitle.hidden = NO;
[greeting release];
[indicatedClass release];
}
else if (buttonIndex == 1) {
self.indicatedClass = classField.text;
NSString *indicatedString = indicatedClass;
NSString *greeting = [[NSString alloc]
initWithFormat:#"%# meets 3 times per rotation", indicatedString];
labelClassTitle.text = greeting;
labelClassTitle.hidden = NO;
[greeting release];
[indicatedClass release];
}
}
- (IBAction)chooseFirstMeeting:(id)sender {
SelectRotationController *selectView = [[SelectRotationController alloc]
initWithNibName:#"SelectRotationController"
bundle:[NSBundle mainBundle]];
[selectView.navigationItem setTitle:#"First Period Day Choose"];
[self.navigationController pushViewController:self.selectRotationController animated:YES];
self.selectRotationController = selectView;
[selectView release];
}
- (IBAction)enteredClassText:(id)sender {
NSMutableArray *classesEntered = [[NSMutableArray alloc] init];
[classesEntered addObject:indicatedClass];
[classesEntered release];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
- (void)viewDidLoad {
self.navigationItem.hidesBackButton = YES;
[super viewDidLoad];
}
- (void)viewDidUnload {
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)dealloc {
[classField release];
[labelClassTitle release];
[indicatedClass release];
[selectRotationController release];
[classesEnteredTable release];
[super dealloc];
}
#end
If viewDidLoad is called "indicatedClass" is not yet initialised and therefore nil.
https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSMutableArray_Class/Reference/Reference.html
Important Raises an NSInvalidArgumentException if anObject is nil.
If you want to save that by leaving the view, add the addObject-Call in the viewDidUnload-method. Definitely you should check if the value is nil ;)
I dont see any alloc for your variable indicatedClass but an release!? It might be that the variable doesnt exists if viewDidUnload is calling.
EDIT
You init an NSMutableArray, add the object to this array and after that you released that object. Therefore the Data is away. You must save your array therewith you can use the content later. Keyword: NSUserDefaults ;)
Check also of nil values:
- (IBAction)enteredClassText:(id)sender {
if (indicatedClass != nil) {
NSMutableArray *classesEntered = [[NSMutableArray alloc] init];
[classesEntered addObject:indicatedClass];
[classesEntered release];
}
}
If the sender is an UILabel you can also use this snippet:
- (IBAction)enteredClassText:(id)sender {
if (sender.text != nil) {
NSMutableArray *classesEntered = [NSMutableArray arrayWithObject:sender.text];
// TODO: Save your array to NSUserDefaults...
}
}

Resources