Bronze Challenge - Works but feels wrong


#1

Hi

I’ve just completed the bronze challenge to save and load the lines for the TouchTracker app but I’m not really happy with it!

In Line.h I conformed to the NSCoding protocol and in the Line.m implemented the encode and init methods:

[code]- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeCGPoint:begin forKey:@“begin”];
[aCoder encodeCGPoint:end forKey:@“end”];
}

  • (id)initWithCoder:(NSCoder *)aDecoder
    {
    self = [super init];
    if (self) {
    [self setBegin:[aDecoder decodeCGPointForKey:@“begin”]];
    [self setEnd:[aDecoder decodeCGPointForKey:@“end”]];
    }
    return self;
    }[/code]
    Then in TouchDrawView.h I created 3 new methods:

[code]+ (TouchDrawView *)sharedStore;

  • (NSString *)linesArchivePath;
  • (BOOL)saveLines;[/code]
    In TouchDrawView.m I added:

[code]+ (TouchDrawView *)sharedStore
{
static TouchDrawView *sharedStore = nil;
if (!sharedStore) {
sharedStore = [[super allocWithZone:nil] init];
}
return sharedStore;
}

  • (id)allocWithZone:(NSZone *)zone
    {
    return [self sharedStore];
    }
  • (NSString *)linesArchivePath
    {
    NSArray *documentDirectories = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentDirectory = [documentDirectories objectAtIndex:0];

    return [documentDirectory stringByAppendingPathComponent:@“lines.archive”];
    }

  • (BOOL)saveLines
    {
    NSString *path = [self linesArchivePath];

    return [NSKeyedArchiver archiveRootObject:completeLines toFile:path];
    }
    [/code]
    Then I changed the initWithFrame method to unarchive any saved Lines into the completeLines array.

[code]- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
linesInProcess = [[NSMutableDictionary alloc] init];

    NSString *path = [self linesArchivePath];
    completeLines = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
    if (!completeLines) {
        completeLines = [[NSMutableArray alloc] init];
    }
    
    [self setBackgroundColor:[UIColor whiteColor]];
    [self setMultipleTouchEnabled:YES];
}
return self;

}
[/code]
And finally, in the AppDelegate.m I saved any drawn Lines to the archive when the app enters the background:

- (void)applicationDidEnterBackground:(UIApplication *)application { BOOL success = [[TouchDrawView sharedStore] saveLines]; if (success) { NSLog(@"Saved all the Lines"); } else { NSLog(@"Didn't save any Lines"); } }

This does work, but when I first start the app, and each time it relaunches (i.e. when it’s killed and then run again) it calls the initWithFrame method in TouchDrawLines.m twice. Should it be doing this?
Also, is this the best way of approaching this save/load task? Do I need a LineStore class or is it OK to ‘add’ the store into the existing TouchDrawView class? Is this singleton sharedStore the way to go with this?

Any help or pointers would be very much appreciated.

Thanks, Mark


#2

I did the exact same thing as you and have the same questions.

I almost did this using NSUserDefaults since that was the previous chapter but I felt that wasn’t what NSUserDefaults really are for.


#3

Registered for the UIApplicationDidEnterBackgroundNotification inside of initWithFrame: to initiate saving changes to disk:

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(saveChangesToDisk) name:UIApplicationDidEnterBackgroundNotification object:nil];
Then removed myself as an observer inside of dealloc:

- (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; }
Doing this allows you to avoid creating a shared store. Still not sure which is the best practice though.


#4

@ Joe :
Generally speaking, is it considered a good programming pratice to create a store for any object that needs to be saved ?

Thanks
Fred


#5

So I created a LineStore to make it work, in order to respect the MCVS model.

Here is my LineStore.h:


#import <Foundation/Foundation.h>

@class Line;

@interface LineStore : NSObject
{
    NSMutableArray *allLines;
}


+ (LineStore *)sharedStore;
- (NSMutableArray *)allTheLines;

- (NSString *)lineArchivePath;
- (BOOL)saveChanges;

@end[/code]

Here is my LineStore.m:
[code]

#import "LineStore.h"
#import "Line.h"

@implementation LineStore

