2
0

Edit indicator, cell reuse fix and item reloading improvement.

[ADDED]     Indicator when user needs to find the edit button to either set a password or login name.
[FIXED]     Cell reuse when changing between transient / element.
[IMPROVED]  Collection view item reloading now avoids reloading items that stay the same and animates better.
This commit is contained in:
Maarten Billemont 2014-08-21 21:51:47 -04:00
parent 2aebcadf70
commit 27f6bd7905
7 changed files with 213 additions and 111 deletions

2
External/Pearl vendored

@ -1 +1 @@
Subproject commit a4c734e07c7441b4a49e83ae0a5de02158095ed7 Subproject commit 5c183fe325c7225c06910b81ac64117eb8a7aad2

Binary file not shown.

View File

@ -35,6 +35,7 @@
@property(nonatomic, strong) IBOutlet UIButton *editButton; @property(nonatomic, strong) IBOutlet UIButton *editButton;
@property(nonatomic, strong) IBOutlet UIScrollView *modeScrollView; @property(nonatomic, strong) IBOutlet UIScrollView *modeScrollView;
@property(nonatomic, strong) IBOutlet UIButton *selectionButton; @property(nonatomic, strong) IBOutlet UIButton *selectionButton;
@property(nonatomic, strong) IBOutlet UIView *indicatorView;
@property(nonatomic) MPPasswordCellMode mode; @property(nonatomic) MPPasswordCellMode mode;
@property(nonatomic, copy) NSString *transientSite; @property(nonatomic, copy) NSString *transientSite;
@ -66,12 +67,19 @@
[self.selectionButton observeKeyPath:@"highlighted" [self.selectionButton observeKeyPath:@"highlighted"
withBlock:^(id from, id to, NSKeyValueChange cause, UIButton *button) { withBlock:^(id from, id to, NSKeyValueChange cause, UIButton *button) {
button.layer.shadowOpacity = button.selected? 1: button.highlighted? 0.3f: 0; button.layer.shadowOpacity = button.selected? 1: button.highlighted? 0.3f: 0;
}]; }];
[self.selectionButton observeKeyPath:@"selected" [self.selectionButton observeKeyPath:@"selected"
withBlock:^(id from, id to, NSKeyValueChange cause, UIButton *button) { withBlock:^(id from, id to, NSKeyValueChange cause, UIButton *button) {
button.layer.shadowOpacity = button.selected? 1: button.highlighted? 0.3f: 0; button.layer.shadowOpacity = button.selected? 1: button.highlighted? 0.3f: 0;
}]; }];
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position.y"];
animation.byValue = @(10);
animation.repeatCount = HUGE_VALF;
animation.autoreverses = YES;
animation.duration = 0.3f;
[self.indicatorView.layer addAnimation:animation forKey:@"bounce"];
} }
- (void)prepareForReuse { - (void)prepareForReuse {
@ -79,6 +87,7 @@
[super prepareForReuse]; [super prepareForReuse];
_elementOID = nil; _elementOID = nil;
self.transientSite = nil;
self.loginModeButton.selected = NO; self.loginModeButton.selected = NO;
self.mode = MPPasswordCellModePassword; self.mode = MPPasswordCellModePassword;
[self updateAnimated:NO]; [self updateAnimated:NO];
@ -182,14 +191,14 @@
[PearlSheet showSheetWithTitle:strf( @"Delete %@?", element.name ) viewStyle:UIActionSheetStyleAutomatic [PearlSheet showSheetWithTitle:strf( @"Delete %@?", element.name ) viewStyle:UIActionSheetStyleAutomatic
initSheet:nil tappedButtonBlock:^(UIActionSheet *sheet, NSInteger buttonIndex) { initSheet:nil tappedButtonBlock:^(UIActionSheet *sheet, NSInteger buttonIndex) {
if (buttonIndex == [sheet cancelButtonIndex]) if (buttonIndex == [sheet cancelButtonIndex])
return; return;
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
[context deleteObject:[self elementInContext:context]]; [context deleteObject:[self elementInContext:context]];
[context saveToStore]; [context saveToStore];
}]; }];
} cancelTitle:@"Cancel" destructiveTitle:@"Delete Site" otherTitles:nil]; } cancelTitle:@"Cancel" destructiveTitle:@"Delete Site" otherTitles:nil];
} }
- (IBAction)doChangeType:(UIButton *)sender { - (IBAction)doChangeType:(UIButton *)sender {
@ -198,27 +207,28 @@
[PearlSheet showSheetWithTitle:@"Change Password Type" viewStyle:UIActionSheetStyleAutomatic [PearlSheet showSheetWithTitle:@"Change Password Type" viewStyle:UIActionSheetStyleAutomatic
initSheet:^(UIActionSheet *sheet) { initSheet:^(UIActionSheet *sheet) {
MPElementEntity *mainElement = [self elementInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]]; MPElementEntity
for (NSNumber *typeNumber in [MPAlgorithmDefault allTypes]) { *mainElement = [self elementInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]];
MPElementType type = [typeNumber unsignedIntegerValue]; for (NSNumber *typeNumber in [MPAlgorithmDefault allTypes]) {
NSString *typeName = [MPAlgorithmDefault nameOfType:type]; MPElementType type = [typeNumber unsignedIntegerValue];
if (type == mainElement.type) NSString *typeName = [MPAlgorithmDefault nameOfType:type];
[sheet addButtonWithTitle:strf( @"● %@", typeName )]; if (type == mainElement.type)
else [sheet addButtonWithTitle:strf( @"● %@", typeName )];
[sheet addButtonWithTitle:typeName]; else
} [sheet addButtonWithTitle:typeName];
} tappedButtonBlock:^(UIActionSheet *sheet, NSInteger buttonIndex) { }
if (buttonIndex == [sheet cancelButtonIndex]) } tappedButtonBlock:^(UIActionSheet *sheet, NSInteger buttonIndex) {
return; if (buttonIndex == [sheet cancelButtonIndex])
return;
MPElementType type = [[MPAlgorithmDefault allTypes][buttonIndex] unsignedIntegerValue]?: MPElementTypeGeneratedLong; MPElementType type = [[MPAlgorithmDefault allTypes][buttonIndex] unsignedIntegerValue]?: MPElementTypeGeneratedLong;
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPElementEntity *element = [self elementInContext:context]; MPElementEntity *element = [self elementInContext:context];
element = [[MPiOSAppDelegate get] changeElement:element saveInContext:context toType:type]; element = [[MPiOSAppDelegate get] changeElement:element saveInContext:context toType:type];
[self setElement:element animated:YES]; [self setElement:element animated:YES];
}]; }];
} cancelTitle:@"Cancel" destructiveTitle:nil otherTitles:nil]; } cancelTitle:@"Cancel" destructiveTitle:nil otherTitles:nil];
} }
- (IBAction)doEdit:(UIButton *)sender { - (IBAction)doEdit:(UIButton *)sender {
@ -318,19 +328,19 @@
message:strf( @"Remember site named:\n%@", self.transientSite ) message:strf( @"Remember site named:\n%@", self.transientSite )
viewStyle:UIAlertViewStyleDefault viewStyle:UIAlertViewStyleDefault
initAlert:nil tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) { initAlert:nil tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
if (buttonIndex == [alert cancelButtonIndex]) { if (buttonIndex == [alert cancelButtonIndex]) {
self.selectionButton.selected = NO; self.selectionButton.selected = NO;
return; return;
} }
[[MPiOSAppDelegate get] [[MPiOSAppDelegate get]
addElementNamed:self.transientSite completion:^(MPElementEntity *element, NSManagedObjectContext *context) { addElementNamed:self.transientSite completion:^(MPElementEntity *element, NSManagedObjectContext *context) {
[self copyContentOfElement:element saveInContext:context]; [self copyContentOfElement:element saveInContext:context];
PearlMainQueue( ^{ PearlMainQueue( ^{
self.selectionButton.selected = NO; self.selectionButton.selected = NO;
} ); } );
}]; }];
} cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonYes, nil]; } cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonYes, nil];
return; return;
} }
@ -372,13 +382,39 @@
self.passwordField.alpha = self.loginModeButton.selected? 0: 1; self.passwordField.alpha = self.loginModeButton.selected? 0: 1;
self.loginNameField.alpha = self.loginModeButton.selected? 1: 0; self.loginNameField.alpha = self.loginModeButton.selected? 1: 0;
self.modeButton.alpha = self.transientSite? 0: 1; self.modeButton.alpha = self.transientSite? 0: 1;
self.loginModeButton.alpha = self.transientSite? 0: 1;
self.counterLabel.alpha = self.counterButton.alpha = mainElement.type & MPElementTypeClassGenerated? 1: 0; self.counterLabel.alpha = self.counterButton.alpha = mainElement.type & MPElementTypeClassGenerated? 1: 0;
self.modeButton.selected = self.mode == MPPasswordCellModeSettings; self.modeButton.selected = self.mode == MPPasswordCellModeSettings;
self.pageControl.currentPage = self.mode == MPPasswordCellModePassword? 0: 1; self.pageControl.currentPage = self.mode == MPPasswordCellModePassword? 0: 1;
self.strengthLabel.alpha = self.mode == MPPasswordCellModePassword? 0: 1; self.strengthLabel.alpha = self.mode == MPPasswordCellModePassword? 0: 1;
self.editButton.enabled = self.loginModeButton.selected || mainElement.type & MPElementTypeClassStored; self.editButton.enabled = self.loginModeButton.selected || mainElement.type & MPElementTypeClassStored;
self.modeScrollView.scrollEnabled = !self.transientSite;
self.pageControl.alpha = self.transientSite? 0: 1;
[self.modeScrollView setContentOffset:CGPointMake( self.mode * self.modeScrollView.frame.size.width, 0 ) animated:animated]; [self.modeScrollView setContentOffset:CGPointMake( self.mode * self.modeScrollView.frame.size.width, 0 ) animated:animated];
// Indicator
if (self.loginModeButton.selected) {
if ([mainElement.loginName length])
self.indicatorView.alpha = 0;
else {
self.indicatorView.alpha = 1;
[self.indicatorView removeFromSuperview];
[self.modeScrollView addSubview:self.indicatorView];
[self.contentView addConstraintsWithVisualFormat:@"V:[indicator][view]" options:NSLayoutFormatAlignAllCenterX
metrics:nil views:@{
@"indicator" : self.indicatorView,
@"view" : self.mode == MPPasswordCellModeSettings? self.editButton: self.modeButton
}];
}
}
switch (self.mode) {
case MPPasswordCellModePassword:
if (mainElement.type & MPElementTypeClassStored)
break;
case MPPasswordCellModeSettings:
break;
}
// Site Name // Site Name
self.siteNameLabel.text = strl( @"%@ - %@", self.transientSite?: mainElement.name, self.siteNameLabel.text = strl( @"%@ - %@", self.transientSite?: mainElement.name,
self.transientSite? @"Tap to create": [mainElement.algorithm shortNameOfType:mainElement.type] ); self.transientSite? @"Tap to create": [mainElement.algorithm shortNameOfType:mainElement.type] );
@ -389,13 +425,13 @@
self.passwordField.attributedPlaceholder = stra( self.passwordField.attributedPlaceholder = stra(
mainElement.type & MPElementTypeClassStored? strl( @"No password" ): mainElement.type & MPElementTypeClassStored? strl( @"No password" ):
mainElement.type & MPElementTypeClassGenerated? strl( @"..." ): @"", @{ mainElement.type & MPElementTypeClassGenerated? strl( @"..." ): @"", @{
NSForegroundColorAttributeName : [UIColor whiteColor] NSForegroundColorAttributeName : [UIColor whiteColor]
} ); } );
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
NSString *password; NSString *password;
if (self.transientSite) if (self.transientSite)
password = [MPAlgorithmDefault generateContentNamed:self.transientSite ofType: password = [MPAlgorithmDefault generateContentNamed:self.transientSite ofType:
[[MPiOSAppDelegate get] activeUserInContext:context].defaultType?: MPElementTypeGeneratedLong [[MPiOSAppDelegate get] activeUserInContext:context].defaultType?: MPElementTypeGeneratedLong
withCounter:1 usingKey:[MPiOSAppDelegate get].key]; withCounter:1 usingKey:[MPiOSAppDelegate get].key];
else else
password = [[self elementInContext:context] resolveContentUsingKey:[MPiOSAppDelegate get].key]; password = [[self elementInContext:context] resolveContentUsingKey:[MPiOSAppDelegate get].key];
@ -411,6 +447,21 @@
PearlMainQueue( ^{ PearlMainQueue( ^{
self.passwordField.text = password; self.passwordField.text = password;
self.strengthLabel.text = timeToCrackString; self.strengthLabel.text = timeToCrackString;
if (!self.loginModeButton.selected) {
if ([password length])
self.indicatorView.alpha = 0;
else {
self.indicatorView.alpha = 1;
[self.indicatorView removeFromSuperview];
[self.modeScrollView addSubview:self.indicatorView];
[self.contentView addConstraintsWithVisualFormat:@"V:[indicator][view]" options:NSLayoutFormatAlignAllCenterX
metrics:nil views:@{
@"indicator" : self.indicatorView,
@"view" : self.mode == MPPasswordCellModeSettings? self.editButton: self.modeButton
}];
}
}
} ); } );
}]; }];

