3022


`UIButton`を使用して、個別App用のキーボードを作成しています。 （iOS8に導入されたカスタムキーボードではありません）

iPhoneの英語キーボードや、デフォルトアプリである計算機（Calculator）では、
ボタンをスワイプしていくと、タッチされている位置のボタンがハイライトされていき、 最終的にタッチを離した位置のボタンがタップされたようになります。
（キーボードの外からドラッグされてきた場合には、ボタンは反応しません）

このような仕組みを実装したいのですが、うまく行きません。 なにか、よき解決方法、よき例などありましたら、示唆いただけないでしょうか？
現在、Swiftを使っていますが、Objective-Cでも構いません。

独力でできる範囲では、

[code]

        self.addTarget(self, action: "buttonHighlight", forControlEvents: UIControlEvents.TouchDown)
        self.addTarget(self, action: "buttonHighlight", forControlEvents: UIControlEvents.TouchDragEnter)
        self.addTarget(self, action: "buttonUnhighlight", forControlEvents: UIControlEvents.TouchUpInside)
        self.addTarget(self, action: "buttonUnhighlight", forControlEvents: UIControlEvents.TouchDragExit)
    
[/code]

を組み込んだ`UIButton`のサブクラスを作成して、

`TouchDown`と`TouchDragEnter`でボタンをハイライト化

`TouchUpInside`と`TouchDragExit`でボタンをハイライト解除

するみたいな方法を検討したのですが、`TouchDragEnter`が、

最初にボタンをタッチしたあと、一度、ボタン外にドラッグされて、「再度」ボタン内にドラッグされてきた 場合

を意味しているため、意図通りになりませんでした。
（この`TouchDragEnter`の挙動については、英語版のStackoverflowにもObjective-Cでの場合ですが、 記事がありました。）

なお、 iPadの英語キーボードの場合には、最初にタッチされた場所のボタンがハイライト
されたあと、ドラッグしてタッチされている位置がボタンの範囲から外れると、 そのボタンのハイライトが解除されるだけで、他のボタンがタッチされている状態に
なっても、それぞれのボタンは反応しません。（ちなみに、ドラッグしたまま元のボタン に戻っても何の反応もしません）

こちらは、上記の方法で、ほぼ同等の状態が出来ています。 （ドラッグしたまま元のボタンに戻ると、ボタンが押された状態になってしまいますが）


CC BY-SA 3.0
2015-01-03T23:29:06.200
3005
2015-01-04T20:43:18.760
2015-01-04T02:11:26.960
6006
6006
question
ios objective-c swift xcode6
iPhoneのキーボードのボタンを再現したいのですが・・・


`ViewController`にコードを追加する必要がありますが、このような方法はどうでしょうか。  
ボタンが3つある場合のサンプルです。`NSLog("button released: %d",
index)`の部分で最終的にタッチを離した位置のボタン番号を取得しています。

[code]

    class ViewController: UIViewController {
      @IBOutlet weak var button0: TestButton!
      @IBOutlet weak var button1: TestButton!
      @IBOutlet weak var button2: TestButton!
      var buttons: [UIButton] = []
    
      override func viewDidLoad() {
        super.viewDidLoad()
        self.buttons = [button0, button1, button2]
        for button: UIButton in buttons {
          button.addTarget(self, action: "buttonTouchDrag:event:", forControlEvents: UIControlEvents.TouchDragInside)
          button.addTarget(self, action: "buttonTouchDrag:event:", forControlEvents: UIControlEvents.TouchDragOutside)
          button.addTarget(self, action: "buttonTouchUp:event:", forControlEvents: UIControlEvents.TouchUpInside)
          button.addTarget(self, action: "buttonTouchUp:event:", forControlEvents: UIControlEvents.TouchUpOutside)
        }
      }
    
      func buttonTouchDrag(sender:UIButton, event: UIEvent) {
        let point = event.allTouches()?.anyObject()?.locationInView(self.view);
        for button: UIButton in buttons {
          if CGRectContainsPoint(button.convertRect(button.bounds, toView: self.view), point!) {
            button.sendActionsForControlEvents(UIControlEvents.TouchDragEnter);
          } else {
            button.sendActionsForControlEvents(UIControlEvents.TouchDragExit);
          }
        }
      }
    
      func buttonTouchUp(sender:UIButton, event: UIEvent) {
        let point = event.allTouches()?.anyObject()?.locationInView(self.view);
        for (index, button) in enumerate(buttons) {
          if CGRectContainsPoint(button.convertRect(button.bounds, toView: self.view), point!) {
            NSLog("button released: %d", index)
          }
          button.sendActionsForControlEvents(UIControlEvents.TouchDragExit);
        }
      }
    }
    
[/code]

