質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
iOS

iOSとは、Apple製のスマートフォンであるiPhoneやタブレット端末のiPadに搭載しているオペレーションシステム(OS)です。その他にもiPod touch・Apple TVにも搭載されています。

Xcode

Xcodeはソフトウェア開発のための、Appleの統合開発環境です。Mac OSXに付随するかたちで配布されています。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

Q&A

解決済

1回答

870閲覧

swift:CALayerとCATextLayerの重なる順番

AppDvl

総合スコア58

iOS

iOSとは、Apple製のスマートフォンであるiPhoneやタブレット端末のiPadに搭載しているオペレーションシステム(OS)です。その他にもiPod touch・Apple TVにも搭載されています。

Xcode

Xcodeはソフトウェア開発のための、Appleの統合開発環境です。Mac OSXに付随するかたちで配布されています。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

0グッド

0クリップ

投稿2020/01/23 14:58

CALayerで丸、CATextLayerで番号を描き、タッチで選択して2つを同時に動かしたい。
しかし、なぜか先にCATextLayerの番号が選択されて、番号だけが動きます。
CALayerの丸を選択して動かすと番号の方も動きます。

重なっているレイヤの内、CALayerの丸の方を先に選択できれば解決すると思いますが、方法がわかりません。
CALayerを優先して選択する方法を教えて下さい。

self.addSublayer(textLayer)とself.addSublayer(ovalShapeLayer)の順番を変えても結果は同じでした。
どなたかご教授ください。
イメージ説明

ViewController

