// // IASKAppSettingsViewController.m // http://www.inappsettingskit.com // // Copyright (c) 2009-2010: // Luc Vandal, Edovia Inc., http://www.edovia.com // Ortwin Gentz, FutureTap GmbH, http://www.futuretap.com // All rights reserved. // // It is appreciated but not required that you give credit to Luc Vandal and Ortwin Gentz, // as the original authors of this code. You can give credit in a blog post, a tweet or on // a info page of your app. Also, the original authors appreciate letting them know if you use this code. // // This code is licensed under the BSD license that is available at: http://www.opensource.org/licenses/bsd-license.php // #import "IASKAppSettingsViewController.h" #import "IASKSettingsReader.h" #import "IASKSettingsStoreUserDefaults.h" #import "IASKPSSliderSpecifierViewCell.h" #import "IASKPSTextFieldSpecifierViewCell.h" #import "IASKSwitch.h" #import "IASKSlider.h" #import "IASKSpecifier.h" #import "IASKSpecifierValuesViewController.h" #import "IASKTextField.h" #import "IASKTextViewCell.h" #import "IASKMultipleValueSelection.h" #if !__has_feature(objc_arc) #error "IASK needs ARC" #endif static NSString *kIASKCredits = @"Powered by InAppSettingsKit"; // Leave this as-is!!! #define kIASKSpecifierValuesViewControllerIndex 0 #define kIASKSpecifierChildViewControllerIndex 1 #define kIASKCreditsViewWidth 285 CGRect IASKCGRectSwap(CGRect rect); @interface IASKAppSettingsViewController () { IASKSettingsReader *_settingsReader; id _settingsStore; id _currentFirstResponder; __weak UIViewController *_currentChildViewController; BOOL _reloadDisabled; /// The selected index for every group (in case it's a radio group). NSArray *_selections; } @property (nonatomic, strong) id currentFirstResponder; @property (nonatomic, strong) NSMutableDictionary *rowHeights; - (void)_textChanged:(id)sender; - (void)synchronizeSettings; - (void)userDefaultsDidChange; - (void)reload; @end @implementation IASKAppSettingsViewController //synthesize properties from protocol @synthesize settingsReader = _settingsReader; @synthesize settingsStore = _settingsStore; @synthesize file = _file; #pragma mark accessors - (IASKSettingsReader*)settingsReader { if (!_settingsReader) { _settingsReader = [[IASKSettingsReader alloc] initWithFile:self.file]; _settingsReader.delegate = self.delegate; // DJS if (self.neverShowPrivacySettings) { _settingsReader.showPrivacySettings = NO; } } return _settingsReader; } - (id)settingsStore { if (!_settingsStore) { _settingsStore = [[IASKSettingsStoreUserDefaults alloc] init]; } return _settingsStore; } - (NSString*)file { if (!_file) { self.file = @"Root"; } return _file; } - (void)setFile:(NSString *)file { _file = [file copy]; self.tableView.contentOffset = CGPointMake(0, -self.tableView.contentInset.top); self.settingsReader = nil; // automatically initializes itself if (!_reloadDisabled) { [self.tableView reloadData]; [self createSelections]; } } - (void)createSelections { NSMutableArray *sectionSelection = [NSMutableArray new]; for (int i = 0; i < _settingsReader.numberOfSections; i++) { IASKSpecifier *specifier = [self.settingsReader headerSpecifierForSection:i]; if ([specifier.type isEqualToString:kIASKPSRadioGroupSpecifier]) { IASKMultipleValueSelection *selection = [[IASKMultipleValueSelection alloc] initWithSettingsStore:self.settingsStore]; selection.tableView = self.tableView; selection.specifier = specifier; selection.section = i; [sectionSelection addObject:selection]; } else { [sectionSelection addObject:[NSNull null]]; } } _selections = sectionSelection; } - (BOOL)isPad { BOOL isPad = NO; #if (__IPHONE_OS_VERSION_MAX_ALLOWED >= 30200) isPad = [[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad; #endif return isPad; } #pragma mark standard view controller methods - (id)init { return [self initWithStyle:UITableViewStyleGrouped]; } - (id)initWithStyle:(UITableViewStyle)style { if (style != UITableViewStyleGrouped) { NSLog(@"WARNING: only UITableViewStyleGrouped style is supported by InAppSettingsKit."); } if ((self = [super initWithStyle:style])) { [self configure]; } return self; } - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { if (nibNameOrNil) { NSLog (@"%@ is now deprecated, we are moving away from nibs.", NSStringFromSelector(_cmd)); self = [super initWithStyle:UITableViewStyleGrouped]; } else { self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; } if (self) { [self configure]; } return self; } - (id)initWithCoder:(NSCoder *)aDecoder { if ((self = [super initWithCoder:aDecoder])) { [self configure]; _showDoneButton = NO; } return self; } - (void)configure { _reloadDisabled = NO; _showDoneButton = YES; _showCreditsFooter = YES; // display credits for InAppSettingsKit creators self.rowHeights = [NSMutableDictionary dictionary]; } - (void)viewDidLoad { [super viewDidLoad]; if ([self isPad]) { self.tableView.separatorStyle = UITableViewCellSeparatorStyleSingleLine; } UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(singleTapToEndEdit:)]; tapGesture.cancelsTouchesInView = NO; [self.tableView addGestureRecognizer:tapGesture]; } - (void)viewWillAppear:(BOOL)animated { NSIndexPath *selectedIndexPath = [self.tableView indexPathForSelectedRow]; [super viewWillAppear:animated]; // if there's something selected, the value might have changed // so reload that row if(selectedIndexPath) { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(animated * UINavigationControllerHideShowBarDuration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:selectedIndexPath] withRowAnimation:UITableViewRowAnimationNone]; // and reselect it, so we get the nice default deselect animation from UITableViewController [self.tableView selectRowAtIndexPath:selectedIndexPath animated:NO scrollPosition:UITableViewScrollPositionNone]; [self.tableView deselectRowAtIndexPath:selectedIndexPath animated:YES]; }); } if (_showDoneButton) { UIBarButtonItem *buttonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(dismiss:)]; self.navigationItem.rightBarButtonItem = buttonItem; } if (!self.title) { self.title = NSLocalizedString(@"Settings", @""); } if ([self.settingsStore isKindOfClass:[IASKSettingsStoreUserDefaults class]]) { NSNotificationCenter *dc = NSNotificationCenter.defaultCenter; IASKSettingsStoreUserDefaults *udSettingsStore = (id)self.settingsStore; [dc addObserver:self selector:@selector(userDefaultsDidChange) name:NSUserDefaultsDidChangeNotification object:udSettingsStore.defaults]; [dc addObserver:self selector:@selector(didChangeSettingViaIASK:) name:kIASKAppSettingChanged object:nil]; [self userDefaultsDidChange]; // force update in case of changes while we were hidden } } - (CGSize)preferredContentSize { return [[self view] sizeThatFits:CGSizeMake(500, 2000)]; } - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; NSNotificationCenter *dc = [NSNotificationCenter defaultCenter]; [dc addObserver:self selector:@selector(synchronizeSettings) name:UIApplicationDidEnterBackgroundNotification object:[UIApplication sharedApplication]]; [dc addObserver:self selector:@selector(reload) name:UIApplicationWillEnterForegroundNotification object:[UIApplication sharedApplication]]; [dc addObserver:self selector:@selector(synchronizeSettings) name:UIApplicationWillTerminateNotification object:[UIApplication sharedApplication]]; [self.tableView beginUpdates]; [self.tableView endUpdates]; } - (void)viewWillDisappear:(BOOL)animated { [NSObject cancelPreviousPerformRequestsWithTarget:self]; // hide the keyboard [self.currentFirstResponder resignFirstResponder]; [super viewWillDisappear:animated]; } - (void)viewDidDisappear:(BOOL)animated { NSNotificationCenter *dc = [NSNotificationCenter defaultCenter]; if ([self.settingsStore isKindOfClass:[IASKSettingsStoreUserDefaults class]]) { IASKSettingsStoreUserDefaults *udSettingsStore = (id)self.settingsStore; [dc removeObserver:self name:NSUserDefaultsDidChangeNotification object:udSettingsStore.defaults]; [dc removeObserver:self name:kIASKAppSettingChanged object:self]; } [dc removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:[UIApplication sharedApplication]]; [dc removeObserver:self name:UIApplicationWillEnterForegroundNotification object:[UIApplication sharedApplication]]; [dc removeObserver:self name:UIApplicationWillTerminateNotification object:[UIApplication sharedApplication]]; [super viewDidDisappear:animated]; } - (void)setHiddenKeys:(NSSet *)theHiddenKeys { [self setHiddenKeys:theHiddenKeys animated:NO]; } - (void)setHiddenKeys:(NSSet*)theHiddenKeys animated:(BOOL)animated { if (_hiddenKeys != theHiddenKeys) { NSSet *oldHiddenKeys = _hiddenKeys; _hiddenKeys = theHiddenKeys; if (animated) { NSMutableSet *showKeys = [NSMutableSet setWithSet:oldHiddenKeys]; [showKeys minusSet:theHiddenKeys]; NSMutableSet *hideKeys = [NSMutableSet setWithSet:theHiddenKeys]; [hideKeys minusSet:oldHiddenKeys]; // calculate rows to be deleted NSMutableArray *hideIndexPaths = [NSMutableArray array]; for (NSString *key in hideKeys) { NSIndexPath *indexPath = [self.settingsReader indexPathForKey:key]; if (indexPath) { [hideIndexPaths addObject:indexPath]; } } // calculate sections to be deleted NSMutableIndexSet *hideSections = [NSMutableIndexSet indexSet]; for (NSInteger section = 0; section < [self numberOfSectionsInTableView:self.tableView ]; section++) { NSInteger rowsInSection = 0; for (NSIndexPath *indexPath in hideIndexPaths) { if (indexPath.section == section) { rowsInSection++; } } if (rowsInSection && rowsInSection >= [self.settingsReader numberOfRowsForSection:section]) { [hideSections addIndex:section]; } } // set the datasource self.settingsReader.hiddenKeys = theHiddenKeys; // calculate rows to be inserted NSMutableArray *showIndexPaths = [NSMutableArray array]; for (NSString *key in showKeys) { NSIndexPath *indexPath = [self.settingsReader indexPathForKey:key]; if (indexPath) { [showIndexPaths addObject:indexPath]; } } // calculate sections to be inserted NSMutableIndexSet *showSections = [NSMutableIndexSet indexSet]; for (NSInteger section = 0; section < [self.settingsReader numberOfSections]; section++) { NSInteger rowsInSection = 0; for (NSIndexPath *indexPath in showIndexPaths) { if (indexPath.section == section) { rowsInSection++; } } if (rowsInSection && rowsInSection >= [self.settingsReader numberOfRowsForSection:section]) { [showSections addIndex:section]; } } if (hideSections.count || hideIndexPaths.count || showSections.count || showIndexPaths.count) { [self.tableView beginUpdates]; UITableViewRowAnimation animation = animated ? UITableViewRowAnimationAutomatic : UITableViewRowAnimationNone; if (hideSections.count) { [self.tableView deleteSections:hideSections withRowAnimation:animation]; } if (hideIndexPaths) { [self.tableView deleteRowsAtIndexPaths:hideIndexPaths withRowAnimation:animation]; } if (showSections.count) { [self.tableView insertSections:showSections withRowAnimation:animation]; } if (showIndexPaths) { [self.tableView insertRowsAtIndexPaths:showIndexPaths withRowAnimation:animation]; } [self.tableView endUpdates]; } } else { self.settingsReader.hiddenKeys = theHiddenKeys; if (!_reloadDisabled) [self.tableView reloadData]; } } UIViewController *childViewController = _currentChildViewController; if([childViewController respondsToSelector:@selector(setHiddenKeys:animated:)]) { [(id)childViewController setHiddenKeys:theHiddenKeys animated:animated]; } } - (void)setNeverShowPrivacySettings:(BOOL)neverShowPrivacySettings { _neverShowPrivacySettings = neverShowPrivacySettings; self.settingsReader = nil; [self reload]; } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; } #pragma mark - #pragma mark Actions - (void)dismiss:(id)sender { [self.settingsStore synchronize]; if (self.delegate && [self.delegate conformsToProtocol:@protocol(IASKSettingsDelegate)]) { [self.delegate settingsViewControllerDidEnd:self]; } } - (void)toggledValue:(id)sender { IASKSwitch *toggle = (IASKSwitch*)sender; IASKSpecifier *spec = [_settingsReader specifierForKey:[toggle key]]; if ([toggle isOn]) { if ([spec trueValue] != nil) { [self.settingsStore setObject:[spec trueValue] forKey:[toggle key]]; } else { [self.settingsStore setBool:YES forKey:[toggle key]]; } } else { if ([spec falseValue] != nil) { [self.settingsStore setObject:[spec falseValue] forKey:[toggle key]]; } else { [self.settingsStore setBool:NO forKey:[toggle key]]; } } [[NSNotificationCenter defaultCenter] postNotificationName:kIASKAppSettingChanged object:self userInfo:[NSDictionary dictionaryWithObject:[self.settingsStore objectForKey:[toggle key]] forKey:[toggle key]]]; } - (void)sliderChangedValue:(id)sender { IASKSlider *slider = (IASKSlider*)sender; [self.settingsStore setFloat:[slider value] forKey:[slider key]]; [[NSNotificationCenter defaultCenter] postNotificationName:kIASKAppSettingChanged object:self userInfo:[NSDictionary dictionaryWithObject:[NSNumber numberWithFloat:[slider value]] forKey:[slider key]]]; } #pragma mark - #pragma mark UITableView Functions - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return [self.settingsReader numberOfSections]; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [self.settingsReader numberOfRowsForSection:section]; } - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { IASKSpecifier *specifier = [self.settingsReader specifierForIndexPath:indexPath]; if ([specifier.type isEqualToString:kIASKTextViewSpecifier]) { CGFloat height = (CGFloat)[self.rowHeights[specifier.key] doubleValue]; return height > 0 ? height : UITableViewAutomaticDimension; } else if ([[specifier type] isEqualToString:kIASKCustomViewSpecifier]) { if ([self.delegate respondsToSelector:@selector(tableView:heightForSpecifier:)]) { return [self.delegate tableView:tableView heightForSpecifier:specifier]; } else { return 0; } } IASK_IF_IOS7_OR_GREATER ( NSDictionary *rowHeights = @{UIContentSizeCategoryExtraSmall: @(44), UIContentSizeCategorySmall: @(44), UIContentSizeCategoryMedium: @(44), UIContentSizeCategoryLarge: @(44), UIContentSizeCategoryExtraLarge: @(47)}; CGFloat rowHeight = (CGFloat)[rowHeights[UIApplication.sharedApplication.preferredContentSizeCategory] doubleValue]; return rowHeight != 0 ? rowHeight : 51; ); return 44; } - (NSString *)tableView:(UITableView*)tableView titleForHeaderInSection:(NSInteger)section { NSString *header = [self.settingsReader titleForSection:section]; if (0 == header.length) { return nil; } return header; } - (UIView *)tableView:(UITableView*)tableView viewForHeaderInSection:(NSInteger)section { if ([self.delegate respondsToSelector:@selector(settingsViewController:tableView:viewForHeaderForSection:)]) { return [self.delegate settingsViewController:self tableView:tableView viewForHeaderForSection:section]; } else { return nil; } } - (CGFloat)tableView:(UITableView*)tableView heightForHeaderInSection:(NSInteger)section { if ([self tableView:tableView viewForHeaderInSection:section] && [self.delegate respondsToSelector:@selector(settingsViewController:tableView:heightForHeaderForSection:)]) { CGFloat result = [self.delegate settingsViewController:self tableView:tableView heightForHeaderForSection:section]; if (result > 0) { return result; } } return UITableViewAutomaticDimension; } - (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section { NSString *footerText = [self.settingsReader footerTextForSection:section]; if (_showCreditsFooter && (section == [self.settingsReader numberOfSections]-1)) { // show credits since this is the last section if ((footerText == nil) || ([footerText length] == 0)) { // show the credits on their own return kIASKCredits; } else { // show the credits below the app's FooterText return [NSString stringWithFormat:@"%@\n\n%@", footerText, kIASKCredits]; } } else { return footerText; } } - (UITableViewCell*)tableView:(UITableView *)tableView newCellForSpecifier:(IASKSpecifier*)specifier { NSString *identifier = [NSString stringWithFormat:@"%@-%ld-%d", specifier.type, (long)specifier.textAlignment, !!specifier.subtitle.length]; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier]; if (cell) { return cell; } UITableViewCellStyle style = (specifier.textAlignment == NSTextAlignmentLeft || specifier.subtitle.length) ? UITableViewCellStyleSubtitle : UITableViewCellStyleDefault; if ([identifier hasPrefix:kIASKPSToggleSwitchSpecifier]) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:kIASKPSToggleSwitchSpecifier]; cell.accessoryView = [[IASKSwitch alloc] initWithFrame:CGRectMake(0, 0, 79, 27)]; [((IASKSwitch*)cell.accessoryView) addTarget:self action:@selector(toggledValue:) forControlEvents:UIControlEventValueChanged]; cell.selectionStyle = UITableViewCellSelectionStyleNone; } else if ([identifier hasPrefix:kIASKPSMultiValueSpecifier] || [identifier hasPrefix:kIASKPSTitleValueSpecifier]) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:identifier]; cell.accessoryType = [identifier hasPrefix:kIASKPSMultiValueSpecifier] ? UITableViewCellAccessoryDisclosureIndicator : UITableViewCellAccessoryNone; } else if ([identifier hasPrefix:kIASKPSTextFieldSpecifier]) { cell = [[IASKPSTextFieldSpecifierViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kIASKPSTextFieldSpecifier]; [((IASKPSTextFieldSpecifierViewCell*)cell).textField addTarget:self action:@selector(_textChanged:) forControlEvents:UIControlEventEditingChanged]; } else if ([identifier hasPrefix:kIASKTextViewSpecifier]) { cell = [[IASKTextViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kIASKTextViewSpecifier]; } else if ([identifier hasPrefix:kIASKPSSliderSpecifier]) { cell = [[IASKPSSliderSpecifierViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kIASKPSSliderSpecifier]; } else if ([identifier hasPrefix:kIASKPSChildPaneSpecifier]) { cell = [[UITableViewCell alloc] initWithStyle:style reuseIdentifier:identifier]; cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; } else if ([identifier isEqualToString:kIASKMailComposeSpecifier]) { cell = [[UITableViewCell alloc] initWithStyle:style reuseIdentifier:identifier]; [cell setAccessoryType:UITableViewCellAccessoryDisclosureIndicator]; } else { cell = [[UITableViewCell alloc] initWithStyle:style reuseIdentifier:identifier]; if ([identifier isEqualToString:kIASKOpenURLSpecifier]) { cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; } } IASK_IF_PRE_IOS6(cell.textLabel.minimumFontSize = kIASKMinimumFontSize; cell.detailTextLabel.minimumFontSize = kIASKMinimumFontSize;); IASK_IF_IOS6_OR_GREATER(cell.textLabel.minimumScaleFactor = kIASKMinimumFontSize / cell.textLabel.font.pointSize; cell.detailTextLabel.minimumScaleFactor = kIASKMinimumFontSize / cell.detailTextLabel.font.pointSize;); return cell; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { IASKSpecifier *specifier = [self.settingsReader specifierForIndexPath:indexPath]; if ([specifier.type isEqualToString:kIASKCustomViewSpecifier] && [self.delegate respondsToSelector:@selector(tableView:cellForSpecifier:)]) { UITableViewCell* cell = [self.delegate tableView:tableView cellForSpecifier:specifier]; assert(nil != cell && "delegate must return a UITableViewCell for custom cell types"); return cell; } UITableViewCell* cell = [self tableView:tableView newCellForSpecifier:specifier]; if ([specifier.type isEqualToString:kIASKPSToggleSwitchSpecifier]) { cell.textLabel.text = specifier.title; cell.detailTextLabel.text = specifier.subtitle; id currentValue = [self.settingsStore objectForKey:specifier.key]; BOOL toggleState; if (currentValue) { if ([currentValue isEqual:specifier.trueValue]) { toggleState = YES; } else if ([currentValue isEqual:specifier.falseValue]) { toggleState = NO; } else { toggleState = [currentValue boolValue]; } } else { toggleState = specifier.defaultBoolValue; } IASKSwitch *toggle = (IASKSwitch*)cell.accessoryView; toggle.on = toggleState; toggle.key = specifier.key; } else if ([specifier.type isEqualToString:kIASKPSMultiValueSpecifier]) { cell.textLabel.text = specifier.title; cell.detailTextLabel.text = [[specifier titleForCurrentValue:[self.settingsStore objectForKey:specifier.key] != nil ? [self.settingsStore objectForKey:specifier.key] : specifier.defaultValue] description]; } else if ([specifier.type isEqualToString:kIASKPSTitleValueSpecifier]) { cell.textLabel.text = specifier.title; id value = [self.settingsStore objectForKey:specifier.key] ? : specifier.defaultValue; NSString *stringValue; if (specifier.multipleValues || specifier.multipleTitles) { stringValue = [specifier titleForCurrentValue:value]; } else { stringValue = [value description]; } cell.detailTextLabel.text = stringValue; cell.userInteractionEnabled = NO; } else if ([specifier.type isEqualToString:kIASKPSTextFieldSpecifier]) { cell.textLabel.text = specifier.title; NSString *textValue = [self.settingsStore objectForKey:specifier.key] != nil ? [self.settingsStore objectForKey:specifier.key] : specifier.defaultStringValue; if (textValue && ![textValue isMemberOfClass:[NSString class]]) { textValue = [NSString stringWithFormat:@"%@", textValue]; } IASKTextField *textField = ((IASKPSTextFieldSpecifierViewCell*)cell).textField; textField.text = textValue; textField.placeholder = specifier.placeholder; textField.key = specifier.key; textField.delegate = self; textField.secureTextEntry = [specifier isSecure]; textField.keyboardType = specifier.keyboardType; textField.autocapitalizationType = specifier.autocapitalizationType; if([specifier isSecure]){ textField.autocorrectionType = UITextAutocorrectionTypeNo; } else { textField.autocorrectionType = specifier.autoCorrectionType; } textField.textAlignment = specifier.textAlignment; textField.adjustsFontSizeToFitWidth = specifier.adjustsFontSizeToFitWidth; } else if ([specifier.type isEqualToString:kIASKTextViewSpecifier]) { IASKTextViewCell *textCell = (id)cell; NSString *value = [self.settingsStore objectForKey:specifier.key] != nil ? [self.settingsStore objectForKey:specifier.key] : specifier.defaultStringValue; textCell.textView.text = value; textCell.textView.delegate = self; textCell.textView.key = specifier.key; textCell.textView.keyboardType = specifier.keyboardType; textCell.textView.autocapitalizationType = specifier.autocapitalizationType; textCell.textView.autocorrectionType = specifier.autoCorrectionType; dispatch_async(dispatch_get_main_queue(), ^{ [self cacheRowHeightForTextView:textCell.textView]; }); } else if ([specifier.type isEqualToString:kIASKPSSliderSpecifier]) { if (specifier.minimumValueImage.length > 0) { ((IASKPSSliderSpecifierViewCell*)cell).minImage.image = [UIImage imageWithContentsOfFile:[_settingsReader pathForImageNamed:specifier.minimumValueImage]]; } if (specifier.maximumValueImage.length > 0) { ((IASKPSSliderSpecifierViewCell*)cell).maxImage.image = [UIImage imageWithContentsOfFile:[_settingsReader pathForImageNamed:specifier.maximumValueImage]]; } IASKSlider *slider = ((IASKPSSliderSpecifierViewCell*)cell).slider; slider.minimumValue = specifier.minimumValue; slider.maximumValue = specifier.maximumValue; slider.value = [self.settingsStore objectForKey:specifier.key] != nil ? [[self.settingsStore objectForKey:specifier.key] floatValue] : [specifier.defaultValue floatValue]; [slider addTarget:self action:@selector(sliderChangedValue:) forControlEvents:UIControlEventValueChanged]; slider.key = specifier.key; [cell setNeedsLayout]; } else if ([specifier.type isEqualToString:kIASKPSChildPaneSpecifier]) { cell.textLabel.text = specifier.title; cell.detailTextLabel.text = specifier.subtitle; } else if ([specifier.type isEqualToString:kIASKOpenURLSpecifier] || [specifier.type isEqualToString:kIASKMailComposeSpecifier]) { cell.textLabel.text = specifier.title; cell.detailTextLabel.text = specifier.subtitle ? : [specifier.defaultValue description]; cell.accessoryType = (specifier.textAlignment == NSTextAlignmentLeft) ? UITableViewCellAccessoryDisclosureIndicator : UITableViewCellAccessoryNone; } else if ([specifier.type isEqualToString:kIASKButtonSpecifier]) { NSString *value = [self.settingsStore objectForKey:specifier.key]; cell.textLabel.text = ([value isKindOfClass:NSString.class] && [self.settingsReader titleForId:value].length) ? [self.settingsReader titleForId:value] : specifier.title; cell.detailTextLabel.text = specifier.subtitle; IASK_IF_IOS7_OR_GREATER (if (specifier.isCritical) { cell.textLabel.textColor = UIColor.redColor; } else if (specifier.textAlignment != NSTextAlignmentLeft) { cell.textLabel.textColor = tableView.tintColor; }); cell.textLabel.textAlignment = specifier.textAlignment; cell.accessoryType = (specifier.textAlignment == NSTextAlignmentLeft) ? UITableViewCellAccessoryDisclosureIndicator : UITableViewCellAccessoryNone; } else if ([specifier.type isEqualToString:kIASKPSRadioGroupSpecifier]) { NSInteger index = [specifier.multipleValues indexOfObject:specifier.radioGroupValue]; cell.textLabel.text = [self.settingsReader titleForId:specifier.multipleTitles[index]]; [_selections[indexPath.section] updateSelectionInCell:cell indexPath:indexPath]; } else { cell.textLabel.text = specifier.title; } cell.imageView.image = specifier.cellImage; cell.imageView.highlightedImage = specifier.highlightedCellImage; if (![specifier.type isEqualToString:kIASKPSMultiValueSpecifier] && ![specifier.type isEqualToString:kIASKPSTitleValueSpecifier] && ![specifier.type isEqualToString:kIASKPSTextFieldSpecifier] && ![specifier.type isEqualToString:kIASKTextViewSpecifier]) { cell.textLabel.textAlignment = specifier.textAlignment; } cell.detailTextLabel.textAlignment = specifier.textAlignment; cell.textLabel.adjustsFontSizeToFitWidth = specifier.adjustsFontSizeToFitWidth; cell.detailTextLabel.adjustsFontSizeToFitWidth = specifier.adjustsFontSizeToFitWidth; return cell; } - (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath { //create a set of specifier types that can't be selected static NSSet* noSelectionTypes = nil; if(nil == noSelectionTypes) { noSelectionTypes = [NSSet setWithObjects:kIASKPSToggleSwitchSpecifier, kIASKPSSliderSpecifier, nil]; } IASKSpecifier *specifier = [self.settingsReader specifierForIndexPath:indexPath]; if([noSelectionTypes containsObject:specifier.type]) { return nil; } else { return indexPath; } } - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { IASKSpecifier *specifier = [self.settingsReader specifierForIndexPath:indexPath]; //switches and sliders can't be selected (should be captured by tableView:willSelectRowAtIndexPath: delegate method) assert(![[specifier type] isEqualToString:kIASKPSToggleSwitchSpecifier]); assert(![[specifier type] isEqualToString:kIASKPSSliderSpecifier]); if ([[specifier type] isEqualToString:kIASKPSMultiValueSpecifier]) { IASKSpecifierValuesViewController *targetViewController = [[IASKSpecifierValuesViewController alloc] init]; [targetViewController setCurrentSpecifier:specifier]; targetViewController.settingsReader = self.settingsReader; targetViewController.settingsStore = self.settingsStore; IASK_IF_IOS7_OR_GREATER(targetViewController.view.tintColor = self.view.tintColor;) _currentChildViewController = targetViewController; [[self navigationController] pushViewController:targetViewController animated:YES]; } else if ([[specifier type] isEqualToString:kIASKPSTextFieldSpecifier]) { IASKPSTextFieldSpecifierViewCell *textFieldCell = (id)[tableView cellForRowAtIndexPath:indexPath]; [textFieldCell.textField becomeFirstResponder]; } else if ([[specifier type] isEqualToString:kIASKPSChildPaneSpecifier]) { if ([specifier viewControllerStoryBoardID]){ NSString *storyBoardFileFromSpecifier = [specifier viewControllerStoryBoardFile]; storyBoardFileFromSpecifier = storyBoardFileFromSpecifier && storyBoardFileFromSpecifier.length > 0 ? storyBoardFileFromSpecifier : [[NSBundle mainBundle].infoDictionary objectForKey:@"UIMainStoryboardFile"]; UIStoryboard *storyBoard = [UIStoryboard storyboardWithName:storyBoardFileFromSpecifier bundle:nil]; UIViewController * vc = [storyBoard instantiateViewControllerWithIdentifier:[specifier viewControllerStoryBoardID]]; IASK_IF_IOS7_OR_GREATER(vc.view.tintColor = self.view.tintColor;) [self.navigationController pushViewController:vc animated:YES]; return; } Class vcClass = [specifier viewControllerClass]; if (vcClass) { if (vcClass == [NSNull class]) { NSLog(@"class '%@' not found", [specifier localizedObjectForKey:kIASKViewControllerClass]); [tableView deselectRowAtIndexPath:indexPath animated:YES]; return; } SEL initSelector = [specifier viewControllerSelector]; if (!initSelector) { initSelector = @selector(init); } UIViewController * vc = [vcClass alloc]; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" vc = [vc performSelector:initSelector withObject:[specifier file] withObject:specifier]; #pragma clang diagnostic pop if ([vc respondsToSelector:@selector(setDelegate:)]) { [vc performSelector:@selector(setDelegate:) withObject:self.delegate]; } if ([vc respondsToSelector:@selector(setSettingsStore:)]) { [vc performSelector:@selector(setSettingsStore:) withObject:self.settingsStore]; } IASK_IF_IOS7_OR_GREATER(vc.view.tintColor = self.view.tintColor;) [self.navigationController pushViewController:vc animated:YES]; return; } NSString *segueIdentifier = [specifier segueIdentifier]; if (segueIdentifier) { @try { [self performSegueWithIdentifier:segueIdentifier sender:self]; } @catch (NSException *exception) { NSLog(@"segue with identifier '%@' not defined", segueIdentifier); [tableView deselectRowAtIndexPath:indexPath animated:YES]; } return; } if (nil == [specifier file]) { [tableView deselectRowAtIndexPath:indexPath animated:YES]; return; } _reloadDisabled = YES; // Disable internal unnecessary reloads IASKAppSettingsViewController *targetViewController = [[[self class] alloc] init]; targetViewController.showDoneButton = NO; targetViewController.showCreditsFooter = NO; // Does not reload the tableview (but next setters do it) targetViewController.delegate = self.delegate; targetViewController.settingsStore = self.settingsStore; targetViewController.file = specifier.file; targetViewController.hiddenKeys = self.hiddenKeys; targetViewController.title = specifier.title; IASK_IF_IOS7_OR_GREATER(targetViewController.view.tintColor = self.view.tintColor;) _currentChildViewController = targetViewController; _reloadDisabled = NO; [[self navigationController] pushViewController:targetViewController animated:YES]; } else if ([[specifier type] isEqualToString:kIASKOpenURLSpecifier]) { [tableView deselectRowAtIndexPath:indexPath animated:YES]; [[UIApplication sharedApplication] openURL:[NSURL URLWithString:[specifier localizedObjectForKey:kIASKFile]] options:@{} completionHandler:nil]; } else if ([[specifier type] isEqualToString:kIASKButtonSpecifier]) { [tableView deselectRowAtIndexPath:indexPath animated:YES]; if ([self.delegate respondsToSelector:@selector(settingsViewController:buttonTappedForSpecifier:)]) { [self.delegate settingsViewController:self buttonTappedForSpecifier:specifier]; } else if ([self.delegate respondsToSelector:@selector(settingsViewController:buttonTappedForKey:)]) { // deprecated, provided for backward compatibility NSLog(@"InAppSettingsKit Warning: -settingsViewController:buttonTappedForKey: is deprecated. Please use -settingsViewController:buttonTappedForSpecifier:"); #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" [self.delegate settingsViewController:self buttonTappedForKey:[specifier key]]; #pragma clang diagnostic pop } else { // legacy code, provided for backward compatibility // the delegate mechanism above is much cleaner and doesn't leak Class buttonClass = [specifier buttonClass]; SEL buttonAction = [specifier buttonAction]; if ([buttonClass respondsToSelector:buttonAction]) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" [buttonClass performSelector:buttonAction withObject:self withObject:[specifier key]]; #pragma clang diagnostic pop NSLog(@"InAppSettingsKit Warning: Using IASKButtonSpecifier without implementing the delegate method is deprecated"); } } } else if ([[specifier type] isEqualToString:kIASKMailComposeSpecifier]) { [tableView deselectRowAtIndexPath:indexPath animated:YES]; if ([MFMailComposeViewController canSendMail]) { MFMailComposeViewController *mailViewController = [[MFMailComposeViewController alloc] init]; mailViewController.navigationBar.barStyle = self.navigationController.navigationBar.barStyle; IASK_IF_IOS7_OR_GREATER(mailViewController.navigationBar.tintColor = self.navigationController.navigationBar.tintColor;); mailViewController.navigationBar.titleTextAttributes = self.navigationController.navigationBar.titleTextAttributes; if ([specifier localizedObjectForKey:kIASKMailComposeSubject]) { [mailViewController setSubject:[specifier localizedObjectForKey:kIASKMailComposeSubject]]; } if ([[specifier specifierDict] objectForKey:kIASKMailComposeToRecipents]) { [mailViewController setToRecipients:[[specifier specifierDict] objectForKey:kIASKMailComposeToRecipents]]; } if ([[specifier specifierDict] objectForKey:kIASKMailComposeCcRecipents]) { [mailViewController setCcRecipients:[[specifier specifierDict] objectForKey:kIASKMailComposeCcRecipents]]; } if ([[specifier specifierDict] objectForKey:kIASKMailComposeBccRecipents]) { [mailViewController setBccRecipients:[[specifier specifierDict] objectForKey:kIASKMailComposeBccRecipents]]; } if ([specifier localizedObjectForKey:kIASKMailComposeBody]) { BOOL isHTML = NO; if ([[specifier specifierDict] objectForKey:kIASKMailComposeBodyIsHTML]) { isHTML = [[[specifier specifierDict] objectForKey:kIASKMailComposeBodyIsHTML] boolValue]; } if ([self.delegate respondsToSelector:@selector(settingsViewController:mailComposeBodyForSpecifier:)]) { [mailViewController setMessageBody:[self.delegate settingsViewController:self mailComposeBodyForSpecifier:specifier] isHTML:isHTML]; } else { [mailViewController setMessageBody:[specifier localizedObjectForKey:kIASKMailComposeBody] isHTML:isHTML]; } } UIViewController *vc = nil; if ([self.delegate respondsToSelector:@selector(settingsViewController:viewControllerForMailComposeViewForSpecifier:)]) { vc = [self.delegate settingsViewController:self viewControllerForMailComposeViewForSpecifier:specifier]; } if (vc == nil) { vc = self; } mailViewController.mailComposeDelegate = vc; _currentChildViewController = mailViewController; // UIStatusBarStyle savedStatusBarStyle = [UIApplication sharedApplication].statusBarStyle; [vc presentViewController:mailViewController animated:YES completion:^{ // [UIApplication sharedApplication].statusBarStyle = savedStatusBarStyle; }]; } else { IASK_IF_PRE_IOS8 ( UIAlertView *alert = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Mail not configured", @"InAppSettingsKit") message:NSLocalizedString(@"This device is not configured for sending Email. Please configure the Mail settings in the Settings app.", @"InAppSettingsKit") delegate: nil cancelButtonTitle:NSLocalizedString(@"OK", @"InAppSettingsKit") otherButtonTitles:nil]; [alert show]; ) IASK_IF_IOS8_OR_GREATER ( UIAlertController *alert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Mail not configured", @"InAppSettingsKit") message:NSLocalizedString(@"This device is not configured for sending Email. Please configure the Mail settings in the Settings app.", @"InAppSettingsKit") preferredStyle:UIAlertControllerStyleAlert]; [alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"OK", @"InAppSettingsKit") style:UIAlertActionStyleCancel handler:^(UIAlertAction * action) {}]]; [self presentViewController:alert animated:YES completion:nil]; ) } } else if ([[specifier type] isEqualToString:kIASKCustomViewSpecifier] && [self.delegate respondsToSelector:@selector(settingsViewController:tableView:didSelectCustomViewSpecifier:)]) { [self.delegate settingsViewController:self tableView:tableView didSelectCustomViewSpecifier:specifier]; } else if ([[specifier type] isEqualToString:kIASKPSRadioGroupSpecifier]) { [_selections[indexPath.section] selectRowAtIndexPath:indexPath]; } else { [tableView deselectRowAtIndexPath:indexPath animated:NO]; } } #pragma mark - #pragma mark MFMailComposeViewControllerDelegate Function -(void)mailComposeController:(MFMailComposeViewController*)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError*)error { // Forward the mail compose delegate if ([self.delegate respondsToSelector:@selector(settingsViewController:mailComposeController:didFinishWithResult:error:)]) { [self.delegate settingsViewController:self mailComposeController:controller didFinishWithResult:result error:error]; } [self dismissViewControllerAnimated:YES completion:nil]; } #pragma mark - #pragma mark UITextFieldDelegate Functions - (void)textFieldDidBeginEditing:(UITextField *)textField { self.currentFirstResponder = textField; } - (void)_textChanged:(id)sender { IASKTextField *text = sender; [_settingsStore setObject:[text text] forKey:[text key]]; [[NSNotificationCenter defaultCenter] postNotificationName:kIASKAppSettingChanged object:self userInfo:[NSDictionary dictionaryWithObject:[text text] forKey:[text key]]]; } - (BOOL)textFieldShouldReturn:(UITextField *)textField{ [textField resignFirstResponder]; self.currentFirstResponder = nil; return YES; } - (void)singleTapToEndEdit:(UIGestureRecognizer *)sender { [self.tableView endEditing:NO]; } #pragma mark - UITextViewDelegate - (void)textViewDidEndEditing:(UITextView *)textView { self.currentFirstResponder = textView; } - (void)textViewDidChange:(IASKTextView *)textView { [self cacheRowHeightForTextView:textView]; CGRect visibleTableRect = UIEdgeInsetsInsetRect(self.tableView.bounds, self.tableView.contentInset); NSIndexPath *indexPath = [self.settingsReader indexPathForKey:textView.key]; CGRect cellFrame = [self.tableView rectForRowAtIndexPath:indexPath]; if (!CGRectContainsRect(visibleTableRect, cellFrame)) { [self.tableView scrollRectToVisible:CGRectInset(cellFrame, 0, - 30) animated:YES]; } [_settingsStore setObject:textView.text forKey:textView.key]; [[NSNotificationCenter defaultCenter] postNotificationName:kIASKAppSettingChanged object:textView.key userInfo:@{textView.key: textView.text}]; } - (void)cacheRowHeightForTextView:(IASKTextView *)textView { CGFloat maxHeight = self.tableView.bounds.size.height - self.tableView.contentInset.top - self.tableView.contentInset.bottom - 60; CGFloat contentHeight = [textView sizeThatFits:CGSizeMake(textView.frame.size.width, 10000)].height + 16; self.rowHeights[textView.key] = @(MAX(44, MIN(maxHeight, contentHeight))); textView.scrollEnabled = contentHeight > maxHeight; [self.tableView beginUpdates]; [self.tableView endUpdates]; } #pragma mark Notifications - (void)synchronizeSettings { [_settingsStore synchronize]; } static NSDictionary *oldUserDefaults = nil; - (void)userDefaultsDidChange { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ IASKSettingsStoreUserDefaults *udSettingsStore = (id)self.settingsStore; NSDictionary *currentDict = udSettingsStore.defaults.dictionaryRepresentation; NSMutableArray *indexPathsToUpdate = [NSMutableArray array]; for (NSString *key in currentDict.allKeys) { if (oldUserDefaults && ![[oldUserDefaults valueForKey:key] isEqual:[currentDict valueForKey:key]]) { NSIndexPath *path = [self.settingsReader indexPathForKey:key]; if (path && ![[self.settingsReader specifierForKey:key].type isEqualToString:kIASKCustomViewSpecifier] && [self.tableView.indexPathsForVisibleRows containsObject:path]) { [indexPathsToUpdate addObject:path]; } } } oldUserDefaults = currentDict; for (UITableViewCell *cell in self.tableView.visibleCells) { if ([cell isKindOfClass:[IASKPSTextFieldSpecifierViewCell class]] && [((IASKPSTextFieldSpecifierViewCell*)cell).textField isFirstResponder]) { [indexPathsToUpdate removeObject:[self.tableView indexPathForCell:cell]]; } } if (indexPathsToUpdate.count) { [self.tableView reloadRowsAtIndexPaths:indexPathsToUpdate withRowAnimation:UITableViewRowAnimationAutomatic]; } }); } - (void)didChangeSettingViaIASK:(NSNotification*)notification { NSString *key = notification.userInfo.allKeys.firstObject; [oldUserDefaults setValue:notification.userInfo[key] forKey:key]; } - (void)reload { // wait 0.5 sec until UI is available after applicationWillEnterForeground [self.tableView performSelector:@selector(reloadData) withObject:nil afterDelay:0.5]; } #pragma mark CGRect Utility function CGRect IASKCGRectSwap(CGRect rect) { CGRect newRect; newRect.origin.x = rect.origin.y; newRect.origin.y = rect.origin.x; newRect.size.width = rect.size.height; newRect.size.height = rect.size.width; return newRect; } @end