20200812のSwiftに関する記事は12件です。

[Swift5]AVAudioEngineを使ってリアルタイム処理を行う最小実装

環境

  • macOS: Catalina
  • Xcode 11.6
  • Swift 5.2

リアルタイムに音声に処理を行う

  • 入力をそのまま出力
  • 入力にエフェクトをかけて出力

ソースコード

class ViewController: UIViewController {

    var engine = AVAudioEngine()

    override func viewDidLoad() {
        super.viewDidLoad()
        setupAudioSession()

        let input = engine.inputNode
        let output = engine.outputNode
        let format = engine.inputNode.inputFormat(forBus: 0)
        engine.connect(input, to: output, format: format)

        try! engine.start()

    }

func setupAudioSession() {
        do {
            let session = AVAudioSession.sharedInstance()
            try session.setCategory(.playAndRecord, options: [.defaultToSpeaker, .allowBluetooth])
            try session.setActive(true)
        } catch {
            fatalError("Failed to configure and activate session.")
        }
    }

補足1 AVAudioEngine

AVAudioEngineは暗黙的にinputとoutputを持っているためノードをattachする必要性がない。
つなげる時はconnectを用いる。

補足2 AudioSession

func setupAudioSession() {
        do {
            let session = AVAudioSession.sharedInstance()
            try session.setCategory(.playAndRecord, options: [.defaultToSpeaker, .allowBluetooth])
            try session.setActive(true)
        } catch {
            fatalError("Failed to configure and activate session.")
        }
    }

これはAVAudioSessionの設定で

ステレオオーディオを録音するには、recordまたはplayAndRecordカテゴリのいずれかを使用するアプリのオーディオセッションが必要。

また、optionでiPhoneのデフォルトのスピーカーを使用することとBluetoothイヤホンで再生と録音が可能に設定。

詳しくはappleサンプルを参考にしてください!

エフェクトをかけたい場合

エフェクトをかけたい時にはノードを追加してあげると簡単にエフェクトをかけることができる。
アプリ起動時はイヤホン付けてデバックしないととハウリングが起きるので注意!

    var engine = AVAudioEngine()
    var delay = AVAudioUnitDelay()
    var reverb = AVAudioUnitReverb()

override func viewDidLoad() {
        super.viewDidLoad()
        setupAudioSession()

        let input = engine.inputNode
        let output = engine.outputNode
        let format = engine.inputNode.inputFormat(forBus: 0)
        delay.delayTime = 2.0
        reverb.loadFactoryPreset(.largeHall)
        reverb.wetDryMix = 40

        engine.attach(delay)
        engine.attach(reverb)
        engine.connect(input, to: delay, format: format)
        engine.connect(delay, to: reverb, format: format)
        engine.connect(reverb, to: output, format: format)

        try! engine.start()

    }

補足3 エフェクトをつなげる時はattachしてから

inputNodeoutputNodeattachする必要性がなかったが、エフェクトNodeを使用する際にはattachしないといけない。


遅延を入れてリバーブをかけるとコンサートホールにいるみたいなエフェクトになった!Bluetoothでもわかるくらいリバーブがいい感じにかかってて感動!

エフェクトクラス一覧

他にもエフェクトがあるので紹介(今回のも含めて)

  • AVAudioUnitReverb リバーブ処理
  • AVAudioUnitTimeEffect 非リアルタイムエフェクト処理
  • AVAudioUnitTimePitch 高品質の再生速度と音程シフトを互いに独立して提供
  • AVAudioUnitVarispeed 再生速度の制御
  • AVAudioUnitDelay 遅延処理
  • AVAudioUnitEQ マルチバンドイコライザの実行 EQ→イコライザの略
  • AVAudioUnitDistortion 歪みエフェクト

まだ使っていないエフェクトを使って音遊びしていきたい。

まとめ

AVAudioEngineは暗黙的にinputとoutputを持っているためノードをつなげる必要性がない。

WWDC2019に新しくAVAudioEngineが新しくなったらしいので、

このセッションを見て復習する。

What's New in AVAudioEngine

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ファイルをNSViewにドラッグしてパスを取得する (Swift)

概要

Aug-12-2020 22-11-12

GitHub

参考

ドラッグ・アンド・ドロップを受け付けるNSViewのカスタムクラスの作成

  • はじめに、呼び出し元にドラッグされた画像の情報を返すため、プロトコルのメソッドとして宣言しておきます。
protocol DragAndDropViewDelegate: class {
    func dragAndDropView(_ view: DragAndDropView,
                         didDragImageFileURLs draggedImages: [DraggedImage])
}
  • 今回、ローカルに保存されている画像ファイルだけでなく画像自体をドラッグした場合のため、ドラッグされるアイテムを下記のstructとして扱うようにします。
struct DraggedImage {
    let image: NSImage?
    let url: URL?
    var uti: String?  // 拡張子情報
}
  • NSViewのカスタムクラスDragAndDropViewを作成し、ファイルがドラッグされたときの処理を書いていきます。
  • 後ほど使用するプロパティを宣言します。
var delegate: DragAndDropViewDelegate?

// Viewがドラッグを許可するファイルのタイプ
let acceptableTypes: [NSPasteboard.PasteboardType] = [.fileURL, .tiff, .png, .URL, .string]

// URL先の画像のUTIが、NSImage.imageTypesのUTIに合致するかという条件
let filteringOptions: [NSPasteboard.ReadingOptionKey : Any] = [.urlReadingContentsConformToTypes : NSImage.imageTypes]

// ViewのUI制御用
var isDragging = false {
    didSet {
        needsDisplay = true
    }
}
  • View上に画像がドラッグされている間、Viewの周りをハイライトさせるための処理。
override func draw(_ dirtyRect: NSRect) {
    super.draw(dirtyRect)

    // ドラッグされている場合にハイライトさせる
    if isDragging {
        NSColor.selectedControlColor.set()
    } else {
        NSColor.windowFrameColor.set()
    }

    let path = NSBezierPath(rect: bounds)
    path.lineWidth = 5
    path.stroke()

}
  • ViewにDragできるファイルのタイプを設定します。
required init?(coder: NSCoder) {
    super.init(coder: coder)

    self.registerForDraggedTypes(acceptableTypes)
}
  • ファイルがドラッグされた場合に処理を続けるかを判断するためのメソッドと、UTI判別用のファイルを宣言しておきます。
// MARK: - Helper Methods

func shouldAllowDrag(_ draggingInfo: NSDraggingInfo) -> Bool {
    var canAccept = false
    let pasteBoard = draggingInfo.draggingPasteboard

    if pasteBoard.canReadObject(forClasses: [NSURL.self], options: nil) ||
        pasteBoard.canReadObject(forClasses: [NSImage.self], options: nil) {
        canAccept = true
    }

    return canAccept
}

func uti(url: URL) -> String? {
    guard let r = try? url.resourceValues(forKeys: [.typeIdentifierKey]) else {
        return nil
    }
    return r.typeIdentifier
}
  • 最後にドラッグに関する処理を実装します。
class DragAndDropView: NSView {
     // (省略)
     // MARK:- NSDraggingDestination Protocol Methods

