Fetch APIを使ってRailsのAPIを叩く
JavaScript(es2015)でFetch APIを使ってRailsのAPIを呼ぶときの方法。
ポイントとしては、'credentials: same-origin'をつけることと、 CSRFを有効にしている場合はheaderにX-CSRF-Tokenをつける点です。
const getCsrfToken = () => { const metas = document.getElementsByTagName('meta'); for (let meta of metas) { if (meta.getAttribute('name') === 'csrf-token') { console.log(meta.getAttribute('content')); return meta.getAttribute('content'); } } return ''; } const postFoo = () => { return fetch('/api/foo', { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': getCsrfToken() }, body: JSON.stringify({ bar: "bar", }) }).then((response) => { if (response.status >= 200 && response.status < 300) { return response.json(); } else { const error = new Error(response.statusText); throw error; } }); }
iOSのFrameworkのVersionについて
Frameworkにはversionがいくつかあってそれらの違いの雑な説明です。
その1
ひとつはお馴染みのinfo.plistに書くやつ これはちょっと省きます
その2
Umbrella Headerに書いてあるやつ
FOUNDATION_EXPORT double FwVersionNumber; FOUNDATION_EXPORT const unsigned char FwVersionString[];
Build SettingsのCurrent Project Versionがこれにあたります
Frameworkをコンパイルするとわかるんですが、 (ここでは例としてFwというframeworkをつくります)
Compile Fw_vers.c
というcのファイルが勝手に生成されてコンパイルされてます。 中身を見てみると
extern const unsigned char FwVersionString[]; extern const double FwVersionNumber; const unsigned char FwVersionString[] __attribute__ ((used)) = "@(#)PROGRAM:Fw PROJECT:Fw-10" "\n"; const double FwVersionNumber __attribute__ ((used)) = (double)10.;
とこんな感じでxxVersionNumberとxxVersionStringが定義されてます。
Umbrella Headerではこれらをexternしているので、Frameworkを利用しているプログラムはこれらの変数を使うことができます。
その3
Framework VersionのAとかはFrameworkの中身のフォルダ構成を見ての通りでCurrentさしているディレクトリになります
その4
current library versionとcompatibility versionについてですが、
Link時のビルドログをみるとわかるようにこれらの値はclangのオプションとして渡されて使われる感じになります。
clang ... -compatibility_version 1 -current_version 1
詳しくはこの辺を読むと良いのではないでしょうか
現場からは以上です。
AutolayoutでレイアウトしたViewを外して元に戻す
InterfaceBuilderやStoryboardでAutolayoutを使って配置したViewをremoveFromSuperviewしてから、 再度addSubviewしたい!ということがたまにあります。
普通にremoveFromSuperviewしてaddSubviewすると元には戻りません。 なぜならremoveFromSuperviewした時点で親と消された子のViewのAutolayoutの制約は消えてしまうからです。
どうすればよいかというremoveFromSuperviewする前に制約をどこかに保存しておき、 addSubview後にそれらの制約を追加すればよいです。
例
view以下にbuttonが配置されていたのを取ったりつけたりする場合です。
private var buttonConstraints = [NSLayoutConstraint]() ... // removeFromSuperviewするとき view.constraints.forEach { if $0.firstItem is UIButton || $0.secondItem is UIButton { self.buttonConstraints.append($0) } } button.removeFromSuperview() ... // addSubviewするとき view.addSubview(button) buttonConstraints.forEach { view.addConstraint($0) }
独自のNotification名を追加
独自のNotification名を追加する場合はこんな書き方がよいような気がします。
extension Notification.Name { struct AppName { public static let DidLogin = Notification.Name(rawValue: "com.example.appname.didLogin") } }
通知名の文字列には通知名の衝突を避けるために独自のprefixをつけています。
com.example.appname
のところはBundle.main.bundleIdentifierでbundle identifierをとってきて
使ったりするのがよいかもしれません。
CircleCIでipaファイルを作ろうとするとExport Failedする
Visual Studio Codeをemacs keybindingにする
Visual Studio Codeを使うときにEmacs Keybindingにする方法です。
Emacs Keymap(Emacs Keymap - Visual Studio Marketplace)というのが存在するので、これを使ってみます。
Shift + Command + X
を押して拡張機能を開きます- 検索窓で
emacs
といれるとEmacs Keymap
というのがでてきますので、インストールを押せばOKです。
Macで出たエラー
MacでC-x C-f
でファイルを開こうとすると以下のようなエラーがでました
command 'workbench.action.files.openFolder' not found
↓を見た感じ、Macだと'openFileFolder'じゃなきゃアカン的なことが書いてありました・・
bind command(workbench.action.files.openFile) failed · Issue #5437 · Microsoft/vscode · GitHub
メニューから Code->基本設定->キーボード ショートカット を選択し、 以下のようにキーバインドを上書きするとなおりました。
// 既定値を上書きするには、このファイル内にキー バインドを挿入します [ { "key": "ctrl+x ctrl+f", "command": "workbench.action.files.openFileFolder" } ]
SwiftでKVOするときはObjective-Cのプロパティ名を使う
当たり前といえば当たり前なんですが、 iOSのKVO(addObserver(_:forKeyPath:options:context:))はObjective-CのNSObjectのメソッドなので、指定するkeyPathはObjective-Cのプロパティ名じゃないとだめです。
例えばUIViewのisHiddenをKVOしたい場合は↓のように、isHidden
ではなくhidden
になります。
// Swift3以前 view.addObserver(self, forKeyPath: "hidden", options: [], context: nil)
// Swift3以降 view.addObserver(self, forKeyPath: #keyPath(UISwitch.hidden), options: [], context: nil)
iOSのフレームワークのクラスに対してKVOを使う際は、補完に頼るとミスることがあるので、Objective-Cの仕様も確認しましょう (#keyPathがもうちょい賢ければ・・)
addObserver(_:forKeyPath:options:context:) - NSObject | Apple Developer Documentation
TwitterKitを使ってログインするときの注意点
TwitterKitを使ってログインするときの注意点を2点
1. Twitterアカウントを登録しているかで挙動が変わる
TwitterKitを使ってTwitterログインするときに、TwitterのアカウントをiOSに設定しているかどうかで挙動が変わります。 (2017年1月時点)
iOSの設定でTwitterアカウントを登録している場合
iOSにTwitterアカウントを登録済の状態でTwitterログインをすると、許可のポップアップが最初に表示されます。
ここで許可すると、Twitterログインが成功します。(特に画面遷移が発生しない)
iOSの設定でTwitterアカウントを登録していない場合
SafariViewControllerが立ち上がり、Twitterログイン画面が表示されます。
といった風に遷移が変わってくるので、動作確認する場合は、2パターンは確認する必要があります。
動作確認時に発生した不具合
Twitterアカウントを登録していない場合はSafariViewControllerが立ち上がりますが、以下のようなエラーがコンソールに表示されてSafariViewControllerが立ち上がらないことがあります。
Desktop applications only support the oauth_callback value 'oob'
対応方法としては、 Twitter Application Management で、callback URLになにかしらURLを設定すると治ります。
2. Info.plistにconsumer keyとsecretが書かれる
Fabricを使ってTwitterKitを取り込むとInfo.plistにconsumer keyとconsumer secretが書かれます。
アプリ側で保持している以上どこに書いても問題はあるとは思うのですが、Info.plistに書くよりはコード上に書いたほうが幾分ましかなと思います。
コードでConsumerKeyを設定する
このようにFabric.with([Twitter.self])
より前にstartWithConsumerKeyを呼んでやらないとクラッシュします。
import TwitterKit func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { ... Twitter.sharedInstance().start(withConsumerKey: "xxx", consumerSecret: "yyy") Fabric.with([Twitter.self]) return true }
2016振り返り
2016も残りわずかとなったところで急いで今年を振り返ります!
仕事
1月にLIMIAのiPhoneアプリをリリースしました。 フルSwiftで開発しました。
2月にはAppStoreの新着ベストアプリにも選ばれました!
3年半ほどお世話になったグリーを6月に退社してSupershipにジョインしました。
開発の途中から参加してものすごい勢いで実装して7月にはSunnychatのiPhoneアプリをリリースしました。 こちらもSwiftです。
プライベート
こどもが産まれまして、ワークライフバランスがこれまでと著しく変わってきました。
今はとにかく自分の時間がとれなくて勉強するのがなかなかたいへんです。
勉強
Elixirを勉強しましたけどコーディングは全然です
ランニング
10月まではほぼ毎週走ってたんですが、こどもが産まれてそれどころではなくなったので最近は全く走れてないです。。
まとめ
2016は仕事、プライベートともに激動の年だったなぁと。
2017は子育てを頑張りつつ、自分の時間もうまくつくってなにかやっていきたいですね
Swiftでの複数にデリゲート multicast delegate
Swiftにはweak reference arrayがないので、NSHashTableを利用します
protocol SampleDelegate : class { func sampleDelegateDidFinish() }
class SampleClass { let delegates = NSHashTable<AnyObject>() // AnyObjectをSampleDelegateにするとSwift3時点ではコンパイルに通らない
delegateを呼ぶとき
func someFunc() { ... delegates.objectEnumerator().enumerated() .map { $0.element as? SampleDelegate } .forEach { $0?.sampleDelegateDidFinish()) } ... }
popToRootViewControllerで画面を消すとviewWillDisappearでnavigationControllerがnilになる
現象が伝えにくいので、図に表すと以下のような感じで、 TabbarController内にNavigationControllerをもたせた状態で、いくつかViewControllerをPushViewControllerします。 そして、2つ以上PushViewControllerした状態で一番上のViewControllerのナビバーを透明にした場合にちょっと問題が発生します。
一番上のViewControllerでは以下のようにして、ナビバーを透明にして、消すときにナビバーを元に戻す用にしています。
class SampleViewController: UIViewController { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default) navigationController?.navigationBar.shadowImage = UIImage() navigationController?.navigationBar.isTranslucent = true } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) navigationController?.navigationBar.setBackgroundImage(nil, for: .default) navigationController?.navigationBar.shadowImage = nil navigationController?.navigationBar.isTranslucent = false } }
普通に戻るボタンを押した場合はこれで正常に動作するのですが、 タブバーのボタンをタップしてrootviewcontrollerに戻った場合に、viewWillDisappearが呼ばれたときにはnavigationControllerがnilになっているため、ナビバーが透明なままになってしまうという問題が発生しました。。
対策
その1
やっつけ仕事感のある対応としてはnavigationControllerを自力で保持するというやりかたで、、
private weak var toKeepNavigationController: UINavigationController? override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) toKeepNavigationController = navigationController navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default) navigationController?.navigationBar.shadowImage = UIImage() navigationController?.navigationBar.isTranslucent = true } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) toKeepNavigationController?.navigationBar.setBackgroundImage(nil, for: .default) toKeepNavigationController?.navigationBar.shadowImage = nil toKeepNavigationController?.navigationBar.isTranslucent = false }
その2
willMoveToParentViewControllerの時点ではnavigationControllerはnilになっていないようだったので、ViewControllerが消えるタイミング(parentがnilになったとき)にナビバーをもとにもどします
override func willMove(toParentViewController parent: UIViewController?) { super.willMove(toParentViewController: parent) if parent == nil { navigationController?.navigationBar.setBackgroundImage(nil, for: .default) navigationController?.navigationBar.shadowImage = nil navigationController?.navigationBar.isTranslucent = false } }
たぶん2のほうがいいと思います!
おかしいぞとかあればツッコミお願いします!
Swift3.0.1で若干変わったIntなどの数値型⇔AnyObjectのcast
Xcode8(Swift3.0.0)だと↓のテストは通るんですが、
import XCTest @testable import TypeTest class TypeTests: XCTestCase { func testType_WhenIntToAnyObject() { let intValue = ["hoge": Int(1) as AnyObject] XCTAssertTrue(intValue["hoge"] is Int) XCTAssertTrue(intValue["hoge"] is Float) XCTAssertTrue(intValue["hoge"] is Double) XCTAssertTrue(intValue["hoge"] is CGFloat) } func testType_WhenCGFloatToAnyObject() { let cgfloatValue = ["hoge": CGFloat(1.0) as AnyObject] XCTAssertTrue(cgfloatValue["hoge"] is Float) XCTAssertTrue(cgfloatalue["hoge"] is Double) XCTAssertTrue(cgfloatValue["hoge"] is CGFloat) } }
Xcode8.1(Swift3.0.1)だと↓のようにAnyObjectにcastする前の型のみ正しく判定されるようになってました
SE-0139によるところかと思われます。 あんまりちゃんと読んでないですが、以下のようなこと書いてあったし。
Bridged NSNumber and NSValue objects must be castable back to their original Swift value types.
雑にAnyObjectをcast backしてる場合はSwfit3.0.1にするとcastに失敗するようになるので注意が必要かもです
CGRectの新旧書き方対応表
古 | 新 |
---|---|
CGRectGetWidth( rect ) | rect.width |
CGRectGetHeight( rect ) | rect.height |
CGRectGetMinX( rect ) | rect.minX |
CGRectGetMidX( rect ) | rect.midX |
CGRectGetMaxX( rect ) | rect.maxX |
CGRectGetMinY( rect ) | rect.minY |
CGRectGetMidY( rect ) | rect.midY |
CGRectGetMaxY( rect ) | rect.maxY |
CGRectIsNull( rect ) | rect.isNull |
CGRectIsEmpty( rect ) | rect.isEmpty |
CGRectIsInfinite( rect ) | rect.isInfinite |
CGRectStandardize( rect ) | rect.standardize |
CGRectIntegral( rect ) | rect.integral |
CGRectInset(rect, 1.0, -2.0) | rect.insetBy(dx: 1.0, dy: -2.0) |
CGRectOffset(rect, -1.0, 2.0) | rect.offsetBy(dx: -1.0, dy: 2.0) |
CGRectUnion(rect1, rect2) | rect1.union(rect2) |
CGRectIntersection( rect1 ,rect2) | rect1.intersect(rect2) |
CGRectContainsRect( rect1,rect2) | rect1.contains(rect2) |
CGRectContainsPoint(rect ,point) | rect.contains(point) |
CGRectIntersectsRect( rect1,rect2 ) | rect1.intersects(rect2) |
Xcode8にしたときのプッシュ通知対応
Project SettingsのCapabilitiesのタブを開き、PushNotificationsをONにします
production.entitlements
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>aps-environment</key> <string>production</string> </dict> </plist>
development.entitlements
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>aps-environment</key> <string>development</string> <key>keychain-access-groups</key> <array> <string>$(AppIdentifierPrefix)share</string> </array> </dict> </plist>
Build SettingsのCode Signing Entitlementsで、Configurationによって使うentitlementsを変えるようにします
最後にこれは必須ではないですが、
if #available(iOS 10.0, *) { UNUserNotificationCenter.currentNotificationCenter().requestAuthorizationWithOptions([.Alert, .Sound, .Badge]) { granted, _ in if granted { UIApplication.sharedApplication().registerForRemoteNotifications() } } } else { let settings = UIUserNotificationSettings(forTypes: [.Alert, .Sound, .Badge], categories: nil) UIApplication.sharedApplication().registerUserNotificationSettings(settings) UIApplication.sharedApplication().registerForRemoteNotifications() }