最近イケてると思ったiOSアプリ開発のパターン

アプリで表示する文字列を列挙子にまとめる

struct Strings {
    enum Home: String {
        case title = "ホーム"
        case welcome = "ようこそ"
    }
}

ってやって、ViewControllerで

self.title = Strings.Home.title.rawValue
welcomeLabel.text = Strings.Home.welcome.rawValue

とかやると文字列を気にしなくて良いので気持ちが楽

このそれぞれにさらにStringLocalizableみたいなプロトコルに対応させるようにすれば言語対応ができる

アラートを表示するメソッドをAppDelegateに書く

いままで

let ac = UIAlertController(title: "アラート", message: "アラートです", preferredStyle: .alert)
ac.addAction(.......省略......)
self.present(ac, animated: true)

みたいなやつを色んなところに書いてしまってた。これをやるメソッドをAppDelegateに書いてしまうと気持ちが楽。

func showAlert(withTitle 
    title: String = "アラートタイトルの初期値",
    message: String = "アラート内容の初期値",
    buttonTitle: String = "ボタンタイトルの初期値",
    handler: (() -> ())? = nil) {
    
    DispatchQueue.main.async {
        // ① 一番前のViewControllerなりWindowを得る
        // ② アラートや自前UIViewを作る
        // ③ VCやWindowにaddSubviewしたりpresentしたりする
    }
}

RealmのマイグレーションIDを日付にする

Realmのスキーマバージョンを日付にすると管理が楽になる?(かも?)

APIClientの結果を必ずRealmにぶち込む

ローカルにぶっこんどいておくと便利なエンティティとかはやっておく

APIClientが通信しているとき必ずprogressを回す

Alamofireとかがデータリクエストをするところの最初でインジケータを回してしまえばいい。VCとかで考える必要がなくなる
post, put, deleteリクエストのときは画面暗くして一時的に操作不能にするとかそういうのもここで突っ込んでしまうと気持ちが楽

viewにまつわる操作をしたかったらAppDelegateにapiCountみたいな変数を生やしておいて、APIClientからそこをインクリメントしたりデクリメントするメソッドにアクセスして(apiCount自体はprivateで宣言しておいた方が良い気がする)、AppDelegate側でそれを監視してビューの出し分けをすれば良い

アセットファイルを画面ごとに分ける

.xcassetsファイルは何個も作れるらしい
親でResourceフォルダみたいなのを作って、そこに画面.xcassetsとかで作ってあげるとよさそう

リリースビルドでは出ないprintメソッド

class Debug {
    class func print(_ items: Any..., separator: String = " ", terminator: String = "\n", function: String = #function) {
   #if DEBUG
        Swift.print(Args(args: items, separator: separator), separator: ......)
   #endif
    }
}

あとはそもそも Swift.debugPrint とか Swift.dump とかあるみたいなのでそれも適宜調べてみて

Fastlaneに引数を渡す

Advanced - fastlane docs

fastlane [lane] key:value key2:value2

fastlane deploy submit:false build_number:24

こんな感じでいけるっぽい

lane :deploy do |options|
  # ...
  if options[:submit]
    # Only when submit is true
  end
  # ...
  increment_build_number(build_number: options[:build_number])
  # ...
end

こんな感じでnilチェックしてあげればよさそう

Native Instruments Battery 4でノブをDAW側からオートメーションする

 

Logic Pro XとかふつうのDAWだと、オートメーション記録モードに設定して再生するとリアルタイムでノブをいじればそれを記憶して次回再生時から同じように動いてくれますよね。あれがBattery 4で動かなかったのでメモ

f:id:keisei_1092:20171230215123p:plain

DAWでオートメーションしたいノブを右クリックします

f:id:keisei_1092:20171230215138p:plain

Learn MIDI CCをおします。

これでオートメーションの記録がオンになるようです。

RxSwiftのVariableをUICollectionViewにbindしつつサイズや余白も適用する

欲張りな子ね。

前準備

