【问题标题】:iPad undo button (a-la Keynote and other apps)iPad 撤消按钮(a-la Keynote 和其他应用程序)
【发布时间】:2013-07-12 16:41:02
【问题描述】:

在 Keynote(和其他应用程序)中,我注意到执行撤消/重做的“标准”界面是在工具栏上提供一个撤消按钮。

单击按钮(始终启用)撤消最近的操作。 (如果最近没有要撤消的操作,则会显示撤消/重做菜单)。

长按撤消按钮打开撤消/重做菜单。

我搜索了实现这一点的方法,到目前为止我找到的最佳答案是following link

不知道有没有人知道更简单的方法?

谢谢!

【问题讨论】:

  • iDevice撤消手势是摇晃设备。
  • 是的,但是摇动 iPad 远不如摇动手机方便,因此 Apple 的应用程序引入了这种约定。
  • tripleee,如果您查看 iPad 应用程序,您会发现常见的撤消方法是撤消菜单,而不是手势。仔细想想,摇晃撤消可能看起来很酷,但完全不切实际,尤其是当您连续进行大量撤消时...
  • -1 拒绝有效的解决方案,并遵循未预先明确的要求。
  • 实际上,鉴于我提供了一个问题的链接 (stackoverflow.com/questions/2655630/…) 并指定我正在进一步寻找,我认为(并且仍然这样做),然后我当然不会接受答案出现在那个链接中......但是因为我不希望这是一场激烈的战争,而且我实际上并不是要拒绝你的答案(只是不接受它......因为它只是在那里,如果您编辑答案,我会 +1 [否则我的投票被锁定]...)

标签: ios ipad


【解决方案1】:

在查看所有方法并与朋友讨论后,以下是我使用的解决方案,对于 UIBarButtonItem,它响应轻击和长按 (TapOrLongPressBarButtonItem)。

它基于以下原则:

  1. UIBarButtonItem 子类
  2. 使用自定义视图(因此处理长按非常简单 - 因为我们的自定义视图响应长按手势处理程序没有问题...)

... 到目前为止 - 这种方法在 other SO thread 中 - 我不喜欢这种方法,因为我找不到足够简单的方法使自定义视图看起来像 iPad 导航栏按钮...太好了...

使用 Water Lou 的 UIGlossyButton(感谢水!)。这种用法被封装在子类中...

生成的代码如下:

@protocol TapOrPressButtonDelegate;
@interface TapOrPressBarButtonItem : UIBarButtonItem {  
    UIGlossyButton* _tapOrPressButton;
    __weak id<TapOrPressButtonDelegate> _delegate;
}
- (id)initWithTitle:(NSString*)title andDelegate:(id<TapOrPressButtonDelegate>)delegate;
@end

@protocol TapOrPressButtonDelegate<NSObject>
- (void)buttonTapped:(UIButton*)button withBarButtonItem:(UIBarButtonItem*)barButtonItem;
- (void)buttonLongPressed:(UIButton*)button withBarButtonItem:(UIBarButtonItem*)barButtonItem;
@end

@implementation TapOrPressBarButtonItem
- (void)buttonLongPressed:(UILongPressGestureRecognizer*)gesture {
    if (gesture.state != UIGestureRecognizerStateBegan)
        return;
    if([_delegate respondsToSelector:@selector(buttonLongPressed:withBarButtonItem:)]) {
        [_delegate buttonLongPressed:_tapOrPressButton withBarButtonItem:self];
    }
}

- (void)buttonTapped:(id)sender {
    if (sender != _tapOrPressButton) {
        return;
    }

    if([_delegate respondsToSelector:@selector(buttonTapped:withBarButtonItem:)]) {
        [_delegate buttonTapped:_tapOrPressButton withBarButtonItem:self];
    }   
}

