2018年1月31日水曜日

メモ:音の同時再生

情報源:【iOS】音楽を止めずに効果音を同時に再生するには

まだちゃんと試してないけど、情報見つけたのでメモ。
AppDelegateに以下のコードを書いておくと同時再生できるんだとか。

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    // Override point for customization after application launch.
    do {
        try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryAmbient)
    } catch {
        print("エラー")
    }
    
    return true

}

AVAudioPlayerで効果音を連続再生

やりたいこと

ボタンを押すたびにピコーンというSEを鳴らすのだが、高速で連続してボタンを押した場合に音が鳴らないことがある。

解決法

音を重ねて鳴らす場合はAVAudioPlayerをしまうプロパティを配列に入れ、再生が終わるまで保持するなどすればいい。(終わったのを消すのがSwiftではちょっと面倒になったけど)

重ねて鳴らさなくていいならば、前の音が再生中なら音を止め、再生ポイント(currentTime)を先頭に戻してやればいい。

具体的には以下のように。

if correctSound.isPlaying {
      correctSound.stop()
}
correctSound.currentTime = 0
correctSound.play()

2018年1月30日火曜日

AVAudioPlayerで初回再生遅延

問題

ゲームで使う短いサウンドをAVAudioPlayerで鳴らすのだが、初回だけ一瞬再生が遅れてしまう。2回目以降の再生では遅延は起きない。

soundPlayer?.prepareToPlay()
をあらかじめ処理しておいてもダメ。

volume = 0
であらかじめ1度再生させ、直後にvolumeを元に戻してみたら、なぜか音が鳴ってしまう。

解決法

現在不明。

2018年1月14日日曜日

任意の位置の文字を取り出す

やりたいこと

たとえば"abcdefghijklmn"という文字列の中から、任意の位置の文字(文字列)を取り出したい。

やり方

let str = "abcdefghijklmn"
let start = str.index(str.startIndex, offsetBy: 3) //①
let end = str.index(start, offsetBy: 2) //②
let subS = str[start..<end] //③
let s = String(subS) //④

昔のBASICだとRight$、Left$、Mid$とかの関数でちょいちょいできたんだけど、Swiftだとちょっと面倒。
文字を取り出す開始位置、終了位置を決め、それをSwiftのRange(範囲指定)の書き方で指定してやる必要がある。

例として4文字目から2文字「de」を取り出してみる。

①が開始位置、②が終了位置を設定するもの。

①が開始位置の指定。
offsetBy:は何文字目かということ。0オリジンのインデックスなので、4文字目なら3。

②が終了位置の指定。
直前で得た開始位置startを使い、そこから何文字目かという書き方をしている。取り出すのが2文字なのでoffsetBy:に2。
逆に終了位置を元の文字列strの先頭から数えて指定する場合は以下でもいい。
let end = str.index(str.startIndex, offsetBy: 5)
これによってともに「f」の文字の位置がendに入る。

それを
③str[start..<end]
と範囲指定をして実際に取り出す。
新しいfor構文の for i in 0...10 と同じようなやり方だ。[ ] で囲むのがなんか配列っぽい。

終了位置を1つ前にする必要あるけど、当然
str[start...end]
という書き方もできる。

注意点

④ただ使うだけならこれだけでいいんだけど、取り出した文字列はSubStringというものになってて元の文字列を参照しているだけなので、元の文字列(ここではstr)が消えると消えてしまうのだそうだ。
だから独立した文字列にするためString( )で囲んでやってる。

Swift4より前では別の書き方をしていたそうだが、それは知らんのでここでは触れない。

もっと簡単にしてよ

開始/終了位置のindexを作り…ってのはわかるけど、SubStringがどーのってのも含め、もっと簡単にして欲しいよね。
それこそBASICのmid$に相当する関数を自作しておいた方がいい。

なお、先頭から/末尾から3文字取り出すっていうのは
let pre = str.prefix(3)
let suf = str.suffix(3)
のように超簡単。SubStringになってるのかどうかは知らんが。

2018年1月12日金曜日

Anyな変数を使い回す

やりたいこと

ゲームとかのステージごとに、その中身を記述したクラスを独立させたいので、それを管理する変数をAny型で作っておき、それを使い回すようにしたい。

