私は同じようなことに関する大量の投稿を見てきましたが、どれもこの問題に完全に一致したり修正したりするものはありません。 iOS 7以降、UIButton
をUITableViewCell
に、またはフッタービューに追加するたびに「正常」に動作します。つまり、ターゲットアクションを受け取りますが、通常、UIButton
をタップすると発生します。 UIがファンキーに見えるようになり、ボタンがタッチに反応しなくなります。
私はこれがiOS7のバグとしてカウントされると確信していますが、誰かが解決策を見つけたか、解決策を見つけるのに役立ちます:)
編集:ボタンを長押しするとハイライト表示されることを忘れていましたが、標準ビューに追加しただけのようなクイックタップではありません。
コード:
ボタンの作成:
UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
button.titleLabel.font = [UIFont systemFontOfSize:14];
button.titleLabel.textColor = [UIColor blueColor];
[button setTitle:@"Testing" forState:UIControlStateNormal];
[button addTarget:self action:@selector(buttonPressed:) forControlEvents: UIControlEventTouchDown];
button.frame = CGRectMake(0, 0, self.view.frame.size.width/2, 40);
私がテストしたもの:
//邪魔になった場合に備えて、UITableView
のジェスチャレコグナイザーを削除します。
for (UIGestureRecognizer *recognizer in self.tableView.gestureRecognizers) {
recognizer.enabled = NO;
}
// Cellからジェスチャーを削除する
for (UIGestureRecognizer *recognizer in self.contentView.gestureRecognizers) {
recognizer.enabled = NO;
}
//これは少し軽いタッチを示していますが、これは望ましい外観ではありません
button.showsTouchWhenHighlighted = YES;
そのテーブルビューでは、このプロパティを追加するだけです。
tableview.delaysContentTouches = NO;
そして、コードの下に追加したばかりのセルを開始した後、cellForRowAtIndexPathを追加します。セルの構造は、iOS 6とiOS 7で明らかに異なります。
iOS 7では、UITableViewCellとコンテンツビューの間にUITableViewCellScrollViewコントロールが1つあります。
for (id obj in cell.subviews)
{
if ([NSStringFromClass([obj class]) isEqualToString:@"UITableViewCellScrollView"])
{
UIScrollView *scroll = (UIScrollView *) obj;
scroll.delaysContentTouches = NO;
break;
}
}
IOS 8以降、同じ手法をUITableViewサブビューに適用する必要があります(テーブルには非表示のUITableViewWrapperViewスクロールビューが含まれます)。 UITableViewCellサブビューを繰り返す必要はもうありません。
for (UIView *currentView in tableView.subviews) {
if ([currentView isKindOfClass:[UIScrollView class]]) {
((UIScrollView *)currentView).delaysContentTouches = NO;
break;
}
}
この回答 はこの質問にリンクする必要があります。
私はこれを受け入れられた答えに加えようとしましたが、それは決して通りませんでした。これは、特定のクラスではなく、セレクターに応答するものを検索するため、セルのdelaysContentTouchesプロパティをオフにするより安全な方法です。
セル内:
for (id obj in self.subviews) {
if ([obj respondsToSelector:@selector(setDelaysContentTouches:)]) {
[obj setDelaysContentTouches:NO];
}
}
TableViewで:
self.tableView.delaysContentTouches = NO;
IOS7とiOS8の両方で機能するソリューションの場合、カスタムUITableView
サブクラスとカスタムUITableViewCell
サブクラスを作成します。
このサンプルUITableView
のinitWithFrame:
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
// iterate over all the UITableView's subviews
for (id view in self.subviews)
{
// looking for a UITableViewWrapperView
if ([NSStringFromClass([view class]) isEqualToString:@"UITableViewWrapperView"])
{
// this test is necessary for safety and because a "UITableViewWrapperView" is NOT a UIScrollView in iOS7
if([view isKindOfClass:[UIScrollView class]])
{
// turn OFF delaysContentTouches in the hidden subview
UIScrollView *scroll = (UIScrollView *) view;
scroll.delaysContentTouches = NO;
}
break;
}
}
}
return self;
}
このサンプルUITableViewCell
のinitWithStyle:reuseIdentifier:
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self)
{
// iterate over all the UITableViewCell's subviews
for (id view in self.subviews)
{
// looking for a UITableViewCellScrollView
if ([NSStringFromClass([view class]) isEqualToString:@"UITableViewCellScrollView"])
{
// this test is here for safety only, also there is no UITableViewCellScrollView in iOS8
if([view isKindOfClass:[UIScrollView class]])
{
// turn OFF delaysContentTouches in the hidden subview
UIScrollView *scroll = (UIScrollView *) view;
scroll.delaysContentTouches = NO;
}
break;
}
}
}
return self;
}
この問題を解決するために私がしたことは、次のコードを使用したUIButtonのカテゴリでした。
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesBegan:touches withEvent:event];
[NSOperationQueue.mainQueue addOperationWithBlock:^{ self.highlighted = YES; }];
}
- (void) touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesCancelled:touches withEvent:event];
[self performSelector:@selector(setDefault) withObject:nil afterDelay:0.1];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesEnded:touches withEvent:event];
[self performSelector:@selector(setDefault) withObject:nil afterDelay:0.1];
}
- (void)setDefault
{
[NSOperationQueue.mainQueue addOperationWithBlock:^{ self.highlighted = NO; }];
}
uITableViewCellでボタンを押すと、ボタンは正しく反応し、UITableViewはdelaysContentTouches
が強制されていないため、正常に動作します。
Swift 2:
for view in tableView.subviews {
if view is UIScrollView {
(view as? UIScrollView)!.delaysContentTouches = false
break
}
}
- (void)viewDidLoad
{
[super viewDidLoad];
for (id view in self.tableView.subviews)
{
// looking for a UITableViewWrapperView
if ([NSStringFromClass([view class]) isEqualToString:@"UITableViewWrapperView"])
{
// this test is necessary for safety and because a "UITableViewWrapperView" is NOT a UIScrollView in iOS7
if([view isKindOfClass:[UIScrollView class]])
{
// turn OFF delaysContentTouches in the hidden subview
UIScrollView *scroll = (UIScrollView *) view;
scroll.delaysContentTouches = NO;
}
break;
}
}
}
これは、上記のRaphaëlPintoの回答のSwiftバージョンです。彼も賛成することを忘れないでください:)
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
super.touchesBegan(touches, withEvent: event)
NSOperationQueue.mainQueue().addOperationWithBlock { () -> Void in self.highlighted = true }
}
override func touchesCancelled(touches: NSSet!, withEvent event: UIEvent!) {
super.touchesCancelled(touches, withEvent: event)
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(0.1 * Double(NSEC_PER_SEC)))
dispatch_after(time, dispatch_get_main_queue()) {
self.setDefault()
}
}
override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
super.touchesEnded(touches, withEvent: event)
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(0.1 * Double(NSEC_PER_SEC)))
dispatch_after(time, dispatch_get_main_queue()) {
self.setDefault()
}
}
func setDefault() {
NSOperationQueue.mainQueue().addOperationWithBlock { () -> Void in self.highlighted = false }
}
Swiftのソリューション、iOS8のみ(iOS7の各セルで追加の作業が必要です):
//
// NoDelayTableView.Swift
// DivineBiblePhone
//
// Created by Chris Hulbert on 30/03/2015.
// Copyright (c) 2015 Chris Hulbert. All rights reserved.
//
// This solves the delayed-tap issue on buttons on cells.
import UIKit
class NoDelayTableView: UITableView {
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
delaysContentTouches = false
// This solves the iOS8 delayed-tap issue.
// http://stackoverflow.com/questions/19256996/uibutton-not-showing-highlight-on-tap-in-ios7
for view in subviews {
if let scroll = view as? UIScrollView {
scroll.delaysContentTouches = false
}
}
}
override func touchesShouldCancelInContentView(view: UIView!) -> Bool {
// So that if you tap and drag, it cancels the tap.
return true
}
}
使用するには、ストーリーボードでクラスをNoDelayTableView
に変更するだけです。
IOS8では、セルのcontentView内に配置されたボタンがすぐに強調表示されるようになりました。
Chris Harrison's answer のわずかに変更されたバージョン。 Swift 2.3:
class HighlightButton: UIButton {
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
super.touchesBegan(touches, withEvent: event)
NSOperationQueue.mainQueue().addOperationWithBlock { _ in self.highlighted = true }
}
override func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?) {
super.touchesCancelled(touches, withEvent: event)
setDefault()
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
super.touchesEnded(touches, withEvent: event)
setDefault()
}
private func setDefault() {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(0.1 * Double(NSEC_PER_SEC))), dispatch_get_main_queue()) {
NSOperationQueue.mainQueue().addOperationWithBlock { _ in self.highlighted = false }
}
}
}
受け入れられた答えは、私にとっていくつかの「タップ」では機能しませんでした。
最後に、Uibuttonカテゴリ(/サブクラス)に以下のコードを追加すると、100%動作します。
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
self.backgroundColor = [UIColor greenColor];
[UIView animateWithDuration:0.05 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
self.backgroundColor = [UIColor clearColor];
} completion:^(BOOL finished)
{
}];
[super touchesBegan:touches withEvent:event];
}
この問題の対処を簡単にするために、UITableViewCell
にカテゴリ拡張を作成しました。これは、UITableViewCell contentView
から(下ではなく)ビュー階層を上るということを除いて、受け入れられた答えと基本的に同じです。
UITableView
に追加されたすべてのセルが、所有するdelaysContentTouches
のUITableView
状態に一致するようにdelaysContentTouches
状態を設定する、完全に「自動」のソリューションを検討しました。これを機能させるには、UITableView
をスウィズルするか、開発者にUITableView
サブクラスの使用を要求する必要があります。どちらも要求したくないので、私はこの解決策に落ち着きました。
カテゴリ拡張とサンプルハーネスはこちら:
https://github.com/TomSwift/UITableViewCell-TS_delaysContentTouches
使用するのは非常に簡単です:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// using static cells from storyboard...
UITableViewCell* cell = [super tableView: tableView cellForRowAtIndexPath: indexPath];
cell.ts_delaysContentTouches = NO;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
return cell;
}
カテゴリのコードは次のとおりです。
@interface UITableViewCell (TS_delaysContentTouches)
@property (nonatomic, assign) BOOL ts_delaysContentTouches;
@end
@implementation UITableViewCell (TS_delaysContentTouches)
- (UIScrollView*) ts_scrollView
{
id sv = self.contentView.superview;
while ( ![sv isKindOfClass: [UIScrollView class]] && sv != self )
{
sv = [sv superview];
}
return sv == self ? nil : sv;
}
- (void) setTs_delaysContentTouches:(BOOL)delaysContentTouches
{
[self willChangeValueForKey: @"ts_delaysContentTouches"];
[[self ts_scrollView] setDelaysContentTouches: delaysContentTouches];
[self didChangeValueForKey: @"ts_delaysContentTouches"];
}
- (BOOL) ts_delaysContentTouches
{
return [[self ts_scrollView] delaysContentTouches];
}
@end
Objcは動的で、scrollViewがdelaysContentTouchesに応答する唯一のクラスであるため、これはios 7と8の両方で機能するはずです(awakeFromNibのように、tableViewControllerの初期のどこかに置きます)。
for (id view in self.tableView.subviews)
{
if ([view respondsToSelector:@selector(delaysContentTouches)]) {
UIScrollView *scrollView = (UIScrollView *)view;
scrollView.delaysContentTouches = NO;
break;
}
}
また、viewController内のテーブルを選択して、ストーリーボードまたはペン先で「delaysContentTouches」をオフにする必要があります。ところで、viewController内でtableViewを使用している場合、これはiOS 7では動作しない可能性があります。少なくとも動作させることはできませんでした。
Swift 3では、このUIView拡張機能はUITableViewCellで使用できます。できればcellForRowAt
メソッドで使用してください。
func removeTouchDelayForSubviews() {
for subview in subviews {
if let scrollView = subview as? UIScrollView {
scrollView.delaysContentTouches = false
} else {
subview.removeTouchDelayForSubviews()
}
}
}
私のためのそのソリューションは動作しません、私はfixed TableViewをサブクラス化し、これら2つのメソッドを実装します
- (instancetype)initWithCoder:(NSCoder *)coder{
self = [super initWithCoder:coder];
if (self) {
for (id obj in self.subviews) {
if ([obj respondsToSelector:@selector(setDelaysContentTouches:)]){
[obj performSelector:@selector(setDelaysContentTouches:) withObject:NO];
}
}
}
return self;
}
- (BOOL)delaysContentTouches{
return NO;
}
Swift iOS 7および8の場合:
最初にユーティリティ関数を書きました:
class func classNameAsString(obj: AnyObject) -> String {
return _stdlib_getDemangledTypeName(obj).componentsSeparatedByString(".").last!
}
次に、UITableViewをサブクラス化し、これを実装します。
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
for view in self.subviews {
if (Utility.classNameAsString(view) == "UITableViewWrapperView") {
if view.isKindOfClass(UIScrollView) {
var scroll = (view as UIScrollView)
scroll.delaysContentTouches = false
}
break
}
}
}
また、UITableViewCellをサブクラス化し、これを実装します。
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
for view in self.subviews {
if (Utility.classNameAsString(view) == "UITableViewCellScrollView") {
if view.isKindOfClass(UIScrollView) {
var scroll = (view as UIScrollView)
scroll.delaysContentTouches = false
}
}
}
}
私の場合、init(coder :)が実行されます。デバッグポイントをinit関数に入れて、どのinit関数が実行されるかを確認してから、上記のコードを使用して動作させてください。誰かを助けたい。