最近イケてると思ったiOSアプリ開発のパターン
UIApplication.shared.delegate as! AppDelegate やめる
static var shared: AppDelegate? { return UIApplication.shared.delegate as? AppDelegate }
コードでAutoLayoutの制約をつける簡易メソッド
func bindFrameToSelf(subview: UIView) { // この中で縦横のAutoLayout制約を宣言 } bindFrameToSelf(subview: hogeView)
アプリで表示する文字列を列挙子にまとめる
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したりする } }
読み込みウィンドウをAppDelegateで保持しておく
private var progressWindow: HogeProgressWindow? private func setupProgressWindow() { // prepare view }
このウィンドウを、APIClientのコネクション数に応じて表示/非表示させれば良さそう
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に引数を渡す
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チェックしてあげればよさそう
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) } }
↑これが参考になった。
素直にRxDataSourcesでも使えばいいのかもしれないけど、 ほげほげ.drive(collectionView.rx.items(...
のやつでViewModelからバインドするにはデリゲートを切る必要があるっぽくて、バインドしつつデリゲートメソッドも生きる方法は今のところこれかなーって感じ。
DataSourceがDelegateを担ってるのアレかもしれないけどどっちもView層だしまあ多少はね?
RxSwiftむずかしい!けどたのしい(^ω^)
最近曲を作る時にしていることとか選んでる音色とか
ドラム
KOMPLETEを手に入れたためUltrabeatsを卒業してBattery 4を使っている
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をレイヤーしまくった方が遥かに楽
ドラムのMIDIトラックでがんばるよりループ音源をレイヤーしまくった方が楽だよという件 最初8小節がドラムだけで後8小節いつも鳴らしてるループ入ってます ループ音源はドラッグ&ドロップだけで使えて時間対効果高いんでやっていくと良いと思ってます(使ったループ音源はこのツイにリプでまとめる) pic.twitter.com/3EjsPB3Cv3
— keisei (@keisei_1092) 2017年12月3日
こんな感じ pic.twitter.com/tjVwc0XFCp
— keisei (@keisei_1092) 2017年12月3日
・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みたいな名前でまとめる。
お好みでループはKickstartやらLFOToolやらでPump気味にしてあげると締まりが出る
ベース
次にベースを探す
Logic上のライブラリから探すなら、左上のツマミマークを押しておくとAlchemyのグリッドが出てくるのでいじりやすくて良い(ショートカットキーはBらしい)
ライブラリ上の音を前後に移動するには [ と ] キーでできる
Alchemyの音源はグリッド左上の音が合わなくても別のグリッドの音が良い感じだったりするので根気強く聴いていく
選んだらMIDIキーボードでリアルタイム打ち込みでがんばる
ピアノロールで全選択してqキーを押すとクオンタイズできる
FunctionsのMIDI Transformからベロシティを均一にしたり音価を揃えたりすることができるので知らない人はググってみて
今回だと Buzz Fuzz Phaser Bass の SOLID GROWL が良さげだったので選んでみた
KickstartなりLFOToolでPump気味にするのを 忘れずに
エレピ
ぼくの定番パターンは「スケールのルートのsus2を弾きながら現在のコードのルートを弾く」
禁則引っかかりまくりだろうけどそのうち学ぶので今は気持ちよければそれでいい
味付けしたければadd9にしたりmaj7にしたりsus2を転回にしたり下をルート+5音にしたりとかやればおk
エレピだけだと味気ないので最近はSupersawをレイヤーしたり木琴をレイヤーしたりクラビネットをレイヤーしたりしている
いつもの作風からドラムとベースとエレピセクションだけ鳴らしてみるとこんな感じ(エレピにスウィングつけるの忘れてて締まりが悪いのはご愛嬌) pic.twitter.com/Raz1jJZmzs
— keisei (@keisei_1092) 2017年12月3日
ウワモノ
さあやっときましたテクノポップの華
Spireのプリセットが非常に良いのでちょくちょく使ってるんですが今回はLogicオンリーで行ってみます
ベースの音選びのときのようにスマートエディットパネルを出しておくと良いでしょう
トータルでの響きを確認したいので、適当な音を選んだら先にフレーズを打ち込んでおきます
その後プリセットを試聴していくとフレーズ単位で試聴が出来て良いです
歯切れをよくするためにリリースは0%にしておくと良い気がしています
最近は1つのモチーフしか打ち込んでないのですが昔は過激派だったので2つや3つの旋律が同時に動いていたときもありました
あとはPanCake2とかオートメーションでパン振ったりいろいろ料理すれば良いです
でしゃばりすぎたらディレイで曇らす
ここまでこんな感じ
ミクノポップでのウワモノのレイヤー例
— keisei (@keisei_1092) 2017年12月3日
最初の8小節ウワモノA→次の8小節ウワモノB→最後の8小節ウワモノAB#ミクノポップをつくろう pic.twitter.com/P5r3cq3G74
パッド
コードを後ろで鳴らすだけ
たまに弦楽器にしたり、たまにクワイアにしたり。
基本的にカットオフちょい下げたSawっぽいので十分
KickstartでPump気味にするの忘れずに
ギター
左右に散らばらせてオケを埋めるのに使う。
鳴ってればなんでもいいのでスケールのルートコードの音とかを適当に。
アコギ(左100%)はこんな感じ。
エレキ(右100%)はこんなん。
空間が埋まれば良いので特に何も考えてない。
KickstartでPump気味にするの忘れずに。あと16分音符を使ったらスウィングをかける。
ここまでできると↓の最後8小節みたいになる。
オケが荒れだしてくるのでリズム変えたり、音変えたりはここらへんでもう一度考える。今回は
・エレピをミュート
・パッドのオクターブ上げる
・ループ系のPumpを深めに変更
・ベースにLogicのSubbassを挿してローを厚めに
#ミクノポップをつくろう アコギ(左100%)とエレキ(右100$)で空間を埋める例。あんまり聴こえないけど意外と大事。8小節ごとに ドラム+ベース→ドラム+ベース+ギター→フルオケ−ギター→フルオケ+ギター。 pic.twitter.com/3SzPxJkE0V
— keisei (@keisei_1092) 2017年12月3日
完成!
#ミクノポップをつくろう バランス調整して完成! pic.twitter.com/JKy8UZdU5f
— keisei (@keisei_1092) 2017年12月3日
モニタリングに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
表示する値パターンを設定
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)