    /// Viewの境界にファイルがドラッグされるときに呼ばれる
    /// 宛先がどのドラッグ操作を実行するのかを示す値を返す必要があります。
    override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation {
        let allow = shouldAllowDrag(sender)
        isDragging = allow

        // NSDragOperation.copy := The data represented by the image can be copied.
        return allow ? NSDragOperation.copy : NSDragOperation()
    }

    /// View上にファイルがドラッグで保持されている間、短い間隔毎に呼ばれるメソッド
    /// 宛先がどのドラッグ操作を実行するのかを示す値を返す必要があります。
    //    override func draggingUpdated(_ sender: NSDraggingInfo) -> NSDragOperation {
    //    }

    /// View上にファイルがドラッグされなくなった際に呼ばれる
    override func draggingExited(_ sender: NSDraggingInfo?) {
        isDragging = false
    }

    /// View上でファイルがドロップされた際に呼ばれる
    /// メッセージが返された場合はYES、performDragOperation:メッセージが送信されます。
    //    override func prepareForDragOperation(_ sender: NSDraggingInfo) -> Bool {
    //    }

    /// View上でファイルがドロップされた後の処理
    override func performDragOperation(_ draggingInfo: NSDraggingInfo) -> Bool {

        isDragging = false
        let pasteBoard = draggingInfo.draggingPasteboard

        // fileURLがドラッグされた場合
        if let urls = pasteBoard.readObjects(forClasses: [NSURL.self], options: filteringOptions) as? [URL],
            urls.count > 0,
            urls[0].isFileURL {
            var draggedImages = [DraggedImage]()
            for url in urls {
                let uti = self.uti(url: url)
                draggedImages.append(DraggedImage(image: nil, url: url, uti: uti))
            }

            delegate?.dragAndDropView(self, didDragImageFileURLs: draggedImages)
            return true
        }

        // 画像イメージがドラッグされた場合
        if let pasteboardItems = pasteBoard.pasteboardItems {
            var draggedImages = [DraggedImage]()
            for pasteboardItem in pasteboardItems {
                for type in pasteboardItem.types {
                    if let data = pasteboardItem.data(forType: type) {
                        if let image = NSImage(data: data) {
                            draggedImages.append(DraggedImage(image: image, url: nil, uti: type.rawValue))
                        }
                    }
                }
            }

            if draggedImages.count > 0 {
                delegate?.dragAndDropView(self, didDragImageFileURLs: draggedImages)
                return true
            }
        }

        // 画像のURL(http://~)がドラッグされた場合
        // 必要無いかも?
        if let urls = pasteBoard.readObjects(forClasses: [NSURL.self], options: nil) as? [URL],
            urls.count > 0 {
            var draggedImages = [DraggedImage]()
            for url in urls {
                draggedImages.append(DraggedImage(image: nil, url: url, uti: nil))
            }

            delegate?.dragAndDropView(self, didDragImageFileURLs: draggedImages)
            return true
        }

        return false
    }

    /// 一連のドラッグ操作が完了したときに呼ばれる
    //    override func concludeDragOperation(_ sender: NSDraggingInfo?) {
    //    }

}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

画像ををNSViewにドラッグして情報を取得する (Swift)

概要

Aug-12-2020 22-11-12

GitHub

参考

ドラッグ・アンド・ドロップを受け付けるNSViewのカスタムクラスの作成

  • はじめに、呼び出し元にドラッグされた画像の情報を返すため、プロトコルのメソッドとして宣言しておきます。
protocol DragAndDropViewDelegate: class {
    func dragAndDropView(_ view: DragAndDropView,
                         didDragImageFileURLs draggedImages: [DraggedImage])
}
  • 今回、ローカルに保存されている画像ファイルだけでなく画像自体をドラッグした場合のため、ドラッグされるアイテムを下記のstructとして扱うようにします。
struct DraggedImage {
    let image: NSImage?
    let url: URL?
    var uti: String?  // 拡張子情報
}
  • NSViewのカスタムクラスDragAndDropViewを作成し、ファイルがドラッグされたときの処理を書いていきます。
  • 後ほど使用するプロパティを宣言します。
var delegate: DragAndDropViewDelegate?

// Viewがドラッグを許可するファイルのタイプ
let acceptableTypes: [NSPasteboard.PasteboardType] = [.fileURL, .tiff, .png, .URL, .string]

// URL先の画像のUTIが、NSImage.imageTypesのUTIに合致するかという条件
let filteringOptions: [NSPasteboard.ReadingOptionKey : Any] = [.urlReadingContentsConformToTypes : NSImage.imageTypes]

// ViewのUI制御用
var isDragging = false {
    didSet {
        needsDisplay = true
    }
}
  • View上に画像がドラッグされている間、Viewの周りをハイライトさせるための処理。
override func draw(_ dirtyRect: NSRect) {
    super.draw(dirtyRect)

    // ドラッグされている場合にハイライトさせる
    if isDragging {
        NSColor.selectedControlColor.set()
    } else {
        NSColor.windowFrameColor.set()
    }

    let path = NSBezierPath(rect: bounds)
    path.lineWidth = 5
    path.stroke()

}
  • ViewにDragできるファイルのタイプを設定します。
required init?(coder: NSCoder) {
    super.init(coder: coder)

    self.registerForDraggedTypes(acceptableTypes)
}
  • ファイルがドラッグされた場合に処理を続けるかを判断するためのメソッドと、UTI判別用のファイルを宣言しておきます。
// MARK: - Helper Methods

func shouldAllowDrag(_ draggingInfo: NSDraggingInfo) -> Bool {
    var canAccept = false
    let pasteBoard = draggingInfo.draggingPasteboard

    if pasteBoard.canReadObject(forClasses: [NSURL.self], options: nil) ||
        pasteBoard.canReadObject(forClasses: [NSImage.self], options: nil) {
        canAccept = true
    }

    return canAccept
}

func uti(url: URL) -> String? {
    guard let r = try? url.resourceValues(forKeys: [.typeIdentifierKey]) else {
        return nil
    }
    return r.typeIdentifier
}
  • 最後にドラッグに関する処理を実装します。
class DragAndDropView: NSView {
     // (省略)
     // MARK:- NSDraggingDestination Protocol Methods