1import UIKit 2 3class ViewController: UIViewController,UIGestureRecognizerDelegate { 4 5 override func viewDidLoad() { 6 super.viewDidLoad() 7 effectiveScale = 1.0 //現在のスケールを初期化 8 //MARK:- ボタンの設置 9 let width = self.view.bounds.width 10 let height = self.view.bounds.height 11 //丸ボタンを生成 12 let ovalBtn = UIButton() 13 ovalBtn.frame = CGRect(x: 0, y: 0, width: 100, height: 50) 14 ovalBtn.center = CGPoint(x: width/3, y: height-30) 15 ovalBtn.addTarget(self, action: #selector(self.ovalBtnTapped(sender:)), for: .touchUpInside) 16 ovalBtn.setTitle("丸",for:.normal) 17 ovalBtn.backgroundColor = UIColor.green 18 self.view.addSubview(ovalBtn) 19 //四角を生成するボタン 20 let rectBtn = UIButton() 21 rectBtn.frame = CGRect(x:0,y:0,width:100,height:50) 22 rectBtn.center = CGPoint(x:width * 2 / 3,y:height - 30) 23 rectBtn.addTarget(self, action: #selector(ViewController.rectBtnTapped(sender:)), for: .touchUpInside) 24 rectBtn.setTitle("四角",for:.normal) 25 rectBtn.backgroundColor = UIColor.red 26 self.view.addSubview(rectBtn) 27 //ピンチ時の処理 28 let pinch = UIPinchGestureRecognizer() 29 pinch.addTarget(self,action:#selector(ViewController.pinchGesture(sender:))) 30 pinch.delegate = self 31 self.view.addGestureRecognizer(pinch) 32 } 33 34 @objc func ovalBtnTapped(sender:UIButton){ 35 //丸を描く @objcを付けられたメソッド、プロパティにはSwiftの実装に加え、Objective-Cで用いられるふたつの隠し引数を持った関数を同時に作成するようになります。このことで@objcを付けたメソッド、プロパティはObective-Cからも正しく利用することができるようになります。 36 let oval = MyShapeLayer() 37 oval.frame = CGRect(x:100,y:100,width:30,height:30) 38 oval.drawOval(lineWidth:1) 39 self.view.layer.addSublayer(oval) 40 } 41 @objc func rectBtnTapped(sender:UIButton){ 42 //四角を描く 43 let rect = MyShapeLayer() 44 rect.frame = CGRect(x:40,y:40,width:50,height:50) 45 rect.drawRect(lineWidth:1) 46 self.view.layer.addSublayer(rect) 47 } 48 49 //MARK:- タッチした時 50 //選択したレイヤーをいれておく 51 private var selectLayer:CALayer! 52 //最後にタッチされた座標をいれておく 53 private var touchLastPoint:CGPoint! 54 55 override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { 56 selectLayer = nil //すでに選択されたあレイヤがあるかもしれないのでnilとしておく 57 let touch : UITouch = touches.first! //touches.first! はタッチをしたことを取得 58 let layer:CALayer = hitLayer(touch: touch)//後で作る関数 タッチされた場所にあるレイヤを取得 59 let touchPoint:CGPoint = touch.location(in: self.view) //タッチされた座標を取得 60 touchLastPoint=touchPoint //最後にタッチされた場所の座標を入れておく 61 self.selectLayerFunc(layer:layer) //選択されたレイヤをselectLayerに入れる 62 } 63 64 //MARK:- タッチをした場所にあるレイヤーを取得するhitLayer(touch:)を作成 65 //座標を変換してレイヤーを返却しています。 66 //returnのhitTest部分で座標上にあるレイヤーを取得しています。 67 func hitLayer(touch:UITouch) -> CALayer{ 68 print("hitLayer") 69 var touchPoint:CGPoint = touch.location(in:self.view) 70 touchPoint = self.view.layer.convert(touchPoint, to: self.view.layer.superlayer) 71 return self.view.layer.hitTest(touchPoint)! 72 } 73 74 //MARK:- 選択されたレイヤーをselectLayerに入れている部分 75 //タッチした座標に、view上でのせているレイヤーがない場合はselectLayerにnilをいれ、CALayerがあった場合にはそのレイヤーをそのレイヤーを格納しています。 76 func selectLayerFunc(layer:CALayer?) { 77 if((layer == self.view.layer) || (layer == nil)){ 78 selectLayer = nil 79 print("空振り、なにもないところをタッチした") 80 let oval = MyShapeLayer() 81 oval.frame = CGRect(x:touchLastPoint.x-15,y:touchLastPoint.y-15,width:30,height:30) 82 oval.drawOval(lineWidth:1) 83 self.view.layer.addSublayer(oval) 84// selectLayer = // 怪しい 85 return 86 } 87 selectLayer = layer 88 print("レイヤをタッチした") 89 } 90 91 //MARK:- タッチが動いた時 92 override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) { 93 let touch:UITouch = touches.first! 94 let touchPoint:CGPoint = touch.location(in: self.view) 95 //直前の座標との差を取得 96 let touchOffsetPoint:CGPoint = CGPoint(x:touchPoint.x - touchLastPoint.x, 97 y:touchPoint.y - touchLastPoint.y) 98 touchLastPoint = touchPoint 99 100 if (selectLayer != nil){ 101 //hitしたレイヤーがあった場合 102 let px:CGFloat = selectLayer.position.x 103 let py:CGFloat = selectLayer.position.y 104 //レイヤーを移動させる 105 CATransaction.begin() 106 CATransaction.setDisableActions(true) 107 selectLayer.position = CGPoint(x:px + touchOffsetPoint.x,y:py + touchOffsetPoint.y) 108 selectLayer.borderWidth = 3.0 109 selectLayer.borderColor = UIColor.green.cgColor 110 CATransaction.commit() 111 112 } 113 } 114 //タッチを終えた時 115 override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) { 116 if(selectLayer != nil){ 117 selectLayer.borderWidth = 0 118 } 119 } 120 //タッチがキャンセルされた時 121 override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) { 122 if(selectLayer != nil){ 123 selectLayer.borderWidth = 0 } 124 } 125 126 private var beginGestureScale:CGFloat! //ピンチ後のスケール 127 private var effectiveScale:CGFloat! //現在のスケール 128 129 //ピンチインアウトをした時によばれる。アクションの中にレイヤーを変形するコードをかきます。 130 @objc func pinchGesture(sender:UIPinchGestureRecognizer){ 131 effectiveScale = beginGestureScale * sender.scale 132 //選択されてるやつだけ 133 if (selectLayer != nil){ 134 selectLayer.setAffineTransform(CGAffineTransform(scaleX: effectiveScale,y:effectiveScale)) 135 } 136 } 137 //ピンチが始まった時に、現在のスケールをbeginGestureScaleにいれておきます 138 func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { 139 if(gestureRecognizer.isKind(of:UIPinchGestureRecognizer.self)){ 140 beginGestureScale = effectiveScale 141 } 142 return true 143 } 144} 145

MyShapeLayer

1import UIKit 2 3class MyShapeLayer: CALayer { 4 //四角を描く関数 - CALayerの上に、そのCALayerのサイズと同じサイズのCAShapeLayerをのせています。 5 func drawRect(lineWidth:CGFloat){ 6 let rect = CAShapeLayer() 7 rect.strokeColor = UIColor.black.cgColor 8 rect.fillColor = UIColor.clear.cgColor 9 rect.lineWidth = lineWidth 10 rect.path = UIBezierPath(rect:CGRect(x:0,y:0,width:self.frame.width,height:self.frame.height)).cgPath 11 self.addSublayer(rect) 12 } 13 14 15 //丸を描く関数 - CALayerの上に、そのCALayerのサイズと同じサイズのCAShapeLayerをのせています。 16 func drawOval(lineWidth:CGFloat){ 17 let ovalShapeLayer = CAShapeLayer() 18 ovalShapeLayer.strokeColor = UIColor.blue.cgColor 19 ovalShapeLayer.fillColor = UIColor.clear.cgColor 20 ovalShapeLayer.lineWidth = lineWidth 21 ovalShapeLayer.path = UIBezierPath(ovalIn: CGRect(x:0, y:0, width:self.frame.width, height: self.frame.height)).cgPath 22 //番号を表示 23 let textLayer = CATextLayer() 24 textLayer.string = "999" 25 textLayer.foregroundColor = UIColor.green.cgColor 26 textLayer.fontSize = 10 27 textLayer.frame = CGRect(x:1,y:1,width:self.frame.width, height: self.frame.height) 28 29 self.addSublayer(textLayer) 30 self.addSublayer(ovalShapeLayer) 31 32 } 33 34} 35

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

CALayerで丸、CATextLayerで番号を描き、タッチで選択して2つを同時に動かしたい。

しかし、なぜか先にCATextLayerの番号が選択されて、番号だけが動きます。

コードを見た感じでは
MyShapeLayer(CALayer)に
・丸(CAShapeLayer)
・テキスト(CATextLayer)

の2つが乗っているように見えます。

CALayer.hitTest関数はそのレイヤそのものや子・孫といった階層まですべてヒットテストをしますので、
https://developer.apple.com/documentation/quartzcore/calayer/1410972-hittest
丸やテキストだけがヒットしてしまう事もあるかもしれません(CAShapeLayerは話を聞いている感じだとヒットしないのかもしれませんが)

なので、

func hitLayer(touch:UITouch) -> CALayer

の関数の実装を見直し、
view.layer.sublayersの中からMyShapeLayer型のものだけを抽出し(インスタンス①とします)、
その①のインスタンスに対してhitTest関数を呼び出し、ヒットしていることが判れば、①のインスタンスを選択レイヤーとして保持すれば期待通りの結果になるのではないかと思います。

以下疑似コードです。コンパイル通るかためしていませんが雰囲気だけでも伝われば。

swift

1for layer in view.layer.sublayers { 2 if let myLayer = layer as? MyShapeLayer { 3 if myLayer.hiiTest(...) != nil { 4 return myLayer 5 } 6 } 7}

投稿2020/01/24 00:48

編集2020/01/24 00:53
takabosoft

総合スコア8356

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

AppDvl

2020/01/26 08:48 編集

takabosoftさん 返信いただきありあがとうございます。 なんとか回答いただいた内容を反映して思っていたとおりに動きました。 スッキリしました。ありがとうございます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問