// // XLFormSectionDescriptor.m // XLForm ( https://github.com/xmartlabs/XLForm ) // // Copyright (c) 2015 Xmartlabs ( http://xmartlabs.com ) // // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #import "XLForm.h" #import "XLFormSectionDescriptor.h" #import "NSPredicate+XLFormAdditions.h" #import "NSString+XLFormAdditions.h" #import "UIView+XLFormAdditions.h" @interface XLFormDescriptor (_XLFormSectionDescriptor) @property (readonly) NSDictionary* allRowsByTag; -(void)addRowToTagCollection:(XLFormRowDescriptor*)rowDescriptor; -(void)removeRowFromTagCollection:(XLFormRowDescriptor*) rowDescriptor; -(void)showFormSection:(XLFormSectionDescriptor*)formSection; -(void)hideFormSection:(XLFormSectionDescriptor*)formSection; -(void)addObserversOfObject:(id)sectionOrRow predicateType:(XLPredicateType)predicateType; -(void)removeObserversOfObject:(id)sectionOrRow predicateType:(XLPredicateType)predicateType; @end @interface XLFormSectionDescriptor() @property NSMutableArray * formRows; @property NSMutableArray * allRows; @property BOOL isDirtyHidePredicateCache; @property (nonatomic) NSNumber* hidePredicateCache; @end @implementation XLFormSectionDescriptor @synthesize hidden = _hidden; @synthesize hidePredicateCache = _hidePredicateCache; -(instancetype)init { self = [super init]; if (self){ _formRows = [NSMutableArray array]; _allRows = [NSMutableArray array]; _sectionInsertMode = XLFormSectionInsertModeLastRow; _sectionOptions = XLFormSectionOptionNone; _title = nil; _footerTitle = nil; _hidden = @NO; _hidePredicateCache = @NO; _isDirtyHidePredicateCache = YES; [self addObserver:self forKeyPath:@"formRows" options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:0]; } return self; } -(instancetype)initWithTitle:(NSString *)title sectionOptions:(XLFormSectionOptions)sectionOptions sectionInsertMode:(XLFormSectionInsertMode)sectionInsertMode{ self = [self init]; if (self){ _sectionInsertMode = sectionInsertMode; _sectionOptions = sectionOptions; _title = title; if ([self canInsertUsingButton]){ _multivaluedAddButton = [XLFormRowDescriptor formRowDescriptorWithTag:nil rowType:XLFormRowDescriptorTypeButton title:@"Add Item"]; [_multivaluedAddButton.cellConfig setObject:@(NSTextAlignmentNatural) forKey:@"textLabel.textAlignment"]; _multivaluedAddButton.action.formSelector = NSSelectorFromString(@"multivaluedInsertButtonTapped:"); [self insertObject:_multivaluedAddButton inFormRowsAtIndex:0]; [self insertObject:_multivaluedAddButton inAllRowsAtIndex:0]; } } return self; } +(instancetype)formSection { return [[self class] formSectionWithTitle:nil]; } +(instancetype)formSectionWithTitle:(NSString *)title { return [[self class] formSectionWithTitle:title sectionOptions:XLFormSectionOptionNone]; } +(instancetype)formSectionWithTitle:(NSString *)title multivaluedSection:(BOOL)multivaluedSection { return [[self class] formSectionWithTitle:title sectionOptions:(multivaluedSection ? XLFormSectionOptionCanInsert | XLFormSectionOptionCanDelete : XLFormSectionOptionNone)]; } +(instancetype)formSectionWithTitle:(NSString *)title sectionOptions:(XLFormSectionOptions)sectionOptions { return [[self class] formSectionWithTitle:title sectionOptions:sectionOptions sectionInsertMode:XLFormSectionInsertModeLastRow]; } +(instancetype)formSectionWithTitle:(NSString *)title sectionOptions:(XLFormSectionOptions)sectionOptions sectionInsertMode:(XLFormSectionInsertMode)sectionInsertMode { return [[[self class] alloc] initWithTitle:title sectionOptions:sectionOptions sectionInsertMode:sectionInsertMode]; } -(BOOL)isMultivaluedSection { return (self.sectionOptions != XLFormSectionOptionNone); } -(void)addFormRow:(XLFormRowDescriptor *)formRow { [self insertObject:formRow inAllRowsAtIndex:([self canInsertUsingButton] ? MAX(0, [self.formRows count] - 1) : [self.allRows count])]; } -(void)addFormRow:(XLFormRowDescriptor *)formRow afterRow:(XLFormRowDescriptor *)afterRow { NSUInteger allRowIndex = [self.allRows indexOfObject:afterRow]; if (allRowIndex != NSNotFound) { [self insertObject:formRow inAllRowsAtIndex:allRowIndex+1]; } else { //case when afterRow does not exist. Just insert at the end. [self addFormRow:formRow]; return; } } -(void)addFormRow:(XLFormRowDescriptor *)formRow beforeRow:(XLFormRowDescriptor *)beforeRow { NSUInteger allRowIndex = [self.allRows indexOfObject:beforeRow]; if (allRowIndex != NSNotFound) { [self insertObject:formRow inAllRowsAtIndex:allRowIndex]; } else { //case when afterRow does not exist. Just insert at the end. [self addFormRow:formRow]; return; } } -(void)removeFormRowAtIndex:(NSUInteger)index { if (self.formRows.count > index){ XLFormRowDescriptor *formRow = [self.formRows objectAtIndex:index]; NSUInteger allRowIndex = [self.allRows indexOfObject:formRow]; [self removeObjectFromFormRowsAtIndex:index]; [self removeObjectFromAllRowsAtIndex:allRowIndex]; } } -(void)removeFormRow:(XLFormRowDescriptor *)formRow { NSUInteger index = NSNotFound; if ((index = [self.formRows indexOfObject:formRow]) != NSNotFound){ [self removeFormRowAtIndex:index]; } else if ((index = [self.allRows indexOfObject:formRow]) != NSNotFound){ if (self.allRows.count > index){ [self removeObjectFromAllRowsAtIndex:index]; } }; } - (void)moveRowAtIndexPath:(NSIndexPath *)sourceIndex toIndexPath:(NSIndexPath *)destinationIndex { if ((sourceIndex.row < self.formRows.count) && (destinationIndex.row < self.formRows.count) && (sourceIndex.row != destinationIndex.row)){ XLFormRowDescriptor * row = [self objectInFormRowsAtIndex:sourceIndex.row]; XLFormRowDescriptor * destRow = [self objectInFormRowsAtIndex:destinationIndex.row]; [self.formRows removeObjectAtIndex:sourceIndex.row]; [self.formRows insertObject:row atIndex:destinationIndex.row]; [self.allRows removeObjectAtIndex:[self.allRows indexOfObject:row]]; [self.allRows insertObject:row atIndex:[self.allRows indexOfObject:destRow]]; } } -(void)dealloc { [self.formDescriptor removeObserversOfObject:self predicateType:XLPredicateTypeHidden]; @try { [self removeObserver:self forKeyPath:@"formRows"]; } @catch (NSException * __unused exception) {} } #pragma mark - Show/hide rows -(void)showFormRow:(XLFormRowDescriptor*)formRow{ NSUInteger formIndex = [self.formRows indexOfObject:formRow]; if (formIndex != NSNotFound) { return; } NSUInteger index = [self.allRows indexOfObject:formRow]; if (index != NSNotFound){ while (formIndex == NSNotFound && index > 0) { XLFormRowDescriptor* previous = [self.allRows objectAtIndex:--index]; formIndex = [self.formRows indexOfObject:previous]; } if (formIndex == NSNotFound){ // index == 0 => insert at the beginning [self insertObject:formRow inFormRowsAtIndex:0]; } else { [self insertObject:formRow inFormRowsAtIndex:formIndex+1]; } } } -(void)hideFormRow:(XLFormRowDescriptor*)formRow{ NSUInteger index = [self.formRows indexOfObject:formRow]; if (index != NSNotFound){ [self removeObjectFromFormRowsAtIndex:index]; } } #pragma mark - KVO -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if (!self.formDescriptor.delegate) return; if ([keyPath isEqualToString:@"formRows"]){ if ([self.formDescriptor.formSections containsObject:self]){ if ([[change objectForKey:NSKeyValueChangeKindKey] isEqualToNumber:@(NSKeyValueChangeInsertion)]){ NSIndexSet * indexSet = [change objectForKey:NSKeyValueChangeIndexesKey]; XLFormRowDescriptor * formRow = [((XLFormSectionDescriptor *)object).formRows objectAtIndex:indexSet.firstIndex]; NSUInteger sectionIndex = [self.formDescriptor.formSections indexOfObject:object]; [self.formDescriptor.delegate formRowHasBeenAdded:formRow atIndexPath:[NSIndexPath indexPathForRow:indexSet.firstIndex inSection:sectionIndex]]; } else if ([[change objectForKey:NSKeyValueChangeKindKey] isEqualToNumber:@(NSKeyValueChangeRemoval)]){ NSIndexSet * indexSet = [change objectForKey:NSKeyValueChangeIndexesKey]; XLFormRowDescriptor * removedRow = [[change objectForKey:NSKeyValueChangeOldKey] objectAtIndex:0]; NSUInteger sectionIndex = [self.formDescriptor.formSections indexOfObject:object]; [self.formDescriptor.delegate formRowHasBeenRemoved:removedRow atIndexPath:[NSIndexPath indexPathForRow:indexSet.firstIndex inSection:sectionIndex]]; } } } } #pragma mark - KVC -(NSUInteger)countOfFormRows { return self.formRows.count; } - (id)objectInFormRowsAtIndex:(NSUInteger)index { return [self.formRows objectAtIndex:index]; } - (NSArray *)formRowsAtIndexes:(NSIndexSet *)indexes { return [self.formRows objectsAtIndexes:indexes]; } - (void)insertObject:(XLFormRowDescriptor *)formRow inFormRowsAtIndex:(NSUInteger)index { formRow.sectionDescriptor = self; [self.formRows insertObject:formRow atIndex:index]; } - (void)removeObjectFromFormRowsAtIndex:(NSUInteger)index { [self.formRows removeObjectAtIndex:index]; } #pragma mark - KVC ALL -(NSUInteger)countOfAllRows { return self.allRows.count; } - (id)objectInAllRowsAtIndex:(NSUInteger)index { return [self.allRows objectAtIndex:index]; } - (NSArray *)allRowsAtIndexes:(NSIndexSet *)indexes { return [self.allRows objectsAtIndexes:indexes]; } - (void)insertObject:(XLFormRowDescriptor *)row inAllRowsAtIndex:(NSUInteger)index { row.sectionDescriptor = self; [self.formDescriptor addRowToTagCollection:row]; [self.allRows insertObject:row atIndex:index]; row.disabled = row.disabled; row.hidden = row.hidden; } - (void)removeObjectFromAllRowsAtIndex:(NSUInteger)index { XLFormRowDescriptor * row = [self.allRows objectAtIndex:index]; [self.formDescriptor removeRowFromTagCollection:row]; [self.formDescriptor removeObserversOfObject:row predicateType:XLPredicateTypeDisabled]; [self.formDescriptor removeObserversOfObject:row predicateType:XLPredicateTypeHidden]; [self.allRows removeObjectAtIndex:index]; } #pragma mark - Helpers -(BOOL)canInsertUsingButton { return (self.sectionInsertMode == XLFormSectionInsertModeButton && self.sectionOptions & XLFormSectionOptionCanInsert); } #pragma mark - Predicates -(NSNumber *)hidePredicateCache { return _hidePredicateCache; } -(void)setHidePredicateCache:(NSNumber *)hidePredicateCache { NSParameterAssert(hidePredicateCache); self.isDirtyHidePredicateCache = NO; if (!_hidePredicateCache || ![_hidePredicateCache isEqualToNumber:hidePredicateCache]){ _hidePredicateCache = hidePredicateCache; } } -(BOOL)isHidden { if (self.isDirtyHidePredicateCache) { return [self evaluateIsHidden]; } return [self.hidePredicateCache boolValue]; } -(BOOL)evaluateIsHidden { if ([_hidden isKindOfClass:[NSPredicate class]]) { if (!self.formDescriptor) { self.isDirtyHidePredicateCache = YES; } else { @try { self.hidePredicateCache = @([_hidden evaluateWithObject:self substitutionVariables:self.formDescriptor.allRowsByTag ?: @{}]); } @catch (NSException *exception) { // predicate syntax error. self.isDirtyHidePredicateCache = YES; }; } } else{ self.hidePredicateCache = _hidden; } if ([self.hidePredicateCache boolValue]){ if ([self.formDescriptor.delegate isKindOfClass:[XLFormViewController class]]){ XLFormBaseCell* firtResponder = (XLFormBaseCell*) [((XLFormViewController*)self.formDescriptor.delegate).tableView findFirstResponder]; if ([firtResponder isKindOfClass:[XLFormBaseCell class]] && firtResponder.rowDescriptor.sectionDescriptor == self){ [firtResponder resignFirstResponder]; } } [self.formDescriptor hideFormSection:self]; } else{ [self.formDescriptor showFormSection:self]; } return [self.hidePredicateCache boolValue]; } -(id)hidden { return _hidden; } -(void)setHidden:(id)hidden { if ([_hidden isKindOfClass:[NSPredicate class]]){ [self.formDescriptor removeObserversOfObject:self predicateType:XLPredicateTypeHidden]; } _hidden = [hidden isKindOfClass:[NSString class]] ? [hidden formPredicate] : hidden; if ([_hidden isKindOfClass:[NSPredicate class]]){ [self.formDescriptor addObserversOfObject:self predicateType:XLPredicateTypeHidden]; } [self evaluateIsHidden]; // check and update if this row should be hidden. } @end