+ (LineStore *)sharedStore
{
    static LineStore *sharedStore = nil;
    if (!sharedStore) {
        sharedStore = [[super allocWithZone:nil] init];
    }

    return sharedStore;
}

+ (id)allocWithZone:(NSZone *)zone
{
    return [self sharedStore];
}



- (id)init
{
    self = [super init];
    if (self) {
        
        NSString *path = [self lineArchivePath];
        allLines = [NSKeyedUnarchiver unarchiveObjectWithFile:path];

        if (!allLines)
            allLines = [[NSMutableArray alloc]init];
    }
    return self;
}



- (NSMutableArray *)allTheLines
{
    return allLines;
}


- (NSString *)lineArchivePath
{
    NSArray *documentDirectories = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    
    // Get one and only document directory from that list
    NSString *documentDirectory = [documentDirectories objectAtIndex:0];
    
    return [documentDirectory stringByAppendingPathComponent:@"lines.archive"];
}

- (BOOL)saveChanges
{
    // return path success or failure
    NSString *path = [self lineArchivePath];
    return [NSKeyedArchiver archiveRootObject:allLines
                                       toFile:path];
}


@end
[/code]

I added the following methods in Line.m:
[code]- (void)encodeWithCoder:(NSCoder *)aCoder
{
    [aCoder encodeFloat:begin.x forKey:@"begin.x"];
    [aCoder encodeFloat:begin.y forKey:@"begin.y"];
    [aCoder encodeFloat:end.x forKey:@"end.x"];
    [aCoder encodeFloat:end.y forKey:@"end.y"];   
}


- (id)initWithCoder:(NSCoder *)aDecoder
{
    self = [super init];
    if (self) {
        [self setBegin:CGPointMake([aDecoder decodeFloatForKey:@"begin.x"], [aDecoder decodeFloatForKey:@"begin.y"])];
        [self setEnd:CGPointMake([aDecoder decodeFloatForKey:@"end.x"], [aDecoder decodeFloatForKey:@"end.y"])];
    }
    return self;
}

I also changed the initWithFrame: method of TouchDrawView.m:

[code]- (id)initWithFrame:(CGRect)r
{
self = [super initWithFrame:r];
if (self) {
linesInProcess = [[NSMutableDictionary alloc] init];

    // completeLine points to [[LineStore sharedStore]allTheLines] !!
    completeLines = [[LineStore sharedStore]allTheLines];
    NSLog(@"%d objects in AllLines", [[[LineStore sharedStore]allTheLines]count]);

    if (!completeLines) {
        completeLines = [[NSMutableArray alloc] init];

    }
    
    [self setBackgroundColor:[UIColor whiteColor]];
    [self setMultipleTouchEnabled:YES];
}

return self;

}[/code]
Funny, I coded this method in 5 minutes, and spent the whole day trying to understand why I didn’t need to update the allLines NSmutableArray (it’s not updated when a line is created … but completeLines points to allLines, so that works seamlessly).

And finally, I trigger the archiving process when the application enters background:
In AppDelegate.m:

[code]- (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.

// BRONZE Challenge
// Archive complete lines
BOOL success = [[LineStore sharedStore] saveChanges];
if (success) {
    NSLog(@"Saved all lines");
} else {
    NSLog(@"Could not save any line");
}

}[/code]

Enjoy !


#6

Interesting approach with the store - I didn’t even consider heading down so far as the Line method…

Another approach: encode completeLines from TouchDrawView itself! That enables you to store them very simply in NSUserDefaults, etc. Though I did struggle with actually registering defaults (truth be told, I did not register defaults - simply worked around their absence). Anyone know whether not registering defaults will definitively screw me? :wink:

Either way, here is my relevant code (100% is in TouchDrawView.m). :slight_smile:

[color=#BF0000]saveCompleteLines:[/color] (my method for encoding lines and saving them to NSUserDefaults)

[code]- (void)saveCompleteLines {

// Encode lines
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:completeLines];

// .. & save them to user defaults
[[NSUserDefaults standardUserDefaults] setObject:data 
                                          forKey:TouchTrackerCompletedLinesPrefKey];

NSLog(@"Saved completeLines to NSUserDefaults: %@",
      [[NSUserDefaults standardUserDefaults] 
       objectForKey:TouchTrackerCompletedLinesPrefKey]);

}
[/code]