- (id)initWithTitle:(NSString*)title andDelegate:(id<TapOrPressButtonDelegate>)delegate {
    if (self = [super init]) {
        // Store delegate reference
        _delegate = delegate;

        // Create the customm button that will have the iPad-nav-bar-default appearance 
        _tapOrPressButton = [UIGlossyButton buttonWithType:UIButtonTypeCustom];
        [_tapOrPressButton setTitle:title forState:UIControlStateNormal];
        [_tapOrPressButton setNavigationButtonWithColor:[UIColor colorWithRed:123.0/255 green:130.0/255 blue:139.0/255 alpha:1.0]];
        // Calculate width...
        CGSize labelSize = CGSizeMake(1000, 30);
        labelSize = [title sizeWithFont:_tapOrPressButton.titleLabel.font constrainedToSize:labelSize lineBreakMode:UILineBreakModeMiddleTruncation];
        _tapOrPressButton.frame = CGRectMake(0, 0, labelSize.width+20, 30);

        // Add a handler for a tap
        [_tapOrPressButton addTarget:self action:@selector(buttonTapped:) forControlEvents:UIControlEventTouchUpInside];
        // Add a handler for a long-press
        UILongPressGestureRecognizer* buttonLongPress_ = [[UILongPressGestureRecognizer alloc] initWithTarget:self
                                                                                                       action:@selector(buttonLongPressed:)];
        [_tapOrPressButton addGestureRecognizer:buttonLongPress_];

        // Set this button as the custom view of the bar item...
        self.customView = _tapOrPressButton;
    }
    return self;
}

// Safe guards...
- (id)initWithImage:(UIImage *)image style:(UIBarButtonItemStyle)style target:(id)target action:(SEL)action {
    NSLog(@"%s not supported!", __FUNCTION__);
    return nil;
}

- (id)initWithImage:(UIImage *)image landscapeImagePhone:(UIImage *)landscapeImagePhone style:(UIBarButtonItemStyle)style target:(id)target action:(SEL)action {
    NSLog(@"%s not supported!", __FUNCTION__);
    return nil;
}

- (id)initWithTitle:(NSString *)title style:(UIBarButtonItemStyle)style target:(id)target action:(SEL)action {
    NSLog(@"%s not supported!", __FUNCTION__);
    return nil;
}
- (id)initWithBarButtonSystemItem:(UIBarButtonSystemItem)systemItem target:(id)target action:(SEL)action {
    NSLog(@"%s not supported!", __FUNCTION__);
    return nil;
}

- (id)initWithCustomView:(UIView *)customView {
    NSLog(@"%s not supported!", __FUNCTION__);
    return nil;
}

@end

而你需要做的就是:

1.实例化如下:

TapOrPressBarButtonItem* undoMenuButton = [[TapOrPressBarButtonItem alloc] initWithTitle:NSLocalizedString(@"Undo", @"Undo Menu Title") andDelegate:self];

2。将按钮连接到导航栏:

[self.navigationItem setLeftBarButtonItem:undoMenuButton animated:NO];

3.实现 TapOrPressButtonDelegate 协议,你就完成了……

-(void)buttonTapped:(UIButton*)button withBarButtonItem:(UIBarButtonItem*)barButtonItem {
    [self menuItemUndo:barButtonItem];
}

-(void)buttonLongPressed:(UIButton*)button withBarButtonItem:(UIBarButtonItem*)barButtonItem {
    [self undoMenuClicked:barButtonItem];
}

希望这对其他人有所帮助...