![Screenshot](https://i.stack.imgur.com/eDhmF.gif)


CC BY-SA 3.0
2015-01-04T08:48:51.743
3022
2015-01-04T09:07:08.497
2015-01-04T09:07:08.497
3639
3639
3005
answer


h2so5さんの回答をいただいた上で、自分でいろいろ試行錯誤し、最終的にUILabelのサブクラスを作成し、ViewControllerのtouchesBegan,
touchesMoved, touchesEnded, touchesCancelledをオーバーライドすることで、同様の仕組みを再現してみました。

h2so5さんの回答をそのまま採用しなかったのは、コメントにも記載したようにUIButtonだと、最初にタップしたボタンの情報がずっと保持されているのが、少し気色悪かったからです。

ただ、自己回答の方法は、自分でもあまりスマートな方法とは思えませんし、不適切なところもあるかも知れません。もし、ご指摘いただける問題等ありましたら、ぜひご意見をいただければと思います。

繰り返しになりますが、h2so5さんと自身の回答のアプローチは大きく異なっていると思いますが、h2so5さんの回答をいただかなければ、自身の回答にはたどり着かなかったのは間違いありません。あらためて、h2so5さんに感謝いたします。

[code]

    import UIKit
    
    class ViewController: UIViewController {
    
        @IBOutlet var buttons: [keyboardButton]!
        var firstTouchInside: Bool = false
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            // Do any additional setup after loading the view, typically from a nib.
    
            for button: keyboardButton in buttons {
                button.userInteractionEnabled = true
                button.backgroundColor = UIColor.whiteColor()
            }
        }
    
        override func didReceiveMemoryWarning() {
            super.didReceiveMemoryWarning()
            // Dispose of any resources that can be recreated.
        }
    
        override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
            super.touchesBegan(touches, withEvent: event)
    
            let touch: UITouch = touches.anyObject() as UITouch
            switch touch.view.tag {
            case 1...3:
                (touch.view as keyboardButton).touchInside()
                self.firstTouchInside = true
            default:
                self.firstTouchInside = false
            }
        }
    
        override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {
            super.touchesMoved(touches, withEvent: event)
    
            if self.firstTouchInside == true {
                let touch: UITouch = touches.anyObject() as UITouch
                let oldLocation: CGPoint = touch.previousLocationInView(self.view)
                let newLocation: CGPoint = touch.locationInView(self.view)
                var oldTag: Int?
                var newTag: Int?
                for button: keyboardButton in buttons {
                    if CGRectContainsPoint(button.frame, oldLocation) {
                        oldTag = button.tag
                    }
                    if CGRectContainsPoint(button.frame, newLocation) {
                        newTag = button.tag
                    }
                }
    
                if oldTag != newTag {
                    if oldTag != nil {
                        (self.view.viewWithTag(oldTag!) as keyboardButton).touchCancel()
                    }
                    if newTag != nil {
                        (self.view.viewWithTag(newTag!) as keyboardButton).touchInside()
                    }
                }
            }
        }
    
        override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
            super.touchesEnded(touches, withEvent: event)
    
            if self.firstTouchInside == true {
                let touch: UITouch = touches.anyObject() as UITouch
                let location: CGPoint = touch.locationInView(self.view)
                for button: keyboardButton in buttons {
                    if CGRectContainsPoint(button.frame, location) {
                        button.touchUpInside()
                    }
                }
            }
        }
    
        override func touchesCancelled(touches: NSSet!, withEvent event: UIEvent!) {
            super.touchesCancelled(touches, withEvent: event)
    
            if self.firstTouchInside == true {
                let touch: UITouch = touches.anyObject() as UITouch
                switch touch.view.tag {
                case 1...3:
                    (touch.view as keyboardButton).touchCancel()
                default:
                    break
                }
            }
        }
    }
    
    class keyboardButton: UILabel {
        var originalBackgroundColor: UIColor?
    
        func touchInside() {
            if self.originalBackgroundColor == nil {
                self.originalBackgroundColor = self.backgroundColor
            }
            var hue: CGFloat = 0
            var saturation: CGFloat = 0
            var brightness: CGFloat = 0
            var alpha: CGFloat = 0
    
            self.backgroundColor?.getHue(
                &hue,
                saturation: &saturation,
                brightness: &brightness,
                alpha: &alpha)
            if brightness > 0.5 {
                brightness = brightness - 0.10
            } else {
                brightness = brightness + 0.10
            }
    
            self.backgroundColor = UIColor(
                hue: hue,
                saturation: saturation,
                brightness: brightness,
                alpha: alpha)
        }
    
        func touchCancel() {
            self.backgroundColor = self.originalBackgroundColor
        }
    
        func touchUpInside() {
            UIView.transitionWithView(
                self,
                duration: 0.5,
                options: (UIViewAnimationOptions.AllowUserInteraction | UIViewAnimationOptions.TransitionCrossDissolve),
                animations: {self.backgroundColor = self.originalBackgroundColor},
                completion: nil)
        }
    }
    
[/code]


CC BY-SA 3.0
2015-01-04T17:36:30.327
3081
2015-01-04T20:43:18.760
2015-01-04T20:43:18.760
6006
6006
3005
answer