-
-
Save rbsgn/240122 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// YSTableViewCells.h | |
// | |
// A set of cells for implementing Preferences-like tables. | |
// | |
// Copyright (c) 2009, Andrey Tarantsov <andreyvit@gmail.com> | |
// | |
// Permission to use, copy, modify, and/or distribute this software for any | |
// purpose with or without fee is hereby granted, provided that the above | |
// copyright notice and this permission notice appear in all copies. | |
// | |
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | |
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | |
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | |
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | |
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | |
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | |
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |
#import <UIKit/UIKit.h> | |
// cell with UISwitch | |
@class YSSwitchCell; | |
// cell that displays a value selected from a list of predefined options | |
@class YSListCell; | |
@class YSHeaderLabelCell; | |
// presents a list of items in UITableView, selected item has a checkmark | |
// used by YSListCell detailController | |
@class YSListPickerController; | |
@protocol YSListPickerControllerDelegate; | |
// presents an editing screen with the given rows and Cancel/Save buttons | |
@class YSFieldEditorController; | |
@protocol YSFieldEditorControllerDelegate; | |
// a specialization of YSFieldEditorController for editing a single text field | |
@class YSStringFieldEditorController; | |
// Presents a grouped UITableView defined declaratively using a dictionary/plist. | |
// Uses selectors and KVC to keep things flexible. | |
@class YSSettingsTableViewController; | |
//////////////////////////////////////////////////////////////////////////////// | |
// Presents a grouped UITableView defined declaratively using a dictionary/plist. | |
// Uses selectors and KVC to keep things flexible. | |
// | |
// Data/plist must be a dictionary, required items are marked with (*): | |
// - Sections: array | |
// - - Header: string | |
// - - Footer: string | |
// - - Rows: array | |
// - - - (item): dictionary | |
// - - - - Factory: string (*), a prefix of the selector to create the cell, e.g. "list" results in "[self listCellWithReuseIdentifier:id row:rowDict]" being called | |
// - - - - ConfigureSelector: string, a selector to populate the cell with data, e.g. "configureNameCell:" | |
// - - - - Action: string, a selector to run when the cell is selected, e.g. "controllerForEditingNameCell:" (pushes the returned view controller if any) | |
// - - - - textLabel.text: string, the text to use for the cell | |
// - - - - ...any key that starts with a lowercase letter is set using [cell setValue:value forKeyPath:key] | |
// - - - - ...any key that starts with an uppercase letter is ignored and can be used by cell factory/configure methods | |
// | |
// Predefined values for "Factory" are "switch" and "list". | |
// Define your own factory methods (myCellWithReuseIdentifier:row:) to add other cell types ("my"). | |
// | |
// "List" additionally interprets "Items" key as an array to pass to the constructor of YSListCell. | |
// See the properties of cell classes for possible KVC keys. | |
// If a cell class responds to "detailController" message, it will be called when the cell is selected if no action is defined in the plist. | |
// | |
// Note that cell classes in this file will use this view controller as their default model; define "-(id)model" method to override. | |
@interface YSSettingsTableViewController : UITableViewController { | |
NSString *_plistName; | |
NSDictionary *_data; | |
NSDictionary *_currentStateData; | |
NSDictionary *_previousVisibility; | |
} | |
// Use this to create the controller programmatically. Can also use initWithNibName:bundle: | |
// (or set nibName via Inteface Builder) to load data from a plist named after the nib file. | |
- (id)initWithDictionary:(NSDictionary *)data; | |
- (id)initWithPlistName:(NSString *)name; | |
@property(nonatomic, copy) NSString *plistName; | |
@end | |
// informal protocol | |
@interface UITableViewCell (YSSettingsTableViewControllerMagicMethods) | |
@property(nonatomic, readonly, retain) UIViewController *detailController; | |
@property(nonatomic, readonly) BOOL allowSelection; | |
@property(nonatomic, readonly) BOOL allowSelectionWhenEditing; | |
@property(nonatomic, readonly) BOOL allowSelectionNow; | |
@end | |
//////////////////////////////////////////////////////////////////////////////// | |
// helper base class for cells that use KVC/KVO to communicate their value; | |
// when deriving is not handy, just copy the code into your own class | |
@interface YSBindableTableViewCell : UITableViewCell { | |
id _model; | |
} | |
- (void)loadValuesFromModel; | |
- (void)saveValuesToModel; | |
- (void)startObservingModel; | |
- (void)stopObservingModel; | |
// if not set explicitly, defaults to the closest view controller in responder chain | |
@property(nonatomic, retain) id model; | |
@end | |
//////////////////////////////////////////////////////////////////////////////// | |
@interface YSSwitchCell : YSBindableTableViewCell { | |
UISwitch *_switchView; | |
NSString *_valueKeyPath; | |
} | |
- (id)initWithReuseIdentifier:(NSString *)reuseIdentifier; | |
@property(nonatomic,retain,readonly) UISwitch *switchView; | |
@property(nonatomic,copy) NSString *valueKeyPath; | |
@end | |
//////////////////////////////////////////////////////////////////////////////// | |
@interface YSListPickerController : UITableViewController { | |
NSArray *_items; | |
id<YSListPickerControllerDelegate> _delegate; | |
NSInteger _selectedItemIndex; | |
} | |
- (id)initWithItems:(NSArray *)values selectedItemIndex:(NSInteger)selectedItemIndex delegate:(id<YSListPickerControllerDelegate>)delegate; | |
@end | |
@protocol YSListPickerControllerDelegate <NSObject> | |
@required | |
- (void)listPickerController:(YSListPickerController *)controller didPickItem:(id)item atIndex:(NSInteger)index; | |
@end | |
//////////////////////////////////////////////////////////////////////////////// | |
@interface YSFieldEditorController : YSSettingsTableViewController { | |
NSMutableDictionary *_dirtyData; | |
id _editableModel; | |
id<YSFieldEditorControllerDelegate> _delegate; | |
} | |
@property(nonatomic, retain) id<YSFieldEditorControllerDelegate> delegate; | |
@property(nonatomic, retain) id editableModel; | |
@end | |
@protocol YSFieldEditorControllerDelegate <NSObject> | |
@required | |
- (void)fieldEditorControllerDidSave:(YSFieldEditorController *)controller; | |
- (void)fieldEditorControllerDidCancel:(YSFieldEditorController *)controller; | |
@end | |
@interface YSStringFieldEditorController : YSFieldEditorController { | |
} | |
- (id)initWithKeyPath:(NSString *)keyPath placeholder:(NSString *)placeholder; | |
@end | |
//////////////////////////////////////////////////////////////////////////////// | |
@interface YSTextFieldCell : YSBindableTableViewCell <UITextFieldDelegate> { | |
UITextField *_textField; | |
NSString *_valueKeyPath; | |
} | |
- (id)initWithReuseIdentifier:(NSString *)reuseIdentifier; | |
@property(nonatomic,retain,readonly) UITextField *textField; | |
@property(nonatomic,copy) NSString *valueKeyPath; | |
@end | |
//////////////////////////////////////////////////////////////////////////////// | |
@interface YSListCell : YSBindableTableViewCell <YSListPickerControllerDelegate> { | |
NSArray *_items; | |
NSInteger _selectedItemIndex; | |
NSString *_valueKeyPath; | |
NSString *_detailControllerTitle; | |
UIViewController *_controller; | |
BOOL _allowSelection, _allowSelectionWhenEditing; | |
} | |
- (id)initWithItems:(NSArray *)values reuseIdentifier:(NSString *)reuseIdentifier; | |
@property(nonatomic,retain,readonly) NSArray *items; | |
@property(nonatomic) NSInteger selectedItemIndex; | |
@property(nonatomic, retain) id selectedValue; // value provided by Value key, index if none | |
@property(nonatomic,copy) NSString *valueKeyPath; | |
@property(nonatomic) BOOL allowSelection; | |
@property(nonatomic) BOOL allowSelectionWhenEditing; | |
@property(nonatomic,copy) NSString *detailControllerTitle; | |
@end | |
//////////////////////////////////////////////////////////////////////////////// | |
@interface YSHeaderLabelCell : YSBindableTableViewCell { | |
UIView *_defaultBackgroundView; | |
UIView *_defaultSelectedBackgroundView; | |
NSString *_valueKeyPath; | |
NSString *_placeholder; | |
NSString *_detailControllerTitle; | |
NSArray *_detailControllerRows; | |
} | |
- (id)initWithReuseIdentifier:(NSString *)reuseIdentifier; | |
@property(nonatomic,copy) NSString *placeholder; | |
@property(nonatomic,copy) NSString *valueKeyPath; | |
@property(nonatomic,copy) NSString *detailControllerTitle; | |
@property(nonatomic,copy) NSArray *detailControllerRows; | |
@end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// YSTableViewCells.m | |
// | |
// Copyright (c) 2009, Andrey Tarantsov <andreyvit@gmail.com> | |
// | |
// Permission to use, copy, modify, and/or distribute this software for any | |
// purpose with or without fee is hereby granted, provided that the above | |
// copyright notice and this permission notice appear in all copies. | |
// | |
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | |
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | |
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | |
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | |
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | |
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | |
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |
#import "YSTableViewCells.h" | |
@implementation YSBindableTableViewCell | |
@synthesize model=_model; | |
- (void)loadValuesFromModel { | |
} | |
- (void)saveValuesToModel { | |
} | |
- (void)startObservingModel { | |
} | |
- (void)stopObservingModel { | |
} | |
- (void)dealloc { | |
[self stopObservingModel]; | |
[_model release], _model = nil; | |
[super dealloc]; | |
} | |
- (void)setModel:(id)newModel { | |
if (newModel == _model) return; | |
[self willChangeValueForKey:@"model"]; | |
if (_model != nil) | |
[self stopObservingModel]; | |
[_model autorelease]; // can't do release, e.g. if _model.someRetainedProp == newModel | |
_model = [newModel retain]; | |
if (_model != nil) { | |
[self loadValuesFromModel]; | |
[self startObservingModel]; | |
} | |
[self didChangeValueForKey:@"model"]; | |
} | |
- (void)didMoveToWindow { | |
if (self.window == nil || self.model != nil) | |
return; | |
UIViewController *controller = nil; | |
for (UIResponder *view = self.superview; view != nil; view = [view nextResponder]) { | |
if ([view isKindOfClass:[UIViewController class]]) { | |
controller = (UIViewController *)view; | |
break; | |
} | |
} | |
if (controller == nil) | |
self.model = nil; // no view controller at all, unlikely but possible | |
else if ([controller respondsToSelector:@selector(model)]) | |
self.model = [controller performSelector:@selector(model)]; | |
else | |
self.model = controller; | |
} | |
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { | |
[self loadValuesFromModel]; | |
} | |
@end | |
#pragma mark | |
#pragma mark - | |
#pragma mark | |
@implementation YSSwitchCell | |
@synthesize switchView=_switchView, valueKeyPath=_valueKeyPath; | |
- (id)initWithReuseIdentifier:(NSString *)reuseIdentifier { | |
if (self = [super initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:reuseIdentifier]) { | |
_switchView = [[UISwitch alloc] initWithFrame:CGRectZero]; | |
[_switchView addTarget:self action:@selector(switchValueDidChange) forControlEvents:UIControlEventValueChanged]; | |
[self.contentView addSubview:_switchView]; | |
} | |
return self; | |
} | |
- (void)dealloc { | |
[_switchView release], _switchView = nil; | |
[super dealloc]; | |
} | |
- (void)layoutSubviews { | |
[super layoutSubviews]; | |
[_switchView sizeToFit]; | |
CGSize size = _switchView.frame.size; | |
_switchView.frame = CGRectMake(self.contentView.frame.size.width - size.width - 10, | |
(self.contentView.frame.size.height - size.height) / 2, | |
size.width, | |
size.height); | |
} | |
- (void)loadValuesFromModel { | |
if (_valueKeyPath) | |
self.switchView.on = [[_model valueForKeyPath:_valueKeyPath] boolValue]; | |
} | |
- (void)saveValuesToModel { | |
if (_valueKeyPath) | |
[_model setValue:[NSNumber numberWithBool:self.switchView.on] forKeyPath:_valueKeyPath]; | |
} | |
- (void)startObservingModel { | |
if (_valueKeyPath) | |
[_model addObserver:self forKeyPath:_valueKeyPath options:0 context:nil]; | |
} | |
- (void)stopObservingModel { | |
if (_valueKeyPath) | |
[_model removeObserver:self forKeyPath:_valueKeyPath]; | |
} | |
- (void)switchValueDidChange { | |
[self saveValuesToModel]; | |
} | |
@end | |
@implementation YSListCell | |
@synthesize items=_items, selectedItemIndex=_selectedItemIndex, valueKeyPath=_valueKeyPath; | |
@synthesize detailControllerTitle=_detailControllerTitle; | |
@synthesize allowSelection=_allowSelection, allowSelectionWhenEditing=_allowSelectionWhenEditing; | |
- (id)initWithItems:(NSArray *)values reuseIdentifier:(NSString *)reuseIdentifier { | |
if (self = [super initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:reuseIdentifier]) { | |
_items = [[NSArray alloc] initWithArray:values]; | |
[self setSelectedItemIndex:0]; | |
self.accessoryType = UITableViewCellAccessoryDisclosureIndicator; | |
self.allowSelection = YES; | |
} | |
return self; | |
} | |
- (void)dealloc { | |
[_items release], _items = nil; | |
[super dealloc]; | |
} | |
- (NSString *)cellTextForItemAtIndex:(NSInteger)index { | |
id value = [_items objectAtIndex:index]; | |
NSString *result; | |
if ([value respondsToSelector:@selector(objectForKey:)]) { | |
result = [value objectForKey:@"CellText"]; | |
if (result == nil) | |
result = [value objectForKey:@"Text"]; | |
} else { | |
result = value; | |
} | |
return result; | |
} | |
- (void)setSelectedItemIndex:(NSInteger)index { | |
NSParameterAssert(index >= 0); | |
NSParameterAssert(index < [_items count]); | |
_selectedItemIndex = index; | |
self.detailTextLabel.text = [self cellTextForItemAtIndex:index]; | |
} | |
- (id)selectedValue { | |
id value = [_items objectAtIndex:_selectedItemIndex]; | |
if ([value respondsToSelector:@selector(objectForKey:)]) { | |
id result = [value objectForKey:@"Value"]; | |
if (result != nil) | |
return result; | |
} | |
return [NSNumber numberWithInteger:_selectedItemIndex]; | |
} | |
- (void)setSelectedValue:(id)value { | |
int index = 0; | |
for (id item in _items) { | |
if ([item respondsToSelector:@selector(objectForKey:)]) { | |
id itemValue = [item objectForKey:@"Value"]; | |
if (itemValue != nil && [itemValue isEqual:value]) { | |
[self setSelectedItemIndex:index]; | |
return; | |
} | |
} | |
++index; | |
} | |
index = [value integerValue]; | |
[self setSelectedItemIndex:index]; | |
} | |
- (BOOL)allowSelection { | |
return YES; | |
} | |
- (UIViewController *)detailController { | |
YSListPickerController *c = [[[YSListPickerController alloc] initWithItems:_items selectedItemIndex:_selectedItemIndex delegate:self] autorelease]; | |
if (self.detailControllerTitle != nil) | |
c.navigationItem.title = self.detailControllerTitle; | |
else | |
c.navigationItem.title = self.textLabel.text; | |
return c; | |
} | |
- (void)loadValuesFromModel { | |
if (_valueKeyPath) | |
self.selectedValue = [_model valueForKeyPath:_valueKeyPath]; | |
} | |
- (void)saveValuesToModel { | |
if (_valueKeyPath) | |
[_model setValue:self.selectedValue forKeyPath:_valueKeyPath]; | |
} | |
- (void)startObservingModel { | |
if (_valueKeyPath) | |
[_model addObserver:self forKeyPath:_valueKeyPath options:0 context:nil]; | |
} | |
- (void)stopObservingModel { | |
if (_valueKeyPath) | |
[_model removeObserver:self forKeyPath:_valueKeyPath]; | |
} | |
- (void)listPickerController:(YSListPickerController *)controller didPickItem:(id)item atIndex:(NSInteger)index { | |
[self setSelectedItemIndex:index]; | |
[self saveValuesToModel]; | |
} | |
@end | |
@implementation YSTextFieldCell | |
@synthesize textField=_textField, valueKeyPath=_valueKeyPath; | |
- (id)initWithReuseIdentifier:(NSString *)reuseIdentifier { | |
if (self = [super initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:reuseIdentifier]) { | |
_textField = [[UITextField alloc] initWithFrame:CGRectZero]; | |
_textField.delegate = self; | |
[self.contentView addSubview:_textField]; | |
} | |
return self; | |
} | |
- (void)dealloc { | |
[_textField release], _textField = nil; | |
[super dealloc]; | |
} | |
- (void)layoutSubviews { | |
[super layoutSubviews]; | |
[_textField sizeToFit]; | |
CGSize size = _textField.frame.size; | |
_textField.frame = CGRectMake(10, | |
(self.contentView.frame.size.height - size.height) / 2, | |
self.contentView.frame.size.width - 20, | |
size.height); | |
} | |
- (void)loadValuesFromModel { | |
if (_valueKeyPath) | |
self.textField.text = [_model valueForKeyPath:_valueKeyPath]; | |
} | |
- (void)saveValuesToModel { | |
if (_valueKeyPath) | |
[_model setValue:self.textField.text forKeyPath:_valueKeyPath]; | |
} | |
- (void)startObservingModel { | |
if (_valueKeyPath) | |
[_model addObserver:self forKeyPath:_valueKeyPath options:0 context:nil]; | |
} | |
- (void)stopObservingModel { | |
if (_valueKeyPath) | |
[_model removeObserver:self forKeyPath:_valueKeyPath]; | |
} | |
- (void)textFieldDidEndEditing:(UITextField *)textField { | |
[self saveValuesToModel]; | |
} | |
@end | |
@implementation YSHeaderLabelCell | |
@synthesize valueKeyPath=_valueKeyPath; | |
@synthesize placeholder=_placeholder; | |
@synthesize detailControllerTitle=_detailControllerTitle, detailControllerRows=_detailControllerRows; | |
- (id)initWithReuseIdentifier:(NSString *)reuseIdentifier { | |
if (self = [super initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:reuseIdentifier]) { | |
self.textLabel.backgroundColor = [UIColor clearColor]; | |
self.editingAccessoryType = UITableViewCellAccessoryDisclosureIndicator; | |
self.placeholder = @""; | |
self.detailControllerTitle = @"Edit"; | |
} | |
return self; | |
} | |
- (void)dealloc { | |
[_defaultBackgroundView release], _defaultBackgroundView = nil; | |
[_defaultSelectedBackgroundView release], _defaultSelectedBackgroundView = nil; | |
[_placeholder release], _placeholder = nil; | |
[_detailControllerTitle release], _detailControllerTitle = nil; | |
[_detailControllerRows release], _detailControllerRows = nil; | |
[super dealloc]; | |
} | |
- (void)updateBackground { | |
if (self.editing) { | |
self.backgroundView = _defaultBackgroundView; | |
self.selectedBackgroundView = _defaultSelectedBackgroundView; | |
} else { | |
self.backgroundView = nil; | |
self.selectedBackgroundView = nil; | |
} | |
[self loadValuesFromModel]; | |
} | |
- (void)setEditing:(BOOL)editing animated:(BOOL)animated { | |
[super setEditing:editing animated:NO]; | |
if (_defaultBackgroundView != nil) | |
[self updateBackground]; | |
} | |
- (void)layoutSubviews { | |
if (_defaultBackgroundView == nil) { | |
_defaultBackgroundView = [self.backgroundView retain]; | |
_defaultSelectedBackgroundView = [self.selectedBackgroundView retain]; | |
[self updateBackground]; | |
} | |
[super layoutSubviews]; | |
} | |
- (void)loadValuesFromModel { | |
if (_valueKeyPath) { | |
NSString *value = [_model valueForKeyPath:_valueKeyPath]; | |
if ([value length] == 0 && self.editing) { | |
self.textLabel.text = self.placeholder; | |
self.textLabel.textColor = [UIColor lightGrayColor]; | |
} else { | |
self.textLabel.text = value; | |
self.textLabel.textColor = [UIColor blackColor]; | |
} | |
} | |
} | |
- (void)startObservingModel { | |
if (_valueKeyPath) | |
[_model addObserver:self forKeyPath:_valueKeyPath options:0 context:nil]; | |
} | |
- (void)stopObservingModel { | |
if (_valueKeyPath) | |
[_model removeObserver:self forKeyPath:_valueKeyPath]; | |
} | |
- (BOOL)allowRowSelection { | |
return self.editing; | |
} | |
- (BOOL)allowSelectionWhenEditing { | |
return YES; | |
} | |
- (UIViewController *)detailController { | |
NSDictionary *section = [NSDictionary dictionaryWithObjectsAndKeys:_detailControllerRows, @"Rows", nil]; | |
NSArray *sections = [NSArray arrayWithObject:section]; | |
NSDictionary *data = [NSDictionary dictionaryWithObjectsAndKeys:sections, @"Sections", nil]; | |
YSFieldEditorController *c = [[[YSFieldEditorController alloc] initWithDictionary:data] autorelease]; | |
c.editableModel = _model; | |
c.navigationItem.title = _detailControllerTitle; | |
return c; | |
} | |
@end | |
#pragma mark | |
#pragma mark - | |
#pragma mark | |
@implementation YSListPickerController | |
- (id)initWithItems:(NSArray *)items selectedItemIndex:(NSInteger)selectedItemIndex delegate:(id<YSListPickerControllerDelegate>)delegate { | |
if (self = [super initWithNibName:nil bundle:nil]) { | |
_items = [[NSArray alloc] initWithArray:items]; | |
_selectedItemIndex = selectedItemIndex; | |
_delegate = [delegate retain]; | |
} | |
return self; | |
} | |
- (void)dealloc { | |
[_items release], _items = nil; | |
[_delegate release], _delegate = nil; | |
[super dealloc]; | |
} | |
- (NSInteger)tableView:(UITableView *)table numberOfRowsInSection:(NSInteger)section { | |
return [_items count]; | |
} | |
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { | |
id item = [_items objectAtIndex:indexPath.row]; | |
if (![item respondsToSelector:@selector(objectForKey:)]) | |
item = [NSDictionary dictionaryWithObject:item forKey:@"Text"]; | |
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Value1"]; | |
if (cell == nil) { | |
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:@"Value1"] autorelease]; | |
} | |
cell.accessoryType = (indexPath.row == _selectedItemIndex ? UITableViewCellAccessoryCheckmark : UITableViewCellAccessoryNone); | |
cell.textLabel.text = [item objectForKey:@"Text"]; | |
return cell; | |
} | |
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { | |
if (indexPath.row != _selectedItemIndex) { | |
[tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:_selectedItemIndex inSection:0]].accessoryType = UITableViewCellAccessoryNone; | |
_selectedItemIndex = indexPath.row; | |
[tableView cellForRowAtIndexPath:indexPath].accessoryType = UITableViewCellAccessoryCheckmark; | |
[_delegate listPickerController:self didPickItem:[_items objectAtIndex:_selectedItemIndex] atIndex:_selectedItemIndex]; | |
} | |
[tableView deselectRowAtIndexPath:indexPath animated:YES]; | |
} | |
@end | |
@implementation YSFieldEditorController | |
@synthesize editableModel=_editableModel, delegate=_delegate; | |
- (void)dealloc { | |
[_editableModel release], _editableModel = nil; | |
[_delegate release], _delegate = nil; | |
[super dealloc]; | |
} | |
- (void)viewDidLoad { | |
[super viewDidLoad]; | |
self.navigationItem.leftBarButtonItem = [[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:@selector(cancelTouched)] autorelease]; | |
self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemSave target:self action:@selector(saveTouched)] autorelease]; | |
if (_dirtyData == nil) { | |
_dirtyData = [[NSMutableDictionary alloc] init]; | |
} | |
} | |
- (id)valueForKeyPath:(NSString *)keyPath { | |
id result = [_dirtyData objectForKey:keyPath]; | |
if (result != nil) | |
return (result == [NSNull null] ? nil : result); | |
result = [_editableModel valueForKeyPath:keyPath]; | |
[_dirtyData setObject:(result == nil ? [NSNull null] : result) forKey:keyPath]; | |
return result; | |
} | |
- (void)setValue:(id)value forKeyPath:(NSString *)keyPath { | |
[_dirtyData setObject:(value == nil ? [NSNull null] : value) forKey:keyPath]; | |
} | |
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context { | |
} | |
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath { | |
} | |
- (void)updateModel { | |
for (UITableViewCell *cell in [self.tableView visibleCells]) | |
if ([cell respondsToSelector:@selector(saveValuesToModel)]) | |
[cell performSelector:@selector(saveValuesToModel)]; | |
for (NSString *keyPath in _dirtyData) { | |
id value = [_dirtyData objectForKey:keyPath]; | |
if (value == [NSNull null]) value = nil; | |
[_editableModel setValue:value forKeyPath:keyPath]; | |
} | |
} | |
- (void)dismiss { | |
if (self.navigationController.parentViewController.modalViewController == self.navigationController) | |
[self.navigationController.parentViewController dismissModalViewControllerAnimated:YES]; | |
else if (self.navigationController.visibleViewController == self && [self.navigationController.viewControllers count] > 0) | |
[self.navigationController popViewControllerAnimated:YES]; | |
} | |
- (void)cancelTouched { | |
[self dismiss]; | |
[_delegate fieldEditorControllerDidCancel:self]; | |
} | |
- (void)saveTouched { | |
[self updateModel]; | |
[self dismiss]; | |
[_delegate fieldEditorControllerDidSave:self]; | |
} | |
@end | |
@implementation YSStringFieldEditorController | |
- (id)initWithKeyPath:(NSString *)keyPath placeholder:(NSString *)placeholder { | |
NSDictionary *row = [NSDictionary dictionaryWithObjectsAndKeys:keyPath, @"keyPath", placeholder, @"placeholder", nil]; | |
NSArray *rows = [NSArray arrayWithObject:row]; | |
NSDictionary *section = [NSDictionary dictionaryWithObjectsAndKeys:rows, @"Rows", nil]; | |
NSArray *sections = [NSArray arrayWithObject:section]; | |
NSDictionary *data = [NSDictionary dictionaryWithObjectsAndKeys:sections, @"Sections", nil]; | |
if (self = [super initWithDictionary:data]) { | |
} | |
return self; | |
} | |
@end | |
#pragma mark | |
#pragma mark - | |
#pragma mark | |
static UITableViewCellAccessoryType accessoryTypeForNSString(NSString *s) { | |
if ([s isEqualToString:@"disclosure-indicator"]) | |
return UITableViewCellAccessoryDisclosureIndicator; | |
if ([s isEqualToString:@"detail-disclosure-button"]) | |
return UITableViewCellAccessoryDetailDisclosureButton; | |
if ([s isEqualToString:@"none"]) | |
return UITableViewCellAccessoryNone; | |
if ([s isEqualToString:@"checkmark"]) | |
return UITableViewCellAccessoryCheckmark; | |
NSCAssert1(NO, @"Unknown accessory type: '%@'", s); | |
return UITableViewCellAccessoryNone; | |
} | |
@implementation YSSettingsTableViewController | |
@synthesize plistName=_plistName; | |
- (id)initWithPlistName:(NSString *)name { | |
if (self = [super initWithStyle:UITableViewStyleGrouped]) { | |
self.plistName = name; | |
} | |
return self; | |
} | |
- (id)initWithDictionary:(NSDictionary *)data { | |
if (self = [super initWithStyle:UITableViewStyleGrouped]) { | |
_data = [data copy]; | |
} | |
return self; | |
} | |
- (void)dealloc { | |
[_currentStateData release], _currentStateData = nil; | |
[_previousVisibility release], _previousVisibility = nil; | |
[_data release], _data = nil; | |
[_plistName release], _plistName = nil; | |
[super dealloc]; | |
} | |
- (void)refilterData { | |
NSDictionary *oldData = _data; | |
NSMutableDictionary *newData = [NSMutableDictionary dictionaryWithDictionary:oldData]; | |
NSDictionary *oldVisibility = _previousVisibility; | |
NSMutableDictionary *newVisibility = [NSMutableDictionary dictionary]; | |
NSMutableArray *insertedRows = [NSMutableArray array]; | |
NSMutableArray *deletedRows = [NSMutableArray array]; | |
NSMutableIndexSet *insertedSections = [NSMutableIndexSet indexSet]; | |
NSMutableIndexSet *deletedSections = [NSMutableIndexSet indexSet]; | |
NSArray *oldSections = [newData objectForKey:@"Sections"]; | |
NSMutableArray *newSections = [NSMutableArray arrayWithCapacity:[oldSections count]]; | |
NSInteger section = 0, row; | |
for (NSDictionary *oldSection in oldSections) { | |
NSMutableDictionary *newSection = [NSMutableDictionary dictionaryWithDictionary:oldSection]; | |
NSArray *oldRows = [newSection objectForKey:@"Rows"]; | |
NSMutableArray *newRows = [NSMutableArray arrayWithCapacity:[oldRows count]]; | |
row = 0; | |
BOOL wereAnyRowsVisible = NO; | |
NSMutableArray *insertedRowsInSection = [NSMutableArray array]; | |
NSMutableArray *deletedRowsInSection = [NSMutableArray array]; | |
for (NSDictionary *oldRow in oldRows) { | |
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:section]; | |
NSString *predicateString = [oldRow objectForKey:@"Predicate"]; | |
BOOL rowVisible = YES; | |
if (predicateString != nil) { | |
NSPredicate *predicate = [NSPredicate predicateWithFormat:predicateString]; | |
rowVisible = [predicate evaluateWithObject:self]; | |
} | |
// NSDictionary *newRow = [NSMutableDictionary dictionaryWithDictionary:oldRow]; | |
// [newRows addObject:[NSDictionary dictionaryWithDictionary:newRow]]; | |
if (oldVisibility) { | |
BOOL wasVisible = [[oldVisibility objectForKey:indexPath] boolValue]; | |
if (wasVisible != rowVisible) | |
if (rowVisible) | |
[insertedRowsInSection addObject:indexPath]; | |
else | |
[deletedRowsInSection addObject:indexPath]; | |
wereAnyRowsVisible |= wasVisible; | |
} | |
if (rowVisible) | |
[newRows addObject:oldRow]; | |
[newVisibility setObject:[NSNumber numberWithBool:rowVisible] forKey:indexPath]; | |
++row; | |
} | |
[newSection setObject:[NSArray arrayWithArray:newRows] forKey:@"Rows"]; | |
BOOL areAnyRowsVisible = [newRows count] > 0; | |
if (oldVisibility) { | |
if (areAnyRowsVisible != wereAnyRowsVisible) { | |
if (areAnyRowsVisible) | |
[insertedSections addIndex:section]; | |
else | |
[deletedSections addIndex:section]; | |
} else { | |
[insertedRows addObjectsFromArray:insertedRowsInSection]; | |
[deletedRows addObjectsFromArray:deletedRowsInSection]; | |
} | |
} | |
if (areAnyRowsVisible) | |
[newSections addObject:[NSDictionary dictionaryWithDictionary:newSection]]; | |
++section; | |
} | |
[newData setObject:[NSArray arrayWithArray:newSections] forKey:@"Sections"]; | |
[_currentStateData release], _currentStateData = nil; | |
_currentStateData = [[NSDictionary alloc] initWithDictionary:newData]; | |
[_previousVisibility release], _previousVisibility = nil; | |
_previousVisibility = [[NSDictionary alloc] initWithDictionary:newVisibility]; | |
if (oldVisibility) { | |
[self.tableView beginUpdates]; | |
[self.tableView insertSections:insertedSections withRowAnimation:UITableViewRowAnimationBottom]; | |
[self.tableView deleteSections:deletedSections withRowAnimation:UITableViewRowAnimationBottom]; | |
[self.tableView insertRowsAtIndexPaths:insertedRows withRowAnimation:UITableViewRowAnimationBottom]; | |
[self.tableView deleteRowsAtIndexPaths:deletedRows withRowAnimation:UITableViewRowAnimationBottom]; | |
[self.tableView endUpdates]; | |
} | |
} | |
- (void)viewDidLoad { | |
if (_data == nil) { | |
if (self.plistName == nil) | |
self.plistName = self.nibName; | |
if (self.plistName == nil) | |
self.plistName = NSStringFromClass([self class]); | |
NSBundle *bundle = self.nibBundle; | |
if (bundle == nil) bundle = [NSBundle mainBundle]; | |
NSString *path = [bundle pathForResource:self.plistName ofType:@"plist"]; | |
_data = [[NSDictionary alloc] initWithContentsOfFile:path]; | |
NSAssert(_data != nil, @"Failed to load .plist for a settings table"); | |
} | |
self.tableView.allowsSelectionDuringEditing = YES; | |
[self refilterData]; | |
[self.tableView reloadData]; | |
UITableViewCell *firstCell = [self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]]; | |
for (UIView *subview in firstCell.contentView.subviews) { | |
if ([subview canBecomeFirstResponder]) { | |
[subview becomeFirstResponder]; | |
break; | |
} | |
} | |
} | |
- (void)viewDidUnload { | |
[_currentStateData release], _currentStateData = nil; | |
[_previousVisibility release], _previousVisibility = nil; | |
} | |
- (void)setEditing:(BOOL)editing animated:(BOOL)animated { | |
[super setEditing:editing animated:animated]; | |
[self refilterData]; | |
} | |
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { | |
NSArray *sections = [_currentStateData objectForKey:@"Sections"]; | |
return [sections count]; | |
} | |
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { | |
NSArray *sections = [_currentStateData objectForKey:@"Sections"]; | |
NSDictionary *sectionInfo = [sections objectAtIndex:section]; | |
NSArray *rows = [sectionInfo objectForKey:@"Rows"]; | |
return [rows count]; | |
} | |
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { | |
NSArray *sections = [_currentStateData objectForKey:@"Sections"]; | |
NSDictionary *sectionInfo = [sections objectAtIndex:section]; | |
return [sectionInfo objectForKey:@"Header"]; | |
} | |
- (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section { | |
NSArray *sections = [_currentStateData objectForKey:@"Sections"]; | |
NSDictionary *sectionInfo = [sections objectAtIndex:section]; | |
return [sectionInfo objectForKey:@"Footer"]; | |
} | |
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { | |
NSArray *sections = [_currentStateData objectForKey:@"Sections"]; | |
NSDictionary *sectionInfo = [sections objectAtIndex:indexPath.section]; | |
NSArray *rows = [sectionInfo objectForKey:@"Rows"]; | |
NSDictionary *row = [rows objectAtIndex:indexPath.row]; | |
NSNumber *h = [row objectForKey:@"Height"]; | |
if (h != nil) | |
return [h floatValue]; | |
NSString *factory = [row objectForKey:@"Factory"]; | |
SEL selector = NSSelectorFromString([factory stringByAppendingString:@"CellHeightForRow:"]); | |
if ([self respondsToSelector:selector]) { | |
CGFloat height; | |
NSInvocation *inv = [NSInvocation invocationWithMethodSignature:[[self class] instanceMethodSignatureForSelector:selector]]; | |
[inv setSelector:selector]; | |
[inv setTarget:self]; | |
[inv invoke]; | |
[inv getReturnValue:&height]; | |
return height; | |
} | |
return 44; | |
} | |
- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath { | |
return UITableViewCellEditingStyleNone; | |
} | |
- (BOOL)tableView:(UITableView *)tableView shouldIndentWhileEditingRowAtIndexPath:(NSIndexPath *)indexPath { | |
return NO; | |
} | |
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { | |
NSArray *sections = [_currentStateData objectForKey:@"Sections"]; | |
NSDictionary *sectionInfo = [sections objectAtIndex:indexPath.section]; | |
NSArray *rows = [sectionInfo objectForKey:@"Rows"]; | |
NSDictionary *row = [rows objectAtIndex:indexPath.row]; | |
NSString *factory = [row objectForKey:@"Factory"]; | |
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:factory]; | |
if (cell == nil) { | |
NSString *selectorName = [factory stringByAppendingString:@"CellWithReuseIdentifier:row:"]; | |
cell = [self performSelector:NSSelectorFromString(selectorName) withObject:factory withObject:row]; | |
} | |
NSString *accessory = [row objectForKey:@"Accessory"]; | |
if ([accessory length] > 0) | |
cell.accessoryType = accessoryTypeForNSString(accessory); | |
accessory = [row objectForKey:@"EditAccessory"]; | |
if ([accessory length] > 0) | |
cell.editingAccessoryType = accessoryTypeForNSString(accessory); | |
NSString *configureMethod = [row objectForKey:@"ConfigureSelector"]; | |
if (configureMethod != nil) | |
[self performSelector:NSSelectorFromString(configureMethod) withObject:cell]; | |
for (NSString *key in row) | |
if (tolower([key characterAtIndex:0]) == [key characterAtIndex:0]) | |
[cell setValue:[row objectForKey:key] forKeyPath:key]; | |
return cell; | |
} | |
- (UITableViewCell *)switchCellWithReuseIdentifier:(NSString *)cellId row:(NSDictionary *)row { | |
return [[[YSSwitchCell alloc] initWithReuseIdentifier:cellId] autorelease]; | |
} | |
- (UITableViewCell *)textFieldCellWithReuseIdentifier:(NSString *)cellId row:(NSDictionary *)row { | |
return [[[YSTextFieldCell alloc] initWithReuseIdentifier:cellId] autorelease]; | |
} | |
- (UITableViewCell *)listCellWithReuseIdentifier:(NSString *)cellId row:(NSDictionary *)row { | |
return [[[YSListCell alloc] initWithItems:[row objectForKey:@"Items"] reuseIdentifier:cellId] autorelease]; | |
} | |
- (UITableViewCell *)headerLabelCellWithReuseIdentifier:(NSString *)cellId row:(NSDictionary *)row { | |
return [[[YSHeaderLabelCell alloc] initWithReuseIdentifier:cellId] autorelease]; | |
} | |
- (UITableViewCell *)staticCellWithReuseIdentifier:(NSString *)cellId row:(NSDictionary *)row { | |
return [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellId] autorelease]; | |
} | |
- (CGFloat)headerLabelCellHeightForRow:(NSDictionary *)row { | |
return 63; | |
} | |
- (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath { | |
NSArray *sections = [_currentStateData objectForKey:@"Sections"]; | |
NSDictionary *sectionInfo = [sections objectAtIndex:indexPath.section]; | |
NSArray *rows = [sectionInfo objectForKey:@"Rows"]; | |
NSDictionary *row = [rows objectAtIndex:indexPath.row]; | |
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath]; | |
NSString *action = [row objectForKey:(cell.editing ? @"EditAction" : @"Action")]; | |
if (action != nil || cell.allowSelectionNow) | |
return indexPath; | |
return nil; | |
} | |
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { | |
NSArray *sections = [_currentStateData objectForKey:@"Sections"]; | |
NSDictionary *sectionInfo = [sections objectAtIndex:indexPath.section]; | |
NSArray *rows = [sectionInfo objectForKey:@"Rows"]; | |
NSDictionary *row = [rows objectAtIndex:indexPath.row]; | |
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath]; | |
NSString *action = [row objectForKey:(cell.editing ? @"EditAction" : @"Action")]; | |
UIViewController *detailController = nil; | |
if (action != nil) | |
detailController = [self performSelector:NSSelectorFromString(action) withObject:cell]; | |
if (detailController == nil && cell.allowSelectionNow) | |
detailController = cell.detailController; | |
if (detailController != nil) | |
[self.navigationController pushViewController:detailController animated:YES]; | |
else if (self == self.navigationController.topViewController) | |
[tableView deselectRowAtIndexPath:indexPath animated:YES]; | |
} | |
@end | |
@implementation UITableViewCell (YSSettingsTableViewControllerMagicMethods) | |
- (UIViewController *)detailController { | |
return nil; | |
} | |
- (BOOL)allowSelectionNow { | |
return (self.editing ? self.allowSelectionWhenEditing : self.allowSelection); | |
} | |
- (BOOL)allowSelection { | |
return NO; | |
} | |
- (BOOL)allowSelectionWhenEditing { | |
return NO; | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment