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

UITableViewで無限スクロール

func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
  if indexPath.row == (items.count - 1){
    items += nextItems
    tableView.reloadData()
  }
}

最後のcellに来たらnextItemsを追加しています。

たまにcellForRowAtIndexPathでこの処理をしている記事を見るけど、cellForRowAtIndexPathはあくまで初期化処理に徹するべきだと思うので、willDisplayCellで処理を記述しています。

実際の使用シーンではnextItemsの取得処理が入ると思います。

また、自分の場合、一番下にスクロールしたのが分かるように「ロード中」のようなテキストを表示するfooterを指定しています。その場合、固定表示したくないので、viewDidLoadで下のように指定してます。

tableView.tableFooteView = footer