[color=#BF0000]loadCompleteLines:[/color] (my method for loading the lines from NSUserDefaults)

[code]- (NSMutableArray *)loadCompleteLines {

// Decode lines
NSData *data = [[NSUserDefaults standardUserDefaults] objectForKey:TouchTrackerCompletedLinesPrefKey];

// .. & assign them to completeLines
completeLines = [NSKeyedUnarchiver unarchiveObjectWithData:data];

NSLog(@"Loaded completeLines from NSUserDefaults: %@",completeLines);

return completeLines;

}
[/code]

[color=#BF0000]endTouches:[/color] (where I call the Save method)

[code]- (void)endTouches:(NSSet *)touches {
// Remove ending touches from dictionary
for (UITouch *t in touches) {
NSValue *key = [NSValue valueWithNonretainedObject:t];
Line *line = [linesInProcess objectForKey:key];

    // If this is a double tap, 'line' will be nil,
    // so make sure not to add it to the array
    if (line) {
        [completeLines addObject:line];
        [linesInProcess removeObjectForKey:key];
        
        [self saveCompleteLines];
    }
}

//Redraw
[self setNeedsDisplay];

}
[/code]

[color=#BF0000]initWithFrame:[/color] (where I load my completed lines from NSUserDefaults - also where i ‘workaround’ not properly registering defaults)

[code]- (id)initWithFrame:(CGRect)r {

NSLog(@"initWithFrame: fired");

self = [super initWithFrame:r];

if (self) {
    linesInProcess = [[NSMutableDictionary alloc] init];
     
    // Load completeLines from NSUserDefaults (unless they are blank)
    // Else just initialize the array
    if ([[self loadCompleteLines] count] > 0) {
        completeLines = [self loadCompleteLines];            
    } else {
        completeLines = [[NSMutableArray alloc] init];
    }
    
    [self setBackgroundColor:[UIColor whiteColor]];
    
    [self setMultipleTouchEnabled:YES];
}

return self;

}[/code]


#7

I spent an extended amount of time trying to achieve the goals of the Bronze challenge with NSUserDefaults. Because the preferences must be a property list, the Line class or its subcomponents CGPoints do not appear to be usable via the defaults.

My goal was subsequently to save without having to implement a Store. I did this via implementing in TouchDrawView.

I set Line.h to the protocol and implemented -(void)encodeWithCoder and -(id)initWithCoder in Line.m

I declared in TouchDrawView.h:

-(NSString *)lineArchivePath; -(BOOL)saveLines;

And implemented as follows in TouchDrawView.m:

[code]-(NSString *)lineArchivePath
{
NSArray *documentDirectories = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentDirectory = [documentDirectories objectAtIndex:0];
return [documentDirectory stringByAppendingPathComponent:@“items.archive”];
}

-(BOOL)saveLines
{
NSString *path = [self lineArchivePath];
return [NSKeyedArchiver archiveRootObject:completeLines toFile:path];
}

-(id)initWithFrame:(CGRect)r
{
self = [super initWithFrame:r];

if (self) {
    linesInProcess = [[NSMutableDictionary alloc] init];
    
    // BRONZE: unarchive the lines
    NSString *path = [self lineArchivePath];
    completeLines = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
    
    if (!completeLines)
        completeLines = [[NSMutableArray alloc] init];        
    
    [self setBackgroundColor:[UIColor whiteColor]];
    [self setMultipleTouchEnabled:YES];
}
return self;

}

-(void)endTouches:(NSSet *)touches
{
for (UITouch *t in touches) {
NSValue *key = [NSValue valueWithNonretainedObject:t];
Line *line = [linesInProcess objectForKey:key];

    if (line) {
        [completeLines  addObject:line];
        [linesInProcess removeObjectForKey:key]; 
        
        // BRONZE Challenge
        [self saveLines];
    }
}
[self setNeedsDisplay];

}
[/code]

On further review, toblerpwn has the more elegant solution. Very interesting use of archiving the data without writing to file.

As for the workaround, toblerpwn, why is the simplest execution:

Not workable in this case?


#8

My first idea for implementing this challenge was to put the code inside the TouchDrawView. However, as soon as I imported TouchDrawView.h in the AppDelegate.m, my spidey-sense went off. The view should not be managing the objects it is working with, it’s only responsibility is to draw the lines and manage touches.

At this point in the book we have used the file system, Core Data, and NSUserDefaults. After thinking about each of them, to me it made sense to use the file system. The lines someone draws is not a setting or preference, so that rules out NSUserDefaults. The line data is very simple and small, so Core Data seemed like overkill. So this is why I went with a file system approach using the MVCS pattern.

My implementation is similar to @FreddyF’s, but instead of encoding the X and Y for the begin and end separately, I encoded the CGPoint instead. In the process of creating the LineStore I ended up creating a few extra methods that are not necessary (such as removeAllObjects found in LineStore), but are helpful.

Line.h

@interface Line : NSObject <NSCoding>

@property (nonatomic) CGPoint begin;
@property (nonatomic) CGPoint end;

- (id)initWithCGPoint:(CGPoint)p;

@end

Line.m

@implementation Line

@synthesize begin, end;

#pragma mark "Protocols"
- (void)encodeWithCoder:(NSCoder *)aCoder
{
    [aCoder encodeCGPoint:begin forKey:@"begin"];
    [aCoder encodeCGPoint:end forKey:@"end"];
}

- (id)initWithCoder:(NSCoder *)aDecoder
{
    self = [super init];
    
    if (self) {
        [self setBegin:[aDecoder decodeCGPointForKey:@"begin"]];
        [self setEnd:[aDecoder decodeCGPointForKey:@"end"]];
    }   //end if
    
    return self;
}

#pragma mark "Initializers"
- (id)initWithCGPoint:(CGPoint)p
{
    self = [super init];
    
    if (self) {
        [self setBegin:p];
        [self setEnd:p];
    }
    
    return self;
}
@end

LineStore.h

@class Line;

@interface LineStore : NSObject
{
    NSMutableArray *completedLines;
}

+ (LineStore *)sharedStore;

- (NSArray *)completedLines;

- (Line *)createLine;
- (void)addLine:(Line *)line;

- (void)removeLine:(Line *)line;
- (void)removeAllObjects;

- (NSString *)lineArchivePath;
- (BOOL)saveChanges;

@end

LineStore.m

#import "Line.h"

@implementation LineStore

#pragma mark - Class Methods
+ (LineStore *)sharedStore
{
    //static variable does not live on the stack and are not destroyed when the method returns.
    //it is like a local variable, no other object or method can use the object pointed to by this
    //variable except via this method.
    static LineStore *sharedStore = nil;
    
    //This skips over the alloc trap in the override of allocWithZone so that here a LineStore can be instantiated.
    //(the super keyword is only relevant to the class in which the method is implemented.
    if (!sharedStore) {
        sharedStore = [[super allocWithZone:nil] init];
    }
    
    return sharedStore;
}

#pragma mark - Initializers
- (id)init
{
    self = [super init];
    if (self) {
        //completedLines = [[NSMutableArray alloc] init];
        
        NSString *path = [self lineArchivePath];
        completedLines = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
        
        //if the array hadn't been saved previously, create a new empty one
        if (!completedLines) {
            completedLines = [[NSMutableArray alloc] init];
        }
    }
    
    return self;
}

#pragma mark - Overrides
//To ensure that another instance of LineStore cannot be allocated
+ (id)allocWithZone:(NSZone *)zone
{
    return [self sharedStore];
}


#pragma mark - Instance Methods

- (NSArray *)completedLines
{
    return completedLines;
}

- (void)removeAllObjects
{
    [completedLines removeAllObjects];
}

- (Line *)createLine
{    
    //Create a new line with a default location
    CGPoint loc = CGPointMake(0.0f, 0.0f);
    Line *newLine = [[Line alloc] initWithCGPoint:loc];
    
    //add it to the store
    [completedLines addObject:newLine];
    
    return newLine;
}

- (void)addLine:(Line *)line
{
    //add line to the store
    [completedLines addObject:line];
}

- (void)removeLine:(Line *)line
{    
    //Remove the Line if and only if it is the exact same object as the one passed in to this message
    [completedLines removeObjectIdenticalTo:line];
}

- (NSString *)lineArchivePath
{
    NSArray *documentationDirectories = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory
                                                                            , NSUserDomainMask
                                                                            , YES);
    
    //Get one and only document directory from that list
    NSString *documentDirectory = [documentationDirectories objectAtIndex:0];
    
    return [documentDirectory stringByAppendingPathComponent:@"lines.archive"];
}

- (BOOL)saveChanges
{
    //returns success or failure
    NSString *path = [self lineArchivePath];
    
    return [NSKeyedArchiver archiveRootObject:completedLines toFile:path];
}

@end

AppDelegate.m (inside of applicationDidEnterBackground:)

    BOOL success = [[LineStore sharedStore] saveChanges];
    if (success) {
        NSLog(@"Save all of the completed lines");
    }
    else
    {
        NSLog(@"Could not save any of the completed lines");
    }

In TouchDrawView.h remove the line: NSMutableArray completedLines;

TouchDrawView.m

@implementation TouchDrawView

#pragma mark - "Overrides"
- (id)initWithFrame:(CGRect)r
{
    self = [super initWithFrame:r];
    
    if (self) {
        linesInProcess = [[NSMutableDictionary alloc] init];
        
        //Don't let the autocomplete fool you on the next line, make sure you are instantiating
        //an NSMutableArray and not an NSMutableDictionary!
        //completeLines = [[NSMutableArray alloc] init];
        
        [self setBackgroundColor:[UIColor whiteColor]];
        
        //must explicitly set this, otherwise this view will only have a single touch active at a
        //time. If another finger touches the view it will be ignored and the view will not be sent
        //touchesBegin:withEvent: or any other UIResponder messages
        [self setMultipleTouchEnabled:YES];
    }
    
    return self;
}

//Create a new Line instance and store it in an NSMutableDictionary
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    for (UITouch *t in touches)
    {
        //Is this a double tab?
        if ([t tapCount] > 1) {
            [self clearAll];
            return;
        }
        
        //Use the touch object (packed in an NSValue) as the key
        NSValue *key = [NSValue valueWithNonretainedObject:t];
        
        //create a line for the value. locationInView returns the current location of the receiver
        //in the coordinate system of the given view.
        CGPoint loc = [t locationInView:self];
        
        Line *newLine = [[Line alloc] init];
        
        [newLine setBegin:loc];
        [newLine setEnd:loc];
        
        //put pair in dictionary
        [linesInProcess setObject:newLine forKey:key];
        
    }   //end for
}

//update the end point of the line associated with the moving touch
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    //update linesInProcess with moved touches
    for (UITouch *t in touches)
    {
        //UITouch, you should never have a reference, hence using valueWithNonretainedObject
        NSValue *key = [NSValue valueWithNonretainedObject:t];
        
        //find the line for this touch
        Line *line = [linesInProcess objectForKey:key];
        
        //update the line
        CGPoint loc = [t locationInView:self];
        [line setEnd:loc];
    }   //end for
    
    //redraw the display
    [self setNeedsDisplay];
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    [self endTouches:touches];
}

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
    [self endTouches:touches];
}