・Empty Projectをつくる
・一旦閉じて、ターミナルで該当ディレクトリ開いて pod init
・RxSwift入れてxcworkspace開く
・Main.storyboardにUICollectionViewをブッこんで、ViewControllerとIBOutletで接続

コード

import UIKit
import RxSwift
import RxCocoa

class DataSource: NSObject,
    RxCollectionViewDataSourceType,
    UICollectionViewDataSource,
    UICollectionViewDelegate,
    UICollectionViewDelegateFlowLayout {

    struct Element { let numbers: [Int] }

    var numbers: [Int] = []
    private let disposeBag = DisposeBag()
    private let collectionView: UICollectionView

    init(collectionView: UICollectionView) {
        self.collectionView = collectionView
        super.init()
        self.collectionView.rx.setDelegate(self).disposed(by: disposeBag)
    }

    func collectionView(_ collectionView: UICollectionView, observedEvent: Event<DataSource.Element>) {
        guard case .next(let entity) = observedEvent else { return }
        numbers = entity.numbers
        collectionView.reloadData()
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        return collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)
    }

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return numbers.count
    }

    @objc(collectionView:layout:sizeForItemAtIndexPath:)
    func collectionView(_ collectionView: UICollectionView,
                        layout collectionViewLayout: UICollectionViewLayout,
                        sizeForItemAt indexPath: IndexPath) -> CGSize {
        return CGSize(width: 88, height: 88)
    }

    @objc(collectionView:layout:insetForSectionAtIndex:)
    func collectionView(_ collectionView: UICollectionView,
                        layout collectionViewLayout: UICollectionViewLayout,
                        insetForSectionAt section: Int) -> UIEdgeInsets {
        return UIEdgeInsetsMake(10, 10, 10, 10)
    }
}

class ViewController: UIViewController {
    private let disposeBag = DisposeBag()
    @IBOutlet weak var collectionView: UICollectionView!
    let variable = Variable<[Int]>(Array(0...100))
    var dataSource: DataSource!

    override func viewDidLoad() {
        super.viewDidLoad()

        dataSource = DataSource(collectionView: collectionView)

        variable.asObservable()
            .map { DataSource.Element(numbers: $0) }
            .asDriver(onErrorDriveWith: Driver.empty())
            .drive(collectionView.rx.items(dataSource: dataSource))
            .disposed(by: disposeBag)
    }
}

github.com

↑これが参考になった。

