cocoapodsでRxSwiftをインストールした際に出てきた謎のエラー「Expected ',' separator」

Environment

  • Swift 4.2
  • Cocoapods 1.5.3
  • XCode 10.0

Problem

表題の通り、Expected ',' separatorっていうエラーがRxSwiftのパッケージ側に出てきて、ビルドがうまくいかなかった。 Cocoapodsで管理しているのですが、Pods以下はロックされていて自分でいじっては居ないし、じゃあRxSwiftのバグかなと思って調べても特に出てこない。

それで、エラーが出ている箇所のソースコードを見ると、引数によくわからないものを渡していることに気づきました。 それでリモートリポジトリのソースコードを確認すると、エラーが出ているローカルのソースコードと違っていました。

// my local library source cord(RxSwift/Observables/Timer.swift)

return _parent._scheduler.scheduleRelative(self, dueTime: _parent._dueTime) { (_f_`) -> Disposable in
// https://github.com/ReactiveX/RxSwift/blob/53cd723d40d05177e790c8c34c36cec7092a6106/RxSwift/Observables/Timer.swift#L78

return _parent._scheduler.scheduleRelative(self, dueTime: _parent._dueTime) { (`self`) -> Disposable in

😳!

Solution

まあ、たぶんXCodeのSwift4.2シンタックスへの自動変換のときに、気づかないで変更しちゃったのかな、と思います。 解決方法としては、以下のようにCocoapodsのキャッシュを削除した上で、プロジェクトのPodsディレクトリ以下を削除して再インストールしましょう。

念の為、DerivedData以下のビルドキャッシュも消したほうが良いかもしれません。

rm -rf ~/Library/Caches/CocoaPods
rm -rf [path to project]/Pods

// optional
rm -rf ~/Library/Developer/Xcode/DerivedData

pod install

訂正(2019/09/27 15:48)

これ、Build Phaseで設定していたswiftlint autocorrectの仕業でした。アホすぎる。。

WKWebViewで最初に読み込んだページに戻る

WKWebViewの最初に読みこんだページ(URL)に戻りたいことがあったので、いろいろ調べたらwebView.goToBackForwardListItem(item: WKBackForwardListItem)で実現できました。

let webView = WKWebView()

let count = webView.backForwardList.backList.count
guard let rootItem = webView.backForwardList.itemAtIndex(-count) else { return }
webView.goToBackForwardListItem(rootItem)

解説

backForwardListで戻る履歴の数を取得します。わざわざ履歴の数を取得するのは、最初のページのWKBackForwardListItemを取得するwebView.backForwardList.itemAtIndex(index: Int)で指定するindexが現在のページからの相対値になるので、履歴数をマイナスで指定する必要があります。 取得したらそのWKBackForwardListItemをgoToBackForwardListItemに渡すだけです。

loadRequestではだめな理由

この方法を使う以外だと、最初に読み込んだURLを保存しておいて、そのURLをloadRequestで読み込むという方法が思い付きました。 ですが、今回はUIとしてあくまで「履歴の最初に戻る」というイメージが持てるような動作にしたいという要件がありました。 loadRequestの場合、履歴のスタックに最新の履歴として、そのリクエストが載ってしまい要件が満たせません(goBackで戻れてしまう)。

まとめ

  • goToBackForwardListItem(rootItem)
    • 戻る履歴は「0」になる(履歴の最初に戻るので)
    • 進む履歴は維持される
    • イメージ:
      • ■□□□□□□□
  • loadRequest(firstPageRequest)
    • 履歴の先頭にfirstPageRequestが載るため、戻る履歴は維持される
    • このリクエストが履歴の先頭になるので、進む履歴は無い
    • イメージ:
      • □□□□□□□□■
      • goToBackForwardListItem(rootItem)の場合より、履歴の数は1つ多くなる

ERROR ITMS-90037: "This bundle is invalid. The Info.plist file is missing or could not be parsed. Please check it for embedded control characters." の対処

iTunesConnectへアップロードを試みたところ、以下のようなエラーが出てしまいアップロードに失敗しました。

ERROR ITMS-90037: "This bundle is invalid. 
The Info.plist file is missing or could not be parsed. 
Please check it for embedded control characters."

いろいろ調べたのですが、以下のStackOverflowがヒットしました。

stackoverflow.com

コメントに従いInfo.plistvim(vi)で開いたところ、<key>LSApplicationQueriesSchemes^P</key>といった感じで^Pという文字が<key>フィールド内にまぎれていました。

Open As -> Source Codeでは見つからないので、vim(vi)で開いて下さい。

それを消した所、無事アップロードできました。

エラーメッセージにPlease check it for embedded control characters.とあるのだから、ちゃんと読めって話ですね。

AlamofireでUser-Agentを編集する

概要

  • Alamofireを使用していてUser-Agentを編集したいとき、ManagerのイニシャライザにNSURLSessionConfigurationインスタンスを渡して対応しました。
  • NSURLSessionConfigurationの設定については、後から動的に変えることができないので、注意が必要です。
    • 変更したい場合は、Managerインスタンスを初期化するしかありません。

2パターンあります

  • User-Agentの末尾に任意の値を挿入
  • User-Agentをすべて入れ替え

User-Agentの末尾に任意の値を挿入

let customUserAgent = "Some-User-Agent-Value"

guard let defaultUserAgent = Manager.defaultHTTPHeaders["User-Agent"] else {
    fatalError("Header User-Agent value is nil")
}
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
configuration.HTTPAdditionalHeaders = Manager.defaultHTTPHeaders
configuration.HTTPAdditionalHeaders?["User-Agent"] = defaultUserAgent + " " + customUserAgent

let manager: Manager = Manager(configuration: configuration)

User-Agentをすべて入れ替え

let customUserAgent = "Some-User-Agent-Value"

let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
configuration.HTTPAdditionalHeaders = Manager.defaultHTTPHeaders
configuration.HTTPAdditionalHeaders?["User-Agent"] = customUserAgent

let manager: Manager = Manager(configuration: configuration)

補足

  • 上記コードのmanager変数は、APIと通信するWebAPIClietのようなクライアントクラスのインスタンスで使用しています。
  • コード例だと省略してますが、上記WebAPIClientのインスタンス変数として使っています。

UITextFieldを含むUITableViewCellのNSIndexPathを取得する方法

UITableViewCellを含むCellが1つであれば、UITextFieldDelegatetextFieldDidEndEditingから取得するだけでいいのですが、これが複数になると引数のtextFieldがどのCellに帰属するのかわからないので面倒です。 悩んでいたら、textFieldDidEndEditingの引数のtextFieldからsuperViewを取得し、indexPathForCellに送ってindexPathを取得する方法に行き着きました。

stackoverflow.com

func textFieldDidEndEditing(textField: UITextField) {
  if let cell = textField.superview?.superview as? TextFieldCell, 
     let indexPath = tableView.indexPathForCell(cell){
    //some code
  }
}

TextFieldCellがUITextFieldを含むUITableViewCellのカスタムクラスです。

CompleteHandlerのないアニメーションにCATransactionでCompleteHandlerを追加する

タイトルの通りです。 CompleteHandlerのないアニメ―ションにCompleteHandlerを追加します。 下の例ではUITableViewediting modeの終了アニメーション後に処理を追加しています。

CATransaction.begin()
CATransaction.setCompletionBlock { () -> Void in
// some code
}
setEditing(false, animated: true)
CATransaction.commit()

対象になるアニメーションの処理(setEditing)をCATransaction.begin()CATransaction.commit()で囲んで、CATransaction.setCompletionBlockにやりたい処理を書きます。

複数のUITextFieldとUITextViewが混在するViewControllerでキーワードを閉じる

通常、であればViewConrollerUITextFieldDelegateUIViewControllerを適用して、textFieldShouldReturnだったり何かしらのタッチイベントでtextField.resignFirstResponder()をすると思います。

ただ、UITextFieldとUITextViewが混在する場合、どちらが編集状態かわからないけど、とにかくキーボードを閉じたい時がある。そういう時はViewControllerのインスタンスプロパティのviewに対してendeditingすればいい。

self.view.endEditing(true)

viewのサブビュー全ての編集状態が終了するので、どちらかの判定を含まず、シンプルにキーボードを閉じることができる。