2
0

Bugfixes with regards to swiping password types.

This commit is contained in:
Maarten Billemont 2014-05-13 07:27:11 -04:00
parent 943c378206
commit fa57b8817b
13 changed files with 135 additions and 255 deletions

2
External/Pearl vendored

@ -1 +1 @@
Subproject commit 7ed74fd245f47339903ab2faeb3f36754155452c
Subproject commit d37640cc0133e56b1d58baf7ed44ce69dcb20f2f

View File

@ -21,7 +21,7 @@
+ (instancetype)get;
- (MPUserEntity *)activeUserForMainThread;
- (MPUserEntity *)activeUserInContext:(NSManagedObjectContext *)moc;
- (MPUserEntity *)activeUserInContext:(NSManagedObjectContext *)context;
- (void)setActiveUser:(MPUserEntity *)activeUser;
@end

View File

@ -28,18 +28,15 @@
return [self activeUserInContext:[MPAppDelegate_Shared managedObjectContextForMainThreadIfReady]];
}
- (MPUserEntity *)activeUserInContext:(NSManagedObjectContext *)moc {
- (MPUserEntity *)activeUserInContext:(NSManagedObjectContext *)context {
NSManagedObjectID *activeUserOID = self.activeUserOID;
if (!activeUserOID || !moc)
if (!activeUserOID || !context)
return nil;
NSError *error;
MPUserEntity *activeUser = (MPUserEntity *)[moc existingObjectWithID:activeUserOID error:&error];
if (!activeUser) {
MPUserEntity *activeUser = [MPUserEntity existingObjectWithID:activeUserOID inContext:context];
if (!activeUser)
[self signOutAnimated:YES];
err(@"Failed to retrieve active user: %@", error);
}
return activeUser;
}

View File

@ -260,12 +260,9 @@ forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
NSManagedObjectID *elementOID = [self elementForTableIndexPath:indexPath].objectID;
[MPiOSAppDelegate managedObjectContextPerformBlockAndWait:^(NSManagedObjectContext *context) {
NSError *error = nil;
MPElementEntity *element = (MPElementEntity *)[context existingObjectWithID:elementOID error:&error];
if (!element) {
err(@"Failed to retrieve element to delete: %@", error);
MPElementEntity *element = [MPElementEntity existingObjectWithID:elementOID inContext:context];
if (!element)
return;
}
inf(@"Deleting element: %@", element.name);
[context deleteObject:element];

View File

@ -18,10 +18,12 @@
#import <Foundation/Foundation.h>
#import "MPPasswordCell.h"
#import "MPPasswordsViewController.h"
@interface MPPasswordElementCell : MPPasswordCell
@property(nonatomic, copy) NSString *transientSite;
@property(nonatomic, weak) MPPasswordsViewController *passwordsViewController;
- (MPElementEntity *)mainElement;
- (MPElementEntity *)elementInContext:(NSManagedObjectContext *)context;

View File

@ -69,15 +69,7 @@
- (MPElementEntity *)elementInContext:(NSManagedObjectContext *)context {
if (!_elementOID)
return nil;
NSError *error = nil;
MPElementEntity *element = _elementOID? (MPElementEntity *)[context existingObjectWithID:_elementOID error:&error]: nil;
if (_elementOID && !element)
err(@"Failed to load element: %@", error);
return element;
return [MPElementEntity existingObjectWithID:_elementOID inContext:context];
}
- (void)reloadData {

View File

@ -92,7 +92,16 @@
[super reloadWithElement:mainElement];
if (!mainElement) {
self.loginButton.alpha = 0;
self.nameLabel.text = @"";
self.contentField.text = @"";
return;
}
self.nameLabel.alpha = 1;
self.loginButton.alpha = 1;
if (self.type != (MPElementType)NSNotFound)
self.typeLabel.text = [mainElement.algorithm nameOfType:self.type];
if (mainElement.requiresExplicitMigration)
@ -166,6 +175,8 @@
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPElementEntity *element = [[MPPasswordElementCell findAsSuperviewOf:self] elementInContext:context];
if (!element)
return;
switch (self.contentFieldMode) {
case MPContentFieldModePassword:

View File

@ -20,6 +20,7 @@
#import "MPCell.h"
#import "MPPasswordCell.h"
#import "MPPasswordElementCell.h"
#import "MPPasswordsViewController.h"
@interface MPPasswordTypesCell : MPPasswordElementCell <UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>

View File

@ -68,9 +68,7 @@
[super applyLayoutAttributes:layoutAttributes];
[self.contentCollectionView.collectionViewLayout invalidateLayout];
if (self.activeType)
[self.contentCollectionView scrollToItemAtIndexPath:[self contentIndexPathForType:self.activeType]
atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:NO];
[self scrollToActiveType];
}
- (void)reloadWithTransientSite:(NSString *)siteName {
@ -104,7 +102,10 @@
if (!self.algorithm)
dbg_return_tr( 0, @, @(section) );
dbg_return_tr( [[self.algorithm allTypes] count] + 1, @, @(section) );
if (self.transientSite)
dbg_return_tr( [[self.algorithm allTypes] count], @, @(section) );
dbg_return_tr( [[self.algorithm allTypes] count] + 1 /* Delete */, @, @(section) );
}
- (MPPasswordLargeCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
@ -125,16 +126,13 @@
NSString *newSiteName = self.transientSite;
if (newSiteName) {
[[UIResponder findFirstResponder] resignFirstResponder];
[PearlAlert showAlertWithTitle:@"Create Site"
message:strf( @"Do you want to create a new site named:\n%@", newSiteName )
viewStyle:UIAlertViewStyleDefault
initAlert:nil tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
if (buttonIndex == [alert cancelButtonIndex]) {
// Cancel
// NSIndexPath *indexPath_ = [collectionView indexPathForCell:cell];
// [collectionView selectItemAtIndexPath:indexPath animated:NO
// scrollPosition:UICollectionViewScrollPositionCenteredHorizontally];
// [collectionView deselectItemAtIndexPath:indexPath animated:YES];
for (NSIndexPath *selectedIndexPath in [collectionView indexPathsForSelectedItems])
[collectionView deselectItemAtIndexPath:selectedIndexPath animated:YES];
return;
@ -142,32 +140,41 @@
// Create
[[MPiOSAppDelegate get] addElementNamed:newSiteName completion:^(MPElementEntity *element) {
[self copyContentOfElement:element];
PearlMainQueue( ^{
[PearlOverlay showTemporaryOverlayWithTitle:strf( @"Added %@", newSiteName ) dismissAfter:2];
PearlMainQueueAfter( 0.2f, ^{
// NSIndexPath *indexPath_ = [collectionView indexPathForCell:cell];
// [collectionView selectItemAtIndexPath:indexPath animated:NO
// scrollPosition:UICollectionViewScrollPositionCenteredHorizontally];
// [collectionView deselectItemAtIndexPath:indexPath animated:YES];
for (NSIndexPath *selectedIndexPath in [collectionView indexPathsForSelectedItems])
[collectionView deselectItemAtIndexPath:selectedIndexPath animated:YES];
} );
[self.passwordsViewController updatePasswords];
} );
}];
} cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonYes, nil];
return;
}
MPElementEntity *element = [self mainElement];
if (!element) {
// [collectionView selectItemAtIndexPath:indexPath animated:NO
// scrollPosition:UICollectionViewScrollPositionCenteredHorizontally];
// [collectionView deselectItemAtIndexPath:indexPath animated:YES];
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
BOOL used = NO;
MPElementEntity *element = [self elementInContext:context];
if (!element)
wrn(@"No element to use for: %@", self);
else if (indexPath.item == 0) {
[context deleteObject:element];
[context saveToStore];
} else
used = [self copyContentOfElement:element];
PearlMainQueueAfter( 0.2f, ^{
for (NSIndexPath *selectedIndexPath in [collectionView indexPathsForSelectedItems])
[collectionView deselectItemAtIndexPath:selectedIndexPath animated:YES];
return;
if (used)
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context_) {
[[self elementInContext:context_] use];
[context_ saveToStore];
}];
} );
}];
}
- (BOOL)copyContentOfElement:(MPElementEntity *)element {
inf( @"Copying password for: %@", element.name );
MPCheckpoint( MPCheckpointCopyToPasteboard, @{
@"type" : NilToNSNull( element.typeName ),
@ -175,37 +182,14 @@
@"emergency" : @NO
} );
[element resolveContentUsingKey:[MPAppDelegate_Shared get].key result:^(NSString *result) {
if (![result length]) {
PearlMainQueue( ^{
// NSIndexPath *indexPath_ = [collectionView indexPathForCell:cell];
// [collectionView selectItemAtIndexPath:indexPath animated:NO
// scrollPosition:UICollectionViewScrollPositionCenteredHorizontally];
// [collectionView deselectItemAtIndexPath:indexPath animated:YES];
for (NSIndexPath *selectedIndexPath in [collectionView indexPathsForSelectedItems])
[collectionView deselectItemAtIndexPath:selectedIndexPath animated:YES];
} );
return;
NSString *result = [element resolveContentUsingKey:[MPAppDelegate_Shared get].key];
if ([result length]) {
[UIPasteboard generalPasteboard].string = result;
[PearlOverlay showTemporaryOverlayWithTitle:@"Password Copied" dismissAfter:2];
return YES;
}
[UIPasteboard generalPasteboard].string = result;
PearlMainQueue( ^{
[PearlOverlay showTemporaryOverlayWithTitle:@"Password Copied" dismissAfter:2];
PearlMainQueueAfter( 0.2f, ^{
// NSIndexPath *indexPath_ = [collectionView indexPathForCell:cell];
// [collectionView selectItemAtIndexPath:indexPath animated:NO
// scrollPosition:UICollectionViewScrollPositionCenteredHorizontally];
// [collectionView deselectItemAtIndexPath:indexPath animated:YES];
for (NSIndexPath *selectedIndexPath in [collectionView indexPathsForSelectedItems])
[collectionView deselectItemAtIndexPath:selectedIndexPath animated:YES];
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
[[self elementInContext:context] use];
[context saveToStore];
}];
} );
} );
}];
return NO;
}
#pragma mark - UIScrollViewDelegate
@ -235,8 +219,18 @@
#pragma mark - Private
- (void)scrollToActiveType {
if (self.activeType && self.activeType != (MPElementType)NSNotFound)
[self.contentCollectionView scrollToItemAtIndexPath:[self contentIndexPathForType:self.activeType]
atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:NO];
}
- (MPElementType)typeForContentIndexPath:(NSIndexPath *)indexPath {
if (self.transientSite)
return [[self.algorithm allTypesStartingWith:MPElementTypeGeneratedPIN][indexPath.item] unsignedIntegerValue];
if (indexPath.item == 0)
return (MPElementType)NSNotFound;
@ -247,20 +241,29 @@
NSArray *types = [self.algorithm allTypesStartingWith:MPElementTypeGeneratedPIN];
for (NSInteger t = 0; t < [types count]; ++t)
if ([types[t] unsignedIntegerValue] == type)
if ([types[t] unsignedIntegerValue] == type) {
if (self.transientSite)
return [NSIndexPath indexPathForItem:t inSection:0];
else
return [NSIndexPath indexPathForItem:t + 1 inSection:0];
}
Throw(@"Unsupported type: %d", type);
Throw(@"Unsupported type: %lud", (long)type);
}
- (void)saveContentType {
if (self.transientSite)
return;
CGPoint centerPoint = CGPointFromCGRectCenter( self.contentCollectionView.bounds );
NSIndexPath *centerIndexPath = [self.contentCollectionView indexPathForItemAtPoint:centerPoint];
self.activeType = [self typeForContentIndexPath:centerIndexPath];
MPElementType type = [self typeForContentIndexPath:centerIndexPath];
if (type == ((MPElementType)NSNotFound))
// Active cell is not a type cell.
return;
self.activeType = type;
if (self.transientSite)
return;
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPPasswordLargeCell *cell = (MPPasswordLargeCell *)[self.contentCollectionView cellForItemAtIndexPath:centerIndexPath];
@ -270,7 +273,7 @@
}
MPElementEntity *element = [self elementInContext:context];
if (element.type == cell.type)
if (!element || element.type == cell.type)
// Nothing changed.
return;
@ -284,8 +287,7 @@
_activeType = activeType;
[self.contentCollectionView scrollToItemAtIndexPath:[self contentIndexPathForType:activeType]
atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:NO];
[self scrollToActiveType];
}
- (void)setSelected:(BOOL)selected {

View File

@ -36,6 +36,7 @@
@property(nonatomic, readonly) MPCoachmark *coachmark;
- (void)setActive:(BOOL)active animated:(BOOL)animated completion:(void (^)(BOOL finished))completion;
- (void)updatePasswords;
- (IBAction)dismissPopdown:(id)sender;
- (IBAction)signOut:(id)sender;

View File

@ -40,7 +40,6 @@
__weak UITapGestureRecognizer *_passwordsDismissRecognizer;
NSFetchedResultsController *_fetchedResultsController;
BOOL _exactMatch;
NSMutableDictionary *_fetchedUpdates;
UIColor *_backgroundColor;
UIColor *_darkenedBackgroundColor;
__weak UIViewController *_popdownVC;
@ -52,7 +51,6 @@
[super viewDidLoad];
_fetchedUpdates = [NSMutableDictionary dictionaryWithCapacity:4];
_backgroundColor = self.passwordCollectionView.backgroundColor;
_darkenedBackgroundColor = [_backgroundColor colorWithAlphaComponent:0.6f];
_coachmark = [MPCoachmark coachmarkForClass:[self class] version:0];
@ -118,11 +116,7 @@ referenceSizeForHeaderInSection:(NSInteger)section {
if (collectionView == self.passwordCollectionView) {
UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout *)collectionViewLayout;
CGFloat itemWidth = UIEdgeInsetsInsetRect(self.passwordCollectionView.bounds, layout.sectionInset).size.width;
if (indexPath.item < 3 || indexPath.item >= ((id<NSFetchedResultsSectionInfo>)self.fetchedResultsController.sections[indexPath.section]).numberOfObjects)
return CGSizeMake( itemWidth, 100 );
return CGSizeMake( (itemWidth - layout.minimumInteritemSpacing) / 2, 44 );
}
Throw(@"Unexpected collection view: %@", collectionView);
@ -155,14 +149,12 @@ referenceSizeForHeaderInSection:(NSInteger)section {
MPPasswordElementCell *cell;
if (indexPath.item < ((id<NSFetchedResultsSectionInfo>)self.fetchedResultsController.sections[indexPath.section]).numberOfObjects) {
MPElementEntity *element = [self.fetchedResultsController objectAtIndexPath:indexPath];
if (indexPath.item < 3)
cell = [MPPasswordTypesCell dequeueCellForElement:element fromCollectionView:collectionView atIndexPath:indexPath];
else
cell = [MPPasswordSmallCell dequeueCellForElement:element fromCollectionView:collectionView atIndexPath:indexPath];
}
else
// New Site.
cell = [MPPasswordTypesCell dequeueCellForTransientSite:self.query fromCollectionView:collectionView atIndexPath:indexPath];
cell.passwordsViewController = self;
[UIView setAnimationsEnabled:YES];
dbg_return(cell, indexPath);
@ -171,139 +163,29 @@ referenceSizeForHeaderInSection:(NSInteger)section {
Throw(@"Unexpected collection view: %@", collectionView);
}
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind
atIndexPath:(NSIndexPath *)indexPath {
return [collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:@"MPPasswordHeader" forIndexPath:indexPath];
}
#pragma mark - NSFetchedResultsControllerDelegate
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
if (controller == _fetchedResultsController) {
dbg(@"controllerWillChangeContent");
NSAssert(![_fetchedUpdates count], @"Didn't finish a previous change update?");
if ([_fetchedUpdates count]) {
[_fetchedUpdates removeAllObjects];
[self.passwordCollectionView reloadData];
}
}
}
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath
forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
if (controller == _fetchedResultsController) {
NSMutableArray *updatesForType = _fetchedUpdates[@(type)];
if (!updatesForType)
_fetchedUpdates[@(type)] = updatesForType = [NSMutableArray new];
[updatesForType addObject:@{
@"object" : NilToNSNull(anObject),
@"indexPath" : NilToNSNull(indexPath),
@"newIndexPath" : NilToNSNull(newIndexPath)
}];
switch (type) {
case NSFetchedResultsChangeInsert:
dbg(@"didChangeObject: insert: %@", [updatesForType lastObject]);
break;
case NSFetchedResultsChangeDelete:
dbg(@"didChangeObject: delete: %@", [updatesForType lastObject]);
break;
case NSFetchedResultsChangeMove:
dbg(@"didChangeObject: move: %@", [updatesForType lastObject]);
break;
case NSFetchedResultsChangeUpdate:
dbg(@"didChangeObject: update: %@", [updatesForType lastObject]);
break;
}
[self.passwordCollectionView reloadSections:[NSIndexSet indexSetWithIndex:indexPath.section]];
[self.passwordCollectionView reloadSections:[NSIndexSet indexSetWithIndex:newIndexPath.section]];
}
}
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id<NSFetchedResultsSectionInfo>)sectionInfo
atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {
if (controller == _fetchedResultsController) {
NSMutableArray *updatesForType = _fetchedUpdates[@(type << 3)];
if (!updatesForType)
_fetchedUpdates[@(type << 3)] = updatesForType = [NSMutableArray new];
[updatesForType addObject:@{
@"sectionInfo" : NilToNSNull(sectionInfo),
@"index" : @(sectionIndex)
}];
switch (type) {
case NSFetchedResultsChangeInsert:
dbg(@"didChangeSection: insert: %@", [updatesForType lastObject]);
break;
case NSFetchedResultsChangeDelete:
dbg(@"didChangeSection: delete: %@", [updatesForType lastObject]);
break;
case NSFetchedResultsChangeMove:
dbg(@"didChangeSection: move: %@", [updatesForType lastObject]);
break;
case NSFetchedResultsChangeUpdate:
dbg(@"didChangeSection: update: %@", [updatesForType lastObject]);
break;
if (controller == _fetchedResultsController)
[self.passwordCollectionView reloadData];
}
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
if (controller == _fetchedResultsController && [_fetchedUpdates count]) {
[self.passwordCollectionView performBatchUpdates:^{
[_fetchedUpdates enumerateKeysAndObjectsUsingBlock:^(NSNumber *typeNumber, NSArray *updates, BOOL *stop) {
BOOL updateIsSection = NO;
NSFetchedResultsChangeType type = [typeNumber unsignedIntegerValue];
if (type >= 1 << 3) {
updateIsSection = YES;
type = type >> 3;
}
switch (type) {
case NSFetchedResultsChangeInsert:
if (updateIsSection) {
for (NSDictionary *update in updates) {
dbg(@"insertSections:%@", update[@"index"]);
[self.passwordCollectionView insertSections:
[NSIndexSet indexSetWithIndex:[update[@"index"] unsignedIntegerValue]]];
}
}
else {
dbg(@"insertItemsAtIndexPaths:%@", [updates valueForKeyPath:@"@unionOfObjects.newIndexPath"]);
[self.passwordCollectionView insertItemsAtIndexPaths:[updates valueForKeyPath:@"@unionOfObjects.newIndexPath"]];
}
break;
case NSFetchedResultsChangeDelete:
if (updateIsSection) {
for (NSDictionary *update in updates) {
dbg(@"deleteSections:%@", update[@"index"]);
[self.passwordCollectionView deleteSections:
[NSIndexSet indexSetWithIndex:[update[@"index"] unsignedIntegerValue]]];
}
}
else {
dbg(@"deleteItemsAtIndexPaths:%@", [updates valueForKeyPath:@"@unionOfObjects.indexPath"]);
[self.passwordCollectionView deleteItemsAtIndexPaths:[updates valueForKeyPath:@"@unionOfObjects.indexPath"]];
}
break;
case NSFetchedResultsChangeMove:
NSAssert(!updateIsSection, @"Move not supported for sections");
for (NSDictionary *update in updates) {
dbg(@"moveItemAtIndexPath:%@ toIndexPath:%@", update[@"indexPath"], update[@"newIndexPath"]);
[self.passwordCollectionView moveItemAtIndexPath:update[@"indexPath"] toIndexPath:update[@"newIndexPath"]];
}
break;
case NSFetchedResultsChangeUpdate:
NSAssert(!updateIsSection, @"Update not supported for sections");
dbg(@"reloadItemsAtIndexPaths:%@", [updates valueForKeyPath:@"@unionOfObjects.indexPath"]);
[self.passwordCollectionView reloadItemsAtIndexPaths:[updates valueForKeyPath:@"@unionOfObjects.indexPath"]];
break;
}
}];
} completion:nil];
[_fetchedUpdates removeAllObjects];
}
}
#pragma mark - UIScrollViewDelegate
#pragma mark - UISearchBarDelegate
@ -319,14 +201,11 @@ referenceSizeForHeaderInSection:(NSInteger)section {
- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar {
if (searchBar == self.passwordsSearchBar) {
// _passwordsDismissRecognizer = [self.view dismissKeyboardForField:self.passwordsSearchBar onTouchForced:NO];
if (searchBar == self.passwordsSearchBar)
[UIView animateWithDuration:0.3f animations:^{
self.passwordCollectionView.backgroundColor = _darkenedBackgroundColor;
}];
}
}
- (void)searchBarTextDidEndEditing:(UISearchBar *)searchBar {

View File

@ -360,11 +360,12 @@ typedef NS_ENUM(NSUInteger, MPActiveUserState) {
if (buttonIndex == [sheet destructiveButtonIndex]) {
// Delete User
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
NSManagedObject *user_ = [context existingObjectWithID:userID error:NULL];
if (user_) {
MPUserEntity *user_ = [MPUserEntity existingObjectWithID:userID inContext:context];
if (!user_)
return;
[context deleteObject:user_];
[context saveToStore];
}
}];
return;
}
@ -372,8 +373,10 @@ typedef NS_ENUM(NSUInteger, MPActiveUserState) {
if (buttonIndex == [sheet firstOtherButtonIndex])
// Reset Password
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPUserEntity *user_ = (MPUserEntity *)[context existingObjectWithID:userID error:NULL];
if (user_)
MPUserEntity *user_ = [MPUserEntity existingObjectWithID:userID inContext:context];
if (!user_)
return;
[[MPiOSAppDelegate get] changeMasterPasswordFor:user_ saveInContext:context didResetBlock:^{
PearlMainQueue( ^{
NSIndexPath *avatarIndexPath = [self.avatarCollectionView indexPathForCell:avatarCell];
@ -477,12 +480,7 @@ typedef NS_ENUM(NSUInteger, MPActiveUserState) {
if ((*isNew = indexPath.item >= [self.userIDs count]))
return nil;
NSError *error = nil;
MPUserEntity *user = (MPUserEntity *)[context existingObjectWithID:self.userIDs[indexPath.item] error:&error];
if (error)
wrn(@"Failed to load user into context: %@", error);
return user;
return [MPUserEntity existingObjectWithID:self.userIDs[indexPath.item] inContext:context];
}
- (void)updateAvatars {

View File

@ -19,7 +19,7 @@
<rect key="frame" x="0.0" y="0.0" width="320" height="504"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="rWM-08-aab" userLabel="Users Root">
<view contentMode="scaleToFill" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="rWM-08-aab" userLabel="Users Root">
<rect key="frame" x="0.0" y="0.0" width="320" height="504"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
@ -27,7 +27,7 @@
<rect key="frame" x="142" y="234" width="37" height="37"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
</activityIndicatorView>
<collectionView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" alpha="0.0" contentMode="scaleToFill" minimumZoomScale="0.0" maximumZoomScale="0.0" dataMode="prototypes" translatesAutoresizingMaskIntoConstraints="NO" id="L6J-pd-gcp" userLabel="Avatar Collection">
<collectionView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" alpha="0.0" contentMode="scaleToFill" misplaced="YES" minimumZoomScale="0.0" maximumZoomScale="0.0" dataMode="prototypes" translatesAutoresizingMaskIntoConstraints="NO" id="L6J-pd-gcp" userLabel="Avatar Collection">
<rect key="frame" x="0.0" y="20" width="320" height="484"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
@ -122,7 +122,7 @@
<outlet property="delegate" destination="S8q-YF-Kt9" id="det-Eh-phM"/>
</connections>
</collectionView>
<button opaque="NO" alpha="0.69999999999999996" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="9u7-pu-Wtv" userLabel="Previous Avatar">
<button opaque="NO" alpha="0.69999999999999996" contentMode="scaleToFill" misplaced="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="9u7-pu-Wtv" userLabel="Previous Avatar">
<rect key="frame" x="0.0" y="127" width="44" height="53"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<constraints>
@ -136,7 +136,7 @@
<action selector="changeAvatar:" destination="S8q-YF-Kt9" eventType="touchUpInside" id="lNu-mK-3zD"/>
</connections>
</button>
<button opaque="NO" alpha="0.69999999999999996" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="fUK-gJ-NRE" userLabel="Next Avatar">
<button opaque="NO" alpha="0.69999999999999996" contentMode="scaleToFill" misplaced="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="fUK-gJ-NRE" userLabel="Next Avatar">
<rect key="frame" x="276" y="127" width="44" height="53"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<constraints>
@ -150,7 +150,7 @@
<action selector="changeAvatar:" destination="S8q-YF-Kt9" eventType="touchUpInside" id="kL5-zV-zbb"/>
</connections>
</button>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="qp1-nX-o4i" userLabel="Entry" customClass="PearlUIView">
<view contentMode="scaleToFill" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="qp1-nX-o4i" userLabel="Entry" customClass="PearlUIView">
<rect key="frame" x="20" y="216" width="280" height="63"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<subviews>
@ -1307,7 +1307,7 @@
</connections>
</collectionViewCell>
</cells>
<collectionReusableView key="sectionHeaderView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" id="yzh-hh-YjZ">
<collectionReusableView key="sectionHeaderView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="MPPasswordHeader" id="yzh-hh-YjZ">
<rect key="frame" x="0.0" y="0.0" width="320" height="108"/>
<autoresizingMask key="autoresizingMask"/>
</collectionReusableView>