【讨论】:

    【解决方案2】:

    如果您使用的是 IB(或在 Xcode4 中设计器...我猜它被调用),那么您可以从第一响应者中选择“撤消”并将该操作拖到按钮上。如果这不包括它,我可以给你更具体的说明。

    这是的样子

    它位于底部“收到的操作”列的左侧

    【讨论】:

    • 谢谢 - 但我正在寻找的是关于如何获得按下 的回调的示例代码(或至少一个可靠的方向/引导) -按一下工具栏项。
    • @Reuven 我发现在ios developer library
    • 很抱歉不清楚。我正在寻找的不是处理撤消 - 它是如何模仿撤消 UIBarButton 的行为,如在 Apple 自己的 Keynote 应用程序中实现(例如)。
    • @Reuven 你想撤消什么?文本?在图片上画画?
    • @coder04,我已经完成了整个撤消/重做工作。我什至在它周围有一个用户界面。所有作品。但是,我正在寻找的是类似于 Keynote for-iPad 的用户体验。您看到 Keynote 的工作原理了吗?
    【解决方案3】:

    我相信关键在于 UINavigationBar 本身。与 UIButtons 或其他正常的触摸跟踪对象不同,我怀疑 UIBarItems 不处理自己的触摸。它们不继承 UIResponder 或 UIControl 方法。但是 UINavigationBar 当然可以。而且我个人多次直接向 UINavigationBar 添加手势。

    我建议您在 UINavigationBar 子类中覆盖触摸处理并检查其子类的触摸。如果孩子是您的特殊撤消按钮,您可以相应地处理它。

    【讨论】:

    • 谢谢 - 但我正在寻找的是关于如何获得按下 的回调的示例代码(或至少一个可靠的方向/引导) -按一下工具栏项。
    • 那么在导航栏上添加一个长按手势识别器,当长按按钮时打开菜单?
    • 是的,基本上你会将手势添加到导航栏并在手势选择器中检查触摸点是否在相关按钮的矩形内。然后做需要做的事情:)
    • 好吧,这又回到了同样的问题——因为当 GR 触发时,我需要检查触摸点是否在相关按钮的矩形内——我不知道矩形有问题的按钮(因为框架不是 UIBarButtonItem 的属性...
    • 您不必检查按钮的矩形,您只需检查导航栏的侧面。该按钮没有框架,因为它不处理自己的命中测试。如果你仔细看,你可以触摸按钮外部,它仍然会被击中。这就是 NavBar 的处理方式。
    【解决方案4】:
    UIButton* undoButton = [UIButton buttonWithType:UIButtonTypeCustom];
    [undoButton addTarget:self action:@selector(undoPressStart:) forControlEvents:UIControlEventTouchDown];
    [undoButton addTarget:self action:@selector(undoPressFinish:) forControlEvents:UIControlEventTouchUpInside];
    UIBarButtonItem* navButton = [[[UIBarButtonItem alloc] initWithCustomView:undoButton] autorelease];
    self.navigationItem.rightBarButtonItem = navButton;
    

    您不必将 UIBarButtonItem 添加为 rightBarButtonItem,这是向您展示如何使用自定义视图(即您要处理事件的 UIButton)创建 UIBarButtonItem 的简单而简单的方法。

    您需要通过维护状态来实现 undoPressStart: 和 undoPressFinish:。我会在开始时说,存储当前的 NSDate 或时间的一些细粒度表示。完成后,如果检查经过的时间并且超过了某个阈值,则显示菜单 - 否则(以及如果从未捕获开始日期)执行撤消。

    作为一项改进,您可能还需要观察 UIControlEventTouchDragExit 事件以取消长按。

    【讨论】:

    • 创建按钮的方法(作为 UIBarButtonItem 的自定义视图)是有效的,并且在某些方面甚至比您概述的更简单 - 因为按钮的视图可以用作 long 的目标-按下手势识别器,我们都准备好了......但这不是一个足够完整的方向,因为创建的自定义按钮没有外观 - 因此看起来不像其他条形按钮项目.. . 所以我不能接受这个答案。
    • 认真的吗?因为您不愿意投入精力来设计自定义按钮,您会拒绝有效的答案?应该改写问题以指出您施加的这种人为限制。
    • 实际上,鉴于我提供了一个问题的链接 (stackoverflow.com/questions/2655630/…) 并指定我正在进一步寻找,我认为(并且仍然这样做),然后我当然不会接受答案出现在那个链接中......但是因为我不希望这是一场激烈的战争,而且我实际上并不是要拒绝你的答案(只是不接受它......因为它只是在那里,如果您编辑答案,我会 +1 [否则我的投票被锁定]...)
    • 没有战争,我很欣赏 +1 的提议,但我不是你要找的,我可以理解。我的观点是,如果您不想实现一种有效且广泛使用的方式,那么应该在问题中指定(也许还需要解释)。我们都喜欢参考其他相关的 SO 线程,因此我可以理解您选择使用链接作为不需要的内容的要求。所以没有火焰。祝您在寻求解决方案时好运。
    猜你喜欢
    • 2017-07-13
    • 1970-01-01
    • 2012-01-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-05
    • 1970-01-01
    相关资源
    最近更新 更多