View File

@ -22,6 +22,7 @@
#import "MPPopdownSegue.h" #import "MPPopdownSegue.h"
#import "MPAppDelegate_Key.h" #import "MPAppDelegate_Key.h"
#import "MPPasswordCell.h" #import "MPPasswordCell.h"
#import "UICollectionView+PearlReloadFromArray.h"
@interface MPPasswordsViewController()<NSFetchedResultsControllerDelegate> @interface MPPasswordsViewController()<NSFetchedResultsControllerDelegate>
@ -36,10 +37,11 @@
NSArray *_notificationObservers; NSArray *_notificationObservers;
__weak UITapGestureRecognizer *_passwordsDismissRecognizer; __weak UITapGestureRecognizer *_passwordsDismissRecognizer;
NSFetchedResultsController *_fetchedResultsController; NSFetchedResultsController *_fetchedResultsController;
BOOL _exactMatch;
UIColor *_backgroundColor; UIColor *_backgroundColor;
UIColor *_darkenedBackgroundColor; UIColor *_darkenedBackgroundColor;
__weak UIViewController *_popdownVC; __weak UIViewController *_popdownVC;
BOOL _showTransientItem;
NSUInteger _transientItem;
} }
#pragma mark - Life #pragma mark - Life
@ -50,6 +52,7 @@
_backgroundColor = self.passwordCollectionView.backgroundColor; _backgroundColor = self.passwordCollectionView.backgroundColor;
_darkenedBackgroundColor = [_backgroundColor colorWithAlphaComponent:0.6f]; _darkenedBackgroundColor = [_backgroundColor colorWithAlphaComponent:0.6f];
_transientItem = NSNotFound;
self.view.backgroundColor = [UIColor clearColor]; self.view.backgroundColor = [UIColor clearColor];
[self.passwordCollectionView automaticallyAdjustInsetsForKeyboard]; [self.passwordCollectionView automaticallyAdjustInsetsForKeyboard];
@ -115,9 +118,12 @@ referenceSizeForHeaderInSection:(NSInteger)section {
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return ![MPiOSAppDelegate get].activeUserOID? 0: if (![MPiOSAppDelegate get].activeUserOID)
((id<NSFetchedResultsSectionInfo>)self.fetchedResultsController.sections[section]).numberOfObjects + return 0;
(!_exactMatch && [[self query] length]? 1: 0);
NSUInteger objects = ((id<NSFetchedResultsSectionInfo>)self.fetchedResultsController.sections[section]).numberOfObjects;
_transientItem = _showTransientItem? objects: NSNotFound;
return objects + (_showTransientItem? 1: 0);
} }
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
@ -152,20 +158,23 @@ referenceSizeForHeaderInSection:(NSInteger)section {
forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath { forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
if (controller == _fetchedResultsController) { if (controller == _fetchedResultsController) {
switch (type) { [self.passwordCollectionView performBatchUpdates:^{
case NSFetchedResultsChangeInsert: [self fetchedItemsDidUpdate];
[self.passwordCollectionView insertItemsAtIndexPaths:@[ newIndexPath ]]; switch (type) {
break; case NSFetchedResultsChangeInsert:
case NSFetchedResultsChangeDelete: [self.passwordCollectionView insertItemsAtIndexPaths:@[ newIndexPath ]];
[self.passwordCollectionView deleteItemsAtIndexPaths:@[ indexPath ]]; break;
break; case NSFetchedResultsChangeDelete:
case NSFetchedResultsChangeMove: [self.passwordCollectionView deleteItemsAtIndexPaths:@[ indexPath ]];
[self.passwordCollectionView moveItemAtIndexPath:indexPath toIndexPath:newIndexPath]; break;
break; case NSFetchedResultsChangeMove:
case NSFetchedResultsChangeUpdate: [self.passwordCollectionView moveItemAtIndexPath:indexPath toIndexPath:newIndexPath];
[self.passwordCollectionView reloadItemsAtIndexPaths:@[ indexPath ]]; break;
break; case NSFetchedResultsChangeUpdate:
} [self.passwordCollectionView reloadItemsAtIndexPaths:@[ indexPath ]];
break;
}
} completion:nil];
} }
} }
@ -229,6 +238,33 @@ referenceSizeForHeaderInSection:(NSInteger)section {
#pragma mark - Private #pragma mark - Private
- (void)fetchedItemsDidUpdate {
NSString *query = self.query;
_showTransientItem = [query length] > 0;
NSUInteger objects = ((id<NSFetchedResultsSectionInfo>)self.fetchedResultsController.sections[0]).numberOfObjects;
if (_showTransientItem && objects == 1 &&
[[[self.fetchedResultsController.fetchedObjects firstObject] name] isEqualToString:query])
_showTransientItem = NO;
if ([self.passwordCollectionView numberOfSections] > 0) {
if (!_showTransientItem && _transientItem != NSNotFound) {
dbg( @"delete transient item: %d", [self.passwordCollectionView numberOfItemsInSection:0] - 1 );
[self.passwordCollectionView deleteItemsAtIndexPaths:
@[ [NSIndexPath indexPathForItem:_transientItem inSection:0] ]];
}
else if (_showTransientItem && _transientItem == NSNotFound) {
dbg( @"insert transient item: %d", objects );
[self.passwordCollectionView insertItemsAtIndexPaths:
@[ [NSIndexPath indexPathForItem:objects inSection:0] ]];
}
else if (_transientItem != NSNotFound) {
dbg( @"reload transient item: %d", objects );
[self.passwordCollectionView reloadItemsAtIndexPaths:
@[ [NSIndexPath indexPathForItem:_transientItem inSection:0] ]];
}
}
}
- (void)registerObservers { - (void)registerObservers {
if ([_notificationObservers count]) if ([_notificationObservers count])
@ -239,34 +275,34 @@ referenceSizeForHeaderInSection:(NSInteger)section {
[[NSNotificationCenter defaultCenter] [[NSNotificationCenter defaultCenter]
addObserverForName:UIApplicationWillResignActiveNotification object:nil addObserverForName:UIApplicationWillResignActiveNotification object:nil
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
Strongify( self ); Strongify( self );
self.passwordSelectionContainer.alpha = 0; self.passwordSelectionContainer.alpha = 0;
}], }],
[[NSNotificationCenter defaultCenter] [[NSNotificationCenter defaultCenter]
addObserverForName:MPSignedOutNotification object:nil addObserverForName:MPSignedOutNotification object:nil
queue:nil usingBlock:^(NSNotification *note) { queue:nil usingBlock:^(NSNotification *note) {
Strongify( self ); Strongify( self );
_fetchedResultsController = nil; _fetchedResultsController = nil;
self.passwordsSearchBar.text = nil; self.passwordsSearchBar.text = nil;
[self updatePasswords]; [self updatePasswords];
}], }],
[[NSNotificationCenter defaultCenter] [[NSNotificationCenter defaultCenter]
addObserverForName:UIApplicationDidBecomeActiveNotification object:nil addObserverForName:UIApplicationDidBecomeActiveNotification object:nil
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
Strongify( self ); Strongify( self );
[self updatePasswords]; [self updatePasswords];
[UIView animateWithDuration:1 animations:^{ [UIView animateWithDuration:1 animations:^{
self.passwordSelectionContainer.alpha = 1; self.passwordSelectionContainer.alpha = 1;
}]; }];
}], }],
[[NSNotificationCenter defaultCenter] [[NSNotificationCenter defaultCenter]
addObserverForName:MPCheckConfigNotification object:nil queue:[NSOperationQueue mainQueue] addObserverForName:MPCheckConfigNotification object:nil queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification *note) { usingBlock:^(NSNotification *note) {
[self updateConfigKey:note.object]; [self updateConfigKey:note.object];
}], }],
]; ];
} }
@ -329,6 +365,8 @@ referenceSizeForHeaderInSection:(NSInteger)section {
} }
[self.fetchedResultsController.managedObjectContext performBlock:^{ [self.fetchedResultsController.managedObjectContext performBlock:^{
NSArray *oldSections = [self.fetchedResultsController sections];
NSError *error = nil; NSError *error = nil;
self.fetchedResultsController.fetchRequest.predicate = self.fetchedResultsController.fetchRequest.predicate =
[query length]? [query length]?
@ -337,37 +375,32 @@ referenceSizeForHeaderInSection:(NSInteger)section {
if (![self.fetchedResultsController performFetch:&error]) if (![self.fetchedResultsController performFetch:&error])
err( @"Couldn't fetch elements: %@", error ); err( @"Couldn't fetch elements: %@", error );
_exactMatch = NO; [self.passwordCollectionView performBatchUpdates:^{
for (MPElementEntity *entity in self.fetchedResultsController.fetchedObjects) [self fetchedItemsDidUpdate];
if ([entity.name isEqualToString:query]) {
_exactMatch = YES;
break;
}
PearlMainQueue( ^{ NSInteger fromSections = self.passwordCollectionView.numberOfSections;
[self.passwordCollectionView performBatchUpdates:^{ NSInteger toSections = [self numberOfSectionsInCollectionView:self.passwordCollectionView];
NSInteger fromSections = self.passwordCollectionView.numberOfSections; for (NSInteger section = 0; section < MAX( toSections, fromSections ); ++section) {
NSInteger toSections = [self numberOfSectionsInCollectionView:self.passwordCollectionView]; if (section >= fromSections) {
for (int section = 0; section < MAX( toSections, fromSections ); section++) { dbg( @"insertSections:%d", section );
if (section >= fromSections) { [self.passwordCollectionView insertSections:[NSIndexSet indexSetWithIndex:section]];
dbg( @"insertSections:%d", section );
[self.passwordCollectionView insertSections:[NSIndexSet indexSetWithIndex:section]];
}
else if (section >= toSections) {
dbg( @"deleteSections:%d", section );
[self.passwordCollectionView deleteSections:[NSIndexSet indexSetWithIndex:section]];
}
else {
dbg( @"reloadSections:%d", section );
[self.passwordCollectionView reloadSections:[NSIndexSet indexSetWithIndex:section]];
}
} }
} completion:^(BOOL finished) { else if (section >= toSections) {
if (finished) dbg( @"deleteSections:%d", section );
[self.passwordCollectionView setContentOffset:CGPointMake( 0, -self.passwordCollectionView.contentInset.top ) [self.passwordCollectionView deleteSections:[NSIndexSet indexSetWithIndex:section]];
animated:YES]; }
}]; else {
} ); dbg( @"reloadItemsInSection:%d", section );
[self.passwordCollectionView reloadItemsFromArray:[oldSections[section] objects]
toArray:[[self.fetchedResultsController sections][section] objects]
inSection:section];
}
}
} completion:^(BOOL finished) {
if (finished)
[self.passwordCollectionView setContentOffset:CGPointMake( 0, -self.passwordCollectionView.contentInset.top )
animated:YES];
}];
}]; }];
} }