    /// Viewの境界にファイルがドラッグされるときに呼ばれる
    /// 宛先がどのドラッグ操作を実行するのかを示す値を返す必要があります。
    override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation {
        let allow = shouldAllowDrag(sender)
        isDragging = allow

        // NSDragOperation.copy := The data represented by the image can be copied.
        return allow ? NSDragOperation.copy : NSDragOperation()
    }

    /// View上にファイルがドラッグで保持されている間、短い間隔毎に呼ばれるメソッド
    /// 宛先がどのドラッグ操作を実行するのかを示す値を返す必要があります。
    //    override func draggingUpdated(_ sender: NSDraggingInfo) -> NSDragOperation {
    //    }

    /// View上にファイルがドラッグされなくなった際に呼ばれる
    override func draggingExited(_ sender: NSDraggingInfo?) {
        isDragging = false
    }

    /// View上でファイルがドロップされた際に呼ばれる
    /// メッセージが返された場合はYES、performDragOperation:メッセージが送信されます。
    //    override func prepareForDragOperation(_ sender: NSDraggingInfo) -> Bool {
    //    }

    /// View上でファイルがドロップされた後の処理
    override func performDragOperation(_ draggingInfo: NSDraggingInfo) -> Bool {

        isDragging = false
        let pasteBoard = draggingInfo.draggingPasteboard

        // fileURLがドラッグされた場合
        if let urls = pasteBoard.readObjects(forClasses: [NSURL.self], options: filteringOptions) as? [URL],
            urls.count > 0,
            urls[0].isFileURL {
            var draggedImages = [DraggedImage]()
            for url in urls {
                let uti = self.uti(url: url)
                draggedImages.append(DraggedImage(image: nil, url: url, uti: uti))
            }

            delegate?.dragAndDropView(self, didDragImageFileURLs: draggedImages)
            return true
        }

        // 画像イメージがドラッグされた場合
        if let pasteboardItems = pasteBoard.pasteboardItems {
            var draggedImages = [DraggedImage]()
            for pasteboardItem in pasteboardItems {
                for type in pasteboardItem.types {
                    if let data = pasteboardItem.data(forType: type) {
                        if let image = NSImage(data: data) {
                            draggedImages.append(DraggedImage(image: image, url: nil, uti: type.rawValue))
                        }
                    }
                }
            }

            if draggedImages.count > 0 {
                delegate?.dragAndDropView(self, didDragImageFileURLs: draggedImages)
                return true
            }
        }

        // 画像のURL(http://~)がドラッグされた場合
        // 必要無いかも?
        if let urls = pasteBoard.readObjects(forClasses: [NSURL.self], options: nil) as? [URL],
            urls.count > 0 {
            var draggedImages = [DraggedImage]()
            for url in urls {
                draggedImages.append(DraggedImage(image: nil, url: url, uti: nil))
            }

            delegate?.dragAndDropView(self, didDragImageFileURLs: draggedImages)
            return true
        }

        return false
    }

    /// 一連のドラッグ操作が完了したときに呼ばれる
    //    override func concludeDragOperation(_ sender: NSDraggingInfo?) {
    //    }

}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

画像をNSViewにドラッグして情報を取得する (Swift)

概要

Aug-12-2020 22-11-12

GitHub

参考

ドラッグ・アンド・ドロップを受け付けるNSViewのカスタムクラスの作成

  • はじめに、呼び出し元にドラッグされた画像の情報を返すため、プロトコルのメソッドとして宣言しておきます。
protocol DragAndDropViewDelegate: class {
    func dragAndDropView(_ view: DragAndDropView,
                         didDragImageFileURLs draggedImages: [DraggedImage])
}
  • 今回、ローカルに保存されている画像ファイルだけでなく画像自体をドラッグした場合にも対応したいため、ドラッグされるアイテムを下記のstructとして扱うようにします。
struct DraggedImage {
    let image: NSImage?
    let url: URL?
    var uti: String?  // 拡張子情報
}
  • NSViewのカスタムクラスDragAndDropViewを作成し、ファイルがドラッグされたときの処理を書いていきます。
  • 後ほど使用するプロパティを宣言します。
var delegate: DragAndDropViewDelegate?

// Viewがドラッグを許可するファイルのタイプ
let acceptableTypes: [NSPasteboard.PasteboardType] = [.fileURL, .tiff, .png, .URL, .string]

// URL先の画像のUTIが、NSImage.imageTypesのUTIに合致するかという条件
let filteringOptions: [NSPasteboard.ReadingOptionKey : Any] = [.urlReadingContentsConformToTypes : NSImage.imageTypes]

// ViewのUI制御用
var isDragging = false {
    didSet {
        needsDisplay = true
    }
}
  • View上に画像がドラッグされている間、Viewの周りをハイライトさせるための処理。
override func draw(_ dirtyRect: NSRect) {
    super.draw(dirtyRect)

    // ドラッグされている場合にハイライトさせる
    if isDragging {
        NSColor.selectedControlColor.set()
    } else {
        NSColor.windowFrameColor.set()
    }

    let path = NSBezierPath(rect: bounds)
    path.lineWidth = 5
    path.stroke()

}
  • ViewにDragできるファイルのタイプを設定します。
required init?(coder: NSCoder) {
    super.init(coder: coder)

    self.registerForDraggedTypes(acceptableTypes)
}
  • ファイルがドラッグされた場合に処理を続けるかを判断するためのメソッドと、UTI判別用のファイルを宣言しておきます。
// MARK: - Helper Methods

func shouldAllowDrag(_ draggingInfo: NSDraggingInfo) -> Bool {
    var canAccept = false
    let pasteBoard = draggingInfo.draggingPasteboard

    if pasteBoard.canReadObject(forClasses: [NSURL.self], options: nil) ||
        pasteBoard.canReadObject(forClasses: [NSImage.self], options: nil) {
        canAccept = true
    }

    return canAccept
}

func uti(url: URL) -> String? {
    guard let r = try? url.resourceValues(forKeys: [.typeIdentifierKey]) else {
        return nil
    }
    return r.typeIdentifier
}
  • 最後にドラッグに関する処理を実装します。
class DragAndDropView: NSView {
     // (省略)
     // MARK:- NSDraggingDestination Protocol Methods

    /// Viewの境界にファイルがドラッグされるときに呼ばれる
    /// 宛先がどのドラッグ操作を実行するのかを示す値を返す必要があります。
    override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation {
        let allow = shouldAllowDrag(sender)
        isDragging = allow

        // NSDragOperation.copy := The data represented by the image can be copied.
        return allow ? NSDragOperation.copy : NSDragOperation()
    }

