33604


Mac でアナログ時計のようなデスクトップアプリケーションを作成しようとしています。(Xcode 8 + Swift 3)  
短針や長針などのPNG画像を重ね合わせて描画したいのですが、Swift自体初めてで正しい方法が分かりません。

まず NSView を拡張したクラスを作り、draw()
メソッドをオーバーライドすることにし、ファイルパスから画像を読み込み、コンテキストを使って描き込もうと考えました。

### 画像の読み込み

[code]

    let image = NSImage(contentsOfFile: path as String);
    
[/code]

### 描画処理

[code]

    class MyView: NSView {
    
        override var isFlipped: Bool { return true }
    
        override func draw(_ dirtyRect: NSRect) {
            super.draw(dirtyRect)
            let bounds = NSRect(x: 0, y: 0, width: 100, height: 100);
            let context = NSGraphicsContext.current()?.cgContext;
            context.draw(image: self.image!, in: bounds)
        }
    }
    
[/code]

省略してありますが大きな流れは上記のとおりです。  
読み込んだ image は NSImage ですが、 context.draw() の引数は CGImage
でなければならないためエラーになっているように見えます。（Argument labels '{image:, in:}' do not match any
available overloads）

[code]

    image!.draw(in: bounds);
    
[/code]

context を使わず上のようにすれば描画自体はできます。ただ回転処理などもする予定なので CoreGraphics
を使っておいたほうが良いのではと思っています。（このあたりもよくわかっていません）

こういったケースでは通常どのようにすれば良いのでしょうか？

* * *

**追記**

NSViewの拡張クラスに isFlipped = true を追加


CC BY-SA 3.0
2017-03-28T04:13:20.433
33596
2017-03-28T22:31:26.740
2017-03-28T08:32:52.453
10083
10083
question
swift macos swift3
NSViewに複数の画像を描画したい


> 読み込んだ image は NSImage ですが、 context.draw() の引数は CGImage
> でなければならないためエラーになっているように見えます。

これは、`NSImage`のメソッド`cgImage(forProposedRect:context:hints:)`を使って、`CGImage`に変換します。

[code]

    override func draw(_ dirtyRect: NSRect) {
            super.draw(dirtyRect)
    
            guard let context = NSGraphicsContext.current(),
                  let image = NSImage(contentsOfFile: path as String) else {
                return
            }
    
            var imageRect = NSRect(x: 32.0, y: 32.0, width: image.size.width, height: image.size.height)
            if let cgImage = image.cgImage(forProposedRect: &imageRect, context: context, hints: nil) {
                context.cgContext.draw(cgImage, in: imageRect)
            }
        }
    }
    
[/code]

* * *

> こういったケースでは通常どのようにすれば良いのでしょうか？

Core Graphicsフレームワークは、正直お勧めできません。アナログ時計といっても、イメージを動かすのは、静的イメージを描画するためのCore
Graphicsは、なじまないと思います。QuartzCoreフレームワークか、ゲーム製作フレームワークのSpriteKitなどがお勧めになります。  
ここでは、QuartzCoreフレームワークを使ったケースを紹介します。QuartzCoreでは、`CALayer`クラスが、描画の単位となっていて、`addSublayer()`によって、レイヤーを多層化し、複雑な構成を実現します。レイヤーごとに`CGAffineTransform`あるいは`CATransform3D`による座標変換を加えることができます。アナログ時計の時針、分針、秒針を、別々に動かすのに、適しています。

[code]

    import Cocoa
    
    class MyView: NSView {
    
        var subLayer = CALayer()
        // Storyboard、Interface Builderからインスタンスを生成した時の、初期化処理。
        override func awakeFromNib() {
            super.awakeFromNib()
    
            wantsLayer = true // レイヤー操作を有効にする。
            subLayer.contents = NSImage(named: "a.png") // レイヤーにイメージを貼り付け。
            subLayer.frame = CGRect(x: 50.0, y: 50.0, width: 50.0, height: 50.0)
            layer?.addSublayer(subLayer) // MyViewのviewのサブレイヤーにする。
        }
        // マウスをクリックしたら、
        override func mouseDown(with event: NSEvent) {
            let affinTransform = subLayer.affineTransform()
            subLayer.setAffineTransform(affinTransform.rotated(by: CGFloat.pi / 4.0))
            // レイヤーが45度ずつ回転する。
    
    //        subLayer.transform = CATransform3DRotate(subLayer.transform, CGFloat.pi / 4.0, 0.0, 0.0, 1.0)
            // CATransform3Dを利用する場合は、こちら。
        }
    }
    
[/code]

ここでは、QuartzCoreの、`CALayer`と`CATransform3D`を紹介しましたが、`CAAnimation`クラスと、そのサブクラスについても、勉強してみてください。


CC BY-SA 3.0
2017-03-28T07:34:02.640
33604
2017-03-28T22:31:26.740
2017-03-28T22:31:26.740
18540
18540
33596
answer