Silver Challenge solution


#1

This one was quite time consuming so I hope I haven’t missed anything out. Start from a fresh duplicate of Homepwner (not the bronze challenge).

  1. Create UIViewController subclass with .xib (BNRAssetNameSelector). This is for selecting name of new Asset. The .xib has unconnected label (top) “New Asset Name”, a textfield (middle) connected to property “assetName” and “Done” button (bottom) connected to method “done:” when “Done” is pressed, it will dismiss the current view controller.

BNRAssetNameSelector.h

#import <UIKit/UIKit.h>
@interface BNRAssetNameSelector : UIViewController
@property (weak, nonatomic) IBOutlet UITextField *assetName;
@end

BNRAssetNameSelector.m

#import "BNRAssetNameSelector.h"
@interface BNRAssetNameSelector ()
@end
@implementation BNRAssetNameSelector
- (IBAction)done:(id)sender
{
    [self.navigationController popViewControllerAnimated:YES]; 
}
@end
  1. In BNRItemStore make the allAssetTypes method return NSMutableArray (in .h and .m files), make allAssetType property an NSMutableArray and move it to the .h file.

  2. In BNRAssetTypeViewController.m

  • Add a BNRAssetNameSelector as a property.
  • In viewDidLoad, add button on the right side of the nav controller, create corresponding action method “addAsset” and use it to push view controller for BNRAssetNameSelector.
  • In viewDidAppear add the new asset type to the allAssetTypes array in the BNRItemStore.

Here’s my entire BNRAssetTypeViewController.m

#import "BNRAssetTypeViewController.h"

#import "BNRItemStore.h"
#import "BNRItem.h"
#import "BNRAssetNameSelector.h"

@interface BNRAssetTypeViewController ()

@property (nonatomic) BNRAssetNameSelector *nameSelector;

@end

@implementation BNRAssetTypeViewController

- (instancetype)init
{
    return [super initWithStyle:UITableViewStylePlain];

}

- (void)addAsset
{
    self.nameSelector = [[BNRAssetNameSelector alloc] init];
    [self.navigationController pushViewController:self.nameSelector
                                         animated:YES];
}

- (void)viewDidAppear:(BOOL)animated
{
    
    if (self.nameSelector.assetName.text) {
        NSString *label = self.nameSelector.assetName.text;
        BNRItemStore *itemStore = [BNRItemStore sharedStore];
    
        NSManagedObject *type;
    
        type = [NSEntityDescription insertNewObjectForEntityForName:@"BNRAssetType"
                                         inManagedObjectContext:itemStore.context];
        [type setValue:label forKey:@"label"];
        [itemStore.allAssetTypes addObject:type];
        [self.tableView reloadData];
        self.nameSelector.assetName.text = nil;
    }
}

- (instancetype)initWithStyle:(UITableViewStyle)style
{
    return [self init];
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    UIBarButtonItem *addAssetTypeButton = [[UIBarButtonItem alloc]
                                           initWithTitle:@"Add Asset"
                                           style:UIBarButtonItemStyleBordered
                                           target:self
                                           action:@selector(addAsset)];
    self.navigationItem.rightBarButtonItem = addAssetTypeButton;
    
    [self.tableView registerClass:[UITableViewCell class]
           forCellReuseIdentifier:@"UITableViewCell"];
}

- (NSInteger)tableView:(UITableView *)tableView
 numberOfRowsInSection:(NSInteger)section
{
    return [[[BNRItemStore sharedStore] allAssetTypes] count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView
         cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"UITableViewCell"
                                                            forIndexPath:indexPath];
    NSArray *allAssets = [[BNRItemStore sharedStore] allAssetTypes];
    NSManagedObject *assetType = allAssets[indexPath.row];
    
    // Use key-value coding to get the asset type's label
    NSString *assetLabel = [assetType valueForKey:@"label"];
    cell.textLabel.text = assetLabel;
    
    // Checkmark the one that is currently selected
    if (assetType == self.item.assetType) {
        cell.accessoryType = UITableViewCellAccessoryCheckmark;
    } else {
        cell.accessoryType = UITableViewCellAccessoryNone;
    }
    
    return cell;
}

- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
    
    cell.accessoryType = UITableViewCellAccessoryCheckmark;
    
    NSArray *allAssets = [[BNRItemStore sharedStore] allAssetTypes];
    NSManagedObject *assetType = allAssets[indexPath.row];
    self.item.assetType = assetType;
    
    [self.navigationController popViewControllerAnimated:YES];
    
}

@end

#2

In my case, I created new BNRNewAssetViewController class and xib file with a text field. The new asset type is saved when the view disappears.

BNRAssetTypeViewController.m

-(void)viewWillAppear:(BOOL)animated
{
    self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc]
                            initWithBarButtonSystemItem:UIBarButtonSystemItemAdd
                                                 target:self
                                                 action:@selector(addAssetType)];
}

