株式会社はてなに入社しました
ことしもこの季節かと思うと、1年が過ぎるのは早いなぁと感じます。
import Sora struct Test { init() { Sora.shared.audioEnabled = false } }
と書くと使えるはずなんですが、Module 'Sora' has no member named 'shared'
とエラーが出てしまいます。これを直すのは簡単で、
こういう感じにSimulatorではなくて
こうする。もしくは、実機に向けてビルドしてあげればよい。
にはちゃんと (Sora iOS SDK はシミュレーターに対応していません)
と書かれているが、見逃していた。アプリを作る場合、最初はSimulatorを使いながらあれこれ実装していくとおもうので、お気をつけください。
与太話です。
数ヶ月前、マネジメントをしながらメイン機能の開発をしていたら完全にタスクがオーバーフローして大変なことになったんですよ。 その時に「両立が難しいのは分かったが、なぜ両立が難しいんだろうか。」をひたすら考えていたら思いついた「たとえ話」です。
素潜り漁師と漁船で例えます。
NSKeyedArchiver
と NSKeyedUnarchiver
を使って、UserDefaultsやKeyChainに色々入れたり出したりすることがあると思います。iOS 11まではこうやって書いていました。
// ArchiveしてUserDefaultにセット let rootObject: [Int] = [1, 2, 3, 4, 5] let archivedData = NSKeyedArchiver.archivedData(withRootObject: rootObject) UserDefaults.standard.set(archivedData, forKey: "key") // UserDefaultからゲットしてUnarchiveする let data = UserDefaults.standard.data(forKey: "key")! let unarchivedObject = NSKeyedUnarchiver.unarchiveObject(with: data) as? [Int] print(unarchivedObject) // => Optional([1, 2, 3, 4, 5])
ここで使っている archivedData(withRootObject:)
と unarchiveObject(with:)
はiOS 12以降のAPIではDeprecatedになっているので、置き換えます。
// ArchiveしてUserDefaultにセット let rootObject: [Int] = [1, 2, 3, 4, 5] let archivedData = try! NSKeyedArchiver.archivedData(withRootObject: rootObject, requiringSecureCoding: false) UserDefaults.standard.set(archivedData, forKey: "key") // UserDefaultからゲットしてUnarchiveする let data = UserDefaults.standard.data(forKey: "key")! let unarchivedObject = try! NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? [Int] print(unarchivedObject) // => Optional([1, 2, 3, 4, 5])
Xcodeからは
'unarchiveObject(with:)' was deprecated in iOS 12.0: Use +unarchivedObjectOfClass:fromData:error: instead
というWarningが出るんですが、素直に unarchivedObjectOfClass(fromData:, error:)
を使うと
let unarchivedObject = try! NSKeyedUnarchiver.unarchivedObject(ofClasses: [NSArray.self], from: data) as? [Int]
というコードを書く必要があり(Array
じゃなくてNSArray
です)難しさが増すので unarchiveTopLevelObjectWithData()
を使うのをおすすめします。
嫁の友人4人が泊りがけで我が家に遊びに来ることに。コミュ障な旦那は家から逃げ出すため、弟を召喚して温泉旅行を計画。せっかくだからと両親も誘って家族旅行にした。
この記事をよんで「山梨ええやん」となったので、
というストイックな家族旅行プランが立案・施行された。
新宿駅から1時間半ほど特急あずさに乗って到着。初めて降りた。
クソデカ武田信玄が迎えてくれる。
ここから両親が乗ってきた車と合流する。ここから解散までは車移動。 自分で運転したかったので、コンビニで1日保険に加入した。
昼飯はほうとうを食べに。ぶっちゃけほうとうってそんなに好きじゃないんだけど、 観光なのでまぁそれっぽいものを食べましょうということで。
鶏のもつ煮も有名らしいので、弟はそちらを注文。「うん、ほうとうですね。」という味でした。
ほうとうを食べた後は主目的の温泉へ。甲府駅から車で1時間半ほど走った山奥にある。
エッサホイサと運転してようやく到着。車から降りると硫黄の匂いに包まれる。
早速温泉に向かう。入湯料は1人1000円。
は〜〜〜〜最高かよ〜〜〜〜〜。もちろん源泉かけ流しなので無限にお湯が生成されており最高。 ヌルヌルするというかもうヌッルンヌッルン。あまりにも最高なので最高だった。 1時間半の山道ドライブの大変さなんてすべて吹っ飛ぶ非常に価値がある温泉。
ちなみに飲むこともできます。
硫黄泉なので「わー……すっごい……」という味。
注意点としては、
という感じ。小さな温泉宿なので、「日帰り入浴のお客さんが多すぎて、宿泊のお客さんが温泉を楽しめない」ということがないように配慮しているんですね。
日帰りでいける温泉がメインの旅行なので宿はアパホテル。朝食付きツインを2部屋とって、父・母と俺・弟で泊まった。値段も1人5000円弱で安く済む。安いのは便利なので便利。 チェックインをして部屋に入ったら、快適に過ごすための環境整備をした。
ホテルのテレビでSwitchのゲームをしたい場合に純正のドックを持っていくとかさばるので、こういうものがあるといい(弟所有品)
scorel ニンテンドースイッチ ミニドック 熱対策/テーブルモード/テレビモード対応 切り替え 充電スタンド 充電器「基盤内蔵」
あとは
エレコム 無線LAN ポータブル ルーター 11ac/a 11n/b/g 433+150Mbps USBケーブル付属 WRH-583GN2-S
Anker SoundCore ポータブル Bluetooth4.0 スピーカー 24時間連続再生可能【デュアルドライバー/ワイヤレススピーカー/内蔵マイク搭載】(ブラック)
などがあれば、アニメや映画を快適に視聴できる環境が完成する。便利な時代になったもんだなぁ。 一通り環境整備が終わったら、ホテル近くの居酒屋で夕食。飲んだり食べたりした。
ホテルに戻ったらコンビニで買ったお酒を飲みながら、ひたすら弟とスマブラSPをしてから寝た。
朝です。朝食付きなので "THE ホテル朝食" を食べます。
ビジネスホテルの朝食っていいよね。特に当日に仕事がない時に食べるホテル朝食が好き。
2日目は山口温泉へ。めっちゃいい雰囲気の体重計がある。
入湯料は600円。かけ流し、加温・加水なしのお湯がアホみたいなにジャブジャブ湧いている。
この源泉はとてもぬるくて、冬だと露天に1度入ると外気が寒くて出られなくなる。 「はー、あったまったわー」という感覚は得られないけれど、温泉効果で湯上がりに服を着るとポカポカと体が暖かい。不思議なものだ。
湯治のための温泉なので、のんびりと1時間〜2時間お湯につかるのがよい。ぬるいお湯なので全然のぼせないので無限に入っていられる。 イメージとしてはスーパー銭湯とかにある "つぼ湯" みたいな感じ。
お風呂上がりは牛乳を。
山口温泉を堪能したら帰路につく。途中のパーキングエリアで昼食。
こういうパーキングエリアの食券買うタイプのラーメンとか定食っていいよね。
八王子駅で解散。おしまい。
「いい温泉に入る」ということにフォーカスしたら、満足度の高い旅行ができた。今回訪れた、奈良田温泉 白根館、山口温泉ともに非常に良いお湯を堪能することができた。両親も温泉に満足してくれていたようで一安心。
他にも行きたい温泉はいくつもあるので、折を見てストイック旅行をしたいと思った。
年末ですね。
ノイズキャンセリング・ヘッドホンが欲しくて買った。SONYと悩んだけどBOSE好きなのでBOSEにした。 ピュアなオーディオが好きな人はSONYで、作られたけど聞きやすい音が好きな人はBOSEが良い。 これはもう味の好き嫌いとか宗派の話になってくるので見た目とかで適当に決めれば良いんじゃないかな。
とにかく静かになる。定時後のオフィスでよく飲み会やゲーム大会が発生するんだけど、そういう場合でも問題なく作業ができる。 通常業務時間でもエアコンの駆動音とかが消えるので、普段地味にうるさかったことに気がつける。とにかく集中できて最高。
Logicool ロジクール MX1600sGR ANYWHERE 2S ワイヤレス モバイルマウス グラファイト FLOW Bluetooth/USB接続 Windows・Mac対応 レーザー採用
10年前くらいからロジクールのAnywhereシリーズを使っていて、ちょっと高いけどこれ以外のマウスは使う予定がない。 Darkfieldレーザーセンサーの力でガラスのテーブルだろうと膝の上だろうと、とにかくどこでも安定して使えるのが良い。 あと昔のモデルから着実に機能がアップしているのが良い。このバージョンだと
という進化が発生していて最強だった。仕事でWindowsとMacを行き来する人とかは使うと便利かと。マウスとしての性能は完璧。
ポータブルスピーカー。値段的にはこっち↓のほうがお手頃なのでこっち使ってる人が多そう。
Anker SoundCore ポータブル Bluetooth4.0 スピーカー 24時間連続再生可能【デュアルドライバー/ワイヤレススピーカー/内蔵マイク搭載】(ブラック)
しっかりとした音が出てよい。そこらへんの下手なスピーカーより良いんじゃないかな。 なおかつこいつ自身がモバイルバッテリーになっていて、iPhoneを充電することができる。 基本的には家で使っていて、キッチンで動画を見るときに使っている。 防水なのでキッチンでも安心だし、バッテリーもかなり持つ。
難点としてはちょっと大きめで重い点ぐらい。
日本製 made in japan 長ザル&ボウルセット(グリーン) KK-302【まとめ買い5個セット】
地味に便利。まな板の向こう側に置けるので、取り回しがめちゃくちゃしやすい。きゅうりやアスパラガスも入れられるのが便利。
コカ・コーラ 綾鷹 茶葉のあまみ ペットボトル 525ml×24本
めっちゃうまい。箱買いした。めっちゃうまい。
Nature Remo mini 家電コントロ-ラ- REMO2W1
Nature Remo出てて気になって居たけど高くて躊躇していたところに、これが発売されたので購入。 弊家は猫を飼っているので、夏の間はこれで室温を監視して、暑くなりすぎていたらエアコンをつけたり消したりしていた。 ずっとつけっぱなしだと寒くなりすぎることもあるので不安だったけど、こいつのおかげで安心して日々を過ごすことができた。
お風呂、キッチンのシンク、コンロがこれ1本ですべて掃除できる。 手荒れもしないし汚れもスイスイ落ちるので最強の洗剤だと思っている。
スコッチブライト キッチンスポンジ ハイブリッドネット グリーン 2個 HBNT-75G 2PM
ちょうどいい固さ、しなり具合なので、すこし力を入れて汚れを落としたいときにすごい安心感がある。もちろん汚れも落ちやすい。 すぐヘタってしまうスポンジもあるけど、こいつはずっといい感じの固さなので便利。
OKR(オーケーアール) シリコンバレー式で大胆な目標を達成する方法
急に意識が高まったので買った。 これによってすべてが解決するわけではないけど、一定のフレームワークがあるっていうのは便利。守破離。
このレシピ通りにファイナルカレーを作ったら、めちゃくちゃ旨かった。スパイスからでカレー作るの楽しい。
美味しい。明太子パスタでも、パンに塗ってトーストでもOK。
中華風の料理が食べたかったら、とにかくこれをゴリゴリやれば良い。キャベツとネギを炒めて鶏ガラスープとこいつをゴリゴリすればOKなので便利。ちょっと辛い。
タバスコを使いたい場面でこれを使う。なお、次の日にアスホーがバーニングする。
それでは良いお年を。
1つのStoryboardに1つのViewControllerという運用をしていると、ViewControllerのインスタンス生成するコードを書くことが多くなると思います。雑に書くと、
let storyboard = UIStoryboard(name: "SomeViewController", bundle: .main) let vc = storyboard.instantiateInitialViewController() as! SomeViewController present(vc, animated: true, completion: nil)
こんな感じでViewControllerを作ってモーダル表示できますね。
さて、これだと生成する度にこのコードを書かないといけないので大変です。なので生成したいViewController側に書いちゃいましょう。
final class SomeViewController: UIViewController { class func viewController() -> SomeViewController { let storyboard = UIStoryboard(name: "SomeViewController", bundle: .main) let viewController = storyboard.instantiateInitialViewController() as! SomeViewController return viewController } } ... // 使う時 let vc = SomeViewController.viewController() present(vc, animated: true, completion: nil)
1回定義してしまえば使う時はさっと使えて便利です。実装も SomeViewController
側にまとめられるので安心。
ただ、この方法だとViewControllerを作る度に class func
を作る必要があってわりと手間です。また、よくある事故として UIStoryboard(name: "SomeViewController", bundle: .main)
のnameを変え忘れ問題があります。めんどくさいので書かなくても済むようにしたいです。
まずは次のような protocol-extension
を書きます。
protocol Instantiatable { static var storyboardName: String { get } } extension Instantiatable where Self: UIViewController { static var storyboardName: String { return "" } private static var _storyboardName: String { if storyboardName.isEmpty { return className } else { return storyboardName } } private static var storyboard: UIStoryboard { return UIStoryboard.init(name: _storyboardName, bundle: nil) } private static var className: String { return String(describing: Self.self) } static func instantiateFromStoryboard() -> Self { guard let vc = storyboard.instantiateInitialViewController() as? Self else { fatalError("Can no instantiate \(Self.className) from \(storyboardName).storyboard") } return vc } static func instantiateFromStoryboard(withIdentifier id: String) -> Self { guard let vc = storyboard.instantiateViewController(withIdentifier: id) as? Self else { fatalError("Can no instantiate \(Self.className) from \(storyboardName).storyboard with id: \(id)") } return vc } }
あとは自作のViewControllerで、
final class SomeViewController: UIViewController, Instantiatable { // ... }
とすれば
let vc = SomeViewController.instantiateFromStoryboard() present(vc, animated: true, completion: nil)
と使うことができます。簡単ですね。もし、Storyboard IDを元にしたいのであれば
let vc = SomeViewController.instantiateFromStoryboard(withIdentifier: "SomeID")
とすればよいです。また、.storyboard
の名前がViewControllerのクラス名と異なっている(今回の例の場合は SomeViewController.storyboard
ではない)場合には
final class SomeViewController: UIViewController, Instantiatable { static let storyboardName = "OtherStoryboardName" //... }
のように storyboardName
を指定すれば良いです。この場合は SomeViewController.storyboard
ではなく OtherStoryboardName.storyboard
を見に行きます。
これで Instantiatable
protocolを実装すれば、追加のコード無しでインスタンスを簡単に生成できるようになりました。便利ですね。