8

我在每个单元格中都有一个带有 UIImageView 的 UICollectionView,现在我想添加 Copy Callout,就像在 Photos.app 中一样:

在此处输入图像描述

我在 UICollectionViewDelegate 中看到了这个方法:

- (BOOL)collectionView:(UICollectionView *)collectionView shouldShowMenuForItemAtIndexPath:(NSIndexPath *)indexPath {
    return YES;
}

经过几分钟的研究,我发现了 UIMenuController 类,据我所知,我必须使用它来获取菜单,但无论如何,我认为必须有更简单的方法,然后创建 UIGestureRecognizer,并创建、定位等。我的用户界面菜单。

我在正确的轨道上吗?你怎么能实现这个功能?

4

3 回答 3

18

是的,你在正确的轨道上。您还可以使用此技术实现剪切、复制、粘贴之外的自定义操作。

UICollectionView 的自定义操作

// ViewController.h
@interface ViewController : UICollectionViewController

// ViewController.m
-(void)viewDidLoad
{
    [super viewDidLoad];
    self.collectionView.delegate = self;

    UIMenuItem *menuItem = [[UIMenuItem alloc] initWithTitle:@"Custom Action"
                                                      action:@selector(customAction:)];
    [[UIMenuController sharedMenuController] setMenuItems:[NSArray arrayWithObject:menuItem]];

}

#pragma mark - UICollectionViewDelegate methods
- (BOOL)collectionView:(UICollectionView *)collectionView
      canPerformAction:(SEL)action
    forItemAtIndexPath:(NSIndexPath *)indexPath
            withSender:(id)sender {
    return YES;  // YES for the Cut, copy, paste actions
}

- (BOOL)collectionView:(UICollectionView *)collectionView
shouldShowMenuForItemAtIndexPath:(NSIndexPath *)indexPath {
    return YES;
}

- (void)collectionView:(UICollectionView *)collectionView
         performAction:(SEL)action
    forItemAtIndexPath:(NSIndexPath *)indexPath
            withSender:(id)sender {
    NSLog(@"performAction");
}

#pragma mark - UIMenuController required methods
- (BOOL)canBecomeFirstResponder {
    // NOTE: The menu item will on iOS 6.0 without YES (May be optional on iOS 7.0)
    return YES;
}

- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
    NSLog(@"canPerformAction");
     // The selector(s) should match your UIMenuItem selector
    if (action == @selector(customAction:)) {
        return YES;
    }
    return NO;
}

#pragma mark - Custom Action(s)
- (void)customAction:(id)sender {
    NSLog(@"custom action! %@", sender);
}