    /// View上にファイルがドラッグで保持されている間、短い間隔毎に呼ばれるメソッド
    /// 宛先がどのドラッグ操作を実行するのかを示す値を返す必要があります。
    //    override func draggingUpdated(_ sender: NSDraggingInfo) -> NSDragOperation {
    //    }

    /// View上にファイルがドラッグされなくなった際に呼ばれる
    override func draggingExited(_ sender: NSDraggingInfo?) {
        isDragging = false
    }

    /// View上でファイルがドロップされた際に呼ばれる
    /// メッセージが返された場合はYES、performDragOperation:メッセージが送信されます。
    //    override func prepareForDragOperation(_ sender: NSDraggingInfo) -> Bool {
    //    }

    /// View上でファイルがドロップされた後の処理
    override func performDragOperation(_ draggingInfo: NSDraggingInfo) -> Bool {

        isDragging = false
        let pasteBoard = draggingInfo.draggingPasteboard

        // fileURLがドラッグされた場合
        if let urls = pasteBoard.readObjects(forClasses: [NSURL.self], options: filteringOptions) as? [URL],
            urls.count > 0,
            urls[0].isFileURL {
            var draggedImages = [DraggedImage]()
            for url in urls {
                let uti = self.uti(url: url)
                draggedImages.append(DraggedImage(image: nil, url: url, uti: uti))
            }

            delegate?.dragAndDropView(self, didDragImageFileURLs: draggedImages)
            return true
        }

        // 画像イメージがドラッグされた場合
        if let pasteboardItems = pasteBoard.pasteboardItems {
            var draggedImages = [DraggedImage]()
            for pasteboardItem in pasteboardItems {
                for type in pasteboardItem.types {
                    if let data = pasteboardItem.data(forType: type) {
                        if let image = NSImage(data: data) {
                            draggedImages.append(DraggedImage(image: image, url: nil, uti: type.rawValue))
                        }
                    }
                }
            }

            if draggedImages.count > 0 {
                delegate?.dragAndDropView(self, didDragImageFileURLs: draggedImages)
                return true
            }
        }

        // 画像のURL(http://~)がドラッグされた場合
        if let urls = pasteBoard.readObjects(forClasses: [NSURL.self], options: nil) as? [URL],
            urls.count > 0 {
            var draggedImages = [DraggedImage]()
            for url in urls {
                draggedImages.append(DraggedImage(image: nil, url: url, uti: nil))
            }

            delegate?.dragAndDropView(self, didDragImageFileURLs: draggedImages)
            return true
        }

        return false
    }

    /// 一連のドラッグ操作が完了したときに呼ばれる
    //    override func concludeDragOperation(_ sender: NSDraggingInfo?) {
    //    }

}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[SwiftUI]TextField�のリターンイベントはonCommit

TextFieldを使う時にリターンが押された際の処理は必須だと思います。
SwiftUIのTextFieldでは、リターンのイベント処理をonCommitで定義できます。

TextField("ぷれいすほるだー", text: $name, 
    onCommit: {
        //任意の処理
    })
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Swift値型、参照型 初心者が違いを説明してみた

はじめに

皆さんは以下のようにほぼ同じコードを書いているのにも関わらず、
構造体ではエラーが起きて、クラスではエラーが起きない理由を説明することができますか。

構造体(値型)

struct Sample {
    var num: Int
}

var sample1 = Sample(num: 1)
let sample2 = Sample(num: 1)

sample1.num = 2
sample2.num = 2 //=> エラー 

クラス(参照型)

class Sample {
    var num: Int

    init(num: Int) {
        self.num = num
    }
}

var sample1 = Sample(num: 1)
let sample2 = Sample(num: 1)

sample1.num = 2
sample2.num = 2

自分はできませんでした。ところがクラスは参照型、構造体は値型ということがきちんと理解できればこの理由は説明することができ、クラスと構造体に対する理解も深まると思います。

値型の場合

値型の場合、今回だとnumという変数を格納するための入れ物がまず作られます。そしてその入れ物に直接値を格納していきます。

sample2はletで定義されているので、入れ物の中身を後から変えることはできません。

入れ物を作って値を格納する。初心者としてはイメージ通りの挙動だと感じると思います。しかし参照型はこれとはちょっと違った方法で値をセットします。
IMG_E3AC4A1D8DEA-1.jpeg

参照型の場合

参照型でも値型と同様に入れ物が作られます。しかし入れ物に入れる物が値そのものではなく、別の場所に格納された値の住所を入れます。参照型は、その住所を使って値にアクセスをします。ここが値型と参照型の大きな違いで、

let sample2 = Sample(num: 1) //参照型
sample2.num = 2

がエラーを起こさない理由は、入れ物に入った住所を書き換えるのではなく、住所の先にある値を変えているからです。
住所の先の値を変える分には、sample2がletで定義されていようが関係なく変更することができます。
IMG_0ACBC7AAE41E-1.jpeg

参照型のいい点

let sample1 = Sample(a: 1, b: 2, c: 5, d: 1, .....)
let sample2 = sample1

上記のようにプロパティの数が膨大な場合に、sample1からsample2を複製しようとしたとき、値型では入れ物から中身に値まですべてコピーします。これではコンピュータの容量もたくさん食うことになります。

その点参照型ではコピーするのは住所だけとなっているので、容量も食わずにコンピュータにも優しいです。

IMG_0B0433DE021B-1.jpeg

let sample1 = Sample(a: 1, b: 2, c: 5, d: 1, .....)
let sample2 = sample1
sample1.b = 100
print(sample2.b) //=> 100 
//住所の先の値を変えてしまっているので、sample1と同じ住所を持つsample2の値も変化する

また参照型で複製をしているときに、値を変更してしまうと大本の値を変更していることになり、他の要素にも影響を与えます。ここは嬉しい点でもあり、知らずに使ってしまうと混乱の原因にもなるので気をつけましょう。
IMG_0FECB6E5305F-1.jpeg

まとめ

値型と参照型の違いを解説してみました。間違い等ありましたら知らせていただけると嬉しいです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Swift値型、参照型 初心者がイラストを使って説明してみた

はじめに

皆さんは以下のようにほぼ同じコードを書いているのにも関わらず、
構造体ではエラーが起きて、クラスではエラーが起きない理由を説明することができますか。

構造体(値型)

struct Sample {
    var num: Int
}

var sample1 = Sample(num: 1)
let sample2 = Sample(num: 1)

sample1.num = 2
sample2.num = 2 //=> エラー 

クラス(参照型)

class Sample {
    var num: Int