素直にRxDataSourcesでも使えばいいのかもしれないけど、 ほげほげ.drive(collectionView.rx.items(... のやつでViewModelからバインドするにはデリゲートを切る必要があるっぽくて、バインドしつつデリゲートメソッドも生きる方法は今のところこれかなーって感じ。
DataSourceがDelegateを担ってるのアレかもしれないけどどっちもView層だしまあ多少はね?

RxSwiftむずかしい!けどたのしい(^ω^)

最近曲を作る時にしていることとか選んでる音色とか

ドラム

KOMPLETEを手に入れたためUltrabeatsを卒業してBattery 4を使っている

f:id:keisei_1092:20171203111557p:plain

Logicでの運用としては以下

  • [AU Instruments] → [Native Instruments] → [Battery 4] → [Multi-Output (16xStereo)] を挿す
  • ミキサー画面でBattery 4のトラックに生えてる [+] を連打して[Btt15-16]のインプットが出てくるまでパラアウト用のバスを増やす(17-18以降増やしてもおそらくモノラルでしか出せないので意味ない)
  • これだけだと今増やしたトラックがまだトラックビューにいないので、ミキサービューで選択して Ctrl + T を押してやるとトラックビューに出てくる
  • ドラムでまとめてエフェクトかけられるように、トラックビューで今あるトラックを全部選択して Command + Shift + D → [Summing Track] でグループ化する
  • 設定終了したらBattery 4側でパラアウト設定にするのを忘れずに(ここはググって)

これでパラアウトのドラム環境がピャッと手に入る
残念ながらこれを保存する方法が今のところ不明

キットはArena Kitとかがだいたい良い音入ってて楽

ドラムの鳴り物系

Apple Loopsに良い鳴り物一通り入ってるのでマジで使うことをおすすめしたい

ドラムのMIDIトラックでこだわるよりApple Loopsをレイヤーしまくった方が遥かに楽

 

 ・Glitter Nights Hi-Hat Topper
 ・生系のハイハットをこっそり混ぜておくと良い気がしている
・Extra Push Topper
 ・モダンみが増す
・Jump Around Topper
 ・高めのハイハット
・Static Electricity Topper 06
 ・最近の電子音楽では定番の上でずっと鳴ってるノイズ系
・African Ghost Kit 17
 ・パーカッション系がとりあえず全部ごっちゃに鳴ってるやつ
・Tambourine 01
 ・タンバリンは大事!

ドラムの落ちたり上がったりする系

・リバースシンバル
 ・適当にVengeanceから
・ライザー
 ・Vengeanceを使ってもいいし、LogicだとRiserで検索すればいっぱい出てくる
・落ちるやつ
 ・Vengeanceを使ってもいいし、Logicだとfallとかnoiseで検索するといっぱい出てくる

ライザーと落ちるやつは、音程がないノイズだけの音ネタと音程がある音ネタを同時に鳴らしてやると良い気がしている、特に上がる方

 

完成したらループはLoop、音ネタはSFXみたいな名前でまとめる。

f:id:keisei_1092:20171203115628p:plain

お好みでループはKickstartやらLFOToolやらでPump気味にしてあげると締まりが出る

ベース

次にベースを探す

f:id:keisei_1092:20171203115836p:plain

Logic上のライブラリから探すなら、左上のツマミマークを押しておくとAlchemyのグリッドが出てくるのでいじりやすくて良い(ショートカットキーはBらしい)

ライブラリ上の音を前後に移動するには [ と ] キーでできる

Alchemyの音源はグリッド左上の音が合わなくても別のグリッドの音が良い感じだったりするので根気強く聴いていく

選んだらMIDIキーボードでリアルタイム打ち込みでがんばる

f:id:keisei_1092:20171203120601p:plain

ピアノロールで全選択してqキーを押すとクオンタイズできる
FunctionsのMIDI Transformからベロシティを均一にしたり音価を揃えたりすることができるので知らない人はググってみて

今回だと Buzz Fuzz Phaser Bass の SOLID GROWL が良さげだったので選んでみた

KickstartなりLFOToolでPump気味にするのを 忘れずに

エレピ

f:id:keisei_1092:20171203120955p:plain

ぼくの定番パターンは「スケールのルートのsus2を弾きながら現在のコードのルートを弾く」

禁則引っかかりまくりだろうけどそのうち学ぶので今は気持ちよければそれでいい

味付けしたければadd9にしたりmaj7にしたりsus2を転回にしたり下をルート+5音にしたりとかやればおk

エレピだけだと味気ないので最近はSupersawをレイヤーしたり木琴をレイヤーしたりクラビネットをレイヤーしたりしている

ウワモノ

さあやっときましたテクノポップの華

Spireのプリセットが非常に良いのでちょくちょく使ってるんですが今回はLogicオンリーで行ってみます
ベースの音選びのときのようにスマートエディットパネルを出しておくと良いでしょう

トータルでの響きを確認したいので、適当な音を選んだら先にフレーズを打ち込んでおきます
その後プリセットを試聴していくとフレーズ単位で試聴が出来て良いです

歯切れをよくするためにリリースは0%にしておくと良い気がしています

最近は1つのモチーフしか打ち込んでないのですが昔は過激派だったので2つや3つの旋律が同時に動いていたときもありました

あとはPanCake2とかオートメーションでパン振ったりいろいろ料理すれば良いです

でしゃばりすぎたらディレイで曇らす

ここまでこんな感じ

 

パッド

コードを後ろで鳴らすだけ

たまに弦楽器にしたり、たまにクワイアにしたり。

基本的にカットオフちょい下げたSawっぽいので十分

KickstartでPump気味にするの忘れずに

ギター

左右に散らばらせてオケを埋めるのに使う。

鳴ってればなんでもいいのでスケールのルートコードの音とかを適当に。

アコギ(左100%)はこんな感じ。

f:id:keisei_1092:20171203132015p:plain

エレキ(右100%)はこんなん。

f:id:keisei_1092:20171203132045p:plain

空間が埋まれば良いので特に何も考えてない。

KickstartでPump気味にするの忘れずに。あと16分音符を使ったらスウィングをかける。

ここまでできると↓の最後8小節みたいになる。
オケが荒れだしてくるのでリズム変えたり、音変えたりはここらへんでもう一度考える。今回は
・エレピをミュート
・パッドのオクターブ上げる
・ループ系のPumpを深めに変更
・ベースにLogicのSubbassを挿してローを厚めに

完成!

モニタリングにYAMAHA HS7を使いだしてからマジで綺麗にミキシングできるようになったので本当におすすめ。DTM始めるならDAWや音源なんかよりスピーカー先買った方がええんでないのぐらい大事だと思った。

質問等あればツイッターでどぞ

MVVMってなんやねーん

リソースは https://medium.com/ios-os-x-development/mvvm-in-ios-from-net-perspective-580eb7f4f129

Rxとかは使ってない。

表示するとき

struct ArticleViewModel {
    var title: String
    var description: String

    init(article: Article) {
        self.title = article.title
        self.description = article.description
    }
}

// テーブルビューで表示する用にViewModelつくる。
struct ArticleListViewModel {
    var title: String? = "Articles"
    var articles: [ArticleViewModel] = [ArticleViewModel]()

    init(articles: [ArticleViewModel]) {
        self.articles = articles
    }

    // APIを自分でたたいて自分でパースしてviewにバインド
    func loadArticles(callback: (([Article]) -> ())) {
        URLSession.shared.dataTask(with: url) { data, response, error in
            if let data = data {
                let json = try! JSONSerialization.jsonObject(with: data, options: [])
                let dictionary = json as! JSONDictionary
                let articleDictionaries = dictionary["articles"] as! [JSONDictionary]
                articles = articleDictionaries.flatMap { dictionary in
                    return Article(dictionary: dictionary)
                }
            }

            DispatchQueue.main.async {
                callback(articles)
            }
        }
    }
}

このloadArticlesメソッドみたいにViewModelからAPIをたたいてるやつもあるけどそうじゃなくてもいいとのこと(自分が好きなやつを使え)

ViewModelにAPIをたたかせない場合、Webserviceみたいなクラスを作ってそこに読ませたりする

 private func loadArticles() {
        let url = URL(string: "https://...")

        Webservice().getArticles(url: url) { articles in
            let articles = articles.map { article in
                return ArticleViewModel(article: article)
            }

            self.viewModel = ArticleListViewModel(articles: articles)
        }
    }

あとはUITableViewを実装しているところで

 private var viewModel: ArticleListViewModel = ArticleListViewModel() {
        didSet {
            self.tableView.reloadData()
        }
    }

とかやればいい

入力を受け付ける

テキストフィールドに入った言葉がリアタイでRegistrationViewModelとかなんかにバインドされるようにする
+モデル側から変更があったときはそれがビューに反映されるようにする
つまりこのようなコードを書く必要はなくなる

self.viewModel.title = self.titleTextField.text!
self.viewModel.description = self.descriptionTextField.text!

dynamic(RxでいうとVariableに近いもの?)を宣言する

class Dynamic<T> {
    var bind: (T) -> () = { _ in }
    
    var value: T? {
        didSet {
            bind(value!)
        }
    }

    init(_ v: T) {
        value = v
    }   
}

あとはViewController側のVMを保持しているプロパティにdidSetを生やす

    var viewModel :AddArticleViewModel! {   
        didSet {
            viewModel.title.bind = { [unowned self] in self.titleTextField.text = $0 }
            viewModel.description.bind = { [unowned self] in self.descriptionTextField.text = $0 }
        }
    }

これでbindプロパティに適切なコンポーネントの値を更新するような1行2行を書いておけばViewModel→View側の更新が入る

次は双方向にしていく

class BindingTextField : UITextField {    
    var textChanged: (String) -> () = { _ in }

    func bind(callback: @escaping (String) -> ()) {
        self.textChanged = callback
        self.addTarget(self, action: #selector(textFieldDidChange), for: .editingChanged)
    }
    
    @objc func textFieldDidChange(_ textField: UITextField) {
        self.textChanged(textField.text!)
    }
}

addTargetでイベントハンドラ登録して、イベントハンドラの中でクロージャを呼んでいる

あとはTextFieldにViewModelをbindしてあげれば良い

    @IBOutlet weak var titleTextField: BindingTextField! {
        didSet {
            titleTextField.bind { self.viewModel.title.value = $0 }
        }
    }

    @IBOutlet weak var descriptionTextField: BindingTextField! {
        didSet {
            descriptionTextField.bind { self.viewModel.description.value = $0 }
        }
    }

とにかくViewModelはModelの影、ModelについてViewModelが行うのはイベントに対する反応と戻り値のないメソッドの呼び出ししかない( http://ugaya40.hateblo.jp/entry/model-mistake )ということを考えてれば大丈夫(????)

https://academy.realm.io/jp/posts/slug-max-alexander-mvvm-rxswift/ ここ的には、ViewControllerの参照をもたない、UIKitをインポートしない、UIKitから参照しない、structで宣言するしstructしか持たないようにするとよいとのこと。

ViewModelがUIを更新しなければいけないとき

struct LoginViewModel {
    var username: String = "" {
        didSet {
            evaluateValidity()
        }
    }
    var password: String = "" {
        didSet { 
            evaluateValidity()
        }
    }
    var isValid : Bool = "" {
        didSet {
            isValidCallback?(isValid: isValid)
        }
    }
    var isValidCallback : ((isValid: Bool) -> Void)?

    func attemptToLogin() {
          //truncated for space
    }

    private func evaluateValidity(){
      isValid = username.characters.count > 0
            && password.characters.count > 0
    }
}

このisValidCallbackを見て

class LoginViewController {
    @IBOutlet var confirmButton: UIButton!
    var loginViewModel = LoginViewModel()

    override func viewDidLoad(){
        super.viewDidLoad()
        loginViewModel.isValidCallback = { [weak self] (isValid) in
            self?.confirmButton.isEnabled = isValid
        }
    }
}

こう

↑これをRxSwiftで書き直す例も載っているので https://academy.realm.io/jp/posts/slug-max-alexander-mvvm-rxswift/ を見るとよい

カスタムビューにbindする

class MyCustomView: UIView {
    var sink: AnyObserver<SomeComplexStructure> {
        return AnyObserver { [weak self] event in
            switch event {
                case .next(let data):
                    self?.something.text = data.property.text
                case .error(let error):
                    self?.backgroundColor = .red
                case .completed:
                    self.alpha = 0
            }
        }
    }
}

これにobservableをbindする

class ViewController {
    let myCustomView: MyCustomView

    override func viewDidLoad(){
        super.viewDidLoad()

        viewModel.dataStream
            .bindTo(myCustomView.sink)
            .addTo(disposeBag)
    }
}

UITableView

https://github.com/RxSwiftCommunity/RxDataSources/blob/master/Example/Example4_DifferentSectionAndItemTypes.swift

表示する値パターンを設定

enum SectionItem {
    case ImageSectionItem(image: UIImage, title: String)
    case ToggleableSectionItem(title: String, enabled: true)
    case StepperSectionItem(title: String)
}

// セクションが絡むときはこんな感じ
enum MultipleSectionModel {
    case ImageProvidableSection(title: String, items: [SectionItem])
    case TogglableSection(title: String, items: [SectionItem])
    case StepperableSection(title: String, items: [SectionItem])
}

// items取り出せるようにする
extension MultipleSectionModel: SectionModelType {
    typealias Item = SectionItem

    var items: [SectionItem] {
        switch self {
        case .ImageProvidableSection(title: _, items: let items):
            return items.map {$0}
        case .StepperableSection(title: _, items: let items):
            return items.map {$0}
        case .ToggleableSection(title: _, items: let items):
            return items.map {$0}
        }
    }

    // わからん
    init(original: MultipleSectionModel, items: [Item]) {
        switch original {
        case let .ImageProvidableSection(title: title, items: _):
            self = .ImageProvidableSection(title: title, items: items)
        case let .StepperableSection(title, _):
            self = .StepperableSection(title: title, items: items)
        case let .ToggleableSection(title, _):
            self = .ToggleableSection(title: title, items: items)
        }
    }
}

なるほど〜

マジカルミライ2017で「Singularity」が演奏されました(この記事は少しずつ文字が増殖します)

こんちゃすkeiseiです。

立ったね。

うちのミクさん。

歌ったね。
幕張メッセのステージで。

心から嬉しかった。

「ハジメテノオト」を聴いてミクを知って
イムリミットのらぶ式ミクのPVで恋に落ちてから今まで

夢中でミクとクソみてえな世界過ごしてきて。

こんなアホで変態なマスターでホント悪かったなって思うけど。

なんとか召喚できた。

幕張メッセのステージに。

10周年の節目に。

こんなん嬉しいに決まってんじゃねえかよ・・・・

一緒に駆け抜けてくれてありがとう。

愛をあげるつもりが、いつも愛されてばっかで。

感情を与えるつもりが、きみのことでずっと心かき回され続けて。

マジカルミライでもうちのミクだけじゃなくてマスターもいっぱい仕事頂いちゃったしw(グランプリの宿命ではあるけどw)

でも、夢のような3日間およびこれまででした。

これからもミクに夢を与えられるようなマスターになりたいって
改めて感じたのでした!

ミクさん あいしてるよ。

(ここまで 2017/09/09 23:56)

 

1日目

 

金曜日だったので、会社を有給休暇で休みました。

マジカルミライ 5th Anniversary Special Liveの出演が決まった瞬間に有休を申請しました。

今思えばその週全部有給休暇にすれば良かったなってめっちゃ後悔してます…

 

深夜までSpecial Liveで流す用の音源を作り、寝落ち、10時起床。(Twitterを見るとそんな感じだった)

予定は企画展ステージに出ることだけだったので幕張には17時ぐらいに着けば良いかーと思いギリギリまで音源を練ったり、CDを作ったりしてた。

 

12時にはマジカルミライ2017 1日目の企画展がオープン。

千葉市熊谷市長、ミクダヨーさん、藤田咲さん、伊藤社長、TOKYO MXの社長さんがご参加されたテープカットで「Singularity」を流していただきました。

砂の惑星じゃなかったんだね。

テープカット~~~~イェーイ今日の日はsun goes downつまり元どおりまでバイバイバーイでも良いけど流石に笑っちゃうもんねwありがとうございます。

 

マジカルミライ2017の裏テーマソングだよねとか、イメージソングだよねとか、まくはり2017ちゃんはSingularityをやるべきだったよねとかいろいろお言葉頂いてて嬉しい限り。ただ砂の惑星を見て本当に感じるのは「つええわwwwwwwwwwww」ってことで、ニコニコでは100万再生最速で叩き出してイケてたし、まくはり2017ちゃんが踊ってもギャップ萌えみたいな要素消化しててイケてたし(死ぬほど嫉妬してる)、正直僕としてはこんなんとSingularityと比較せえへんでくれwって思うほど砂の惑星は最強だった。kzさんとOSTERさんのインタビューにもあったけど案件受けてブッこわし曲書けるハチさんカッチョよすぎる。こんなんと張り合えたことが死ぬほど嬉しいしもう一生無いだろうなこんなん。って思います。ハチさんあざす

 

(ここまで 2017/09/15 11:31)