//Create a line using functions from Core Graphics
- (void)drawRect:(CGRect)rect
{
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetLineWidth(context, 10);
    CGContextSetLineCap(context, kCGLineCapRound);
    
    //Draw complete lines in black
    [[UIColor blackColor] set];
    //for (Line *line in completeLines)
    for (Line *line in [[LineStore sharedStore] completedLines])
    {
        CGContextMoveToPoint(context, [line begin].x, [line begin].y);
        CGContextAddLineToPoint(context, [line end].x, [line end].y);
        CGContextStrokePath(context);
    }   //end for
    
    //Draw lines in process in red
    [[UIColor redColor] set];
    for (NSValue *v in linesInProcess)
    {
        Line *line = [linesInProcess objectForKey:v];
        
        CGContextMoveToPoint(context, [line begin].x, [line begin].y);
        CGContextAddLineToPoint(context, [line end].x, [line end].y);
        CGContextStrokePath(context);
    }   //end for
}

#pragma mark "Methods"
- (void)clearAll
{
    //Clear the collections
    [linesInProcess removeAllObjects];
    //[completeLines removeAllObjects];
    [[LineStore sharedStore] removeAllObjects];
    
    //Redraw
    [self setNeedsDisplay]; //Marks the receiver’s entire bounds rectangle as needing to be redrawn.
}