    init(num: Int) {
        self.num = num
    }
}

var sample1 = Sample(num: 1)
let sample2 = Sample(num: 1)

sample1.num = 2
sample2.num = 2

自分はできませんでした。ところがクラスは参照型、構造体は値型ということがきちんと理解できればこの理由は説明することができ、クラスと構造体に対する理解も深まると思います。

値型の場合

値型の場合、今回だとnumという変数を格納するための入れ物がまず作られます。そしてその入れ物に直接値を格納していきます。

sample2はletで定義されているので、入れ物の中身を後から変えることはできません。

入れ物を作って値を格納する。初心者としてはイメージ通りの挙動だと感じると思います。しかし参照型はこれとはちょっと違った方法で値をセットします。
IMG_E3AC4A1D8DEA-1.jpeg

参照型の場合

参照型でも値型と同様に入れ物が作られます。しかし入れ物に入れる物が値そのものではなく、別の場所に格納された値の住所を入れます。参照型は、その住所を使って値にアクセスをします。ここが値型と参照型の大きな違いで、

let sample2 = Sample(num: 1) //参照型
sample2.num = 2

がエラーを起こさない理由は、入れ物に入った住所を書き換えるのではなく、住所の先にある値を変えているからです。
住所の先の値を変える分には、sample2がletで定義されていようが関係なく変更することができます。
IMG_0ACBC7AAE41E-1.jpeg

参照型のいい点

let sample1 = Sample(a: 1, b: 2, c: 5, d: 1, .....)
let sample2 = sample1

上記のようにプロパティの数が膨大な場合に、sample1からsample2を複製しようとしたとき、値型では入れ物から中身に値まですべてコピーします。これではコンピュータの容量もたくさん食うことになります。

その点参照型ではコピーするのは住所だけとなっているので、容量も食わずにコンピュータにも優しいです。

IMG_0B0433DE021B-1.jpeg

let sample1 = Sample(a: 1, b: 2, c: 5, d: 1, .....)
let sample2 = sample1
sample1.b = 100
print(sample2.b) //=> 100 
//住所の先の値を変えてしまっているので、sample1と同じ住所を持つsample2の値も変化する

また参照型で複製をしているときに、値を変更してしまうと大本の値を変更していることになり、他の要素にも影響を与えます。ここは嬉しい点でもあり、知らずに使ってしまうと混乱の原因にもなるので気をつけましょう。
IMG_0FECB6E5305F-1.jpeg

まとめ

値型と参照型の違いを解説してみました。間違い等ありましたら知らせていただけると嬉しいです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

initの初歩 ?【Swift】

 initとは??

  • インスタンスを生成するときに、自動で呼び出されるメソッド。
  • イニシャライザとも呼びます。
  • クラスのプロパティの初期値を設定するときに使用。

Swiftのメソッドは、3種類あります。
その一つが、「initメソッド」 Swiftのメソッドの種類

スクリーンショット 2020-08-12 10.33.58.png
キノコード / プログラミング学習動画のYouTuber 

class Car {
    var color = ""
    var wheel = 0

    init() {
        print("initは、インスタンスを生成するときに自動で呼び出される、特殊なメソッドです。")
    }

}

let car = Car() // インスタンス生成。
実行結果.
initは、インスタンスを生成するときに自動で呼び出される、特殊なメソッドです。
  • 英 initialize: 「初期化する」
  • 「初期化」=初めて変数にデータを入れること。

 インスタンスとは??

クラスは設計図。
クラスは、インスタンス化しないと使うことができません。
instance=「実態」

let car = Car() // クラス名()を、変数に代入。 「インスタンス化」

インスタンス化とは、

クラスという「設計図」から、インスタンスという実際に使える「モノ」を作ること。

 補足

クラス名()とすることでインスタンスが生成できます。

Car() // インスタンス生成。

クラス名().変数名とすることで、そのクラスの変数にアクセスできます。

print(Car().wheel) // インスタンス生成。
「クラス名().変数名」の実行結果.
initは、インスタンスを生成するときに自動で呼び出される、特殊なメソッドです。
0

しかし、インスタンスを「変数や定数に代入」してから
使用する方法が一般的です。


 init内で、プロパティの初期値を設定してみる。

class Car {
    var color : String // 型の指定。 
    var wheel : Int

    init() {
        color = "Red" // init内で、プロパティの初期値を設定。
        wheel = 4     // 同じく。
    }

}

let car = Car() // インスタンス生成。

print(car.color)
print(car.wheel)
実行結果.
Red
4
  • var color = ""でも良いですが、不要なので型指定だけ。
  • (varを削除すると「colorって何?」となるので、エラー。)
  • 型指定では、:を使います。
  • インスタンスは、(car.color)のように「インスタンス名.○○」で値を参照。

また、インスタンス生成時にinitが優先的に呼び出されるので、

var color = "Green"でも、var color = "Blue"でも、
実行結果には反映されない。

 なぜ、initを使うのか。?

理由は色々あるらしいです。

クラスの記述内容がより分かりやすくなり、
明確に初期化を行いソースコードの安全性を高める為。

プロパティが正しく初期化できない場合...

インスタンス生成時に「引数で、インスタンスプロパティの初期値を与える」
ということが出来ない。

メモリ安全でない。
(メモリ確保、初期化がされる前にインスタンスにアクセスしてしまう)

 initメソッドに、『引数』を指定してみる。

  • self.
  • = color, = wheel
  • (color: String, wheel: Int)

スクリーンショット 2020-08-12 13.42.32.png

 『self』

selfとは、「インスタンス自身」を指す言葉。

クラスのインスタンスメソッド内でのselfは、
自分自身(クラスのインスタンス)を示します。

自分のクラス内の何かにアクセスしたい時に使います。

 (color: String, wheel: Int)について。

 クラスの引数に、値だけ指定するとエラー?

// インスタンス生成のとき。

let car = Car("Red", 4) // <--- 引数に、値だけ指定するとエラー。
error.
Missing argument labels 'color:wheel:' in call

パラメーター(ラベル)、必須です。

パラメータとラベルは、同名でもOKだけど、違うモノらしい。
[Swift] 関数ラベルの使い方を学ぶ

 順番が違ってもエラー?

// インスタンス生成のとき。

let car = Car( wheel: 4, color: "Red")  // <--- 順番が違ってもエラー
error.
Argument 'color' must precede argument 'wheel'

おしまい。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

initの初歩?【Swift】

 initとは??