たとえば各ステージの内容をZigzagGame.swiftというステージと、NumberGame.swiftというステージを用意した場合、Storyboardで別のViewControllerにせず、一つのViewController上で切り替えて表示したい。

やり方

ステージを管理する変数をインスタンス変数として作っておく。
optionalにしてnilにできるようにするのが肝。

    var gameStage:Any? = ()
もしくは
    var gameStage:AnyObject?

また、ステージを判断できる変数も。
    var stage = 0


その後、ステージを切り替えて使う際には、前のステージで作った部品を消して、gameStage変数にnilを入れて初期化し、新たなステージのクラスで使う。
nilを入れて初期化しないと、変数の型が前のクラスになってるからエラーになっちゃうからね。
Any(要するにどんな型でも受け付ける)型なので、使う際にas! 〜 として型を明確にしてやらないといけないのがちょっと面倒だけど。

    //ステージ切り替え例
    @IBAction func nextGame(_ sender: Any) {
        if stage == 0 {
            //前のステージクリア
            (gameStage as! ZigzagGame).eyePointer.removeFromSuperview()
            (gameStage as! ZigzagGame).shapeLayer.removeFromSuperlayer()
            gameStage = nil
            //新しいステージ
            gameStage = NumberGame(baseView: self.baseView)
            (gameStage as! NumberGame).makeButtons()
            stage = 1
        } else {
            //前のステージクリア
            //数字のボタンを消す
            for i in 0..<20 {
                (gameStage as! NumberGame).buttonCollection[i].removeFromSuperview()
            }
            gameStage = nil
            //新しいステージ
            gameStage = ZigzagGame(baseView: self.baseView)
            (gameStage as! ZigzagGame).apexCount = 30
            (gameStage as! ZigzagGame).animeDuration = 10
            (gameStage as! ZigzagGame).movePath()
            stage = 0
        }
    }

Any、AnyObject、AnyClass

いろんな型を入れられる変数型が複数ある。
  • Any
    • 整数、関数、構造体などのすべての型を扱える
    • var 宣言時は = () として初期化できる(何が入るかは知らんが)
  • AnyObject
    • クラスのインスタンスのみ扱える
    • var 宣言時は = () として初期化できないようだ
    • 荻原剛志さんの「詳解Swift」では‪Objective-C‬との情報交換で利用するって書いてあるけど、別に他のことに使ってもいいよね。
  • AnyClass
    • AnyObject.type として、「すべてのクラス型を暗黙に適合するためのプロトコル」って解説があるけど、よくわかんねぇっす。
    • ひょっとして変数型じゃないのかな? 関数の引数の型宣言として使って、クラス名を得るのに使うっぽい感じも? まあいいや

これでいいのか知らんが

知識がないので、ゲームステージの切り替えをこんなやり方でやっていいのか知らんけどね。もしかしたら笑われてしまうやり方かもしれませんよ。

Layerに描いた図形を全部消す

やりたいこと

UIViewのLayerに描いたドロー図形を全部クリアしたい。

やり方

Layerをインスタンス変数として持っておき、それをremoveFromSuperLayer()してやればいい。


let shapeLayer = CAShapeLayer()
    

func drawFigure() -> Void {
    let path = UIBezierPath()
    
    path.move(to: CGPoint(x: 384, y: 512))
    path.addLine(to: CGPoint(x: 600, y: 600))
    path.addLine(to: CGPoint(x: 600, y: 200))
    
    shapeLayer.strokeColor = UIColor.red.cgColor
    shapeLayer.fillColor = UIColor.blue.cgColor
    shapeLayer.path = path.cgPath
    self.view.layer.addSublayer(shapeLayer)
}
    
@IBAction func deleteFigure(_ sender: Any) {
    self.shapeLayer.removeFromSuperlayer()

}

注意点

インスタンス変数として持っておくところが肝。単に
self.view.layer.removeFromSuperlayer()
とやると関係ないLayerがなくなっちゃうから画面が真っ黒になっちゃう。
viewのどのLayerを削除するのか指示するために必要なのだな。

もしかしたら変数として持ってなくても消せるやり方あるのか知らんけどさ。