-(void)addAssetType
{
    BNRNewAssetViewController *nac = [[BNRNewAssetViewController alloc] init];
    nac.dismissBlock = ^{
        [self.tableView reloadData];
    };
    
    [self.navigationController pushViewController:nac animated:YES];
}


BNRNewAssetViewController.h

@property (nonatomic, copy) void (^dismissBlock)(void);


BNRNewAssetViewController.m

#import "BNRItemStore.h"

@property (weak, nonatomic) IBOutlet UITextField *assetTypeTextField;

-(void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear];
    
    [self.view endEditing:YES];
    
    [[BNRItemStore sharedStore] createAssetType:self.assetTypeTextField.text];
    
    self.dismissBlock();
}


BNRItemStore.h

-(void)createAssetType:(NSString *)label;


BNRItemStore.m

-(void)createAssetType:(NSString *)label
{
    NSManagedObject *type =
        [NSEntityDescription insertNewObjectForEntityForName:@"BNRAssetType"
                                      inManagedObjectContext:self.context];
    [type setValue:label forKey:@"label"];
    [_allAssetTypes addObject:type];
}

#3

Using monkeyboy’s solution I get an error on

type = [NSEntityDescription insertNewObjectForEntityForName:@"BNRAssetType"
                                             inManagedObjectContext:BNRItemStore.context];

Property ‘context’ not found on object type of ‘BNRItemStore’

What method to I need to declare in BNRItemStore.h ?

Thanks


#4

[quote=“macspeed”]Using monkeyboy’s solution I get an error on

type = [NSEntityDescription insertNewObjectForEntityForName:@"BNRAssetType"
                                             inManagedObjectContext:BNRItemStore.context];

Property ‘context’ not found on object type of ‘BNRItemStore’

What method to I need to declare in BNRItemStore.h ?

Thanks[/quote]

You’re probably wanting it to be [BNRItemStore sharedStore].context


#5

I used a similar approach to the one displayed above, except instead of creating a customized view, I used the UIAlertView class as I felt it was very suitable for this situation. I’ll go on to explain my procedures.

  1. ) Modifying the BNRItemStore.h/BNRItemStore.m File
    [*] Declare this function prototype in the BNRItemStore.h header, it will be used to create the new Asset
    - (void)createAssetWithName:(NSString *)assetName;

    [*] Add this implementation to the BNRItemStore.m File
    [code]- (void)createAssetWithName:(NSString *)assetName {
    NSManagedObject *asset = [NSEntityDescription insertNewObjectForEntityForName:@“BNRAssetType” inManagedObjectContext:self.context];
    [asset setValue:assetName forKey:@“label”];

    [_allAssetTypes addObject:asset];
    }[/code]

2 ) Implementing the UIAlertView and Delegate Methods
[*] Add this class extension to the BNRAssetTypeController.h if it does not exist
[code]@interface BNRAssetTypeViewController () <UIAlertViewDelegate, UITextFieldDelegate>

@property (nonatomic) UIAlertView *addAssetAlert;

@end[/code]

 [*] Inside the viewDidLoad method of the same file, create our navigationItem button, the "add" button, and assign it to the left/right.
      [code]UIBarButtonItem *addButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(addNewAsset)];
self.navigationItem.rightBarButtonItem = addButton;

[/code]

 [*] Implementing the addNewAsset method that is called when a new asset might be added to our table view
      [code]- (void)addNewAsset {
NSLog(@"Adding New Asset!");

if (!_addAssetAlert) {
    _addAssetAlert = [[UIAlertView alloc] initWithTitle:@"New Asset"
                                                message:@"Enter New Asset Name"
                                               delegate:self
                                      cancelButtonTitle:@"Cancel"
                                      otherButtonTitles:@"Ok!", nil];
    
    self.addAssetAlert.alertViewStyle = UIAlertViewStylePlainTextInput;
}

[self.addAssetAlert show];

}
[/code]

  [*] Implementing the AlertView Delegate method to either add/cancel the new asset addition
      [code]- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
switch (buttonIndex) {
    case 0:
        NSLog(@"Index %i %@", buttonIndex, [self.addAssetAlert buttonTitleAtIndex:buttonIndex]);
        [self.addAssetAlert resignFirstResponder];
        break;
    case 1:
        NSLog(@"Index %i %@", buttonIndex, [self.addAssetAlert buttonTitleAtIndex:buttonIndex]);
        
        UITextField *textField = [self.addAssetAlert textFieldAtIndex:0];
        if ( textField.text.length > 0 )
            [[BNRItemStore sharedStore] createAssetWithName:textField.text];
        [self.tableView reloadData];
        break;
}

}
[/code]

It’s a very pragmatic solution, I hope.