  • インスタンスを生成するときに、自動で呼び出されるメソッド。
  • イニシャライザとも呼びます。
  • クラスのプロパティの初期値を設定するときに使用。

Swiftのメソッドは、3種類あります。
その一つが、「initメソッド」 Swiftのメソッドの種類

スクリーンショット 2020-08-12 10.33.58.png
キノコード / プログラミング学習動画のYouTuber 

class Car {
    var color = ""
    var wheel = 0

    init() {
        print("initは、インスタンスを生成するときに自動で呼び出される、特殊なメソッドです。")
    }

}

let car = Car() // インスタンス生成。
実行結果.
initは、インスタンスを生成するときに自動で呼び出される、特殊なメソッドです。
  • 英 initialize: 「初期化する」
  • 「初期化」=初めて変数にデータを入れること。

 インスタンスとは??

クラスは設計図。
クラスは、インスタンス化しないと使うことができません。
instance=「実態」

let car = Car() // クラスの呼び出しを、変数に代入。 「インスタンス化」

インスタンス化とは、

クラスという「設計図」から、インスタンスという実際に使える「モノ」を作ること。


 init内で、プロパティの初期値を設定してみる。

class Car {
    var color : String // 型の指定。 
    var wheel : Int

    init() {
        color = "Red" // init内で、プロパティの初期値を設定。
        wheel = 4     // 同じく。
    }

}

let car = Car() // インスタンス生成。

print(car.color)
print(car.wheel)
実行結果.
Red
4
  • var color = ""でも良いですが、不要なので型指定だけ。
  • (varを削除すると「colorって何?」となるので、エラー。)
  • 型指定では、:を使います。
  • インスタンスは、(car.color)のように「インスタンス名.○○」で値を参照。

また、インスタンス生成時にinitが優先的に呼び出されるので、

var color = "Green"でも、var color = "Blue"でも、
実行結果には反映されない。

 なぜ、initを使うのか。?

クラスの記述内容がより分かりやすくなり、
明確に初期化を行いソースコードの安全性を高める為。

 initメソッドに、『引数』を指定してみる。

  • self.
  • = color, = wheel
  • (color: String, wheel: Int)

スクリーンショット 2020-08-12 13.42.32.png

 self

selfとは、「インスタンス自身」を指す言葉。

クラスのインスタンスメソッド内でのselfは、
自分自身(クラスのインスタンス)を示します。

自分のクラス内の何かにアクセスしたい時に使います。

  • 「インスタンス変数」 それぞれのインスタンスに属する変数。
  • 「インスタンス メソッド」 基本的に、"メソッド"と言うと、これを指します。

Swiftのメソッドの種類

 (color: String, wheel: Int)について。

 クラスの引数に、値だけ指定するとエラー?

// インスタンス生成のとき。

let car = Car("Red", 4) // <--- 引数に、値だけ指定するとエラー。
error.
Missing argument labels 'color:wheel:' in call

パラメーター(ラベル)、必須です。

パラメータとラベルは、同名でもOKだけど、違うモノらしい。
[Swift] 関数ラベルの使い方を学ぶ

 順番が違ってもエラー?

// インスタンス生成のとき。

let car = Car( wheel: 4, color: "Red")  // <--- 順番が違ってもエラー
error.
Argument 'color' must precede argument 'wheel'

おしまい。

 おまけ

タイヤ8個の緑車を追加。

class Car {
    var color : String
    var wheel : Int

    init(color: String, wheel: Int) {
        self.color = color
        self.wheel = wheel
    }

}

let car = Car(color: "Red", wheel: 4) // インスタンス生成。
let secondCar = Car(color: "Green", wheel: 8) // インスタンス生成。

print(car.color, car.wheel) // 「,」で区切れます。
print(secondCar.color, secondCar.wheel)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UIBarButtonItemのSystemItemで使えるUIImageを取得する

経緯

アプリからTwitterやメモアプリ等の外部アプリに画像など、情報を共有したい時のボタンにアクションボタンを使用したい場合がある
↓こういうやつ
IMG_7856.PNG
こういう場合に限らず、画像の横にある本の画像、またはゴミ箱画像とかも使いたい。
UIBarButtonItemだと使えるけど、それからUIImageを引き出す事は基本的にできない…
それに画像をネットから同じような画像を探すにしても、商用利用などを確認するのも面倒すぎる…?‍♂️

という事で、UIBarButtonItemのSystemItemからimageだけを使用する方法を共有します?‍♂️

実装

検証環境は下記の通りです
OS:10.15.6
Xcode:11.6
Swift:5.0

早速ですが、実装したコードの紹介をします

UIBarButtonItem.SystemItem+extention.swift
extension UIBarButtonItem.SystemItem {
    func image() -> UIImage? {
        let tempItem = UIBarButtonItem(barButtonSystemItem: self,
                                       target: nil,
                                       action: nil)

        let bar = UIToolbar()
        bar.setItems([tempItem],
                     animated: false)
        bar.snapshotView(afterScreenUpdates: true)

        // imageを取得する
        let itemView = tempItem.value(forKey: "view") as! UIView
        for view in itemView.subviews {
            if let button = view as? UIButton,
                let image = button.imageView?.image {
                return image.withRenderingMode(.alwaysTemplate)
            }
        }
        return nil
    }
}

使う時はこのように↓

UIBarButtonItem.SystemItem.action.image()

実行結果↓
スクリーンショット 2020-08-12 11.24.04.png
問題なく表示できました〜!☺️

候補: SF Symbol・systemNameを検討する

そもそも論ですが、iOS13からはSF SymbolUIImageViewでsystemNameが使用できるようになっています。
使用する方法は以下の通りです。

// UIKit
UIImageView(systemName: "xxx")

// SwiftUI
Image(systemname: "xxx")

この"xxx"に入る文字列は、下記URLよりDLできるSF Symbolアプリから確認できます。
https://developer.apple.com/design/resources/
FigamaやSketch等のデザインツールを利用すると細かくサイズ等を調整したりできるみたいなので
対応できる方はこっちの方がいいと思います。
※記事投稿時点ではBeta版です

最後に

Twitterのアカウントがありますのでフォローしてくれると嬉しいです!!
@swift_nita
なお今回のサンプルはGithubに上げていますのでご参考までに!
https://github.com/ni-ta/ButtonItemSystemItem

参考

Use UIBarButtonItem icon in UIButton
SF Symbolsの使い方とカスタマイズの仕方

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

メニューバーに常駐するmacOSアプリを作成し、画像をドラッグ・アンド・ドロップで認識できるようにする

概要