//handles both cases of touchesEnded and touchesCancelled
- (void)endTouches:(NSSet *)touches
{
    //remove ending touches from dictionary
    for (UITouch *t in touches)
    {
        NSValue *key = [NSValue valueWithNonretainedObject:t];
        Line *line = [linesInProcess objectForKey:key];
        
        //if this is a double tap, 'line' will be nil, so make sure not to add it to the array
        if (line) {
            //[completeLines addObject:line];
            [[LineStore sharedStore] addLine:line];
            [linesInProcess removeObjectForKey:key];
        }
    }   //end for
    
    //redraw the display
    [self setNeedsDisplay];
}

@end

#9

I also thought about whether I should use Archiving, NSUsersDefault and Core Data to do this challenge and I agreed with Diditsave. NSUsersDefault does use plist, however it’s mainly for settings and preferences, plist will be saved to Library/Preferences. So even though it will work, I don’t think we should use this in this challenge.

Core data can save big data, and for this app there aren’t too much data, also filtering (NSPredicate) is not necessary in this case.

So I have decided to use NSKeyArchiver and NSKeyUnArchiver. However my approach is a little bit different from Diditsave’s. First I didn’t create any itemStore (too complicated!) and second, my save load and retrieve path methods are all in the viewController file, not in the touchDrawView class. touchDrawView is just a UIView and UIView shouldn’t handle the saving and retrieving path. So what I did was:

  1. in the touchDrawView class, I made the completeLines a property, I synthesize it in the .m file so that it also created the setter and getter for me.

  2. in the view controller class I created an instance of the UIView. I also implemented the load method, save method, getPath methods there. ie. in save method, I’ll retrieve the completeLines and use NSKeyArchiver to archive it, and in init method I’ll call the load method.

  3. in appdelegate.m when app goes to the background, I called the view controller to call the save methods

