NSView上のドラッグによって、ウィンドウをドラッグさせる

# 概要

- 指定したカスタムビュー上でドラッグした場合に、ウィンドウの位置を移動させる

![Jun-25-2019 15-30-04](https://i.imgur.com/Zc8Fw4y.gif)

# GitHub

https://github.com/pommdau/Qiita_DraggableCustomView

# 実装
## カスタムビュー（DraggingView）
- コード全体

``` ObjectiveC
#import "DraggingView.h"

@interface DraggingView()
@property NSPoint currentPoint;
@end

@implementation DraggingView

- (void)drawRect:(NSRect)dirtyRect {
    [super drawRect:dirtyRect];
    // Drawing code here.
    // 分かりやすいようにビューに色を付ける
    NSRect bounds = [self bounds];
    [[[NSColor blueColor] colorWithAlphaComponent:0.1] set];
    [NSBezierPath fillRect:bounds];
}

#pragma mark Events
- (void)mouseDown:(NSEvent *)event {
    NSPoint p = [event locationInWindow];
    _currentPoint = [self convertPoint:p fromView:nil]; // viewの座標系に変換
    [self setNeedsDisplay:YES];
}

- (void)mouseDragged:(NSEvent *)event {
    NSPoint previousPoint = _currentPoint;
    NSPoint p = [event locationInWindow];
    _currentPoint = [self convertPoint:p fromView:nil]; // viewの座標系に変換
    
    // 移動したマウスの距離だけウィンドウの位置を移動させる
    double  distance_x   = _currentPoint.x - previousPoint.x;
    double  distance_y   = _currentPoint.y - previousPoint.y;
    NSRect  windowFrame  = self.window.frame;
    NSPoint windowOrigin = windowFrame.origin; // 現在のウィンドウ位置
    windowOrigin.x  += distance_x;
    windowOrigin.y  += distance_y;
    _currentPoint.x -= distance_x;             // ウィンドウの位置が変わったので、次のマウス移動の計算のために補正を行う
    _currentPoint.y -= distance_y;
    [self.window setFrameOrigin:windowOrigin]; // ウィンドウの位置を移動
    [self setNeedsDisplay:YES];
}

- (void)mouseUp:(NSEvent *)event {
    NSPoint p = [event locationInWindow];
    _currentPoint = [self convertPoint:p fromView:nil]; // viewの座標系に変換
    [self setNeedsDisplay:YES];
}

@end
```

- `NSView`は`NSResponder`を継承していて、これにはイベントを処理するメソッドが定義されています。
- 今回はその中の`mouseDown``mouseDragged``mouseUp`を使用します。
    - [mouseDragged(with:)](https://developer.apple.com/documentation/appkit/nsresponder/1527420-mousedragged)

``` ObjectiveC
- (void)mouseDown:(NSEvent *)event {
    NSPoint p = [event locationInWindow];
    _currentPoint = [self convertPoint:p fromView:nil]; // viewの座標系に変換
    [self setNeedsDisplay:YES];
}
```

- マウスを押し込んだ際に呼ばれるメソッド
- 引数の`NSEvent`オブジェクトから作成できる座標情報はウィンドウのものなので、今回は必要ないが、利便性のため`NSView`の座標系に変換することが多い。
- `_currentPoint`に押された位置を記録しておく

``` ObjectiveC
- (void)mouseDragged:(NSEvent *)event {
    NSPoint previousPoint = _currentPoint;
    NSPoint p = [event locationInWindow];
    _currentPoint = [self convertPoint:p fromView:nil]; // viewの座標系に変換
    
    // 移動したマウスの距離だけウィンドウの位置を移動させる
    double  distance_x   = _currentPoint.x - previousPoint.x;
    double  distance_y   = _currentPoint.y - previousPoint.y;
```

- ドラッグ前のマウス位置は`previousPoint`へ保存
- ドラッグ後のマウス位置を`_currentPoint`へ保存
- この2点からドラッグした距離を計算する

``` ObjectiveC
NSRect  windowFrame  = self.window.frame;
NSPoint windowOrigin = windowFrame.origin; // 現在のウィンドウ位置
windowOrigin.x  += distance_x;
windowOrigin.y  += distance_y;
_currentPoint.x -= distance_x;             // ウィンドウの位置が変わったので、次のマウス移動の計算のために補正を行う
_currentPoint.y -= distance_y;
[self.window setFrameOrigin:windowOrigin]; // ウィンドウの位置を移動
[self setNeedsDisplay:YES];
```

- 移動した距離だけウィンドウの位置を変更する
- **このとき`_currentPoint`を更新しておくことを忘れないように。**
    - ウィンドウ位置が変更したため`_currentPoint`を補完しないと、次回のドラッグ距離計算のときにずれが生じてしまう。
 
``` ObjectiveC
- (void)mouseUp:(NSEvent *)event {
    NSPoint p = [event locationInWindow];
    _currentPoint = [self convertPoint:p fromView:nil]; // viewの座標系に変換
    [self setNeedsDisplay:YES];
}
```

- ドラッグが終了したときに呼ばれるメソッド。
- 今回は必要なし。

# 参考
- ヒレガス本の18章
- `NSView`を透明度を変えて塗りつぶす方法
    - [Change opacity for pre-defined color?](https://stackoverflow.com/questions/21612437/change-opacity-for-pre-defined-color)