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 UIScrollView *modeScrollView;
@property(nonatomic, strong) IBOutlet UIButton *selectionButton;
@property(nonatomic, strong) IBOutlet UIView *indicatorView;
@property(nonatomic) MPPasswordCellMode mode;
@property(nonatomic, copy) NSString *transientSite;
@ -66,12 +67,19 @@
[self.selectionButton observeKeyPath:@"highlighted"
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"
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 {
@ -79,6 +87,7 @@
[super prepareForReuse];
_elementOID = nil;
self.transientSite = nil;
self.loginModeButton.selected = NO;
self.mode = MPPasswordCellModePassword;
[self updateAnimated:NO];
@ -182,14 +191,14 @@
[PearlSheet showSheetWithTitle:strf( @"Delete %@?", element.name ) viewStyle:UIActionSheetStyleAutomatic
initSheet:nil tappedButtonBlock:^(UIActionSheet *sheet, NSInteger buttonIndex) {
if (buttonIndex == [sheet cancelButtonIndex])
return;
if (buttonIndex == [sheet cancelButtonIndex])
return;
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
[context deleteObject:[self elementInContext:context]];
[context saveToStore];
}];
} cancelTitle:@"Cancel" destructiveTitle:@"Delete Site" otherTitles:nil];
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
[context deleteObject:[self elementInContext:context]];
[context saveToStore];
}];
} cancelTitle:@"Cancel" destructiveTitle:@"Delete Site" otherTitles:nil];
}
- (IBAction)doChangeType:(UIButton *)sender {
@ -198,27 +207,28 @@
[PearlSheet showSheetWithTitle:@"Change Password Type" viewStyle:UIActionSheetStyleAutomatic
initSheet:^(UIActionSheet *sheet) {
MPElementEntity *mainElement = [self elementInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]];
for (NSNumber *typeNumber in [MPAlgorithmDefault allTypes]) {
MPElementType type = [typeNumber unsignedIntegerValue];
NSString *typeName = [MPAlgorithmDefault nameOfType:type];
if (type == mainElement.type)
[sheet addButtonWithTitle:strf( @"● %@", typeName )];
else
[sheet addButtonWithTitle:typeName];
}
} tappedButtonBlock:^(UIActionSheet *sheet, NSInteger buttonIndex) {
if (buttonIndex == [sheet cancelButtonIndex])
return;
MPElementEntity
*mainElement = [self elementInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]];
for (NSNumber *typeNumber in [MPAlgorithmDefault allTypes]) {
MPElementType type = [typeNumber unsignedIntegerValue];
NSString *typeName = [MPAlgorithmDefault nameOfType:type];
if (type == mainElement.type)
[sheet addButtonWithTitle:strf( @"● %@", typeName )];
else
[sheet addButtonWithTitle:typeName];
}
} tappedButtonBlock:^(UIActionSheet *sheet, NSInteger buttonIndex) {
if (buttonIndex == [sheet cancelButtonIndex])
return;
MPElementType type = [[MPAlgorithmDefault allTypes][buttonIndex] unsignedIntegerValue]?: MPElementTypeGeneratedLong;
MPElementType type = [[MPAlgorithmDefault allTypes][buttonIndex] unsignedIntegerValue]?: MPElementTypeGeneratedLong;
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPElementEntity *element = [self elementInContext:context];
element = [[MPiOSAppDelegate get] changeElement:element saveInContext:context toType:type];
[self setElement:element animated:YES];
}];
} cancelTitle:@"Cancel" destructiveTitle:nil otherTitles:nil];
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPElementEntity *element = [self elementInContext:context];
element = [[MPiOSAppDelegate get] changeElement:element saveInContext:context toType:type];
[self setElement:element animated:YES];
}];
} cancelTitle:@"Cancel" destructiveTitle:nil otherTitles:nil];
}
- (IBAction)doEdit:(UIButton *)sender {
@ -318,19 +328,19 @@
message:strf( @"Remember site named:\n%@", self.transientSite )
viewStyle:UIAlertViewStyleDefault
initAlert:nil tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
if (buttonIndex == [alert cancelButtonIndex]) {
self.selectionButton.selected = NO;
return;
}
if (buttonIndex == [alert cancelButtonIndex]) {
self.selectionButton.selected = NO;
return;
}
[[MPiOSAppDelegate get]
addElementNamed:self.transientSite completion:^(MPElementEntity *element, NSManagedObjectContext *context) {
[self copyContentOfElement:element saveInContext:context];
PearlMainQueue( ^{
self.selectionButton.selected = NO;
} );
}];
} cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonYes, nil];
[[MPiOSAppDelegate get]
addElementNamed:self.transientSite completion:^(MPElementEntity *element, NSManagedObjectContext *context) {
[self copyContentOfElement:element saveInContext:context];
PearlMainQueue( ^{
self.selectionButton.selected = NO;
} );
}];
} cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonYes, nil];
return;
}
@ -372,13 +382,39 @@
self.passwordField.alpha = self.loginModeButton.selected? 0: 1;
self.loginNameField.alpha = self.loginModeButton.selected? 1: 0;
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.modeButton.selected = self.mode == MPPasswordCellModeSettings;
self.pageControl.currentPage = self.mode == MPPasswordCellModePassword? 0: 1;
self.strengthLabel.alpha = self.mode == MPPasswordCellModePassword? 0: 1;
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];
// 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
self.siteNameLabel.text = strl( @"%@ - %@", self.transientSite?: mainElement.name,
self.transientSite? @"Tap to create": [mainElement.algorithm shortNameOfType:mainElement.type] );
@ -389,13 +425,13 @@
self.passwordField.attributedPlaceholder = stra(
mainElement.type & MPElementTypeClassStored? strl( @"No password" ):
mainElement.type & MPElementTypeClassGenerated? strl( @"..." ): @"", @{
NSForegroundColorAttributeName : [UIColor whiteColor]
} );
NSForegroundColorAttributeName : [UIColor whiteColor]
} );
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
NSString *password;
if (self.transientSite)
password = [MPAlgorithmDefault generateContentNamed:self.transientSite ofType:
[[MPiOSAppDelegate get] activeUserInContext:context].defaultType?: MPElementTypeGeneratedLong
[[MPiOSAppDelegate get] activeUserInContext:context].defaultType?: MPElementTypeGeneratedLong
withCounter:1 usingKey:[MPiOSAppDelegate get].key];
else
password = [[self elementInContext:context] resolveContentUsingKey:[MPiOSAppDelegate get].key];
@ -411,6 +447,21 @@
PearlMainQueue( ^{
self.passwordField.text = password;
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 "MPAppDelegate_Key.h"
#import "MPPasswordCell.h"
#import "UICollectionView+PearlReloadFromArray.h"
@interface MPPasswordsViewController()<NSFetchedResultsControllerDelegate>
@ -36,10 +37,11 @@
NSArray *_notificationObservers;
__weak UITapGestureRecognizer *_passwordsDismissRecognizer;
NSFetchedResultsController *_fetchedResultsController;
BOOL _exactMatch;
UIColor *_backgroundColor;
UIColor *_darkenedBackgroundColor;
__weak UIViewController *_popdownVC;
BOOL _showTransientItem;
NSUInteger _transientItem;
}
#pragma mark - Life
@ -50,6 +52,7 @@
_backgroundColor = self.passwordCollectionView.backgroundColor;
_darkenedBackgroundColor = [_backgroundColor colorWithAlphaComponent:0.6f];
_transientItem = NSNotFound;
self.view.backgroundColor = [UIColor clearColor];
[self.passwordCollectionView automaticallyAdjustInsetsForKeyboard];
@ -115,9 +118,12 @@ referenceSizeForHeaderInSection:(NSInteger)section {
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return ![MPiOSAppDelegate get].activeUserOID? 0:
((id<NSFetchedResultsSectionInfo>)self.fetchedResultsController.sections[section]).numberOfObjects +
(!_exactMatch && [[self query] length]? 1: 0);
if (![MPiOSAppDelegate get].activeUserOID)
return 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 {
@ -152,20 +158,23 @@ referenceSizeForHeaderInSection:(NSInteger)section {
forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
if (controller == _fetchedResultsController) {
switch (type) {
case NSFetchedResultsChangeInsert:
[self.passwordCollectionView insertItemsAtIndexPaths:@[ newIndexPath ]];
break;
case NSFetchedResultsChangeDelete:
[self.passwordCollectionView deleteItemsAtIndexPaths:@[ indexPath ]];
break;
case NSFetchedResultsChangeMove:
[self.passwordCollectionView moveItemAtIndexPath:indexPath toIndexPath:newIndexPath];
break;
case NSFetchedResultsChangeUpdate:
[self.passwordCollectionView reloadItemsAtIndexPaths:@[ indexPath ]];
break;
}
[self.passwordCollectionView performBatchUpdates:^{
[self fetchedItemsDidUpdate];
switch (type) {
case NSFetchedResultsChangeInsert:
[self.passwordCollectionView insertItemsAtIndexPaths:@[ newIndexPath ]];
break;
case NSFetchedResultsChangeDelete:
[self.passwordCollectionView deleteItemsAtIndexPaths:@[ indexPath ]];
break;
case NSFetchedResultsChangeMove:
[self.passwordCollectionView moveItemAtIndexPath:indexPath toIndexPath:newIndexPath];
break;
case NSFetchedResultsChangeUpdate:
[self.passwordCollectionView reloadItemsAtIndexPaths:@[ indexPath ]];
break;
}
} completion:nil];
}
}
@ -229,6 +238,33 @@ referenceSizeForHeaderInSection:(NSInteger)section {
#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 {
if ([_notificationObservers count])
@ -239,34 +275,34 @@ referenceSizeForHeaderInSection:(NSInteger)section {
[[NSNotificationCenter defaultCenter]
addObserverForName:UIApplicationWillResignActiveNotification object:nil
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
Strongify( self );
Strongify( self );
self.passwordSelectionContainer.alpha = 0;
}],
self.passwordSelectionContainer.alpha = 0;
}],
[[NSNotificationCenter defaultCenter]
addObserverForName:MPSignedOutNotification object:nil
queue:nil usingBlock:^(NSNotification *note) {
Strongify( self );
Strongify( self );
_fetchedResultsController = nil;
self.passwordsSearchBar.text = nil;
[self updatePasswords];
}],
_fetchedResultsController = nil;
self.passwordsSearchBar.text = nil;
[self updatePasswords];
}],
[[NSNotificationCenter defaultCenter]
addObserverForName:UIApplicationDidBecomeActiveNotification object:nil
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
Strongify( self );
Strongify( self );
[self updatePasswords];
[UIView animateWithDuration:1 animations:^{
self.passwordSelectionContainer.alpha = 1;
}];
}],
[self updatePasswords];
[UIView animateWithDuration:1 animations:^{
self.passwordSelectionContainer.alpha = 1;
}];
}],
[[NSNotificationCenter defaultCenter]
addObserverForName:MPCheckConfigNotification object:nil queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification *note) {
[self updateConfigKey:note.object];
}],
[self updateConfigKey:note.object];
}],
];
}
@ -329,6 +365,8 @@ referenceSizeForHeaderInSection:(NSInteger)section {
}
[self.fetchedResultsController.managedObjectContext performBlock:^{
NSArray *oldSections = [self.fetchedResultsController sections];
NSError *error = nil;
self.fetchedResultsController.fetchRequest.predicate =
[query length]?
@ -337,37 +375,32 @@ referenceSizeForHeaderInSection:(NSInteger)section {
if (![self.fetchedResultsController performFetch:&error])
err( @"Couldn't fetch elements: %@", error );
_exactMatch = NO;
for (MPElementEntity *entity in self.fetchedResultsController.fetchedObjects)
if ([entity.name isEqualToString:query]) {
_exactMatch = YES;
break;
}
[self.passwordCollectionView performBatchUpdates:^{
[self fetchedItemsDidUpdate];
PearlMainQueue( ^{
[self.passwordCollectionView performBatchUpdates:^{
NSInteger fromSections = self.passwordCollectionView.numberOfSections;
NSInteger toSections = [self numberOfSectionsInCollectionView:self.passwordCollectionView];
for (int section = 0; section < MAX( toSections, fromSections ); section++) {
if (section >= fromSections) {
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]];
}
NSInteger fromSections = self.passwordCollectionView.numberOfSections;
NSInteger toSections = [self numberOfSectionsInCollectionView:self.passwordCollectionView];
for (NSInteger section = 0; section < MAX( toSections, fromSections ); ++section) {
if (section >= fromSections) {
dbg( @"insertSections:%d", section );
[self.passwordCollectionView insertSections:[NSIndexSet indexSetWithIndex:section]];
}
} completion:^(BOOL finished) {
if (finished)
[self.passwordCollectionView setContentOffset:CGPointMake( 0, -self.passwordCollectionView.contentInset.top )
animated:YES];
}];
} );
else if (section >= toSections) {
dbg( @"deleteSections:%d", section );
[self.passwordCollectionView deleteSections:[NSIndexSet indexSetWithIndex:section]];
}
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 */; };
93D399433EA75E50656040CB /* Twitter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 93D394077F8FAB8167647187 /* Twitter.framework */; };
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 */; };
93D39A8EA1C49CE43B63F47B /* PearlUICollectionView.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39D8A953779B35403AF6E /* PearlUICollectionView.m */; };
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 */; };
93D39BA1EA3CAAC8A220B4A6 /* MPAppSettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3916C1D8F1427DFBDEBCA /* MPAppSettingsViewController.m */; };
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 */; };
93D39D8F78978196D6ABDEDE /* MPNavigationController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39CC01630D0421205C4C4 /* MPNavigationController.m */; };
93D39E281E3658B30550CB55 /* NSDictionary+Indexing.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39AA1EE2E1E7B81372240 /* NSDictionary+Indexing.m */; };
@ -381,12 +383,14 @@
/* Begin PBXFileReference section */
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>"; };
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>"; };
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>"; };
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>"; };
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>"; };
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>"; };
@ -2628,6 +2632,8 @@
93D39A4759186F6D2D34AA6B /* PearlSizedTextView.h */,
93D3977321EB249981821AB0 /* UITextView+PearlAttributes.m */,
93D39AA10CD00D05937671B1 /* UITextView+PearlAttributes.h */,
93D3908DF8EABBD952065DC0 /* UICollectionView+PearlReloadFromArray.m */,
93D39246FC21C6E63E35D615 /* UICollectionView+PearlReloadFromArray.h */,
);
path = "Pearl-UIKit";
sourceTree = "<group>";
@ -2741,6 +2747,7 @@
93D39536EB550E811CCD04BC /* UIResponder+PearlFirstResponder.h in Headers */,
93D393DB5325820241BA90A7 /* PearlSizedTextView.h in Headers */,
93D392A8777DC30C11361647 /* UITextView+PearlAttributes.h in Headers */,
93D39A53D76CA70786423458 /* UICollectionView+PearlReloadFromArray.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -3308,6 +3315,7 @@
DAA141201922FF020032B392 /* PearlTween.m in Sources */,
93D391ECBD9BD2C64115B5DD /* PearlSizedTextView.m in Sources */,
93D39E34FD28D24FE3442C48 /* UITextView+PearlAttributes.m in Sources */,
93D39D47FC623E91FC39D20C /* UICollectionView+PearlReloadFromArray.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

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

View File

@ -1262,6 +1262,13 @@
<action selector="doMode:" destination="W2g-yv-V3V" eventType="touchUpInside" id="72U-sh-nce"/>
</connections>
</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">
<rect key="frame" x="138" y="77" width="23" height="37"/>
<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 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="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 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"/>
@ -1280,6 +1288,7 @@
<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="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 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"/>
@ -1337,6 +1346,7 @@
<outlet property="counterButton" destination="uZi-FT-Fe8" id="oaF-bB-Sgc"/>
<outlet property="counterLabel" destination="PKP-M9-T8E" id="m9u-Qx-Z9N"/>
<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="loginNameField" destination="3I9-vf-IZK" id="jK4-LI-4ST"/>
<outlet property="modeButton" destination="b5f-wN-2xb" id="m2X-BZ-Lbv"/>
@ -2439,6 +2449,6 @@ See </string>
</simulatedMetricsContainer>
<inferredMetricsTieBreakers>
<segue reference="GZk-I4-JyH"/>
<segue reference="k2G-nL-x3l"/>
<segue reference="Ql4-wf-T8u"/>
</inferredMetricsTieBreakers>
</document>