9
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

MacアプリTunacan開発の技術的メモ

Last updated at Posted at 2015-06-20

画像の高さを揃えて並べてつなげる、というだけのTunacanというMacアプリを作りました。
開発の過程で学んだ技術的内容のメモです。
iOSアプリをある程度かけるけど、Macアプリは初心者、という人向け。

UI関連

Drag & Drop

tunacan-bridge-image-effect2.gif

ドラッグアンドドロップはiOSアプリではあまり登場しないインターフェイスだが、Macの方には仕組みが用意されている。

NSViewregisterForDraggedTypesを呼ぶと、どのようなタイプのドラッグアンドドロップを受け付けるかを指定できる。awakeFromNib内とかで指定。

self.registerForDraggedTypes([NSFilenamesPboardType])

NSFilenamesPboardTypeを指定するとFinderからのファイルのドラッグアンドドロップが可能に。他にも色とかも指定できる。

画像がビュー上にドラッグされるとdraggingEnteredが呼ばれる。

override func draggingEntered(sender: NSDraggingInfo) -> NSDragOperation {
    return .Copy
}

ドラッグアンドドロップを許可する場合はこのメソッドで適切な値(NSDragOperation)を返す。対応しないものがドラッグされてきた場合にはNoneを返すようにするべき。ドラッグされてきた画像をコピーして使う場合はCopyを、リンク的な使い方しかしない場合はLinkを返す。

ドラッグ中にドラッグされた場所に応じて見た目を変更したい場合はdraggingUpdatedを使え、とのこと。

override func draggingUpdated(sender: NSDraggingInfo) -> NSDragOperation {
    println(sender.draggingLocation())
    return .Copy
}

ファイルがドロップされると、prepareForDragOperationperformDragOperationconcludeDragOperationが呼ばれる。prepareForDragOperationperformDragOperationfalseを返すとドロップ処理はキャンセルされ、concludeDragOperationも呼ばれなくなる。対応していないファイルがドロップされた時はここで弾く。

override func performDragOperation(sender: NSDraggingInfo) -> Bool {
    let pasteboard = sender.draggingPasteboard()
        
    if contains(pasteboard.types as! [NSString], NSFilenamesPboardType) {
        let files: [String] = pasteboard.propertyListForType(NSFilenamesPboardType) as! [String]
        for file in files {
            if let url = NSURL(fileURLWithPath: file) {
                println(url)
            }
        }
    }
    return true
}

公式ドキュメントによると、「performDragOperationの中で大まかなドラッグアンドドロップの処理をして、最後にconcludeDragOperationでUI上に反映させる」というのが正しい実装方法っぽい。

マウスイベント

mouseEnteredもmouseExitedも呼ばれない

マウス関連デリゲートメソッドとして、mouseEnteredmouseExitedなどがあるが、ただオーバーライドしてもこれらのメソッドは呼ばれない。

マウスイベントを受け取るには、TrackingAreaを設定してやる必要がある。

An NSTrackingArea object defines a region of view that generates mouse-tracking and cursor-update events when the mouse is over that region.

NSViewaddTrackingArea:メソッドでエリアを指定すると、そのエリアで発生したマウスイベントが通知されるようになる。NSViewにはupdateTrackingAreaメソッドが実装されており、このメソッドをオーバーライドしてトラッキングエリアを更新することができる。

var trackingArea: NSTrackingArea?
override func updateTrackingAreas() {
    if let trackingArea = trackingArea {
        self.removeTrackingArea(trackingArea)
    }
        
        
    let newTrackingArea = NSTrackingArea(rect: self.bounds, options: [.activeAlways, .mouseEnteredAndExited, .mouseMoved], owner: self, userInfo: nil)
    self.addTrackingArea(newTrackingArea)
    self.trackingArea = newTrackingArea
}

TrackingAreaのオプションには、通知されるイベントの種類やタイミング(アプリがアクティブな時だけ受け取る、とか)を指定可能。

Live Window Resizing

When the user resizes your window, the movement of the window should be smooth. If your code tries to do too much work during this time, the window movement may seem choppy and unresponsive to the user.

Windowのリサイズ中に処理しすぎないこと。レスポンスが悪くなる。リサイズ中かどうかをinLiveResizeメソッドで取得できるので、これがtrueの時は最小限の描画だけするようにする。

デフォルトボタン(青くする)

key equivalentでリターンを指定すると、デフォルトボタンになる。

グラフィック関連

CALayerの再配置

NSView.layout()

constraint-based layout systemで表現できないようなレイアウトを実現するには、NSViewのlayoutメソッドをオーバーライドする。レイアウトが必要な場合はlayout()を直接呼ぶのではなく、self.needsLayout = trueを実行すること。

CALayerのアニメーションの無効化

CALayerのtransformやopacityを変更すると、自動で変形のアニメーションが再生される。アニメーションを切ったり、長さを調整したい場合はlayerのプロパティをいじる前にCATransition.setAnimationDurationを呼ぶ。

CATransaction.setAnimationDuration(0)

imageLayer.frame = CGRectMake(CGRectGetMidX(workspace) - resizedWidth * 0.5, CGRectGetMidY(workspace) - resizedHeight * 0.5, resizedWidth, resizedHeight)

CALayerにCIFilterをかける

cifilter-on-calayer_1.gif

CALayerfiltersCIFilterを追加すると、CALayerの表示内容にフィルターをかけることができる。

let renderingFilter = CIFilter(name: "CIGaussianBlur")
renderingFilter.name = "blurFilter"
imageLayer = CALayer()
imageLayer.filters = [
    renderingFilter
]

OS X 10.9以降でこの機能を使う場合は、NSViewの方でlayerUsesCoreImageFiltersを有効にする必要がある。

self.layerUsesCoreImageFilters = true

フィルターのパラメータを後から変更したい場合は、CALayersetValue(forKeyPath:)を使わないと反映されないので注意。

imageLayer.setValue(5, forKeyPath: "filters.blurFilter.inputRadius")

その他

Instead, you should consider writing your own custom classes that can be managed by a higher-level NSView subclass.
...

A good example of when to use custom objects is a photo browser that displays thumbnail images of hundreds or even thousands of photos. Wrapping each photo in an NSView instance would both be prohibitively expensive and inefficient. Instead, you would be better off by creating a lightweight class to manage one or more photos and a custom view to manage that lightweight class.

複雑なUIを表現するときはカスタムビューをつくることを検討する。NSViewのインスタンス作りまくるとパフォーマンスが落ちる。

9
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?