Brion VIBBER has uploaded a new change for review.
https://gerrit.wikimedia.org/r/88159
Change subject: Implemented category swipe to delete.
......................................................................
Implemented category swipe to delete.
Even works multi-touch - you can swipe more than one at the same time.
Change-Id: I52de3900c95e2416106e91f5177025e094b1151e
---
M Commons-iOS/DetailScrollViewController.m
M Commons-iOS/UILabelDynamicHeight.m
2 files changed, 253 insertions(+), 70 deletions(-)
git pull ssh://gerrit.wikimedia.org:29418/apps/ios/commons
refs/changes/59/88159/1
diff --git a/Commons-iOS/DetailScrollViewController.m
b/Commons-iOS/DetailScrollViewController.m
index 6bfcaea..749b279 100644
--- a/Commons-iOS/DetailScrollViewController.m
+++ b/Commons-iOS/DetailScrollViewController.m
@@ -40,7 +40,7 @@
#define DETAIL_DOCK_DISTANCE_FROM_BOTTOM ((UI_USER_INTERFACE_IDIOM() ==
UIUserInterfaceIdiomPad) ? 146.0f : 126.0f)
#define DETAIL_TABLE_MAX_OVERLAY_ALPHA 0.85f
-#define LABEL_PADDING_INSET UIEdgeInsetsMake(7.0f, 7.0f, 7.0f, 7.0f)
+#define LABEL_PADDING_INSET UIEdgeInsetsMake(17.0f, 11.0f, 17.0f, 11.0f)
@interface DetailScrollViewController ()
@@ -56,11 +56,13 @@
UIImage *previewImage_;
BOOL isFirstAppearance_;
DescriptionParser *descriptionParser_;
- UISwipeGestureRecognizer *swipeRecognizerDown_;
UIView *navBackgroundView_;
UIView *backgroundView_;
UIView *viewAboveBackground_;
UIView *viewBelowBackground_;
+ UIPanGestureRecognizer *detailsPanRecognizer_;
+ CFTimeInterval timeLastDetailsPan_;
+ CFTimeInterval timeLastCategoryPan_;
}
#pragma mark - Init / dealloc
@@ -75,6 +77,8 @@
viewAboveBackground_ = nil;
viewBelowBackground_ = nil;
self.categoriesNeedToBeRefreshed = NO;
+ timeLastDetailsPan_ = CACurrentMediaTime();
+ timeLastCategoryPan_ = CACurrentMediaTime();
}
return self;
}
@@ -201,7 +205,7 @@
[self configureBackgrounds];
// Enable vertical sliding
- UIPanGestureRecognizer *detailsPanRecognizer_ = [[UIPanGestureRecognizer
alloc] initWithTarget:self action:@selector(handleDetailsPan:)];
+ detailsPanRecognizer_ = [[UIPanGestureRecognizer alloc]
initWithTarget:self action:@selector(handleDetailsPan:)];
detailsPanRecognizer_.delegate = self;
[self.view addGestureRecognizer:detailsPanRecognizer_];
@@ -334,15 +338,29 @@
-(void)handleDetailsPan:(UIPanGestureRecognizer *)recognizer
{
static CGPoint originalCenter;
+ static CGPoint originalTouch;
+
if (recognizer.state == UIGestureRecognizerStateBegan)
{
originalCenter = recognizer.view.center;
+ originalTouch = [recognizer locationInView:recognizer.view.superview];
//recognizer.view.layer.shouldRasterize = YES;
}
if (recognizer.state == UIGestureRecognizerStateChanged)
{
CGPoint translate = [recognizer
translationInView:recognizer.view.superview];
translate.x = 0; // Don't move sideways
+
+ CGPoint currentTouch = [recognizer
locationInView:recognizer.view.superview];
+ CFTimeInterval elapsedTime = CACurrentMediaTime() -
timeLastCategoryPan_;
+ if ((elapsedTime > 0.25) && (fabsf(currentTouch.y - originalTouch.y) >
fabsf(currentTouch.x - originalTouch.x))) {
+ // If enough time has elapsed since timeLastCategoryPan_ and
there's been more vertical than horizontal touch movement
+ // then it's safe to carry on and allow details to pan vertically
+ timeLastDetailsPan_ = CACurrentMediaTime();
+ }else{
+ translate.y = 0;
+ }
+
recognizer.view.center = CGPointMake(originalCenter.x + translate.x,
originalCenter.y + translate.y);
}
if (recognizer.state == UIGestureRecognizerStateEnded ||
@@ -363,6 +381,8 @@
// Ensure the table isn't scrolled so far down or up
[self ensureScrollingDoesNotExceedThreshold];
+
+ timeLastDetailsPan_ = CACurrentMediaTime();
}
}
@@ -623,7 +643,7 @@
#pragma mark - To do - from table code
-
+// remove prepareForSegue and other cruft
// Hide only the license row if viewing details of already-uploaded image
// if (self.selectedRecord.complete.boolValue) {
@@ -809,7 +829,64 @@
}
-#pragma mark - Description and Category retrieval
+#pragma mark - Description retrieval
+
+- (void)getPreviouslySavedDescriptionForRecord:(FileUpload *)record
+{
+ CommonsApp *app = CommonsApp.singleton;
+ MWApi *api = [app startApi];
+ NSMutableDictionary *params = [@{
+ @"action": @"query",
+ @"prop": @"revisions",
+ @"rvprop": @"content",
+ @"rvparse": @"1",
+ @"rvlimit": @"1",
+ @"rvgeneratexml": @"1",
+ @"titles": [@"File:"
stringByAppendingString:record.title],
+
+ // Uncomment to test image w/multiple
descriptions - see console for output (comment out the line above when doing so)
+ // @"titles": [@"File:"
stringByAppendingString:@"2011-08-01 10-31-42 Switzerland Segl-Maria.jpg"],
+
+ } mutableCopy];
+
+ MWPromise *req = [api getRequest:params];
+
+ __weak UITextView *weakDescriptionTextView = self.descriptionTextView;
+ __weak UILabelDynamicHeight *weakDescriptionTextLabel =
self.descriptionTextLabel;
+
+ [req done:^(NSDictionary *result) {
+ for (NSString *page in result[@"query"][@"pages"]) {
+ for (NSDictionary *category in
result[@"query"][@"pages"][page][@"revisions"]) {
+ //NSMutableString *pageHTML = [category[@"*"] mutableCopy];
+
+ descriptionParser_.xml = category[@"parsetree"];
+ descriptionParser_.done = ^(NSDictionary *descriptions){
+
+ //for (NSString *description in descriptions) {
+ // NSLog(@"[%@] description = %@", description,
descriptions[description]);
+ //}
+
+ // Show description for locale
+ NSString *language = [[NSLocale preferredLanguages]
objectAtIndex:0];
+ language = [MWI18N filterLanguage:language];
+ weakDescriptionTextView.text = ([descriptions
objectForKey:language]) ? descriptions[language] : descriptions[@"en"];
+ // reloadData so description cell can be resized according
to the retrieved description's text height
+// [weakTableView reloadData];
+ };
+ [descriptionParser_ parse];
+ }
+ }
+ }];
+
+ [req always:^(NSDictionary *result) {
+ if ([weakDescriptionTextView.text isEqualToString: [MWMessage
forKey:@"details-description-loading"].text]){
+ weakDescriptionTextView.text = [MWMessage
forKey:@"details-description-none-found"].text;
+ }
+ weakDescriptionTextLabel.text = weakDescriptionTextView.text;
+ }];
+}
+
+#pragma mark - Category retrieval
- (void)getPreviouslySavedCategoriesForRecord:(FileUpload *)record
{
@@ -856,6 +933,8 @@
// [self.tableView reloadData];
}];
}
+
+#pragma mark - Category layout
-(void)updateCategoryContainer
{
@@ -916,6 +995,11 @@
[label addGestureRecognizer:tapGesture];
label.textAlignment = NSTextAlignmentCenter;
label.paddingColor = DETAIL_EDITABLE_TEXTBOX_BACKGROUND_COLOR;
+ }else{
+ label.userInteractionEnabled = YES;
+ UIPanGestureRecognizer *catPanRecognizer_ =
[[UIPanGestureRecognizer alloc] initWithTarget:self
action:@selector(handleCategoryPan:)];
+ catPanRecognizer_.delegate = self;
+ [label addGestureRecognizer:catPanRecognizer_];
}
}
}
@@ -953,71 +1037,6 @@
[self.categoryContainer layoutIfNeeded];
}
--(void)addCategoryTapped
-{
- CategorySearchTableViewController *catVC =
[self.navigationController.storyboard
instantiateViewControllerWithIdentifier:@"CategorySearchTableViewController"];
- if (self.selectedRecord) {
- catVC.title = [MWMessage forKey:@"catadd-title"].text;
- catVC.selectedRecord = self.selectedRecord;
- }
- [self.navigationController pushViewController:catVC animated:YES];
-}
-
-- (void)getPreviouslySavedDescriptionForRecord:(FileUpload *)record
-{
- CommonsApp *app = CommonsApp.singleton;
- MWApi *api = [app startApi];
- NSMutableDictionary *params = [@{
- @"action": @"query",
- @"prop": @"revisions",
- @"rvprop": @"content",
- @"rvparse": @"1",
- @"rvlimit": @"1",
- @"rvgeneratexml": @"1",
- @"titles": [@"File:"
stringByAppendingString:record.title],
-
- // Uncomment to test image w/multiple
descriptions - see console for output (comment out the line above when doing so)
- // @"titles": [@"File:"
stringByAppendingString:@"2011-08-01 10-31-42 Switzerland Segl-Maria.jpg"],
-
- } mutableCopy];
-
- MWPromise *req = [api getRequest:params];
-
- __weak UITextView *weakDescriptionTextView = self.descriptionTextView;
- __weak UILabelDynamicHeight *weakDescriptionTextLabel =
self.descriptionTextLabel;
-
- [req done:^(NSDictionary *result) {
- for (NSString *page in result[@"query"][@"pages"]) {
- for (NSDictionary *category in
result[@"query"][@"pages"][page][@"revisions"]) {
- //NSMutableString *pageHTML = [category[@"*"] mutableCopy];
-
- descriptionParser_.xml = category[@"parsetree"];
- descriptionParser_.done = ^(NSDictionary *descriptions){
-
- //for (NSString *description in descriptions) {
- // NSLog(@"[%@] description = %@", description,
descriptions[description]);
- //}
-
- // Show description for locale
- NSString *language = [[NSLocale preferredLanguages]
objectAtIndex:0];
- language = [MWI18N filterLanguage:language];
- weakDescriptionTextView.text = ([descriptions
objectForKey:language]) ? descriptions[language] : descriptions[@"en"];
- // reloadData so description cell can be resized according
to the retrieved description's text height
-// [weakTableView reloadData];
- };
- [descriptionParser_ parse];
- }
- }
- }];
-
- [req always:^(NSDictionary *result) {
- if ([weakDescriptionTextView.text isEqualToString: [MWMessage
forKey:@"details-description-loading"].text]){
- weakDescriptionTextView.text = [MWMessage
forKey:@"details-description-none-found"].text;
- }
- weakDescriptionTextLabel.text = weakDescriptionTextView.text;
- }];
-}
-
- (NSString *)categoryShortList
{
// Assume the list will be cropped off in the label if it's long. :)
@@ -1029,6 +1048,154 @@
}
}
+#pragma mark - Category swipe to delete
+
+- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer
*)otherGestureRecognizer
+{
+ return YES;
+}
+
+-(void)handleCategoryPan:(UIPanGestureRecognizer *)recognizer
+{
+ // The static dict is used because it is not guaranteed that a given call
to the recognizer
+ // will be from the same object. This approach makes category
swipe-to-delete work multi-touch!
+ static NSMutableDictionary *dict = nil;
+ if (!dict) dict = [[NSMutableDictionary alloc] init];
+
+ // key() retrieves the correct object handle
+ NSString *(^key)() = ^(){
+ return [NSString stringWithFormat: @"%p", recognizer.view];
+ };
+
+ if (recognizer.state == UIGestureRecognizerStateBegan)
+ {
+ dict[key()] = @{
+ @"originalCenter": [NSValue
valueWithCGPoint:recognizer.view.center],
+ @"originalTouch": [NSValue
valueWithCGPoint:[recognizer locationInView:recognizer.view.superview]],
+ @"originalAlpha": @(recognizer.view.alpha)
+ };
+ }
+ if (recognizer.state == UIGestureRecognizerStateChanged)
+ {
+ CGPoint translate = [recognizer
translationInView:recognizer.view.superview];
+ translate.y = 0; // Don't move vertically
+
+ CGPoint currentTouch = [recognizer
locationInView:recognizer.view.superview];
+ CFTimeInterval elapsedTime = CACurrentMediaTime() -
timeLastDetailsPan_;
+ CGPoint originalTouch = [dict[key()][@"originalTouch"] CGPointValue];
+ if ((elapsedTime > 0.5) && (fabsf(currentTouch.x - originalTouch.x) >
fabsf(currentTouch.y - originalTouch.y))) {
+ // If enough time has elapsed since timeLastDetailsPan_ and
there's been more horizontal than vertical touch movement
+ // then it's safe to carry on and allow category to pan
horizontally
+ timeLastCategoryPan_ = CACurrentMediaTime();
+
+ float alpha = 1.5f - fabsf(translate.x /
(recognizer.view.frame.size.width / 2.0f));
+ //NSLog(@"alpha = %f", alpha);
+ recognizer.view.alpha = alpha;
+
+ translate.x = translate.x * 0.66f;
+
+ }else{
+ translate.x = 0;
+ }
+
+ CGPoint originalCenter = [dict[key()][@"originalCenter"] CGPointValue];
+ recognizer.view.center = CGPointMake(originalCenter.x + translate.x,
originalCenter.y + translate.y);
+
+ }
+ if (recognizer.state == UIGestureRecognizerStateEnded ||
+ recognizer.state == UIGestureRecognizerStateFailed ||
+ recognizer.state == UIGestureRecognizerStateCancelled)
+ {
+ //CGPoint velocity = [recognizer
velocityInView:recognizer.view.superview];
+ //NSLog(@"if fabsf(x velocity) is great here then delete = %@",
NSStringFromCGPoint(velocity));
+
+ if (recognizer.view.alpha < 0.18f) {
+ [self deleteLabelsCategory:(UILabel *)recognizer.view];
+ }
+
+ CGPoint originalCenter = [dict[key()][@"originalCenter"] CGPointValue];
+ recognizer.view.center = originalCenter;
+
+ timeLastCategoryPan_ = CACurrentMediaTime();
+
+ CGFloat originalAlpha = [dict[key()][@"originalAlpha"] floatValue];
+
+ recognizer.view.alpha = originalAlpha;
+
+ [dict removeObjectForKey:key()];
+ }
+}
+
+-(void)deleteLabelsCategory:(UILabel *)label
+{
+ // Remove the label of the category to be deleted from self.selectedRecord.
+ // Also adds proper constraints between the labels above and below the one
+ // being deleted.
+ UILabel *viewAbove = nil;
+ UILabel *viewBelow = nil;
+ for (NSLayoutConstraint *c in label.superview.constraints) {
+ if ((c.firstItem == label)) {
+ // Find the view above
+ if((c.firstAttribute == NSLayoutAttributeTop) &&
(c.secondAttribute == NSLayoutAttributeBottom)){
+ viewAbove = c.secondItem;
+ NSLog(@"above text %@", viewAbove.text);
+ }
+ }
+ if ((c.secondItem == label)) {
+ // Find the view below
+ if((c.firstAttribute == NSLayoutAttributeTop) &&
(c.secondAttribute == NSLayoutAttributeBottom)){
+ viewBelow = c.firstItem;
+ NSLog(@"below text %@", viewBelow.text);
+ }
+ }
+ }
+
+ if (viewAbove != nil) {
+ UILabel *selectedLabel = (UILabel *)label;
+ __strong NSString *category = selectedLabel.text;
+
+ [label removeFromSuperview];
+
+ if ((viewBelow != nil)) {
+ [viewAbove.superview addConstraints:
+ [NSLayoutConstraint
constraintsWithVisualFormat:@"V:[viewAbove]-[viewBelow]"
+ options:0
+ metrics:0
+
views:NSDictionaryOfVariableBindings(viewAbove, viewBelow)]
+ ];
+ }else{
+ [viewAbove.superview addConstraints:
+ [NSLayoutConstraint
constraintsWithVisualFormat:@"V:[viewAbove]-|"
+ options:0
+ metrics:0
+
views:NSDictionaryOfVariableBindings(viewAbove)]
+ ];
+ }
+ [UIView animateWithDuration:0.2f
+ delay:0.0f
+ options:UIViewAnimationOptionCurveEaseInOut
+ animations:^{
+ [viewAbove.superview layoutIfNeeded];
+ }
+ completion:^(BOOL finished){
+ [self.selectedRecord removeCategory:category];
+ [CommonsApp.singleton saveData];
+ }];
+ }
+}
+
+#pragma mark - Category addition
+
+-(void)addCategoryTapped
+{
+ CategorySearchTableViewController *catVC =
[self.navigationController.storyboard
instantiateViewControllerWithIdentifier:@"CategorySearchTableViewController"];
+ if (self.selectedRecord) {
+ catVC.title = [MWMessage forKey:@"catadd-title"].text;
+ catVC.selectedRecord = self.selectedRecord;
+ }
+ [self.navigationController pushViewController:catVC animated:YES];
+}
+
#pragma mark - KVO
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
change:(NSDictionary *)change context:(void *)context
diff --git a/Commons-iOS/UILabelDynamicHeight.m
b/Commons-iOS/UILabelDynamicHeight.m
index d35c4fd..833b7f4 100644
--- a/Commons-iOS/UILabelDynamicHeight.m
+++ b/Commons-iOS/UILabelDynamicHeight.m
@@ -127,6 +127,22 @@
}
}
+-(void)setCenter:(CGPoint)center
+{
+ [super setCenter:center];
+
+ [self.paddingView setCenter:center];
+ [self.borderView setCenter:center];
+}
+
+-(void)setAlpha:(CGFloat)alpha
+{
+ [super setAlpha:alpha];
+
+ [self.paddingView setAlpha:alpha];
+ [self.borderView setAlpha:alpha];
+}
+
-(void)setBounds:(CGRect)bounds
{
[super setBounds:bounds];
--
To view, visit https://gerrit.wikimedia.org/r/88159
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I52de3900c95e2416106e91f5177025e094b1151e
Gerrit-PatchSet: 1
Gerrit-Project: apps/ios/commons
Gerrit-Branch: master
Gerrit-Owner: Brion VIBBER <[email protected]>
Gerrit-Reviewer: Mhurd <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits