CoreGraphicsコードをSwift 3.0に変換する

Xcode 8.0が登場しました。
旧プロジェクトをXcode 8.0で開くと、Swift 3.0(あるいはSwift 2.4)に変換するよう促すダイアログが出現しました。

CoreGraphicsコードをSwift 3.0への自動コンバートにかけたところ、変換されない部分が残りました。

        let pathRef = CGMutablePath()
        CGPathMoveToPoint(pathRef, nil, 20, 0)
        CGPathAddLineToPoint(pathRef, nil, 200, 0)
        CGPathAddCurveToPoint(pathRef, nil, 205.523, 0, 210, 4.435, 210, 10)
        CGPathAddLineToPoint(pathRef, nil, 210, 122)
        CGPathAddCurveToPoint(pathRef, nil, 210, 127.565, 205.523, 132, 200, 132)
        CGPathAddLineToPoint(pathRef, nil, 10, 132)
        CGPathAddCurveToPoint(pathRef, nil, 4.477, 132, -0, 127.565, -0, 122)
        CGPathAddLineToPoint(pathRef, nil, -0, 20)
        CGPathAddCurveToPoint(pathRef, nil, -0, 9.087, 9.081, 0, 20, 0)
        pathRef.closeSubpath()

変換後、このようなコードになったのですが最終的には

        let pathRef = CGMutablePath()
        pathRef.move(to: CGPoint(x: 20, y: 0))
        pathRef.addLine(to: CGPoint(x: 200, y: 0))
        pathRef.addCurve(to: CGPoint(x: 210, y: 10), control1: CGPoint(x: 205.523, y: 0), control2: CGPoint(x: 210, y: 4.435))
        pathRef.addLine(to: CGPoint(x: 210, y: 122))
        pathRef.addCurve(to: CGPoint(x: 200, y: 132), control1: CGPoint(x: 210, y: 127.565), control2: CGPoint(x: 205.523, y: 132))
        pathRef.addLine(to: CGPoint(x: 10, y: 132))
        pathRef.addCurve(to: CGPoint(x: -0, y: 122), control1: CGPoint(x: 4.477, y: 132), control2: CGPoint(x: -0, y: 127.565))
        pathRef.addLine(to: CGPoint(x: -0, y: 20))
        pathRef.addCurve(to: CGPoint(x: 20, y: 0), control1: CGPoint(x: -0, y: 9.087), control2: CGPoint(x: 9.081, y: 0))
        pathRef.closeSubpath()

と変換したい。

文字列の処理がやりやすいのはなにかしらと考えて、急遽Rubyを学ぶことにしました。


文字列操作とファイル操作の基本的なやりかたを知ったので、以下の変換プログラム converter.rbを書きました。
CGPathMoveToPoint(pathRef, nil, 20, 0)

pathRef.move(to: CGPoint(x: 20, y: 0))

CGPathAddLineToPoint(pathRef, nil, 200, 0)

pathRef.addLine(to: CGPoint(x: 200, y: 0))

CGPathAddCurveToPoint(pathRef, nil, 205.523, 0, 210, 4.435, 210, 10)

pathRef.addCurve(to: CGPoint(x: 210, y: 10), control1: CGPoint(x: 205.523, y: 0), control2: CGPoint(x: 210, y: 4.435))

と変換するものです。

converter.rb

