objective-c 带有 UIButtons 的 UIScrollview - 如何重新创建跳板?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/650437/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
UIScrollview with UIButtons - how to recreate springboard?
提问by Patrick
I'm trying to create a springboard-like interface within my app. I'm trying to use UIButtons added to a UIScrollView. The problem I'm running in to is with the buttons not passing any touches to the UIScrollView - if I try to flick/slide and happen to press on the button it doesn't register for the UIScrollView, but if I flick the space between buttons it will work. The buttons do click/work if I touch them.
我正在尝试在我的应用程序中创建一个类似跳板的界面。我正在尝试使用添加到 UIScrollView 的 UIButtons。我遇到的问题是按钮没有将任何触摸传递给 UIScrollView - 如果我尝试轻弹/滑动并碰巧按下按钮,它不会注册 UIScrollView,但是如果我轻弹之间的空间按钮它会起作用。如果我触摸它们,按钮会点击/工作。
Is there a property or setting that forces the button to send the touch events up to its parent (superview)? Do the buttons need to be added to something else before being added the UIScrollView?
是否有强制按钮将触摸事件发送到其父级(超级视图)的属性或设置?在添加 UIScrollView 之前,是否需要将按钮添加到其他内容中?
Here is my code:
这是我的代码:
//init scrolling area
UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, 480, 480)];
scrollView.contentSize = CGSizeMake(480, 1000);
scrollView.bounces = NO;
scrollView.delaysContentTouches = NO;
//create background image
UIImageView *rowsBackground = [[UIImageView alloc] initWithImage:[self scaleAndRotateImage:[UIImage imageNamed:@"mylongbackground.png"]]];
rowsBackground.userInteractionEnabled = YES;
//create button
UIButton *btn = [[UIButton buttonWithType:UIButtonTypeCustom] retain];
btn.frame = CGRectMake(100, 850, 150, 150);
btn.bounds = CGRectMake(0, 0, 150.0, 150.0);
[btn setImage:[self scaleAndRotateImage:[UIImage imageNamed:@"basicbutton.png"]] forState:UIControlStateNormal];
[btn addTarget:self action:@selector(buttonClick) forControlEvents:UIControlEventTouchUpInside];
//add "stuff" to scrolling area
[scrollView addSubview:rowsBackground];
[scrollView addSubview:btn];
//add scrolling area to cocos2d
//this is just a UIWindow
[[[Director sharedDirector] openGLView] addSubview:scrollView];
//mem-mgmt
[rowsBackground release];
[btn release];
[scrollView release];
采纳答案by Brad Larson
In order for UIScrollViewto determine the difference between a click that passes through to its content view(s) and a touch that turns into a swipe or pinch, it needs to delay the touch and see if your finger has moved during that delay. By setting delaysContentTouchesto NOin your above example, you're preventing this from happening. Therefore, the scroll view is always passing the touch to the button, instead of canceling it when it turns out that the user is performing a swipe gesture. Try setting delaysContentTouchesto YES.
为了UIScrollView确定传递到其内容视图的点击与变成滑动或捏合的触摸之间的差异,它需要延迟触摸并查看您的手指在延迟期间是否移动。通过在上面的示例中设置delaysContentTouches为NO,您可以防止这种情况发生。因此,滚动视图总是将触摸传递给按钮,而不是在用户执行滑动手势时取消它。尝试设置delaysContentTouches为YES。
It might also be a good idea, structurally, to add all the views to be hosted in your scroll view to a common content view and only use that one view as the scroll view's subview.
从结构上讲,将要托管在滚动视图中的所有视图添加到公共内容视图中,并且仅使用该视图作为滚动视图的子视图,这也可能是一个好主意。
回答by Roman Kishchenko
Solution that worked for me included:
对我有用的解决方案包括:
- Setting
canCancelContentTouchesinUIScrollViewtoYES. - Extending
UIScrollViewto overridetouchesShouldCancelInContentView:(UIView *)viewto returnYESwhenviewis aUIButton.
- 设置
canCancelContentTouches在UIScrollView到YES。 - 扩展
UIScrollView到覆盖touchesShouldCancelInContentView:(UIView *)view以YES在view是 a时返回UIButton。
According to documentation touchesShouldCancelInContentViewreturns "YESto cancel further touch messages to view, NOto have view continue to receive those messages. The default returned value is YESif view is not a UIControlobject; otherwise, it returns NO."
根据文档touchesShouldCancelInContentView返回“YES取消进一步的触摸消息查看,NO让视图继续接收这些消息。默认返回值是YES如果视图不是UIControl对象;否则返回NO。”
Since UIButtonis a UIControlthe extension is necessary to get canCancelContentTouchesto take effect which enables scrolling.
由于UIButton是一个UIControl扩展名canCancelContentTouches才能生效,从而启用滚动。
回答by Richard Chen
I have a similar case that a number of buttons on a UIScrollView, and I want to scroll these buttons. At the beginning, I subclassed both UIScrollView and UIButton. However, I noticed that my subclassed UIScrollView did not receive touchesEnded event, so I changed to only subclass UIButton.
我有一个类似的情况,UIScrollView 上有多个按钮,我想滚动这些按钮。一开始,我继承了 UIScrollView 和 UIButton。但是,我注意到我的子类 UIScrollView 没有收到 touchesEnded 事件,因此我更改为仅子类 UIButton。
@interface MyPhotoButton : UIButton {
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;
@end
@implementation MyPhotoButton
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesMoved:touches withEvent:event];
[self setEnabled:NO];
[self setSelected:NO];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesEnded:touches withEvent:event];
[self setEnabled:YES];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesCancelled:touches withEvent:event];
[self setEnabled:YES];
}
@end
回答by Steven Noyes
OK here is your answer:
好的,这是你的答案:
Subclass UIButton. (NOTE: call [super ....] at the start of each override.
子类 UIButton。(注意:在每次覆盖开始时调用 [super ....]。
- Add a property. One of type BOOL (called enableToRestore)
- Add a property. One of type CGPoint (called startTouchPosition)
- in the awakeFromNib and initWithFrame, set the enableToRestore to the isEnabled property)
- Override "touchesBegan: withEvent:" to store the start of the touch position.
- Override "touchesMoved: withEvent:" to check to see if there is horizontal movement.
- If YES, set enabled to NO and selected to NO.
- 添加属性。BOOL 类型之一(称为 enableToRestore)
- 添加属性。CGPoint 类型之一(称为 startTouchPosition)
- 在awakeFromNib 和initWithFrame 中,将enableToRestore 设置为isEnabled 属性)
- 覆盖“touchesBegan: withEvent:”以存储触摸位置的开始。
- 覆盖“touchesMoved: withEvent:”以检查是否存在水平移动。
- 如果是,将启用设置为否并选择为否。
Sample code:
示例代码:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
[super touchesBegan:touches withEvent:event];
[self setStartTouchPosition:[touch locationInView:self]];
}
//
// Helper Function
//
- (BOOL)isTouchMovingHorizontally:(UITouch *)touch
{
CGPoint currentTouchPosition = [touch locationInView:self];
BOOL rValue = NO;
if (fabsf([self startTouchPosition].x - currentTouchPosition.x) >= 2.0)
{
rValue = YES;
}
return (rValue);
}
//
// This is called when the finger is moved. If the result is a left or right
// movement, the button will disable resulting in the UIScrollView being the
// next responder. The parrent ScrollView will then re-enable the button
// when the finger event is ended of cancelled.
//
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesMoved:touches withEvent:event];
if ([self isTouchMovingHorizontally:[touches anyObject]])
{
[self setEnabled:NO];
[self setSelected:NO];
}
}
This will activate the UIScrollView.
这将激活 UIScrollView。
Subclass UIScrollView. (NOTE: call [super ....] at the start of each override.
子类 UIScrollView。(注意:在每次覆盖开始时调用 [super ....]。
- Override both "touchesEnded: withEvent:" and "touchesCancelled: withEvent:"
- In the override, reset all subviews (and their subviews) enabled flag.
- NOTE: Use a Category and add the method to UIView:
- 覆盖“touchesEnded:withEvent:”和“touchesCancelled:withEvent:”
- 在覆盖中,重置所有子视图(及其子视图)启用标志。
- 注意:使用类别并将方法添加到 UIView:
.
.
- (void) restoreAllEnables
{
NSArray *views = [self subviews];
for (UIView *aView in views)
{
if ([aView respondsToSelector:@selector(restoreEnable)])
{
[aView restoreEnable];
}
}
}
- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesEnded:touches withEvent:event];
[self restoreAllEnables];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesEnded:touches withEvent:event];
[self restoreAllEnables];
}
- In the Category:
- 在类别中:
.
.
-(void) restoreEnable
{
NSArray *views = [self subviews];
if ([self respondsToSelector:@selector(enableToRestore)])
{
[self setEnabled:[self enableToRestore]];
}
for (UIView *aView in views)
{
if ([aView respondsToSelector:@selector(restoreEnable)])
{
[aView restoreEnable];
}
}
}
EDIT Note: I never got Answer 3 to work. Likewise: the setDelaysContentTouches:NO (set in the view controller or someplace) is to be set for best results on Answer 4. This provides very fast response to the buttons. Setting setDelaysContentTouches:YES puts a serious impact (150ms) on response time to the buttons and makes light,fast touching not possible.
编辑注意:我从来没有让答案 3 起作用。同样:setDelaysContentTouches:NO(在视图控制器或某处设置)将被设置为答案 4 的最佳结果。这提供了对按钮的非常快速的响应。设置 setDelaysContentTouches:YES 会对按钮的响应时间产生严重影响(150 毫秒),并使轻快的触摸变得不可能。
回答by Rog
UIScrollView handles a lot of events itself. You need to handle touchesDidEnd and hit test for buttons inside the UIScrollView manually.
UIScrollView 自己处理很多事件。您需要手动处理 UIScrollView 内的按钮的 touchesDidEnd 和点击测试。
回答by user41806
Another way is:
1. Substitute de button by a simple custom UIView
2. Put the flag "userInterationEnable = yes;" on the init method
3. In the view override the UIResponder method "touchesEnded" here you can trigger the action you need like a button.
另一种方法是:
1. 用简单的自定义 UIView 替换按钮
2. 放置标志“userInterationEnable = yes;” 在 init 方法上
3. 在视图中覆盖 UIResponder 方法“touchesEnded”,在这里您可以像按钮一样触发您需要的操作。
回答by Captain Fim
In my experience the first answer, i.e., simply setting delaysContentTouchesto YES, does not change anything with regard to the problem. The buttons will still not deliver tracking results to the scroll view. The third answer is both simple and very usable. Thanks sieroaoj!
根据我的经验,第一个答案,即简单地设置delaysContentTouches为YES,不会改变与问题有关的任何内容。这些按钮仍然不会向滚动视图提供跟踪结果。第三个答案既简单又非常有用。谢谢sieroaoj!
However, for the third answer to work you also need delaysContentTouchesset to YES. Otherwise the method touchesEndedwill also be called for tracking within the view. Therefore I could solve the problem by:
但是,要使第三个答案起作用,您还需要 delaysContentTouches将YES. 否则,该方法touchesEnded也将被调用以在视图内进行跟踪。因此,我可以通过以下方式解决问题:
- Substitute de button by a simple custom UIView
- Put the flag "userInterationEnable = yes;" on the init method
- In the view override the UIResponder method "touchesEnded" here you can trigger the action you
- 用一个简单的自定义 UIView 替换按钮
- 放置标志“userInterationEnable = yes;” 在 init 方法上
- 在视图中覆盖 UIResponder 方法“touchesEnded”在这里你可以触发你的动作
Fourth. set delaysContentTouchesto YES
第四。设置delaysContentTouches为YES