注意:iOS 7.0 更改行为

  1. 在您的 UICollectionViewCell 子类中,您需要添加自定义操作方法,否则不会出现任何内容。

    // Cell.m
    #import "Cell.h"
    
    @implementation Cell
    
    - (id)initWithFrame:(CGRect)frame {
        self = [super initWithFrame:frame];
        if (self) {
            // custom logic
        }
        return self;
    }
    
    - (void)customAction:(id)sender {
        NSLog(@"Hello");
    
        if([self.delegate respondsToSelector:@selector(customAction:forCell:)]) {
            [self.delegate customAction:sender forCell:self];
        }
    }
    @end
    
  2. 您需要创建一个委托协议并将其设置在每个单元格上以回调维护您的 UICollectionView 的 UIController。这是因为单元格不应该与您的模型无关,因为它只涉及显示内容。

    // Cell.h
    #import <UIKit/UIKit.h>
    
    @class Cell; // Forward declare Custom Cell for the property
    
    @protocol MyMenuDelegate <NSObject>
    @optional
    - (void)customAction:(id)sender forCell:(Cell *)cell;
    @end
    
    @interface Cell : UICollectionViewCell
    
    @property (strong, nonatomic) UILabel* label;
    @property (weak, nonatomic) id<MyMenuDelegate> delegate;
    @end
    
  3. 在您的 ViewController 或 UICollectionViewController 的子类中,您需要遵守协议并实现新方法。

    // ViewController.m
    @interface ViewController () <MyMenuDelegate>
    @end
    
    // @implementation ViewController  ...
    
    - (UICollectionViewCell *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath;
    {
        Cell *cell = [cv dequeueReusableCellWithReuseIdentifier:@"MY_CELL" forIndexPath:indexPath];
        cell.delegate = self;
        return cell;
    }
    // ...
    
    // Delegate method for iOS 7.0 to get action from UICollectionViewCell
    - (void)customAction:(id)sender forCell:(Cell *)cell {
        NSLog(@"custom action! %@", sender);
    }
    

    UICollectionView 的自定义长按操作菜单

  4. 可选:在您的 UIView 子类中,如果您在此处实现方法 canPerformAction,而不是在 UIViewController 中,则可以覆盖默认的剪切、复制、粘贴。否则,该行为将在您的自定义方法之前显示默认方法。

    // Cell.m
    - (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
        NSLog(@"canPerformAction");
        // The selector(s) should match your UIMenuItem selector
    
        NSLog(@"Sender: %@", sender);
        if (action == @selector(customAction:)) {
            return YES;
        }
        return NO;
    }
    

    来自 UICell 的自定义操作 canPerformAction

于 2012-11-29T03:12:25.877 回答
10

这是完整的解决方案:

- (BOOL)collectionView:(UICollectionView *)collectionView shouldShowMenuForItemAtIndexPath:(NSIndexPath *)indexPath {
        return YES;
    }

- (BOOL)collectionView:(UICollectionView *)collectionView canPerformAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender {
        if ([NSStringFromSelector(action) isEqualToString:@"copy:"])
            return YES;
        else
            return NO;
    }

- (void)collectionView:(UICollectionView *)collectionView performAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender {
        if ([NSStringFromSelector(action) isEqualToString:@"copy:"]) {
            UIPasteboard *pasteBoard = [UIPasteboard pasteboardWithName:UIPasteboardNameGeneral create:NO];
            pasteBoard.persistent = YES;
            NSData *capturedImageData = UIImagePNGRepresentation([_capturedPhotos objectAtIndex:indexPath.row]);
            [pasteBoard setData:capturedImageData forPasteboardType:(NSString *)kUTTypePNG];
        }
    }

在我的情况下,我只允许在我的 CollectionView 中复制功能,如果按下副本,我会将单元格内的图像复制到粘贴板。

于 2012-11-19T17:33:03.333 回答
5

也许有点晚了,但我可能为那些仍在寻找这个的人找到了一个更好的解决方案:

在 UICollectionViewController 的 viewDidLoad 中添加您的项目:

UIMenuItem *menuItem = [[UIMenuItem alloc] initWithTitle:@"Title" action:@selector(action:)];
[[UIMenuController sharedMenuController] setMenuItems:[NSArray arrayWithObject:menuItem]];

添加以下委托方法:

//This method is called instead of canPerformAction for each action (copy, cut and paste too)
- (BOOL)collectionView:(UICollectionView *)collectionView canPerformAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender {
        if (action == @selector(action:)) {
            return YES;
        }
        return NO;
    }
    //Yes for showing menu in general
    - (BOOL)collectionView:(UICollectionView *)collectionView shouldShowMenuForItemAtIndexPath:(NSIndexPath *)indexPath {
        return YES;
    }

如果你还没有,子类 UICollectionViewCell。添加您为项目指定的方法:

- (void)action:(UIMenuController*)menuController {

}

这样您就不需要任何 becomeFirstResponder 或其他方法。您将所有操作集中在一个地方,如果您以单元格本身作为参数调用通用方法,则可以轻松处理不同的单元格。

编辑: uicollectionview 不知何故需要此方法的存在(您的自定义操作不会调用此方法,我认为 uicollectionview 只是检查是否存在)

- (void)collectionView:(UICollectionView *)collectionView performAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender {

}
于 2013-09-10T17:20:14.880 回答