  • Macの右上のメニューバーにアイコンを表示するアプリを作成します。
  • また最終的に、そのアイコン上に画像ファイルをドラッグ・アンド・ドロップできるよう実装を行います。
  • 上のバーの名称は以下メニューバーと呼ぶこととします。

image

GitHub

dragdemo

基本的な実装

class AppDelegate: NSObject, NSApplicationDelegate {

    let statusItem = NSStatusBar.system.statusItem(withLength:NSStatusItem.squareLength)

    func applicationDidFinishLaunching(_ aNotification: Notification) {

        if let statusBarButton = statusItem.button {
            statusBarButton = NSImage(named:NSImage.Name("StatusBarButtonImage"))
        }

        constructMenu()
    }

    @objc func printQuote(_ sender: Any?) {
        let quoteText = "Never put off until tomorrow what you can do the day after tomorrow."
        let quoteAuthor = "Mark Twain"

        print("\(quoteText)\(quoteAuthor)")
    }

    func constructMenu() {
        let menu = NSMenu()

        menu.addItem(NSMenuItem(title: "Print Quote", action: #selector(AppDelegate.printQuote(_:)), keyEquivalent: "P"))  // 大文字なので、Commant + "Shitf + p"となる
        menu.addItem(NSMenuItem.separator())
        menu.addItem(NSMenuItem(title: "Quit Quotes", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q"))

        statusItem.menu = menu
    }
}
  • これより以下は、記事の内容外の実装を紹介します。

メニュー項目にサブメニューを持たせる

  • 以下のようなメニューを入れ子にすることを考えます。

-w270

  • クラスとしては、下記のように入れ子になっています

-w667

func constructMenu() {
    let menu = NSMenu()
    menu.addItem(NSMenuItem(title: "Parent_01", action: #selector(AppDelegate.printQuote(_:)), keyEquivalent: ""))
    menu.addItem(NSMenuItem(title: "Parent_02", action: #selector(NSApplication.terminate(_:)), keyEquivalent: ""))
    menu.addItem(NSMenuItem.separator())

    // サブメニューを持つNSMenuItemを作成する
    let paren_03 = NSMenuItem(title: "Parent_03", action: #selector(AppDelegate.printQuote(_:)), keyEquivalent: "")
    let menuForParent = NSMenu()
    menuForParent.addItem(NSMenuItem(title: "Children_01", action: #selector(AppDelegate.printQuote(_:)), keyEquivalent: ""))
    menuForParent.addItem(NSMenuItem.separator())
    menuForParent.addItem(NSMenuItem(title: "Children_02", action: #selector(NSApplication.terminate(_:)), keyEquivalent: ""))
    paren_03.submenu = menuForParent

    menu.addItem(paren_03)

    statusItem.menu = menu
}

メニューアイコンにファイルをドラッグ・アンド・ドロップする

概要

  • 以下のようにメニューアイコンに画像ファイルをドラッグし、アプリ側で認識するよう実装を行います。

dragdemo

Conforming Types
NSCollectionView, NSView

実装

  • AppDelegate外でアイコンを変更するため、publicメソッドchangeMenubarIconImageを用意しておく。
    • Notificationを使うほうが疎でいいかも。
class AppDelegate: NSObject, NSApplicationDelegate {

    public let statusItem = NSStatusBar.system.draggableStatusItem(withLength: NSStatusItem.squareLength)

    public func changeMenubarIconImage(isDragging isDragIcon: Bool) {
        if let statusBarButton = statusItem.button {
            if isDragIcon {
                statusBarButton.image = NSImage(named:NSImage.Name("StatusBarButtonImageDragged"))
            } else {
                statusBarButton.image = NSImage(named:NSImage.Name("StatusBarButtonImage"))
            }
        }
    }

    public func receiveImageDraggedOnMenubarIcon(for urls: [URL]) {
        print("?URL Detected!")
        for url in urls {
            print(url)
        }
    }
    ...
extension NSStatusBar {

    func draggableStatusItem(withLength length: CGFloat) -> NSStatusItem {
        let statusItem = self.statusItem(withLength: length)

        if let button = statusItem.button {
            button.image = NSImage(named:NSImage.Name("StatusBarButtonImage"))
            button.registerForDraggedTypes([.fileURL, .tiff, .png, .URL, .string])
        }

        return statusItem
    }
}

extension NSStatusBarButton {


    // MARK: - Helper Methods

    private func filteringOptions() -> [NSPasteboard.ReadingOptionKey : Any] {
        return [.urlReadingContentsConformToTypes : NSImage.imageTypes]
    }

    private func shouldAllowDrag(_ draggingInfo: NSDraggingInfo) -> Bool {
        var canAccept = false
        let pasteBoard = draggingInfo.draggingPasteboard

        if pasteBoard.canReadObject(forClasses: [NSURL.self], options: filteringOptions()) ||
            pasteBoard.canReadObject(forClasses: [NSImage.self], options: filteringOptions()) {
            canAccept = true
        }

        return canAccept
    }

    private func setDraggingInfo(isDragging: Bool) {
        if let appDelegate = NSApplication.shared.delegate as? AppDelegate {
            appDelegate.changeMenubarIconImage(isDragging: isDragging)
        }
    }


    // MARK:- NSDraggingDestination Methods

    open override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation {
        print("draggingEntered")

        let allow = shouldAllowDrag(sender)
        setDraggingInfo(isDragging: allow)

        return allow ? .copy : NSDragOperation()
    }


    open override func draggingExited(_ sender: NSDraggingInfo?) {
        setDraggingInfo(isDragging: false)
    }


    open override func performDragOperation(_ sender: NSDraggingInfo) -> Bool {

        let pasteBoard = sender.draggingPasteboard

        // 今回はfileURLがドラッグされた場合のみ実装
        if let urls = pasteBoard.readObjects(forClasses: [NSURL.self], options: filteringOptions()) as? [URL],
            urls.count > 0,
            urls[0].isFileURL {

            if let appDelegate = NSApplication.shared.delegate as? AppDelegate {
                appDelegate.receiveImageDraggedOnMenubarIcon(for: urls)
            }

            return true
        }

        return false
    }


    open override func draggingEnded(_ sender: NSDraggingInfo) {
        setDraggingInfo(isDragging: false)
        print("draggingEnded")
    }
}

余談

  • registerForDraggedTypes with NSStatusBarButton?
    • NSStatusBarButtonにドラッグ・アンド・ドロップ可能なNSViewaddSubviewすればと回答
    • 試しましたが、アイコンの外観は変更はできましたが、ドラッグのアクションを受け取るのが不可でした。
      • Responder Chainあたりを考えるとうまくいくのだろうか…?

メニューバーのアイコンを非表示にする

  • isVisibleを設定等から設定できるようにすれば良さそうです。
statusItem.isVisible = false  // で非表示になる

ステータスメニューを実装する際にはそれを非表示にすることができるオプションを環境設定などに用意しておくべきです。

その他参考した記事

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【入門】iOS アプリ開発 #3【Sound を再生する】

Sound

今回はゲーム中のサウンドを流す処理を作成したい。SpriteKit は簡単なサウンド制御も提供している。「パックマン」程度であれば、この範囲で実現することができそうだ。

サウンドは効果音(Sound Effect) と BGM(BackGround Music) がある。この2種類を実現するクラスを作成する。

Sound Manager class の作成

効果音を再生する API は、

playSE(.EatDot)

とする。

BGMを再生する API は、

playBGM(.BgmPower)

とする。

このような API を持つ、次の CgSoundManager クラスを作成する。

CgSoundManager class

/// Sound management class plays sound with SpriteKit.
class CgSoundManager {

    // Kind of sound items to play back.
    enum EnKindOfSound: Int {
        case EatDot = 0
        case EatFruit
        // ・・・略・・・
        case Intermission
    }

    // List of sound files to load.
    private let table_urls: [[(resourceName: String, typeName: String, interval: Int)]] = [
        [ ("16_pacman_eatdot_256ms", "wav", 256) ],
        [ ("16_pacman_eatfruit_438ms", "wav", 438) ],
        // ・・・略・・・
        [ ("16_pacman_intermission_5200ms", "wav", 5200) ]
    ]
    private var view: SKScene?
    private var actions: [SKAction] = []
    private var table_playingTime: [Int] = []

    // Adjustment time for processing to play sound.
    private let triggerThresholdTime: Int = 16 //ms

    /// Create and initialize a sound manager object.
    /// - Parameters:
    ///   - view: SKScene object that organizes all of the active SpriteKit content.
    init(view: SKScene) {
        self.view = view
        table_playingTime = Array<Int>(repeating: 0, count: table_urls.count)

        for t in table_urls {
            appendSoundResource(resourceName: t[0].resourceName, typeName: t[0].typeName)
        }
        // ・・・略・・・
    }

    /// Append sound resources to SpriteKit.
    /// - Parameters:
    ///   - resourceName: File name for sound resource.
    ///   - typeName: Type name for sound resource.
    private func appendSoundResource(resourceName: String, typeName: String) {
        let fileName = resourceName+"."+typeName
        let sound: SKAction = SKAction.playSoundFileNamed(fileName, waitForCompletion: false)
        actions.append(sound)
    }

    /// Play back a specified sound.
    /// If the specified item is playing back, it will not be played back.
    /// - Parameter number: Kind of sound items to play back.
    func playSE(_ number: EnKindOfSound) {
        guard soundEnabled && number.rawValue < actions.count else { return }

        let _number = number.rawValue
        if table_playingTime[_number] <= triggerThresholdTime {
            let table = table_urls[_number]
            table_playingTime[_number] = table[0].interval
            view?.run(actions[_number])
        }
    }

enum で定義された値と、再生するサウンド・ファイルの table_urls テーブルを対応させておく。

クラス初期化時に、これらのファイルを SKActionのオブジェクトとして生成しておく。

またサウンド・ファイルの再生時間を値として管理しておく。これは同じ効果音が複数重ならないようにするためで、ある効果音が再生中に同じものを再生する場合は再生しないようにする。

また BGM を再生するときには、指定したサウンド・ファイルの再生が終了したことを知り、同じものを繰り返し再生するために使用する。自動で開始するためのトリガーは、updateメソッド内で行う。

サウンド・ファイル

今回のパックマンに使うサウンド・ファイルは 15個で、フォーマットは WAV形式、16bit、モノラル、サンプリング・レート 22050Hz で作成した。

8bit だと、少し音がこもる感じになるので、16bit にして処理の重さを気にしてサンプリング・レートを 22050Hzへ落とした。

再生でプチプチ・ノイズが出ないように、先頭は FadeIn、終端は FadeOut処理をしておく。

サウンド・ファイルの再生時間は編集ツールで確認しておく。

[Sound Files]
* 16_pacman_eatdot_256ms.wav 11,372bytes
* 16_pacman_eatfruit_438ms.wav 19,360bytes
* 16_pacman_eatghost_544ms.wav 24,040bytes
* 16_pacman_miss_1536ms.wav 67,788bytes
* 16_pacman_extrapac_1952ms.wav 86,166bytes
* 16_credit_224ms.wav 9,634bytes
* 16_BGM_normal_400ms.wav 17,852bytes
* 16_BGM_power_400ms.wav 17,750bytes
* 16_BGM_return_528ms.wav 23,366bytes
* 16_BGM_spurt1_352ms.wav 15,592bytes
* 16_BGM_spurt2_320ms.wav 14,212bytes
* 16_BGM_spurt3_592ms.wav 26,272bytes
* 16_BGM_spurt4_512ms.wav 23,018bytes
* 16_pacman_beginning_4224ms.wav 187,054bytes
* 16_pacman_intermission_5200ms.wav 229,388bytes

テスト・プログラム

GitHub に公開しているテスト・プログラムを実行すると、以下のような画面が表示され、BGMが5秒毎に切り替わる。スクリーンをタッチすると効果音が再生される。

Sound Manager クラスは、SoundManager.swift ファイルにコーディングしている。コメント入れて 200行未満となった。

class GameScene: SKScene {

    private var sound: CgSoundManager!

    override func didMove(to view: SKView) {

        // Create and reset sound object.
        sound = CgSoundManager(view: self)
    }

    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        sound.playSE(.EatDot)
    }

    private let bgm: [CgSoundManager.EnKindOfSound] = [.BgmNormal, .BgmSpurt1, .BgmSpurt2, .BgmPower, .BgmReturn]
    private var bgmIndex: Int = 0
    private var bgmTime: Int = 0

    override func update(_ currentTime: TimeInterval) {
        // Called before each frame is rendered

        // Play back BGM.
        if bgmTime == 0 {
            bgmTime = 16*60*5  // 5s
            sound.playBGM(bgm[bgmIndex])
            bgmIndex += 1
            if bgmIndex >= bgm.count {
                bgmIndex = 0
            }
        } else {
            bgmTime -= 16
        }

        // Update sound manager.
        sound.update(interval: 16 /* ms */)
    }
}

CgSoundManagerクラスは、SKViewオブジェクトをパラメータとして、オブジェクトを生成する。

SKScene からオーバーライドした touchesEndedイベントのメソッドで、SEを再生する。

また、同様に updateイベントのメソッドで、5秒毎にBGMを切り替えて再生する。

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む