ピンチの方向に応じて、x 方向、y 方向、または両方向にスケーリングする Pinch Recognizer が必要でした。ここで他の質問の多くを調べましたが、回答の一部しかありませんでした. カスタム UIPinchGestureRecognizer を使用する完全なソリューションを次に示します。
3 に答える
UIPinchGestureRecognizer のカスタム バージョンを作成しました。2 本の指の間の線の傾きを使用して、スケールの方向を決定します。3 つのタイプがあります。水平; および組み合わせ(対角線)。下部にある私のメモを参照してください。
-(void) scaleTheView:(UIPinchGestureRecognizer *)pinchRecognizer
{
if ([pinchRecognizer state] == UIGestureRecognizerStateBegan || [pinchRecognizer state] == UIGestureRecognizerStateChanged) {
if ([pinchRecognizer numberOfTouches] > 1) {
UIView *theView = [pinchRecognizer view];
CGPoint locationOne = [pinchRecognizer locationOfTouch:0 inView:theView];
CGPoint locationTwo = [pinchRecognizer locationOfTouch:1 inView:theView];
NSLog(@"touch ONE = %f, %f", locationOne.x, locationOne.y);
NSLog(@"touch TWO = %f, %f", locationTwo.x, locationTwo.y);
[scalableView setBackgroundColor:[UIColor redColor]];
if (locationOne.x == locationTwo.x) {
// perfect vertical line
// not likely, but to avoid dividing by 0 in the slope equation
theSlope = 1000.0;
}else if (locationOne.y == locationTwo.y) {
// perfect horz line
// not likely, but to avoid any problems in the slope equation
theSlope = 0.0;
}else {
theSlope = (locationTwo.y - locationOne.y)/(locationTwo.x - locationOne.x);
}
double abSlope = ABS(theSlope);
if (abSlope < 0.5) {
// Horizontal pinch - scale in the X
[arrows setImage:[UIImage imageNamed:@"HorzArrows.png"]];
arrows.hidden = FALSE;
// tranform.a = X-axis
NSLog(@"transform.A = %f", scalableView.transform.a);
// tranform.d = Y-axis
NSLog(@"transform.D = %f", scalableView.transform.d);
// if hit scale limit along X-axis then stop scale and show Blocked image
if (((pinchRecognizer.scale > 1.0) && (scalableView.transform.a >= 2.0)) || ((pinchRecognizer.scale < 1.0) && (scalableView.transform.a <= 0.1))) {
blocked.hidden = FALSE;
arrows.hidden = TRUE;
} else {
// scale along X-axis
scalableView.transform = CGAffineTransformScale(scalableView.transform, pinchRecognizer.scale, 1.0);
pinchRecognizer.scale = 1.0;
blocked.hidden = TRUE;
arrows.hidden = FALSE;
}
}else if (abSlope > 1.7) {
// Vertical pinch - scale in the Y
[arrows setImage:[UIImage imageNamed:@"VerticalArrows.png"]];
arrows.hidden = FALSE;
NSLog(@"transform.A = %f", scalableView.transform.a);
NSLog(@"transform.D = %f", scalableView.transform.d);
// if hit scale limit along Y-axis then don't scale and show Blocked image
if (((pinchRecognizer.scale > 1.0) && (scalableView.transform.d >= 2.0)) || ((pinchRecognizer.scale < 1.0) && (scalableView.transform.d <= 0.1))) {
blocked.hidden = FALSE;
arrows.hidden = TRUE;
} else {
// scale along Y-axis
scalableView.transform = CGAffineTransformScale(scalableView.transform, 1.0, pinchRecognizer.scale);
pinchRecognizer.scale = 1.0;
blocked.hidden = TRUE;
arrows.hidden = FALSE;
}
} else {
// Diagonal pinch - scale in both directions
[arrows setImage:[UIImage imageNamed:@"CrossArrows.png"]];
blocked.hidden = TRUE;
arrows.hidden = FALSE;
NSLog(@"transform.A = %f", scalableView.transform.a);
NSLog(@"transform.D = %f", scalableView.transform.d);
// if we have hit any limit don't allow scaling
if ((((pinchRecognizer.scale > 1.0) && (scalableView.transform.a >= 2.0)) || ((pinchRecognizer.scale < 1.0) && (scalableView.transform.a <= 0.1))) || (((pinchRecognizer.scale > 1.0) && (scalableView.transform.d >= 2.0)) || ((pinchRecognizer.scale < 1.0) && (scalableView.transform.d <= 0.1)))) {
blocked.hidden = FALSE;
arrows.hidden = TRUE;
} else {
// scale in both directions
scalableView.transform = CGAffineTransformScale(scalableView.transform, pinchRecognizer.scale, pinchRecognizer.scale);
pinchRecognizer.scale = 1.0;
blocked.hidden = TRUE;
arrows.hidden = FALSE;
}
} // else for diagonal pinch
} // if numberOfTouches
} // StateBegan if
if ([pinchRecognizer state] == UIGestureRecognizerStateEnded || [pinchRecognizer state] == UIGestureRecognizerStateCancelled) {
NSLog(@"StateEnded StateCancelled");
[scalableView setBackgroundColor:[UIColor whiteColor]];
arrows.hidden = TRUE;
blocked.hidden = TRUE;
}
}
ビュー コントローラのヘッダー ファイルにプロトコルを追加することを忘れないでください。
@interface WhiteViewController : UIViewController <UIGestureRecognizerDelegate>
{
IBOutlet UIView *scalableView;
IBOutlet UIView *mainView;
IBOutlet UIImageView *arrows;
IBOutlet UIImageView *blocked;
}
@property (strong, nonatomic) IBOutlet UIView *scalableView;
@property (strong, nonatomic) IBOutlet UIView *mainView;
@property (strong, nonatomic)IBOutlet UIImageView *arrows;
@property (strong, nonatomic)IBOutlet UIImageView *blocked;
-(void) scaleTheView:(UIPinchGestureRecognizer *)pinchRecognizer;
@end
そして、viewDidLoad にレコグナイザーを追加します。
- (void)viewDidLoad
{
UIPinchGestureRecognizer *pinchGesture = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(scaleTheView:)];
[pinchGesture setDelegate:self];
[mainView addGestureRecognizer:pinchGesture];
arrows.hidden = TRUE;
blocked.hidden = TRUE;
[scalableView setBackgroundColor:[UIColor whiteColor]];
}
これは、ピンチをキャプチャするためにメイン ビューを使用するように設定されています。2 番目のビューを操作します。このようにして、ビューが小さくなっても拡大縮小できます。スケーラブル ビューに直接反応するように変更できます。
制限: ビューの開始サイズを任意に選択したので、2.0 のスケール制限がフルスクリーンに等しくなります。私の下限スケールは0.1に設定されています。
ユーザー インタラクション: ビューの背景色を変更したり、方向を示すためにビューに矢印を追加/変更したりするなど、多くのユーザー インタラクションをいじっています。特にこのコードで許可されているように方向を変更する場合は、スケーリング プロセス中にフィードバックを提供することが重要です。
BUG: Apple の UIPinchGestureRecognizer にバグがあります。ご想像のとおり、2 本の指のタッチで UIGestureRecognizerStateBegan を登録します。しかし、StateBegan または StateChanged になったら、指を 1 本離すだけでその状態が維持されます。両方の指が離されるまで、StateEnded または StateCancelled に移動しません。これにより、コードにバグが発生し、多くの頭痛の種になりました! if numberOfTouches > 1 で修正されます。
将来: 勾配の設定を変更して、1 方向または 2 方向のみにスケーリングすることができます。矢印の画像を追加すると、指を回転させると変化することがわかります。
この代替ソリューションは、勾配ではなく方位角に基づいてスケーリングの方向を決定します。角度測定を使用してさまざまなゾーンを調整する方が少し簡単だと思います。
@objc func viewPinched(sender: UIPinchGestureRecognizer) {
// Scale the view either vertically, horizontally, or diagonally based on the axis of the initial pinch
let locationOne = sender.location(ofTouch: 0, in: sender.view)
let locationTwo = sender.location(ofTouch: 1, in: sender.view)
let diffX = locationOne.x - locationTwo.x
let diffY = locationOne.y - locationTwo.y
// Break the plane into 3 equal segments
// Inverse tangent will return between π/2 and -π/2. Absolute value can be used to only consider 0 to π/2 - don't forget to handle divide by 0 case
// Breaking π/2 into three equal pieces, we get regions of 0 to π/6, π/6 to 2π/6, and 2π/6 to π/2 (note 2π/6 = π/3)
// Radian reminder - π/2 is 90 degreees :)
let bearingAngle = diffY == 0 ? CGFloat.pi / 2.0 : abs(atan(diffX/diffY))
if sender.state == .began {
// Determine type of pan based on bearing angle formed by the two touch points.
// Only do this when the pan begins - don't change type as the user rotates their fingers. Require a new gesture to change pan type
if bearingAngle < CGFloat.pi / 6.0 {
panType = .vertical
} else if bearingAngle < CGFloat.pi / 3.0 {
panType = .diagonal
} else if bearingAngle <= CGFloat.pi / 2.0 {
panType = .horizontal
}
}
// Scale the view based on the pan type
switch panType {
case .diagonal: transform = CGAffineTransform(scaleX: sender.scale, y: sender.scale)
case .horizontal: transform = CGAffineTransform(scaleX: sender.scale, y: 1.0)
case .vertical: transform = CGAffineTransform(scaleX: 1.0, y: sender.scale)
}
}