not sure if my approach is good or not but hey, it worked :slight_smile:


#10

This forum is great for all the possibilities for various solutions, and I use it often, so I thought I’d post what I got working.
I also didn’t like using UserDefaults or CoreData, nor making a “LineStore” class variable, nor putting too much new/unrelated code in TouchViewController, and thought that the saving code should go in AppDelegate. The only problem I had was being able to access TouchDrawView’s saveLines method from AppDelegate. My original line in AppDelegate’s applicationDidEnterBackground was:

but this complained about not knowing about the saveLines method. Working with a colleague, he split the line into a step at a time, and saw/explained that at compile time, not enough is known about each item in this hierarchy. So he suggested the following, that works as expected:

UIWindow *w = [self window]; TouchViewController *rvc = (TouchViewController*) w.rootViewController; TouchDrawView *v = (TouchDrawView *)rvc.view; BOOL success=[v saveLines];
So is this, or any of these other posted solutions, a preferred way?


#11

The challenge with the above is that to the compiler, the type of UIView (from view) is unknown. If you made the following modification to specify that the view is of class TouchDrawView, you could get everything on one line:

    BOOL success = [(TouchDrawView *)[[[self window] rootViewController] view] saveLines];

That said, view’s really shouldn’t be doing the work of saving / loading. Either the ViewContoller or ItemStore should do that (depending on whether you’re using MVC or MVCS).


#12

If we’re talking about strictly adhering to the MVC/S design. Isn’t it true that as the application stands now it doesn’t really adhere to that design pattern?
The model class header file Line.h is, in the chapter, directly imported to the view class implementation file TouchDrawView.m and we declare an NSMutableArray and NSMutable Dictionary in TouchDrawView.h. Even if you create a store to handle saving/loading the Line objects, you are still transferring data between the model and the view, which really should be done through the view controller, right?

Just an observation… and it makes me wonder if there might ever be a situation when saving/loading through the view would be useful? I guess once you get to storyboards, you can’t really do that though, so maybe not. Maybe a better question would be if there is ever a time when bending the rules of the MVC/S design pattern would be useful and practically acceptable.