View File

@ -34,6 +34,7 @@
93D3992FA1546E01F498F665 /* PearlNavigationController.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D398567FD02DB2647B8CF3 /* PearlNavigationController.h */; }; 93D3992FA1546E01F498F665 /* PearlNavigationController.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D398567FD02DB2647B8CF3 /* PearlNavigationController.h */; };
93D399433EA75E50656040CB /* Twitter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 93D394077F8FAB8167647187 /* Twitter.framework */; }; 93D399433EA75E50656040CB /* Twitter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 93D394077F8FAB8167647187 /* Twitter.framework */; };
93D399BBC0A7EC746CB1B19B /* MPLogsViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D391943675426839501BB8 /* MPLogsViewController.h */; }; 93D399BBC0A7EC746CB1B19B /* MPLogsViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D391943675426839501BB8 /* MPLogsViewController.h */; };
93D39A53D76CA70786423458 /* UICollectionView+PearlReloadFromArray.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D39246FC21C6E63E35D615 /* UICollectionView+PearlReloadFromArray.h */; };
93D39A5FF670957C0AF8298D /* MPPasswordCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39DEA995041A13DC9CAF7 /* MPPasswordCell.m */; }; 93D39A5FF670957C0AF8298D /* MPPasswordCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39DEA995041A13DC9CAF7 /* MPPasswordCell.m */; };
93D39A8EA1C49CE43B63F47B /* PearlUICollectionView.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39D8A953779B35403AF6E /* PearlUICollectionView.m */; }; 93D39A8EA1C49CE43B63F47B /* PearlUICollectionView.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39D8A953779B35403AF6E /* PearlUICollectionView.m */; };
93D39B76DD5AB108BA8928E8 /* UIScrollView+PearlAdjustInsets.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D39DE2CB351D4E3789462B /* UIScrollView+PearlAdjustInsets.h */; }; 93D39B76DD5AB108BA8928E8 /* UIScrollView+PearlAdjustInsets.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D39DE2CB351D4E3789462B /* UIScrollView+PearlAdjustInsets.h */; };
@ -41,6 +42,7 @@
93D39B8F90F58A5D158DDBA3 /* MPPasswordsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3924EE15017F8A12CB436 /* MPPasswordsViewController.m */; }; 93D39B8F90F58A5D158DDBA3 /* MPPasswordsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3924EE15017F8A12CB436 /* MPPasswordsViewController.m */; };
93D39BA1EA3CAAC8A220B4A6 /* MPAppSettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3916C1D8F1427DFBDEBCA /* MPAppSettingsViewController.m */; }; 93D39BA1EA3CAAC8A220B4A6 /* MPAppSettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3916C1D8F1427DFBDEBCA /* MPAppSettingsViewController.m */; };
93D39C34FE35830EF5BE1D2A /* NSArray+Indexing.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D396D04E57792A54D437AC /* NSArray+Indexing.h */; }; 93D39C34FE35830EF5BE1D2A /* NSArray+Indexing.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D396D04E57792A54D437AC /* NSArray+Indexing.h */; };
93D39D47FC623E91FC39D20C /* UICollectionView+PearlReloadFromArray.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3908DF8EABBD952065DC0 /* UICollectionView+PearlReloadFromArray.m */; };
93D39D596A2E376D6F6F5DA1 /* MPCombinedViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D393310223DDB35218467A /* MPCombinedViewController.m */; }; 93D39D596A2E376D6F6F5DA1 /* MPCombinedViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D393310223DDB35218467A /* MPCombinedViewController.m */; };
93D39D8F78978196D6ABDEDE /* MPNavigationController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39CC01630D0421205C4C4 /* MPNavigationController.m */; }; 93D39D8F78978196D6ABDEDE /* MPNavigationController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39CC01630D0421205C4C4 /* MPNavigationController.m */; };
93D39E281E3658B30550CB55 /* NSDictionary+Indexing.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39AA1EE2E1E7B81372240 /* NSDictionary+Indexing.m */; }; 93D39E281E3658B30550CB55 /* NSDictionary+Indexing.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39AA1EE2E1E7B81372240 /* NSDictionary+Indexing.m */; };
@ -381,12 +383,14 @@
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
93D390519405B76CC6A57C4F /* MPCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPCell.h; sourceTree = "<group>"; }; 93D390519405B76CC6A57C4F /* MPCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPCell.h; sourceTree = "<group>"; };
93D39067C0AFDC581794E2B8 /* NSArray+Indexing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSArray+Indexing.m"; sourceTree = "<group>"; }; 93D39067C0AFDC581794E2B8 /* NSArray+Indexing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSArray+Indexing.m"; sourceTree = "<group>"; };
93D3908DF8EABBD952065DC0 /* UICollectionView+PearlReloadFromArray.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UICollectionView+PearlReloadFromArray.m"; sourceTree = "<group>"; };
93D390FADEB325D8D54A957D /* PearlOverlay.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlOverlay.m; sourceTree = "<group>"; }; 93D390FADEB325D8D54A957D /* PearlOverlay.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlOverlay.m; sourceTree = "<group>"; };
93D390FB3110DCCE68E600DC /* UIScrollView+PearlAdjustInsets.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIScrollView+PearlAdjustInsets.m"; sourceTree = "<group>"; }; 93D390FB3110DCCE68E600DC /* UIScrollView+PearlAdjustInsets.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIScrollView+PearlAdjustInsets.m"; sourceTree = "<group>"; };
93D3914D7597F9A28DB9D85E /* MPPasswordsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPasswordsViewController.h; sourceTree = "<group>"; }; 93D3914D7597F9A28DB9D85E /* MPPasswordsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPasswordsViewController.h; sourceTree = "<group>"; };
93D39156E806BB78E04F78B9 /* PearlSizedTextView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlSizedTextView.m; sourceTree = "<group>"; }; 93D39156E806BB78E04F78B9 /* PearlSizedTextView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlSizedTextView.m; sourceTree = "<group>"; };
93D3916C1D8F1427DFBDEBCA /* MPAppSettingsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAppSettingsViewController.m; sourceTree = "<group>"; }; 93D3916C1D8F1427DFBDEBCA /* MPAppSettingsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAppSettingsViewController.m; sourceTree = "<group>"; };
93D391943675426839501BB8 /* MPLogsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPLogsViewController.h; sourceTree = "<group>"; }; 93D391943675426839501BB8 /* MPLogsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPLogsViewController.h; sourceTree = "<group>"; };
93D39246FC21C6E63E35D615 /* UICollectionView+PearlReloadFromArray.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UICollectionView+PearlReloadFromArray.h"; sourceTree = "<group>"; };
93D3924EE15017F8A12CB436 /* MPPasswordsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPPasswordsViewController.m; sourceTree = "<group>"; }; 93D3924EE15017F8A12CB436 /* MPPasswordsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPPasswordsViewController.m; sourceTree = "<group>"; };
93D392876BE5C011DE73B43F /* MPPopdownSegue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPopdownSegue.h; sourceTree = "<group>"; }; 93D392876BE5C011DE73B43F /* MPPopdownSegue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPopdownSegue.h; sourceTree = "<group>"; };
93D393310223DDB35218467A /* MPCombinedViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPCombinedViewController.m; sourceTree = "<group>"; }; 93D393310223DDB35218467A /* MPCombinedViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPCombinedViewController.m; sourceTree = "<group>"; };
@ -2628,6 +2632,8 @@
93D39A4759186F6D2D34AA6B /* PearlSizedTextView.h */, 93D39A4759186F6D2D34AA6B /* PearlSizedTextView.h */,
93D3977321EB249981821AB0 /* UITextView+PearlAttributes.m */, 93D3977321EB249981821AB0 /* UITextView+PearlAttributes.m */,
93D39AA10CD00D05937671B1 /* UITextView+PearlAttributes.h */, 93D39AA10CD00D05937671B1 /* UITextView+PearlAttributes.h */,
93D3908DF8EABBD952065DC0 /* UICollectionView+PearlReloadFromArray.m */,
93D39246FC21C6E63E35D615 /* UICollectionView+PearlReloadFromArray.h */,
); );
path = "Pearl-UIKit"; path = "Pearl-UIKit";
sourceTree = "<group>"; sourceTree = "<group>";
@ -2741,6 +2747,7 @@
93D39536EB550E811CCD04BC /* UIResponder+PearlFirstResponder.h in Headers */, 93D39536EB550E811CCD04BC /* UIResponder+PearlFirstResponder.h in Headers */,
93D393DB5325820241BA90A7 /* PearlSizedTextView.h in Headers */, 93D393DB5325820241BA90A7 /* PearlSizedTextView.h in Headers */,
93D392A8777DC30C11361647 /* UITextView+PearlAttributes.h in Headers */, 93D392A8777DC30C11361647 /* UITextView+PearlAttributes.h in Headers */,
93D39A53D76CA70786423458 /* UICollectionView+PearlReloadFromArray.h in Headers */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -3308,6 +3315,7 @@
DAA141201922FF020032B392 /* PearlTween.m in Sources */, DAA141201922FF020032B392 /* PearlTween.m in Sources */,
93D391ECBD9BD2C64115B5DD /* PearlSizedTextView.m in Sources */, 93D391ECBD9BD2C64115B5DD /* PearlSizedTextView.m in Sources */,
93D39E34FD28D24FE3442C48 /* UITextView+PearlAttributes.m in Sources */, 93D39E34FD28D24FE3442C48 /* UITextView+PearlAttributes.m in Sources */,
93D39D47FC623E91FC39D20C /* UICollectionView+PearlReloadFromArray.m in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };

View File

@ -63,7 +63,7 @@
isEnabled = "YES"> isEnabled = "YES">
</CommandLineArgument> </CommandLineArgument>
<CommandLineArgument <CommandLineArgument
argument = "3" argument = "0"
isEnabled = "YES"> isEnabled = "YES">
</CommandLineArgument> </CommandLineArgument>
</CommandLineArguments> </CommandLineArguments>

View File

@ -1262,6 +1262,13 @@
<action selector="doMode:" destination="W2g-yv-V3V" eventType="touchUpInside" id="72U-sh-nce"/> <action selector="doMode:" destination="W2g-yv-V3V" eventType="touchUpInside" id="72U-sh-nce"/>
</connections> </connections>
</button> </button>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" alpha="0.0" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="⬇︎" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="tuh-Au-J9k">
<rect key="frame" x="270" y="35" width="17" height="21"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
<pageControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" numberOfPages="2" translatesAutoresizingMaskIntoConstraints="NO" id="5SS-x0-icl"> <pageControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" numberOfPages="2" translatesAutoresizingMaskIntoConstraints="NO" id="5SS-x0-icl">
<rect key="frame" x="138" y="77" width="23" height="37"/> <rect key="frame" x="138" y="77" width="23" height="37"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
@ -1273,6 +1280,7 @@
<constraint firstAttribute="bottom" secondItem="tl3-hd-x35" secondAttribute="bottom" id="2x2-iA-utK"/> <constraint firstAttribute="bottom" secondItem="tl3-hd-x35" secondAttribute="bottom" id="2x2-iA-utK"/>
<constraint firstItem="b5f-wN-2xb" firstAttribute="centerY" secondItem="I0v-ou-hDb" secondAttribute="centerY" id="KPv-e0-uR0"/> <constraint firstItem="b5f-wN-2xb" firstAttribute="centerY" secondItem="I0v-ou-hDb" secondAttribute="centerY" id="KPv-e0-uR0"/>
<constraint firstItem="LvK-28-fbm" firstAttribute="leading" secondItem="tl3-hd-x35" secondAttribute="trailing" id="NXL-Fd-whf"/> <constraint firstItem="LvK-28-fbm" firstAttribute="leading" secondItem="tl3-hd-x35" secondAttribute="trailing" id="NXL-Fd-whf"/>
<constraint firstItem="b5f-wN-2xb" firstAttribute="top" secondItem="tuh-Au-J9k" secondAttribute="bottom" id="SBC-kJ-Gku"/>
<constraint firstItem="b5f-wN-2xb" firstAttribute="centerY" secondItem="Agd-0J-z5o" secondAttribute="centerY" id="W01-oa-2rY"/> <constraint firstItem="b5f-wN-2xb" firstAttribute="centerY" secondItem="Agd-0J-z5o" secondAttribute="centerY" id="W01-oa-2rY"/>
<constraint firstAttribute="bottom" secondItem="LvK-28-fbm" secondAttribute="bottom" id="ZBx-Lf-XHF"/> <constraint firstAttribute="bottom" secondItem="LvK-28-fbm" secondAttribute="bottom" id="ZBx-Lf-XHF"/>
<constraint firstItem="b5f-wN-2xb" firstAttribute="centerY" secondItem="vGk-t6-hZn" secondAttribute="centerY" id="fGB-8g-u5b"/> <constraint firstItem="b5f-wN-2xb" firstAttribute="centerY" secondItem="vGk-t6-hZn" secondAttribute="centerY" id="fGB-8g-u5b"/>
@ -1280,6 +1288,7 @@
<constraint firstItem="tl3-hd-x35" firstAttribute="top" secondItem="bff-RU-OcY" secondAttribute="top" id="jc9-39-FY3"/> <constraint firstItem="tl3-hd-x35" firstAttribute="top" secondItem="bff-RU-OcY" secondAttribute="top" id="jc9-39-FY3"/>
<constraint firstItem="OwP-sb-Wxl" firstAttribute="centerY" secondItem="b5f-wN-2xb" secondAttribute="centerY" id="m22-uE-sVm"/> <constraint firstItem="OwP-sb-Wxl" firstAttribute="centerY" secondItem="b5f-wN-2xb" secondAttribute="centerY" id="m22-uE-sVm"/>
<constraint firstItem="K8q-bM-tzh" firstAttribute="centerY" secondItem="b5f-wN-2xb" secondAttribute="centerY" id="ofO-r3-bjz"/> <constraint firstItem="K8q-bM-tzh" firstAttribute="centerY" secondItem="b5f-wN-2xb" secondAttribute="centerY" id="ofO-r3-bjz"/>
<constraint firstItem="b5f-wN-2xb" firstAttribute="centerX" secondItem="tuh-Au-J9k" secondAttribute="centerX" id="pYG-bz-kd8"/>
<constraint firstAttribute="trailing" secondItem="LvK-28-fbm" secondAttribute="trailing" id="r9d-ym-Frs"/> <constraint firstAttribute="trailing" secondItem="LvK-28-fbm" secondAttribute="trailing" id="r9d-ym-Frs"/>
<constraint firstItem="LvK-28-fbm" firstAttribute="top" secondItem="bff-RU-OcY" secondAttribute="top" id="uqb-hB-Iq8"/> <constraint firstItem="LvK-28-fbm" firstAttribute="top" secondItem="bff-RU-OcY" secondAttribute="top" id="uqb-hB-Iq8"/>
<constraint firstItem="b5f-wN-2xb" firstAttribute="centerY" secondItem="uZi-FT-Fe8" secondAttribute="centerY" id="xz2-kK-B4g"/> <constraint firstItem="b5f-wN-2xb" firstAttribute="centerY" secondItem="uZi-FT-Fe8" secondAttribute="centerY" id="xz2-kK-B4g"/>
@ -1337,6 +1346,7 @@
<outlet property="counterButton" destination="uZi-FT-Fe8" id="oaF-bB-Sgc"/> <outlet property="counterButton" destination="uZi-FT-Fe8" id="oaF-bB-Sgc"/>
<outlet property="counterLabel" destination="PKP-M9-T8E" id="m9u-Qx-Z9N"/> <outlet property="counterLabel" destination="PKP-M9-T8E" id="m9u-Qx-Z9N"/>
<outlet property="editButton" destination="qBo-Kw-vN9" id="g0S-0o-e3D"/> <outlet property="editButton" destination="qBo-Kw-vN9" id="g0S-0o-e3D"/>
<outlet property="indicatorView" destination="tuh-Au-J9k" id="K2E-de-G5P"/>
<outlet property="loginModeButton" destination="Agd-0J-z5o" id="VQI-nl-5Yh"/> <outlet property="loginModeButton" destination="Agd-0J-z5o" id="VQI-nl-5Yh"/>
<outlet property="loginNameField" destination="3I9-vf-IZK" id="jK4-LI-4ST"/> <outlet property="loginNameField" destination="3I9-vf-IZK" id="jK4-LI-4ST"/>
<outlet property="modeButton" destination="b5f-wN-2xb" id="m2X-BZ-Lbv"/> <outlet property="modeButton" destination="b5f-wN-2xb" id="m2X-BZ-Lbv"/>
@ -2439,6 +2449,6 @@ See </string>
</simulatedMetricsContainer> </simulatedMetricsContainer>
<inferredMetricsTieBreakers> <inferredMetricsTieBreakers>
<segue reference="GZk-I4-JyH"/> <segue reference="GZk-I4-JyH"/>
<segue reference="k2G-nL-x3l"/> <segue reference="Ql4-wf-T8u"/>
</inferredMetricsTieBreakers> </inferredMetricsTieBreakers>
</document> </document>