Loading NSTableView from unarchived data - objective-c

I'm trying to implement saving of user preferences in my OS X app. I have an NSTableView which stores its data to my .plist file, but I have troubles loading same data back to my NSTableView.
My model object:
#import <Foundation/Foundation.h>
#interface UserKeys : NSObject <NSCoding>
#property (nonatomic, copy) NSString *userKeyName;
#property (nonatomic, copy) NSString *userApi;
#property (nonatomic, copy) NSString *userCode;
#end
and its implementation:
#import "UserKeys.h"
#implementation UserKeys
- (id)init
{
self = [super init];
if (self) {
//
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super init];
if (self) {
self.userKeyName = [aDecoder decodeObjectForKey:#"userKeyName"];
self.userApi = [aDecoder decodeObjectForKey:#"userApi"];
self.userCode = [aDecoder decodeObjectForKey:#"userCode"];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)enCoder {
[enCoder encodeObject:self.userKeyName forKey:#"userKeyName"];
[enCoder encodeObject:self.userApi forKey:#"userApi"];
[enCoder encodeObject:self.userCode forKey:#"userCode"];
}
#end
My preferences view controller header:
#import <Cocoa/Cocoa.h>
#import "MASPreferencesViewController.h"
#import "UserKeys.h"
#interface PreferencesApiKeysViewController : NSViewController <MASPreferencesViewController>
#property (strong) NSMutableArray *allKeys;
#end
and its implementation:
#import "PreferencesApiKeysViewController.h"
#import "UserKeys.h"
#interface PreferencesApiKeysViewController ()
#property (weak) IBOutlet NSTableView *keysTableView;
#end
#implementation PreferencesApiKeysViewController
- (id)initWithNibName:(NSString *)nibName bundle:(NSBundle *)bundle {
if ((self = [super initWithNibName:nibName bundle:bundle])) {
self.allKeys = [NSMutableArray array];
}
return self;
}
// delegating....
//
- (void)viewWillAppear {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSData *data = [defaults objectForKey:#"UserKeys"];
if (data != nil) {
NSArray *oldArray = [NSKeyedUnarchiver unarchiveObjectWithData:data];
if (oldArray != nil) {
self.allKeys = [[NSMutableArray alloc] initWithArray:oldArray];
} else {
self.allKeys = [NSMutableArray array];
}
}
}
- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
// some code...
return cellView;
}
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
return [self.allKeys count];
}
The error I'm getting is:
-[UserKeys count]: unrecognized selector sent to instance 0x100534df0
When I put a breakpoint in - (void)viewWillAppear, I can see that data has around 250 bytes, but this oldArray seems to be empty. I understand that I'm doing some major mistake here with self.allKeys so that my table is not aware how many rows it should create, but after lots of attempts to get proper data, I'm really out of ideas why is this happening.
So, how to properly unarchive my data and present it in NSTableView?
(I'm using Xcode 4.6)

It is because the object you appear to be dearchiving is a pointer to an object of class UserKeys, rather than an object that is or descends from NSArray. When -initWithAray: hits your oldArray pointer to UserKeys, it doesn't know, or doesn't care what class it is, it simply sends it a -count message so as to ascertain the size it must grow to.

Related

NSMutableArray resetting itself when WindowDidLoad is done

When I pass a NSMutableArray from a controller class to a NSWindowController class using #property and #synthesize I am able to use the objects of the array in the windowDidLoad method.
However, after the method is done and I click a button on the window triggerig an IBAction, the passed value is nil.
Can anyone explain me why this is happening and how I can preserve the NSMutableArray?
Here is the code:
passClass.h
#import <Foundation/Foundation.h>
#class ResultWindowController;
#interface passClass : NSObject {
#private
IBOutlet NSTextField *searchField;
ResultWindowController *resultWindowController;
}
- (IBAction)passIt:(id)sender;
#end
passClass.m
#import "passClass.h"
#import "ResultWindowController.h"
#implementation passClass
- (IBAction)passIt:(id)sender {
NSString *searchString = searchField.stringValue;
NSMutableArray array = [[NSMutableArray alloc]init];
[array addObject:searchString];
[array addObject:searchString];
if(!resultWindowController) {
resultWindowController = [[ResultWindowController alloc] initWithWindowNibName:#"ResultWindow"];
resultWindowController.array =[[NSMutableArray alloc]initWithArray:array copyItems:YES];
[resultWindowController showWindow:self];
}
}
#end
ResultWindowController.h
#import <Cocoa/Cocoa.h>
#interface ResultWindowController : NSWindowController <NSTableViewDataSource> {
IBOutlet NSTableView *resultView;
NSMutableArray *resultList;
//NSMutableArray *array;
}
- (IBAction)returnValue:(id)sender;
#property (nonatomic,strong) NSMutableArray *array;
#end
ResultWindowController.m
#import "Results.h"
#interface ResultWindowController ()
#end
#implementation ResultWindowController
//#synthesize array;
- (id)initWithWindow:(NSWindow *)window
{
self = [super initWithWindow:window];
if (self) {
// Initialization code here.
resultList = [[NSMutableArray alloc] init];
}
return self;
}
- (void)windowDidLoad
{
[super windowDidLoad];
for (NSInteger i = 0; i< [array count];i++)
{
Results *result = [[Results alloc]init];
result.resultName = [self.array objectAtIndex:i];
[resultList addObject:result];
[resultView reloadData];
NSLog (#"self.array: %#", self.array);
// works fine, tableview gets populated, array is correct
}
}
- (NSInteger) numberOfRowsInTableView:(NSTableView *)resultView{
return [resultList count];
}
- (id)tableView:(NSTableView *)resultView objectValueForTableColumn:(NSTableColumn *)resultColumn row:(NSInteger)row{
Results *result = [resultList objectAtIndex:row];
NSString *identifier = [resultColumn identifier];
return [result valueForKey:identifier];
}
- (IBAction)selectedSeries:(id)sender {
NSLog (#"self.array: %#", self.array);
//when I break here the array is nil
}
#end
Here is the NSLog result:
2013-12-26 10:36:49.487 MyProgram[545:303] self.array: (
"test",
"test"
)
2013-12-26 10:37:24.044 MyProgram[545:303] self.array: (null)
Try to remove NSMutableArray *array; from ResultWindowController class declaration and leave only the property declaration for it.
Or you could try initiating the array property in your ResultVWindowsContorller init class and in the - (IBAction)passIt:(id)sender just add objects to array.
I honestly can't see how this works at all unless, in -windowDidLoadNib you are expecting array to be empty.
When you synthesise a property, the default name of the instance variable that is used is prefixed by an underscore. Thus the class in your code has two instance variables, array and _array.
There are several ways to fix this. Here's what I think what you should do is delete the instance variable in your interface definition. Then you'll start getting compilation errors every for each time you use it. Fix them by using the property instead, so for example, the line
result.resultName = [array objectAtIndex:i];
in -windowDidLoadNib becomes
result.resultName = [self.array objectAtIndex:i];

IOS loading a tableview with data

I'm fairly new to iOS and have much more to learn, and hope you guys can guide me from my mistake.
I've recently learned passing data from TableView to DetailView, and thought, why not the other way around. I also start building a StopWatch app, and felt that a log function would be very useful.
With that said, I'm currently building a stopwatch app that works as a timer and have a high score log function. It goes from View(stopwatch) to tableView(log board) I'm using a NSMutableArray as a temp storage to hold the information as they should be lost when the app start/close. Unfortunately, it seem that by following and changing variable here and there, i got myself confuse and stuck now.
Thanks for the suggestion and help you guys gave and thanks #Abizern for giving me tips. Manage to solve all the problem. Shall leave the code here incase anyone in the future do similar things to this.
TimerViewController.h
#import <UIKit/UIKit.h>
#import "SampleData.h"
#import "SampleDataDAO.h"
#import "HighScoreTableViewController.h"
#interface TimerViewController : UIViewController
{
NSTimer *stopWatchTimer; // Store the timer that fires after a certain time
NSDate *startDate; // Stores the date of the click on the start button
}
#property(nonatomic, strong) SampleDataDAO *daoDS;
#property(nonatomic, strong) NSMutableArray *ds;
#property (retain, nonatomic) IBOutlet UILabel *stopWatchLabel;
#property (weak, nonatomic) IBOutlet UIButton *onStartPressed;
#property (weak, nonatomic) IBOutlet UIButton *onStopPressed;
#property (weak, nonatomic) IBOutlet UIButton *onLogPressed;
#property (weak, nonatomic) IBOutlet UIButton *onHighscorePressed;
- (IBAction)onStartPressed:(id)sender;
- (IBAction)onStopPressed:(id)sender;
- (IBAction)onLogPressed:(id)sender;
- (IBAction)onHighscorePressed:(id)sender;
#end
TimerViewController.m
#import "TimerViewController.h"
#interface TimerViewController ()
#end
#implementation TimerViewController
#synthesize stopWatchLabel;
#synthesize onStartPressed;
#synthesize onStopPressed;
#synthesize onLogPressed;
#synthesize onHighscorePressed;
#synthesize ds,daoDS;
- (void)viewDidLoad
{
[super viewDidLoad];
daoDS = [[SampleDataDAO alloc] init];
self.ds = daoDS.PopulateDataSource;
onStopPressed.enabled=false;
}
- (void)viewDidUnload
{
[self setStopWatchLabel:nil];
[self setOnStartPressed:nil];
[self setOnLogPressed:nil];
[self setOnStopPressed:nil];
[self setOnHighscorePressed:nil];
[super viewDidUnload];
// Release any retained subviews of the main view.
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if ([[segue identifier] isEqualToString:#"showDetail"]) {
HighScoreTableViewController *detailViewController = [segue destinationViewController];
detailViewController.arrayOfSampleData = self.ds;
}
}
- (void)updateTimer
{
NSDate *currentDate = [NSDate date];
NSTimeInterval timeInterval = [currentDate timeIntervalSinceDate:startDate];
NSDate *timerDate = [NSDate dateWithTimeIntervalSince1970:timeInterval];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"HH:mm:ss.S"];
[dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0.0]];
NSString *timeString=[dateFormatter stringFromDate:timerDate];
stopWatchLabel.text = timeString;
}
- (IBAction)onStartPressed:(id)sender {
startDate = [NSDate date];
// Create the stop watch timer that fires every 10 ms
stopWatchTimer = [NSTimer scheduledTimerWithTimeInterval:1.0/10.0
target:self
selector:#selector(updateTimer)
userInfo:nil
repeats:YES];
onStartPressed.enabled=false;
onStopPressed.enabled=true;
}
- (IBAction)onStopPressed:(id)sender {
[stopWatchTimer invalidate];
stopWatchTimer = nil;
[self updateTimer];
onStartPressed.enabled=true;
}
- (IBAction)onLogPressed:(id)sender {
NSString * timeCaptured = stopWatchLabel.text;
static NSInteger i = 1 ;
SampleData* mydata = [[SampleData alloc]init];
mydata.clueName=[NSString stringWithFormat:#"clue %d",i++ ];
mydata.timeLog = timeCaptured;
[self.ds addObject:mydata];
NSLog(#"%#",mydata.clueName);
NSLog(#"time %#", mydata.timeLog);
NSLog(#"%d",[self.ds count]);
mydata=nil;
}
- (IBAction)onHighscorePressed:(id)sender {
NSLog(#"Proceeding to HighScore");
}
#end
HighScoreTableView.h
#import <UIKit/UIKit.h>
#import "SampleData.h"
#import "SampleDataDAO.h"
#import "TimerViewController.h"
#interface HighScoreTableViewController : UITableViewController
#property (nonatomic, strong) NSMutableArray *arrayOfSampleData;
#property (nonatomic, strong) SampleData * highscoreData;
#end
HighScoreTableView.m
#import "HighScoreTableViewController.h"
#interface HighScoreTableViewController ()
#end
#implementation HighScoreTableViewController
#synthesize highscoreData;
#synthesize arrayOfSampleData;
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
highscoreData = [[SampleData alloc]init];
[super viewDidLoad];
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.arrayOfSampleData.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"highscoreCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
// Configure the cell...
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
//highscoreData = [self.arrayOfSampleData objectAtIndex:indexPath.row];
highscoreData = (SampleData *)[self.arrayOfSampleData objectAtIndex:indexPath.row]; //if above line doesn't work, use this
cell.textLabel.text=[NSString stringWithFormat:#"%# time %#",highscoreData.clueName, highscoreData.timeLog];
return cell;
}
#pragma mark - Table view delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// Navigation logic may go here. Create and push another view controller.
/*
<#DetailViewController#> *detailViewController = [[<#DetailViewController#> alloc] initWithNibName:#"<#Nib name#>" bundle:nil];
// ...
// Pass the selected object to the new view controller.
[self.navigationController pushViewController:detailViewController animated:YES];
*/
}
#end
SampleData.h
#import <Foundation/Foundation.h>
#interface SampleData : NSObject
#property(nonatomic,strong) NSString * clueName;
#property(nonatomic,strong) NSString * timeLog;
#end
SampleData.m
#import "SampleData.h"
#implementation SampleData
#synthesize clueName,timeLog;
#end
SampleDataDAO.h
#import <Foundation/Foundation.h>
#import "SampleData.h"
#interface SampleDataDAO : NSObject
#property(nonatomic, strong) NSMutableArray * someDataArray;
-(NSMutableArray *)PopulateDataSource;
#end
SampleDataDAO.m (Not sure if this DAO NSObject is needed)
#import "SampleDataDAO.h"
#implementation SampleDataDAO
#synthesize someDataArray;
-(NSMutableArray *)PopulateDataSource
{
someDataArray = [[NSMutableArray alloc] init];
SampleData * mydata = [[SampleData alloc] init];
mydata = nil;
return someDataArray;
}
#end
There are several missteps in your coding:
You do need to use prepareForSegue to pass data from parent to child view controller. In your case from TimerViewController to HighScoreTableViewController.
In your HighScoreTableViewController class, create an iVar array that will hold the array of sampleData that you will pass over from TimerViewController instant via the prepareForSeque. Something like this:
HighScoreTableViewController.h
#property (nonatomic, strong) NSArray *arrayOfSampleData;
3 . In your prepareForSeque of the TimerViewController, this line is wrong:
//TimerViewController.highscoreData = [self.ds objectAtIndex:[self.tableView indexPathForSelectedRow].row];
Try this:
detailViewController.arrayOfSampleData = self.ds;
4 . In the HighScoreTableViewController.m, under viewDidLoad, replace this
highscoreData = (SampleData *)self.highscoreData;
with:
highscoreData = [SampleData alloc]init];
5 . In numberOfRowsInSection, you now can do this:
return self.arrayOfSampleData.count;
6 . In the cellForRowAtIndexPath,
highscoreData = [self.arrayOfSampleData objectAtIndex:indexPath.row];
//highscoreData = (SampleData *)[self.arrayOfSampleData objectAtIndex:indexPath.row]; //if above line doesn't work, use this
cell.textLabel.text = #"%# time %# ", highscoreData.clueName, highscoreData.timeLog;
In your HighScoreTableViewController you need access to your array e.g. by declaring and defining a writable property:
#property(nonatomic, strong) NSMutableArray *myArr;
then you can define
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [self.myArr count];
}
and
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// ... like in your code
// Tried changing variable here and there base on tutorial, but can't seem to get it right.**
SampleData * sample = (SampelData *) [self.myArr objectAtIndex:indexPath.row];
cell.textLabel.text = #"%# time %# ",sample.clueName, sample.timeLog;
NSLog(#"Cell Value %d %#",indexPath.row, cell.textLabel.text);
return cell;
}
So basically you just have to change two lines in the definitions of your methods. Most of the time you work with TableViews it is like this: assign the array you want to read data from to a custom property. Return the size of the array in tableView:numberOfRowsInSection: and take an object from the appropiate index to populate a cell in tableView:cellForRowAtIndexPath:.
If the contents of your array changes you have to do extra action to update your table view.
First declare the array in .h file(ex. NSMutableArray *arrStopwatchDetails).
Create the property of that array like #property(nonatomic,retain)NSMutableArray *arrStopwatchDetails.
Synthesize the array in .m file like #synthesize arrStopwatchDetails.
Allocate the array in viewDidLoad or before you want to used.
ex. self.arrStopwatchDetails = [[NSMutableArray alloc]init];
In numberOfRowsInSection method, return the count of array similar to return [self.arrStopwatchDetails count].
In cellForRowsAtIndexPath method, assign value of array element to the cell text as
SampleData * sample = [[[SampleDataDAO alloc]init ].self.arrStopwatchDetails objectAtIndex:indexPath.row];
cell.textLabel.text = #"%# time %# ",sample.clueName, sample.timeLog;
Thats it.

initWithCoder: not visible in NSObject?

I have an interface:
#import <Foundation/Foundation.h>
#interface Picture : NSObject;
#property (readonly) NSString *filepath;
- (UIImage *)image;
#end
and implementation:
#import "Picture.h"
#define kFilepath #"filepath"
#interface Picture () <NSCoding> {
NSString *filepath;
}
#end
#implementation Picture
#synthesize filepath;
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:filepath forKey:kFilepath];
}
- (UIImage *)image {
return [UIImage imageWithContentsOfFile:filepath];
}
#end
I get error: ARC issue - No visible #interface for 'NSObject' declares the selector' initWithCoder:'
Is there something different about NSCoding when using ARC?
Thanks
There's nothing different between ARC and manual reference counting. NSObject doesn't conform to NSCoding and therefore doesn't supply -initWithCoder: or -encodeWithCoder:. Just don't call through to the superclass implementations of those methods.
- (id)initWithCoder: (NSCoder *)aCoder {
self = [super init];
if (self) {
[aCoder decodeObject: filepath forKey: kFilepath];
}
return self;
}

How to create a class for a type of object?

Ok, I'm still getting used to how Objective-c works.
Lets suppose I'm making a todo list app. Instead of just reading from a plist and loading it into a table, some people say that you should create a class, lets call it ToDo that contains for example:
NSString *title;
NSString *description;
Ok, fine. Now how would I use such a class to load my data from a plist or something? I don't understand how creating a little class helps. Can anyone explain to me how this works?
Just creat a subclass of NSObject with NSCoding protocol(if you want to save it in a plist)
Example: *.h file
#interface ToDo : NSObject <NSCoding> {
NSString *title;
NSString *toDoDescription;
}
#property (copy) NSString *title;
#property (copy) NSString *toDoDescription;
#end
Example: *.m file
#implementation ToDo
#synthesize title, toDoDescription;
- (id)init
{
if ((self = [super init])) {
[self setTitle:#"none"];
[self setToDoDescription:#"none"];
}
return self;
}
- (void)dealloc
{
[title release];
[toDoDescription release];
[super dealloc];
}
// Next two methods and coding protocol are needed to save your custom object into plist
- (id)initWithCoder:(NSCoder *)aDecoder
{
if ((self = [super init])) {
title = [[aDecoder decodeObjectForKey:#"title"] copy];
toDoDescription = [[aDecoder decodeObjectForKey:#"toDoDescription"] copy];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:title forKey:#"title"];
[aCoder encodeObject:toDoDescription forKey:#"toDoDescription"];
}
#end
Use NSKeyedArchiver to covert your object into NSData. And then add it to a plist.

NSUserDefaults for self created classes

I'm having a problem in a usually very simple quest, I want to create a custom class containing an integer, a NSString and a NSDate. However, in my example I only use a NSString to show my problem when storing objects.
This is my class header:
#import
#interface MyClass : NSObject <NSCoding> {
NSString * myString;
}
#property (retain) NSString * myString;
#end
and my body:
#import "MyClass.h"
#implementation MyClass
#synthesize myString;
- (id)initWithCoder:(NSCoder *)coder {
self = [super init];
if (self) {
myString = [coder decodeObjectForKey:#"myString"];
}
return self;
}
- (id)init {
self = [super init];
if (self) {
myString = #"Hi";
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder{
[aCoder encodeObject: myString forKey:#"myString"];
}
- (void)dealloc {
myString = nil;
[super dealloc];
}
#end
My ViewController contains two buttons called Create and Read, as well as a UILabel called label.
#import <UIKit/UIKit.h>
#interface UserDefaultsTestViewController : UIViewController {
IBOutlet UILabel *label;
}
- (IBAction)pushCreate;
- (IBAction)pushLoad;
#end
I will only show the functions I've edited here:
#import "UserDefaultsTestViewController.h"
#import "MyClass.h"
- (IBAction)pushCreate {
MyClass * myClass = [[MyClass alloc] init];
NSData * data = [NSKeyedArchiver archivedDataWithRootObject:myClass];
NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:data forKey:#"Test"];
[myClass release];
}
- (IBAction)pushLoad {
NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults];
NSData * atchive = [defaults objectForKey:#"Test"];
MyClass * myClass = [NSKeyedUnarchiver unarchiveObjectWithData:atchive];
label.text = myClass.myString;
}
However, if I run my code, I get either a EXC_BAD_ACCESS error or it displays it correctly the first time and crashes when doing it a second time, again with the same error.
What am I doing wrong?
You want to do:
self.myString = #"Hi";
Or:
myString = #"Hi";
[myString retain];
Likewise, your dealloc should have:
self.myString = nil;
or
[myString release];