class Converter

    def convert()
        loop do 
            print "Filename? "
            fileName = gets.chomp
            
            if fileName == ""
                break
            end
            
            # Backup
            from = fileName
            to = "_" + fileName + ".bak"
            copy(from, to)
            
            # Conversion
            array = []
            File.open(fileName) do |file|
                file.each_line do |line|
                    line = convertCoreGraphicsCode(line)
                    array.push(line)
                end
            end
                
            # Writing
            File.open(fileName, "w") do |file|
                file.puts(array)
                puts fileName + " 変換終了"
            end
        end
    end
    
    def copy(from, to)
        File.open(from) do |input|
            File.open(to, "w") do |output|
                output.write(input.read)
            end
        end
    end

    def convertCoreGraphicsCode(line)
        if line.include?("CGPathMoveToPoint")
            return convertMoveToPoint(line)
        elsif line.include?("CGPathAddLineToPoint") 
            return convertAddLineToPoint(line)
        elsif line.include?("CGPathAddCurveToPoint")
            return convertAddCurveToPoint(line)
        else
            return line
        end
    end
    
    # CGPathMoveToPoint(clipPath, nil, 240, 122)
    # to
    # clipPath.move(to: CGPoint(x: 240, y: 122))
    def convertMoveToPoint(line)
        # indentを取得
        index = line.index("CGPathMoveToPoint")
        indent = line[0, index]
        
        # path名を含むかたまりを取得("CGPathMoveToPoint(clipPath,")
        pathStr = line.match(/CGPathMoveToPoint\(\w+,/)
        
        # path名を取得("clipPath")
        pathName = pathStr[0].sub("CGPathMoveToPoint\(", "").chop
        # puts pathName
        
        # 数値
        figuresStr = line.sub(/CGPathMoveToPoint\(\w+, nil,/, "").chomp.chop.lstrip
        figuresStr = " " + figuresStr
        figures = figuresStr.split(",")
        # puts figures
        
        dstStr = "%s%s.move(to: CGPoint(x:%s, y:%s))" % [indent, pathName, figures[0], figures[1]]
        # puts dstStr
        return dstStr
    end
    
    
    # CGPathAddLineToPoint(clipPath, nil, 240, 122)
    # to
    # clipPath.addLine(to: CGPoint(x: 240, y: 122))
    def convertAddLineToPoint(line)
        # indentを取得
        index = line.index("CGPathAddLineToPoint")
        indent = line[0, index]
        
        # path名を含むかたまりを取得("CGPathAddLineToPoint(clipPath,")
        pathStr = line.match(/CGPathAddLineToPoint\(\w+,/)
        
        # path名を取得("clipPath")
        pathName = pathStr[0].sub("CGPathAddLineToPoint\(", "").chop
        # puts pathName
        
        # 数値
        figuresStr = line.sub(/CGPathAddLineToPoint\(\w+, nil,/, "").chomp.chop.lstrip
        figuresStr = " " + figuresStr
        figures = figuresStr.split(",")
        # puts figures

        dstStr = "%s%s.addLine(to: CGPoint(x:%s, y:%s))" % [indent, pathName, figures[0], figures[1]]
        # puts dstStr
        return dstStr
    end
    
    # CGPathAddCurveToPoint(pathRef2, nil, 4.477, 132, 0, 127.565, 0, 122)
    # to
    # pathRef2.addCurve(to: CGPoint(x: 0, y: 122), control1: CGPoint(x: 4.477, y: 132), control2: CGPoint(x: 0, y: 127.565))
    def convertAddCurveToPoint(line)

        # indentを取得
        index = line.index("CGPathAddCurveToPoint")
        indent = line[0, index]
        
        # path名を含むかたまりを取得("CGPathAddCurveToPoint(pathRef2,")
        pathStr = line.match(/CGPathAddCurveToPoint\(\w+,/)
        
        # path名を取得("pathRef2")
        pathName = pathStr[0].sub("CGPathAddCurveToPoint\(", "").chop
        # puts pathName
        
        # 数値を取得
        figuresStr = line.sub(/CGPathAddCurveToPoint\(\w+, nil,/, "").chomp.chop.lstrip
        figuresStr = " " + figuresStr
        figures = figuresStr.split(",")
        # puts figures
        
        dstStr = "%s%s.addCurve(to: CGPoint(x:%s, y:%s), control1: CGPoint(x:%s, y:%s), control2: CGPoint(x:%s, y:%s))" % [indent, pathName, figures[4], figures[5], figures[0], figures[1], figures[2], figures[3]]
        # puts dstStr
        return dstStr 
    end

end

converter = Converter.new
converter.convert()

変換したいswiftファイルがあるフォルダに、converter.rbをおき、Terminalでそのフォルダへ移動した後、

ruby converter.rb  

と入力するとプログラムが実行されます。
実行すると、

FileName?  

とたずねられるので、そこで変換したいファイル名を入力すると変換が行われます。
(このさい、”_元ファイル名.bak”というバックアップファイルも作成します。)
ファイル名を入力せずにreturnキーを押すと、プログラムは終了します。

関連

Autodesk Graphic(旧iDraw)はCore Graphicsのコードを生成できる – nackpan Blog

Dropbox API v1からv2への移行

作成したiOSアプリ「Repete」(語学学習支援プレイヤー)には、Dropbox上のオーディオファイルを取り入れる機能があります。
Dropboxへのアクセスには、Dropboxが提供するAPIを使用しています。
API v1 is now deprecated | Dropbox Developer Blog
Dropbox API v1が廃止され、新たなアプリおよび既存のアプリはAPI v2を使ってDropboxと接続する必要があるとのことで、今回、移行処理を行いました。

File Type Permissionが廃止された

API v1には、File type Permissionという分類がありました。特定の種類のファイルのみアクセスできるというものです。
API v1 → API v2 migration guide – Developers – Dropbox
API v2では、File Types Permissionがなくなりました。移行にあたっては新たにアプリを登録して、ユーザーにもう一度認証してもらうことになります。

Tip: When messaging to your users the need to re-link their Dropbox account, we suggest the following language: “The Dropbox integration has changed. Please re-link your account to continue [syncing/backing up data/accessing files/etc].”

再リンクの必要を伝えるメッセージの例。

Fie Types Permissionのように特定の種類のファイルのみを取得するには、ファイル拡張子を用いて判別します。
Developer guide – Dropbox
こちらに、API v1でFile type判別に用いていた拡張子の一覧があります。

インストール

Swift製のライブラリをインストール。
Dropbox for Swift Developers | Install
こちらのページにそって、インストール。
AlamofireとSwiftyDropboxがインストールされます。
今回インストールしたバージョンは、
Alamofire: 3.3.1
SwiftyDropbox: 3.2.0

チュートリアル

Dropbox for Swift Developers | Tutorial
チュートリアルでは、アプリを登録して簡単なプロジェクトを作成しました。
チュートリアルプロジェクトでできること。
* Dropboxとの連携許可
* ユーザーのアカウント名を表示
* 指定したフォルダ直下の内容を表示
* ファイルのアップロード
* ファイルのdiskへのダウンロード
* ファイルのメモリへのダウンロード

おおよそやりたいことはできるのだけれど、そのほかに知りたい点がいくつか。
Tutorialを改造しながら、さぐりさぐりで調べていきました。

そのほか調べた事柄

ファイルの情報を知る

getMetadataを用いる。

if let client = Dropbox.authorizedClient {
    // ファイルのpathを指定して情報を取得
    // ("hello.txt"がルートフォルダにあるとします)
    client.files.getMetadata(path: "/hello.txt").response {
        response, error in
        
        print("*** get Metadata ***")
        if let metadata = response {
            print("result:\(metadata.name)")
            print("")
            
        }
    }
}

フォルダかファイルか?

取得したmetadataがフォルダなのかファイルなのか知りたい。
Files.FolderMetadata、Files.FileMetadataを用いる。

if let client = Dropbox.authorizedClient {
    // ルートフォルダ直下の内容を取得して、名前を表示。ファイルの場合はサイズも表示
    client.files.listFolder(path: "")
        .response { response, error in
            print()
            print("*** List folder ***")
            if let result = response {
                for entry in result.entries {
                    if entry is Files.FileMetadata {
                        let file = entry as! Files.FileMetadata
                        print("\(file.name) \(file.size)")
                        
                    } else {
                        print("\(entry.name)")
                    }
                }
            } else {
                print(error!)
            }
    }
}

ファイルの種類を限定

Dropbox for Swift Developers | Overview
こちらのページで紹介されているSample Appの”PhotoWatch”では、

// Check that file is a photo (by file extension)
if entry.name.hasSuffix(".jpg") || entry.name.hasSuffix(".png") {
    // Add photo!
    self.filenames?.append(entry.name)
}

という書き方をしています。
拡張子をチェックして判別しています。
参考:Developer guide – Dropbox
Dropbox API v1でFile type判別に用いていた拡張子の一覧あり。

フォルダ下のすべてのフォルダ・ファイルを取得する

client.files.listFolder(path: "", recursive: true, includeMediaInfo: false, includeDeleted: false, includeHasExplicitSharedMembers: false)

listFolderのさいに、recursiveをtrueにすることで再帰的に調べてフォルダ下のすべてのフォルダ・ファイルが取得できます。

進行状況

進行状況を知るには?

if let client = Dropbox.authorizedClient {
    let destination : (NSURL, NSHTTPURLResponse) -> NSURL = { temporaryURL, response in
        let fileManager = NSFileManager.defaultManager()
        let directoryURL = fileManager.URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0]
        // generate a unique name for this file in case we've seen it before
        let UUID = NSUUID().UUIDString
        let pathComponent = "\(UUID)-\(response.suggestedFilename!)"
        return directoryURL.URLByAppendingPathComponent(pathComponent)
    }
    
    // ルートフォルダにある"audio.mp3"ファイルをダウンロードする
    client.files.download(path: "/audio.mp3", destination: destination)
        .progress{ bytesRead, totalBytesRead, totalBytesExpectedToRead in
            
            print("bytesRead:\(bytesRead) totalBytesRead:\(totalBytesRead) totalBytesExpectedToRead:\(totalBytesExpectedToRead)")
            
        }
        .response { response, error in
            if let (metadata, url) = response {
                print()
                print("*** Downloaded file to disk ***")
                print("Downloaded file name: \(metadata.name)")
                print("Downloaded file url: \(url)")
            } else {
                print(error!)
            }
    }
}

参考:swift – SwiftyDropbox download progress – Stack Overflow

リクエストをキャンセルする

ダウンロードのリクエストをキャンセルするには?
swifty dropbox download cancel – Dropbox Community
こちらのページを読むと、初期はダウンロードキャンセル機能はなかったようです。
最新版ではあるとのこと。
SwiftyDropbox 3.2.0ではありました。

import UIKit
import SwiftyDropbox

class ViewController: UIViewController {
    
    var dlRequest: DownloadRequestFile<Files.FileMetadataSerializer, Files.DownloadErrorSerializer>?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Verify user is logged into Dropbox
        if let client = Dropbox.authorizedClient {

            // Download a file to disk
            // ルートフォルダにある"audio.mp3"ファイルをダウンロードする。
            let destination : (NSURL, NSHTTPURLResponse) -> NSURL = { temporaryURL, response in
                let fileManager = NSFileManager.defaultManager()
                let directoryURL = fileManager.URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0]
                // generate a unique name for this file in case we've seen it before
                let UUID = NSUUID().UUIDString
                let pathComponent = "\(UUID)-\(response.suggestedFilename!)"
                return directoryURL.URLByAppendingPathComponent(pathComponent)
            }
            
            print("Downloading")
            dlRequest = client.files.download(path: "/audio.mp3", destination: destination).progress{ bytesRead, totalBytesRead, totalBytesExpectedToRead in
                
                    print("bytesRead:\(bytesRead) totalBytesRead:\(totalBytesRead) totalBytesExpectedToRead:\(totalBytesExpectedToRead)")

                }.response { response, error in
                    if let (metadata, url) = response {
                        print()
                        print("*** Downloaded file to disk ***")
                        print("Downloaded file name: \(metadata.name)")
                        print("Downloaded file url: \(url)")
                    } else {
                        print(error!)
                    }
            }
        }
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    @IBAction func linkButtonPressed(sender: AnyObject) {
        if Dropbox.authorizedClient == nil {
            Dropbox.authorizeFromController(self)
        }
    }

    // (storyboard上にキャンセル用ボタンを配置し、actionでつないだ)
    @IBAction func cancelButtonPressed(sender: AnyObject) {
        if let request = dlRequest {
            request.cancel()
            dlRequest = nil
        }
    }
}

こんな感じでしょうか。
Storyboard側にキャンセル用ボタンを配置してViewController.swiftのcancelButtonPressedとactionでつないであるものとします。
Dropboxにリンクしている状態で、viewDidLoadが呼ばれると、ルートフォルダの”audio.mp3″のダウンロードが開始されます。
キャンセルボタンを押すと、ダウンロードがキャンセルされます。


以上、SwiftyDropboxの使い方について、いくつか知った事柄について記しました。

[iOS][Swift]ミュージックライブラリの音楽の再生、情報の表示(MPMusicPlayerController使用)

MPMusicPlayerControllerを使ったミュージックライブラリアイテムの再生に関してこちらに記事を書きました。
[iOS][Swift]ミュージックライブラリにアクセスして音楽を再生する(MPMusicPlayerController使用) | nackpan Blog
この記事では、ミュージックライブラリから一つの曲を選んで再生・一時停止・停止を行いました。

今回のサンプルでは、MPMusicPlayerControllerを使用して複数のアイテムを順に再生します。
また、再生中の音楽アイテムの情報(アートワーク、アルバム名等)を表示します。
前回のサンプルでは、選曲するとすぐに再生を開始していましたが、今回は選曲するとアイテム情報を表示しますが再生は開始しないこととしました。PLAYボタンを押すと再生開始です。
再生されているアイテムが変更されると、それを検知して変更後のアイテム情報を表示します。
ファイル 2016-04-13 7 24 35
(今回のサンプルのスクリーンショット)

MusicPlayerControllerを使って、ミュージックライブラリのアイテムを再生し、情報を表示する

iPodライブラリアクセス プログラミングガイド(PDF)
iPod Library Access Programming Guide(英語版)
このappleのドキュメントにライブラリにアクセスして再生する方法、必要な曲を選択する方法、現在再生中の音楽の情報を知る方法等、まとめてあります。
(ちなみに、日本語ドキュメント – Apple DeveloperのページにappleのiOS用日本語ドキュメントがまとめてあります。英語版へのリンクもあります)

iOSシミュレータでは動作しないので、実機を用いてください。

Single View Applicationでプロジェクトを作成。
ss 2015-09-16 6.46.53

ViewController.SwiftにMediaPlayerフレームワークをimportします。

import UIKit
import MediaPlayer

class ViewController: UIViewController {

UI配置

ボタンとラベルとイメージビュー(Image View)を配置。
ss 2016-04-13 2.02.06

UIとViewControllerとの接続

ラベルおよびイメージビューとViewController.swiftをOutletを作成して接続。
ss 2016-04-12 21.53.43outletLabels
ss 2016-04-12 21.52.55

ボタンとViewController.swiftをactionを作成して接続。
ss 2016-04-13 2.35.53Action
ss 2015-09-13 9.09.50

プレイヤー準備

プレイヤーを表すpropertyをViewController.swiftに加えます。

class ViewController: UIViewController, MPMediaPickerControllerDelegate {
    
    @IBOutlet weak var imageView: UIImageView!
    @IBOutlet weak var artistLabel: UILabel!
    @IBOutlet weak var albumLabel: UILabel!
    @IBOutlet weak var songLabel: UILabel!

    var player = MPMusicPlayerController()
    
    override func viewDidLoad() {

プレイヤーのインスタンスを作成。

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        
        player = MPMusicPlayerController.applicationMusicPlayer()
        //player = MPMusicPlayerController.systemMusicPlayer()
        
    }

(ここで、applicationMusicPlayerではなく、systemMusicPlayerを用いると、「ミュージック」アプリでの再生状況(再生アイテムや、シャッフル、リピートなどのモード)を反映したものになる)

再生中アイテムの変更通知

今回は、複数の曲を選択して順に再生していきます。
アイテム情報を表示するには、現在再生中のアイテムがなにか分かっていなければなりません。
ミュージックプレーヤー通知という仕組みで、再生中アイテムに変更があった場合には通知を受け取ることができます。
この通知を受けて、プレイヤーが現在再生中のアイテムを取得し情報表示を更新します。

まず、再生中アイテム変更イベントを監視します。

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        
        player = MPMusicPlayerController.applicationMusicPlayer()
        //player = MPMusicPlayerController.systemMusicPlayer()
        
        // 再生中のItemが変わった時に通知を受け取る
        let notificationCenter = NSNotificationCenter.defaultCenter()
        notificationCenter.addObserver(self, selector: #selector(ViewController.nowPlayingItemChanged(_:)), name: MPMusicPlayerControllerNowPlayingItemDidChangeNotification, object: player)
        // 通知の有効化
        player.beginGeneratingPlaybackNotifications()
        
    }

再生中のアイテムが変更になったときに、ViewController.nowPlayingItemChanged(_:)を呼び出すように指定しています。

また、ViewControllerのdeinit内に、通知を受け取る必要がなくなった場合の後処理を書いておきます。

    deinit {
        // 再生中アイテム変更に対する監視をはずす
        let notificationCenter = NSNotificationCenter.defaultCenter()
        notificationCenter.removeObserver(self, name: MPMusicPlayerControllerNowPlayingItemDidChangeNotification, object: player)
        // ミュージックプレーヤー通知の無効化  
        player.endGeneratingPlaybackNotifications()
    }

再生中のアイテムが変更になった際に呼び出されるViewController.nowPlayingItemChanged(_:)の内容を書きます。

    /// 再生中の曲が変更になったときに呼ばれる  
    func nowPlayingItemChanged(notification: NSNotification) {
        
        if let mediaItem = player.nowPlayingItem {
            updateSongInformationUI(mediaItem)
        }
        
    }

MPMusicPlayerControllerのプロパティnowPlayingItemで再生中のアイテムを取得できます。
(今回は使いませんが、アイテムをsetすることもできます)
nowPlayingItemはMPMediaItem型のプロパティです。

MPMediaItem

ミュージックライブラリに対するアクセスでは、MPMediaItemというクラスでアイテム情報を扱います。
MPMediaItem Class Reference
MPMediaItemは、title, artist, artworkなどなど、さまざまな情報をもっています。
アイテム情報を表示する際には、現在再生中のアイテムから情報を取得してラベルやイメージビューに表示します。

    /// 曲情報を表示する
    func updateSongInformationUI(mediaItem: MPMediaItem) {
    
        // 曲情報表示
        // (a ?? b は、a != nil ? a! : b を示す演算子です)  
        // (aがnilの場合にはbとなります)
        artistLabel.text = mediaItem.artist ?? "不明なアーティスト"
        albumLabel.text = mediaItem.albumTitle ?? "不明なアルバム"
        songLabel.text = mediaItem.title ?? "不明な曲"
        
        // アートワーク表示
        if let artwork = mediaItem.artwork {
            let image = artwork.imageWithSize(imageView.bounds.size)
            imageView.image = image
        } else {
            // アートワークがないとき
            // (今回は灰色表示としました)
            imageView.image = nil
            imageView.backgroundColor = UIColor.grayColor()
        }
        
    }

メディアアイテムピッカー

曲を選択するために、メディアアイテムピッカーを用います。
(メディアアイテムピッカーというのは、iOSで用意されているあらかじめ設定済みのViewController。ミュージックライブラリの選択画面と同じようなことが出来る)
ss 2015-09-13 11 41 39

メディアアイテムピッカーでの「選択完了したとき」や「キャンセルされたとき」のイベントを、ViewControllerで受け取れるようにします。
そのために、ViewControllerをメディアアイテムピッカーのデリゲートとして設定します。
「選曲」ボタンを押すと、メディアアイテムピッカーを作成して、デリゲートの設定を行い、ライブラリの曲を選択できるようにします。
「選択完了したとき」「キャンセルされたとき」のメソッドを記述します。

import UIKit
import MediaPlayer

class ViewController: UIViewController, MPMediaPickerControllerDelegate {
    @IBAction func pick(sender: AnyObject) {
        // MPMediaPickerControllerのインスタンスを作成
        let picker = MPMediaPickerController()
        // ピッカーのデリゲートを設定
        picker.delegate = self
        // 複数選択にする。(falseにすると、単数選択になる)
        picker.allowsPickingMultipleItems = true
        // ピッカーを表示する
        presentViewController(picker, animated: true, completion: nil)
        
    }
    
    /// メディアアイテムピッカーでアイテムを選択完了したときに呼び出される
    func mediaPicker(mediaPicker: MPMediaPickerController, didPickMediaItems mediaItemCollection: MPMediaItemCollection) {
        
        // プレイヤーを止める
        player.stop()
        
        // 選択した曲情報がmediaItemCollectionに入っているので、これをplayerにセット。
        player.setQueueWithItemCollection(mediaItemCollection)
        
        // 選択した曲から最初の曲の情報を表示
        if let mediaItem = mediaItemCollection.items.first {
            updateSongInformationUI(mediaItem)
        }
        
        // ピッカーを閉じ、破棄する
        dismissViewControllerAnimated(true, completion: nil)
        
    }
    
    //選択がキャンセルされた場合に呼ばれる
    func mediaPickerDidCancel(mediaPicker: MPMediaPickerController) {
        // ピッカーを閉じ、破棄する
        dismissViewControllerAnimated(true, completion: nil)
    }

選択完了した時に呼び出されるmediaPicker(mediaPicker: MPMediaPickerController, didPickMediaItems mediaItemCollection: MPMediaItemCollection)で、MPMediaItemCollection型の引数mediaItemCollectionがありますが、これにアイテム情報が入っています。
MPMediaItemCollection Class Reference
MPMediaItemCollectionクラスは、MPMediaItemを集めて管理しているクラスです。
そのプロパティitemsがMPMediaItemの配列になっていますので、今回はそこから先頭のMPMediaItemを取得してそれを元に情報を表示しています。

再生・一時停止・停止

各ボタンのアクションに、「再生」「一時停止」「停止」機能を加えます。

    @IBAction func pushPlay(sender: AnyObject) {
        player.play()
    }

    @IBAction func pushPause(sender: AnyObject) {
        player.pause()
    }

    @IBAction func pushStop(sender: AnyObject) {
        player.stop()
    }

iOSシミュレータでは動作しません。実機を用いてください。 「選曲」ボタンを押すと、メディアアイテムピッカーが表示されるので、曲を選択してください。複数の曲を選択できます。「PLAY」ボタンで音楽の再生。「PAUSE」ボタンで一時停止。「STOP」ボタンで音楽を止めて、再生位置を一番始めに戻します。


ViewController.swift全文

//
//  ViewController.swift
//  MPMusicPlayerControllerDemo2
//
//  Created by KUWAJIMA MITSURU on 2016/04/12.
//  Copyright © 2016年 nackpan. All rights reserved.
//

import UIKit
import MediaPlayer

class ViewController: UIViewController, MPMediaPickerControllerDelegate {
    
    @IBOutlet weak var imageView: UIImageView!
    @IBOutlet weak var artistLabel: UILabel!
    @IBOutlet weak var albumLabel: UILabel!
    @IBOutlet weak var songLabel: UILabel!

    var player = MPMusicPlayerController()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        
        player = MPMusicPlayerController.applicationMusicPlayer()
        //player = MPMusicPlayerController.systemMusicPlayer()
        
        // 再生中のItemが変わった時に通知を受け取る
        let notificationCenter = NSNotificationCenter.defaultCenter()
        notificationCenter.addObserver(self, selector: #selector(ViewController.nowPlayingItemChanged(_:)), name: MPMusicPlayerControllerNowPlayingItemDidChangeNotification, object: player)
        // 通知の有効化
        player.beginGeneratingPlaybackNotifications()
        
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    @IBAction func pick(sender: AnyObject) {
        // MPMediaPickerControllerのインスタンスを作成
        let picker = MPMediaPickerController()
        // ピッカーのデリゲートを設定
        picker.delegate = self
        // 複数選択にする。(falseにすると、単数選択になる)
        picker.allowsPickingMultipleItems = true
        // ピッカーを表示する
        presentViewController(picker, animated: true, completion: nil)
        
    }
    
    /// メディアアイテムピッカーでアイテムを選択完了したときに呼び出される
    func mediaPicker(mediaPicker: MPMediaPickerController, didPickMediaItems mediaItemCollection: MPMediaItemCollection) {
        
        // プレイヤーを止める
        player.stop()
        
        // 選択した曲情報がmediaItemCollectionに入っているので、これをplayerにセット。
        player.setQueueWithItemCollection(mediaItemCollection)
        
        // 選択した曲から最初の曲の情報を表示
        if let mediaItem = mediaItemCollection.items.first {
            updateSongInformationUI(mediaItem)
        }
        
        // ピッカーを閉じ、破棄する
        dismissViewControllerAnimated(true, completion: nil)
        
    }

    
    /// 選択がキャンセルされた場合に呼ばれる
    func mediaPickerDidCancel(mediaPicker: MPMediaPickerController) {
        // ピッカーを閉じ、破棄する
        dismissViewControllerAnimated(true, completion: nil)
    }
    
    /// 曲情報を表示する
    func updateSongInformationUI(mediaItem: MPMediaItem) {
    
        // 曲情報表示
        // (a ?? b は、a != nil ? a! : b を示す演算子です)  
        // (aがnilの場合にはbとなります)
        artistLabel.text = mediaItem.artist ?? "不明なアーティスト"
        albumLabel.text = mediaItem.albumTitle ?? "不明なアルバム"
        songLabel.text = mediaItem.title ?? "不明な曲"
        
        // アートワーク表示
        if let artwork = mediaItem.artwork {
            let image = artwork.imageWithSize(imageView.bounds.size)
            imageView.image = image
        } else {
            // アートワークがないとき
            // (今回は灰色表示としました)
            imageView.image = nil
            imageView.backgroundColor = UIColor.grayColor()
        }
        
    }
    

    
    
    @IBAction func pushPlay(sender: AnyObject) {
        player.play()
    }
    
    @IBAction func pushPause(sender: AnyObject) {
        player.pause()
    }
    
    @IBAction func pushStop(sender: AnyObject) {
        player.stop()
    }
    
    
    /// 再生中の曲が変更になったときに呼ばれる  
    func nowPlayingItemChanged(notification: NSNotification) {
        
        if let mediaItem = player.nowPlayingItem {
            updateSongInformationUI(mediaItem)
        }
        
    }
    
    deinit {
        // 再生中アイテム変更に対する監視をはずす
        let notificationCenter = NSNotificationCenter.defaultCenter()
        notificationCenter.removeObserver(self, name: MPMusicPlayerControllerNowPlayingItemDidChangeNotification, object: player)
        // ミュージックプレーヤー通知の無効化  
        player.endGeneratingPlaybackNotifications()
    }
    

}

関連

[iOS][Swift]ミュージックライブラリにアクセスして音楽を再生する(MPMusicPlayerController使用) | nackpan Blog
[iOS][Swift]MPMediaQueryを使って曲を絞り込む | nackpan Blog
[iOS][Swift]ミュージックライブラリにアクセスして音楽を再生する(AVAudioPlayer使用) | nackpan Blog

Autodesk Graphic(旧iDraw)はCore Graphicsのコードを生成できる

Autodesk Graphic(旧iDraw)は、図形からCore Graphicsのコードを書き出せることを先日知ったので、記します。

言語を選択

環境設定 > 読み込み / 書き出し
で、SwiftかObjective-Cかを選択
ss 2016-02-07 8.05.12

クリップボードへ書き出し

必要なレイヤーを選択して、Ctrl+クリック(もしくは編集メニュー)から、
別名でコピー > Core Graphics Codeを選択。
ss 2016-02-14 22.19.30
これでクリッブボードにコードがコピーされています。
ペースト(Ctrl + Vなど)すると、以下のようなコードが貼り付けられます。

#if os(iOS)
    let ctx = UIGraphicsGetCurrentContext() // iOS
#else
    let contextPtr = NSGraphicsContext.currentContext()!.graphicsPort   // OS X
    let opaqueCtx = COpaquePointer(contextPtr)
    let ctx = Unmanaged<CGContext>.fromOpaque(opaqueCtx).takeUnretainedValue()
#endif

// enable the following lines for flipped coordinate systems
// CGContextTranslateCTM(ctx, 0, self.bounds.size.height)
// CGContextScaleCTM(ctx, 1, -1)
let colorSpace = CGColorSpaceCreateDeviceRGB()

let scaleFactor: CGFloat = 1;
// CGContextScaleCTM(ctx, scaleFactor, scaleFactor);

/*  Shape   */
let pathRef = CGPathCreateMutable()
CGPathMoveToPoint(pathRef, nil, 10, -0)
CGPathAddLineToPoint(pathRef, nil, 230, -0)
CGPathAddCurveToPoint(pathRef, nil, 235.523, -0, 240, 4.477, 240, 10)
CGPathAddLineToPoint(pathRef, nil, 240, 121)
CGPathAddCurveToPoint(pathRef, nil, 240, 126.523, 235.523, 131, 230, 131)
CGPathAddLineToPoint(pathRef, nil, 10, 131)
CGPathAddCurveToPoint(pathRef, nil, 4.477, 131, 0, 126.523, 0, 121)
CGPathAddLineToPoint(pathRef, nil, 0, 10)
CGPathAddCurveToPoint(pathRef, nil, 0, 4.477, 4.477, -0, 10, -0)
CGPathCloseSubpath(pathRef)

// (以下略)

省略してしまいましたが、インナーシャドー、グラデーション、輪郭線を用いた角丸四角形と三角形を描くのに130行程度のコードになっています。

生成されたコードからUIImageを作成

生成されたコードでは、CurrentContextを用いて、そこに描画しています。
サイズを指定してContextを作成し、それをCurrentContextとするのには
UIGraphicsBeginImageContextWithOptionsが使えます。

// サイズを指定してContextを作成し、それをCurrentContextとする
// (sizeはCGSize型の変数)  
UIGraphicsBeginImageContextWithOptions(size, false, 0)

// (ここにAutodesk Graphicsが生成したコード)

// CurrentContextからUIImage?の作成  
let image: UIImage? = UIGraphicsGetImageFromCurrentImageContext()

// このContextをスタックから取り除く(CurrentContextではなくなる)
UIGraphicsEndImageContext()


こんなかんじで、UIImageに持ってくることができます。

スケールについて
let scaleFactor: CGFloat = 1;
// CGContextScaleCTM(ctx, scaleFactor, scaleFactor);

生成されたコードではコメントアウトされているCGContextScaleCTMを生かしてscaleFactorを変えれば、スケールの変更ができます。

一部書き方があっていないところあり

Autodesk GraphicのVer 3.0.1を使用しているのですが、一部、書き方がSwift 2.1に適合していない部分がありました。
グラデーションのOption指定部分を修正しました。

// 修正前
CGContextDrawLinearGradient(ctx, gradientRef, CGPoint(x: 130.334, y: 82.171), CGPoint(x: 130.334, y: 44.818), CGGradientDrawingOptions(kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation))
// 修正後
CGContextDrawLinearGradient(ctx, gradientRef, CGPoint(x: 130.334, y: 82.171), CGPoint(x: 130.334, y: 44.818), [.DrawsBeforeStartLocation, .DrawsAfterEndLocation])
テキスト

テキスト部分を書き出してみるとCoreTextを使用したコードで書き出されました。
Path化されてしまうのではなく、テキスト情報が残っています。
ss 2016-02-14 22.54.08

#if os(iOS)
    let ctx = UIGraphicsGetCurrentContext() // iOS
#else
    let contextPtr = NSGraphicsContext.currentContext()!.graphicsPort   // OS X
    let opaqueCtx = COpaquePointer(contextPtr)
    let ctx = Unmanaged<CGContext>.fromOpaque(opaqueCtx).takeUnretainedValue()
#endif

// enable the following lines for flipped coordinate systems
// CGContextTranslateCTM(ctx, 0, self.bounds.size.height)
// CGContextScaleCTM(ctx, 1, -1)
let colorSpace = CGColorSpaceCreateDeviceRGB()

let scaleFactor: CGFloat = 1;
// CGContextScaleCTM(ctx, scaleFactor, scaleFactor);

/*  Text   */
let textBox = CGRect(x: 13.912, y: 5.804, width: 65.76, height: 38)
let textStr: CFString = "ABC"

let attributedStr = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0)
CFAttributedStringReplaceString(attributedStr, CFRange(location: 0, length: 0), textStr)

let fontRef = CTFontCreateWithName("HelveticaNeue", 32, nil)
let textRange = CFRange(location: 0, length: CFAttributedStringGetLength(attributedStr))
CFAttributedStringSetAttribute(attributedStr, textRange, kCTFontAttributeName, fontRef)

let textColorComps: [CGFloat] = [0.953, 0.388, 0.388, 1]
let textColor = CGColorCreate(colorSpace, textColorComps)
CFAttributedStringSetAttribute(attributedStr, textRange, kCTForegroundColorAttributeName, textColor)

var alignment = CTTextAlignment.TextAlignmentCenter
var paragraphSettings = CTParagraphStyleSetting(spec: CTParagraphStyleSpecifier.Alignment, valueSize: UInt(sizeof(UInt8)), value: &alignment)
let paragraphStyle = CTParagraphStyleCreate(&paragraphSettings, 1)
CFAttributedStringSetAttribute(attributedStr, textRange, kCTParagraphStyleAttributeName, paragraphStyle)

let textBoxPath = CGPathCreateWithRect(CGRect(x: 0, y: 0, width: textBox.size.width, height: textBox.size.height), nil)
let framesetter = CTFramesetterCreateWithAttributedString(attributedStr)
let frameRef = CTFramesetterCreateFrame(framesetter, CFRange(location: 0, length: 0), textBoxPath, nil)
CGContextSaveGState(ctx)
CGContextTranslateCTM(ctx, textBox.origin.x, textBox.origin.y)

CGContextSetTextMatrix(ctx, CGAffineTransformIdentity)
CGContextTranslateCTM(ctx, 0.0, textBox.size.height)
CGContextScaleCTM(ctx, 1.0, -1.0)
CTFrameDraw(frameRef, ctx)

CGContextRestoreGState(ctx)

一部でエラーが出たので修正しました

// 修正前
var alignment = CTTextAlignment.TextAlignmentCenter
var paragraphSettings = CTParagraphStyleSetting(spec: CTParagraphStyleSpecifier.Alignment, valueSize: UInt(sizeof(UInt8)), value: &alignment)
// (略)
CTFrameDraw(frameRef, ctx)
// 修正後
var alignment = CTTextAlignment.Center
var paragraphSettings = CTParagraphStyleSetting(spec: CTParagraphStyleSpecifier.Alignment, valueSize: Int(sizeof(UInt8)), value: &alignment)
// (略)
CTFrameDraw(frameRef, ctx!)

ということで、Autodesk GraphicはCore Graphics Codeを書き出せます。

関連

CoreGraphicsコードをSwift 3.0に変換する – nackpan Blog

Rendowをアップデートしました(ver 1.0.7)

iOSアプリ 「Rendow」をアップデートしました (ver 1.0.6 –> ver 1.0.7)

「Rendow」は、走る速度に連動して再生速度が変化するオーディオプレイヤーです

「速く走ると音楽も速くなる」や「走ると通常再生、歩くとスロー」などなど
ちょっとおもしろいので、使ってみてくださいね


変更点

• アプリの使いかたや機能に関しての変更はないんですけども、プログラムを一部改修してSwift 2.x系で可能になった書き方を取り入れました

[iOS]マイクへのアクセス許可を求めるダイアログを再度表示する

iOSアプリで、録音機能を使うときには最初にマイクへのアクセス許可を問うダイアログが表示されます。
ss2015-11-01

許可しない・OK どちらかを選ぶことになります。

選んだ後は、そのアプリで録音機能を使ってもダイアログは表示されません。

開発中に、はじめて実行した状態に戻して、「マイクへのアクセス許可ダイアログ」を表示する必要がありました。
アプリをiPhoneから削除して、再度ビルドし直して実行してみましたが、アクセス許可ダイアログは表示されません。
「許可しない・OK」の選んだ方の状態になっています。

困りました。調べてみるとStackOverFlowにそれに関する質問と回答がありました。
ios7 – Resetting iOS 7 microphone access permission – Stack Overflow
こちらの回答によると、
設定 > 一般 > リセット > 位置情報とプライバシーをリセット
で、「マイクへのアクセス許可」情報もリセットできます。

これで、録音をしようとした際に「マイクへのアクセス許可」ダイアログが表示されるようになりました。

ただ、すべてのアプリのプライバシー設定がリセットされるので、他のアプリも再度アクセス許可を出していく必要があります。

[iOS][Swift]録音する

iOSでAVAudioRecorderを使って、音声を録音する方法を紹介します。

AVAudioRecorder Class Reference

今回は
<図>
に示すサンプルを作成しました。
“Record”ボタンを押すと録音開始(それとともにボタンの表記が”Stop”に変わる)
“Stop”ボタンを押すと録音停止
“Play Recording”ボタンを押すと録音した音声の再生開始(それとともにボタンの表記が”Stop Playing”に変わる)
“Stop Playing”ボタンを押すと再生停止
となります。

録音機能の実装

必要なフレームワークをインポート
import AVFoundation

AVAudioRecorderとAVAudioPlayerを使用するのに必要なAVFoudationフレームワークをインポートします

レコーダーとプレイヤー

レコーダーとプレイヤーを保持するために、プロパティとして設定します
レコーダとしてAVAudioRecorder?型のプロパティを設定

    var audioRecorder: AVAudioRecorder?

オーディオセッションの設定

        /// 録音可能カテゴリに設定する
        let session = AVAudioSession.sharedInstance()
        do {
            try session.setCategory(AVAudioSessionCategoryPlayAndRecord)
        } catch  {
            // エラー処理
            fatalError("カテゴリ設定失敗")
        }

        // sessionのアクティブ化
        do {
            try session.setActive(true)
        } catch {
            // audio session有効化失敗時の処理
            // (ここではエラーとして停止している)
            fatalError("session有効化失敗")
        }

レコーダーの設定

• 録音したファイルの保存先URLを設定します
• 録音時の音質やチャンネル数を設定します
• 準備した保存先URLと録音設定を元にレコーダーを作成します

    func setupAudioRecorder() {

        // 録音用URLを設定
        let dirURL = documentsDirectoryURL()
        let fileName = "recording.caf"
        let recordingsURL = dirURL.URLByAppendingPathComponent(fileName)

        // 録音設定
        let recordSettings: [String: AnyObject] =
        [AVEncoderAudioQualityKey: AVAudioQuality.Min.rawValue,
            AVEncoderBitRateKey: 16,
            AVNumberOfChannelsKey: 2,
            AVSampleRateKey: 44100.0]

        do {
            audioRecorder = try AVAudioRecorder(URL: recordingsURL, settings: recordSettings)
        } catch {
            audioRecorder = nil
        }

    }

    /// DocumentsのURLを取得
    func documentsDirectoryURL() -> NSURL {
        let urls = NSFileManager.defaultManager().URLsForDirectory(NSSearchPathDirectory.DocumentDirectory, inDomains: NSSearchPathDomainMask.UserDomainMask)

        if urls.isEmpty {
            //
            fatalError("URLs for directory are empty.")
        }

        return urls[0]
    }

録音開始・停止

                // 録音開始
                recorder.record()

[Xcode]プロジェクト名を変更する

開発中のプロジェクトrepete_swiftの名前をrepeteに変更したい

Renaming a Project or App
こちらのAppleのドキュメントにしたがってやっていこう

Xcode 7.0.1 + iOS用 + Swiftプロジェクトで実行。

• PROJECTを選択。
ss 2015-10-19 22.46.37

• Utilities(右側ペイン)のIdentity and TypeのNameフィールドで、新たな名前を入力して、Enterキーを押す。
ss 2015-10-19 22.24.56

• ダイアログが出るので、OKを押す。
ss 2015-10-19 22.20.51

これでプロジェクト名が変更される。

やってみたら、変更できた。
ビルドも成功。

以前、プロジェクト名変更しようとして、うまくビルドできなかった記憶があったので、何かトラブルが発生するかと思ったらするっといけた。
こちらの記事(Xcode6 プロジェクト名変更方法 – Qiita)では、Xcode 6での例だけど名前入力だけではうまくいかないとある。

Xcode 7では関連している箇所が漏らさず変更されるようになったのだろうか。