画像の高さを揃えて並べてつなげる、というだけのTunacanというMacアプリを作りました。
開発の過程で学んだ技術的内容のメモです。
iOSアプリをある程度かけるけど、Macアプリは初心者、という人向け。
UI関連
Drag & Drop
ドラッグアンドドロップはiOSアプリではあまり登場しないインターフェイスだが、Macの方には仕組みが用意されている。
NSView
でregisterForDraggedTypes
を呼ぶと、どのようなタイプのドラッグアンドドロップを受け付けるかを指定できる。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
}
ファイルがドロップされると、prepareForDragOperation
、performDragOperation
、concludeDragOperation
が呼ばれる。prepareForDragOperation
かperformDragOperation
でfalse
を返すとドロップ処理はキャンセルされ、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も呼ばれない
マウス関連デリゲートメソッドとして、mouseEntered
やmouseExited
などがあるが、ただオーバーライドしてもこれらのメソッドは呼ばれない。
マウスイベントを受け取るには、TrackingArea
を設定してやる必要がある。
An NSTrackingArea object defines a region of view that generates mouse-tracking and cursor-update events when the mouse is over that region.
NSView
のaddTrackingArea:
メソッドでエリアを指定すると、そのエリアで発生したマウスイベントが通知されるようになる。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をかける
CALayer
のfilters
にCIFilter
を追加すると、CALayer
の表示内容にフィルターをかけることができる。
let renderingFilter = CIFilter(name: "CIGaussianBlur")
renderingFilter.name = "blurFilter"
imageLayer = CALayer()
imageLayer.filters = [
renderingFilter
]
OS X 10.9以降でこの機能を使う場合は、NSView
の方でlayerUsesCoreImageFilters
を有効にする必要がある。
self.layerUsesCoreImageFilters = true
フィルターのパラメータを後から変更したい場合は、CALayer
のsetValue(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のインスタンス作りまくるとパフォーマンスが落ちる。