AFHTTPSessionManagerでHTTPBodyを設定する
AFNetworkingのAFHTTPSessionManagerを使っていてBodyに素のXMLを入れてPOSTしたかったのだが、そのやり方がなかなか見当たらずソースコードを読んで解決したのでメモしておく。
ざっくり言うとAFHTTPSessionManagerでのPOSTはこんな感じに行う。
let manager = AFHTTPSessionManager() manager.POST(url, parameters: params, success: { (task, response) -> Void in // success }) { (task, error) -> Void in // failure }
POSTするパラメータがあれば、上のメソッドのparameters
のところにdictionaryとかで渡せばいいはずだが(確か)、今回やりたいのはBodyにXMLのテキストを入れるということだった。
まず気軽にparametersにXML文字列を直接渡してみようなどとやってみたら失敗した。
let manager = AFHTTPSessionManager() let xmlstr = "<?xml ..." manager.POST(url, parameters: xmlstr, success: { (task, response) -> Void in // success }) { (task, error) -> Void in // failure }
これでできたリクエストを見てみるとBodyにはこんなのが入っていた。
(null)=%3C%3Fxml%20...
どうやら名前が空で値がURLエンコード済文字列のパラメータをBodyとして設定してしまっているらしい。
ソースコードを読むとそれがデフォルトの動作だということがわかった。
// AFURLRequestSerialization.m switch (self.queryStringSerializationStyle) { case AFHTTPRequestQueryStringDefaultStyle: query = AFQueryStringFromParametersWithEncoding(parameters, self.stringEncoding); break; }
じゃあデフォルトじゃない動作は、というと上のコードの近くにあった。
// AFURLRequestSerialization.m if (self.queryStringSerialization) { NSError *serializationError; query = self.queryStringSerialization(request, parameters, &serializationError); if (serializationError) { if (error) { *error = serializationError; } return nil; } }
self.queryStringSerialization
はBlockで、このブロックを設定してparameterのシリアライズをカスタマイズできるよ、ということのようだった。
つまり文字列をそのままBodyに入れたい場合は、
let manager = AFHTTPSessionManager() let xmlstr = "<?xml ..." manager.requestSerializer.setQueryStringSerializationWithBlock { (request, params, error) -> String! in return params as! String } manager.POST(url, parameters: xmlstr, success: { (task, response) -> Void in // success }) { (task, error) -> Void in // failure }
manager.requestSerializer.setQueryStringSerializationWithBlock
でparameterをそのまま文字列と返してあげればよかった。
これで解決できた。良かった。
- 作者: 佐々木達也,瀬川雄介,内藤賢司
- 出版社/メーカー: シーアンドアール研究所
- 発売日: 2015/03/26
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (4件) を見る
NSTextFieldで入力を検出する
iOS開発からCocoaにも手を付け始めた人間によくあることだろうが、UIKitとAppKitでお作法が同じだったり微妙に違ったりすることでちょくちょくつまずくことがある。
今日はテキストフィールドでユーザーが入力を行うたびに入力値のチェックをしてOKボタンの有効/無効を切り替えるという処理を書こうとしてつまづいた。
UITextFieldの場合
UITextField
の場合はStoryboardからCtrl+ドラッグでActionを作るときにEditing Changed
イベントを選んであげればOK。
NSTextFieldの場合
Storyboardを使って同じようなことをやろうとしてもiOSのようにコントロールに発生したイベントに対応したActionを作ることができない。ここまで書いて気がついたけどAppKitはイベント型(?)じゃないということなんだね。
ではどうすればいいかといえば、Delegateが存在するのでそれを実装してあげれば良い。DelegateはiOS開発でも毎回使うだろうからiOS Developerなら大丈夫のはず。
class ViewController: NSViewController, NSTextFieldDelegate { @IBOutlet weak var textField: NSTextField! override func viewDidLoad() { super.viewDidLoad() // Do view setup here. textField.delegate = self } override func controlTextDidChange(obj: NSNotification) { doSomething() } }
できた。
Cocoaアプリケーション(OSXアプリ)はじめました
実験的な意味も含めてCocoaアプリの開発を始めた。完成したものはMac App Storeに並べてもらうべくiOSと同じように申請して審査してもらっている。
そもそもMac App Storeに並んでいるアプリはなんと呼ぶのが正解なのかすらよくわかっていないが、Cocoaアプリということにしておく。
iOSの開発は5年ぐらいやっているけれど、Cocoaアプリの開発は全くの初めてだったから調べながら進めている。今は単機能のSingle Windowアプリを3つ作った程度で、ドキュメントベースのアプリケーションのような複雑なことはしていないが、Cocoaアプリ開発についてのコメントというか感想を書いてみようと思う。ただの感想文ですよ。
情報が少ない、見つけにくい
まず言えるのはこれだと思う。iOS開発の情報はネットの世界に溢れている。日本語でも大量の情報があるから大抵のことは調べればできる。
その反面、Cocoaアプリケーションの情報はなかなか見つからない。日本語で書かれたそこそこ新しい情報がたまにヒットするが、大抵はAppleのドキュメントページが検索のトップに来る。そしてその情報も数年前に書かれたものだったりする(必ずしも情報が古いというわけではなく、SDKが変わっていないというだけだったりするが)。
検索自体もしづらい。例えばAVFoundation OSX Swift ○○
とか調べようとするとiOSの検索結果がずらりと出てくる。iOSとは独立してはっきりしたネーミングがあればよかったのにと思う。
最近気がついたのは、Cocoa
というキーワードを入れておくとなかなか良さそうな検索結果を得られる気がする。
お作法が割と違う
UI周りがまあまあ違う。iOSのように画面サイズを制限されない分、自由にビューを配置したり複数のウィンドウを使ったり色々なことができる。
例えばiOSではUIImage
を使っているところをCocoaではNSImage
を使うことになるんだけど、このクラスのインターフェースが微妙に異なるので同じように使えるもんだと思っているといちいち躓く。
で、ドキュメントを探したり例を探そうとすると最初の話「情報が見つからない」に行き着いて時間がかかる。
慣れるまでは同じことを何度も調べる可能性があるので見つけたページはEvernoteのWebClipすることにしている。この機能は最近全く使っていなかったけれど久しぶりに重宝しそうだ。
iOSの経験はかなり活きる
お作法が微妙に異なるとは言え、SDKの思想はiOSとほぼ変わらないからiOS経験者は比較的すんなり入ることができる。
例えば、縦横の表を表現するときにはNSTableView
を使うが、iOSの経験のない人がいきなりこのViewを使おうとしたら混乱するんじゃないかと思う。予想です。
iOSのUITableView
もこのNSTableView
もdelegate
とdataSource
を用いてコンテンツや挙動を制御するわけだけど、行と列がある分NSTableView
のほうが複雑と言っていいと思う。先に複雑じゃないUITableView
を触っていれば、比較的すんなり入れるんじゃないかな。
Cocoaアプリケーション開発でもStoryBoardやSegueのようなUI開発の仕組みがあるし、Cocoapodsなども活用できるからiOS経験者にはハードル低いんじゃないかな。みんなやったほうがいいよ。
おわり
これからは備忘録的にちょくちょく書いていく予定。
ちなみに最初に作ったのはiPhoneやiPadのAppStoreで使えるPreview動画を自動生成するアプリ。ネーミングやデザインなどはお察しだけどなかなか便利なのができたよ。
Movie Resize for App Previews
カテゴリ: ユーティリティ, グラフィック&デザイン
Single WindowなOSX Appで全てのWindowが閉じたときの挙動でリジェクト
MacのアプリケーションはWindowを赤ボタンとか⌘W
で閉じてもアプリケーション自体が終了しないことが多いが、その場合メインウィンドウを再表示できるようにしておかないとMac App Storeに申請した際にリジェクトを食らってしまう(食らった)。
1つめの対応方法として、Dockアイコンをクリックしたときにメインウィンドウを再表示するコードをAppDelegate
に書く。
func applicationShouldHandleReopen(sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool { if flag == false && sender.windows.count > 0 { sender.windows[0].makeKeyAndOrderFront(self) } return true }
もしくは全てのウィンドウが閉じたときアプリケーションを終了させてしまうというのもOKらしい。これは推測だけど、申請したのがシングルウィンドウなアプリケーションだったからこの対処方法でもいいよと言ってくれたんじゃないかと思う。ドキュメントベースの場合はウィンドウが閉じることって頻繁にあるだろうから多分だめなんじゃないかな。
その場合はAppDelegate
にこんなコードを書く。
func applicationShouldTerminateAfterLastWindowClosed(sender: NSApplication) -> Bool { return true }
今回は単機能のアプリだったので後者で対応することにしてみた。
【追記あり】デフォルトアプリケーションがXcode-betaになってしまったのを戻した
XCodeのベータ版を入れたらxcodeprojなどを開くときのデフォルトアプリケーションがベータ版のほうになってしまった。いやいや君普段はそんなに使わないから。
僕はターミナルでそのプロジェクトのGitディレクトリに移動して、そこからopen hogehoge.xcodeproj
とかやることが多いのでデフォルトアプリケーションは重要なのだ。
デフォルトアプリケーションの変更はFinderの情報を見る
あたりからできるというのは知っていたが、ついでに*.h
など、基本XCodeで開くよねとなっているファイルたちのデフォルトも変えたかったからそれだと物足りなかった。全部のファイルタイプに同じことするの面倒なので。
そこで強引な方法で解決することにした。
- XCode-beta.appを削除
- ゴミ箱から戻す
これでOK!
追記
だめだったー。ベータを起動したら元に戻ってました。なんとかならんのこれ。
UIWebView内リンクでの遷移をNavigationでやるやつ、Swiftバージョン
iOSでUIWebView内のリンクをタップしたらNavigationで画面遷移させる実験 - PILOG
↑こんなん書いたけどこのときはObjective-Cで書いてたのでSwiftで書き換えてみました。オプショナルが全然慣れませんがこれから勉強します。
import UIKit import TKRSegueOptions class ViewController: UIViewController, UIWebViewDelegate { let HOST = "http://localhost:4000" @IBOutlet weak var webView: UIWebView! override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. webView.delegate = self var urlstring = HOST + "/" if let path = self.segueOptions?["path"] as? String { urlstring = HOST + path } let url = NSURL(string: urlstring) let req = NSURLRequest(URL: url!) webView.loadRequest(req) } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool { if(request.URL?.scheme == "callback"){ println(request.URL?.absoluteString) let urlstring = request.URL?.absoluteString let href = urlstring?.substringFromIndex(advance(urlstring!.startIndex, 11)) performSegueWithIdentifier("next", options: ["path": href!]) } return true } }
- 作者: 荻原剛志
- 出版社/メーカー: SBクリエイティブ
- 発売日: 2014/12/10
- メディア: 大型本
- この商品を含むブログ (2件) を見る
iOSでUIWebView内のリンクをタップしたらNavigationで画面遷移させる実験
しばらくiOSから離れていてSwiftはほとんど触ったことないのでとりあえずObjective-Cでやりました。このあとSwiftで実装し直そうと思ってるけれど、一旦エントリにします。
いつだったかDHHが既存のWebサイトを活かしてアプリ作るよーみたいなことを言ってたのを覚えていたので、今回は「いや、実際どうやってやるんだろうな」というのを考えてみたわけです。
Hybrid sweet spot: Native navigation, web content, https://t.co/LWmqbmg3iX — How we build mobile apps for Basecamp with tiny teams.
— DHH (@dhh) 2014, 5月 8
何をやりたいか
こういうページ遷移がある普通のWebサイトがあるとして、
このページをiOSのUIWebViewで表示するんだけど、リンクをタップしたときに新しいViewControllerを作ってNavigationで遷移するということをやってみます。普通にやるとリンクをタップしたら同一WebView内画面遷移するところを、あたかもiOSのネイティブのように動かしてみようということね。
結論からいうとそれは可能です。↓のアニGIFを見てもらえばリンクを押したときにナビゲーションで次のビューがスタックしてくるのがわかると思います。
こうすることでWebにコンテンツを自由にデプロイしつつ、ユーザー体験はネイティブに近づけられるんじゃないかと。
こんなことはすでに昔からみんながやってたりしたかもしれないけど、最近は端末も速くなっているし、個人的にはWebView+ナビゲーションってあまり見たことがない気がするのでその部分の実験ということで見てもらえると良いです。
Webページの準備
まずはHTML側のソースです。ここで見るべきは、<a>
タグについてるcallback-to-ios
クラスぐらい。
<!DOCTYPE html> <html> <body> <p>1ページ目</p> <a href="/secondpage.html" class="callback-to-ios">2ページ目ヘ</a> </body> </html>
続いてJavascriptです。こっちが肝ね。
$ -> reportBackToIOS = (href)-> iframe = $('<iframe />').attr('src', "callback://" + href) $('body').append(iframe) iframe.remove() iframe = null $('.callback-to-ios').click -> reportBackToIOS($(@).attr("href")) false
まずリンクのクリック時にreportBackToIOS
を呼び出した上で、falseを返してリンクを辿らないようにしてあります。
reportBackToIOS
は名前の通りiOSにリンクがタップされたことを知らせる役割を担っています。iOSのUIWebViewではWebView内でURLの読み込みが発生する直前にコールバックを通るんですが、ここではそれを利用しています。
具体的には、iOSに渡したい情報をURL(の一部)として持ったiframe
を作って一旦HTMLのbodyに追加し、すぐに削除していますね。iframe
がbodyに追加されたとき、src
アトリビュートで持っているURL(ここでは、'callback://' + 遷移先のURL)をiframe内で開こうとするため、WebViewのコールバックが呼ばれることになるわけです。
iOS側の対応
ViewController
に適当にUIWebViewを置いてあり、ViewController
→ViewController
の遷移をするSegueがnext
という名前で定義されているとします(実際にはViewControllerからViewControllerへのSegueは定義できないので、隠しボタンからViewControllerへのSegueです。)。
#define HOST @"http://localhost:4000" - (void)viewDidLoad { [super viewDidLoad]; self.webView.delegate = self; NSString* urlstring = NULL; NSString* path = self.segueOptions.stringValue; if(path) { urlstring = [NSString stringWithFormat:@"%@%@", HOST, path]; } else { urlstring = [NSString stringWithFormat:@"%@%@", HOST, @"/"]; } NSURL* url = [NSURL URLWithString:urlstring]; NSURLRequest* urlRequest = [NSURLRequest requestWithURL:url]; [self.webView loadRequest:urlRequest]; }
肝心のコールバックはこんな感じで実装しておきます。
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType { if ([[[request URL] scheme] isEqualToString:@"callback"]) { NSString* href = [[[request URL] absoluteString] substringFromIndex:11]; [self performSegueWithIdentifier:@"next" options:href]; return NO; } return YES; }
shouldStartLoadWithRequest
は新しいリクエストが来た時に通るコールバックで、何もしないときはYES
を返すことで読み込みをスタートさせることができるわけですが、今回はcallback://
で始まるURLの場合だけはnext
という名前のSegueを実行してNO
を返すことにします。NOを返せばURLの読み込みは行われないので、callback://****
みたいなURLを処理しちゃってエラー、ということも起こりません。
これで最初に紹介したGIFアニメのようなページ遷移ができるようになりました。便利なような、使いどころ難しいようなそんな感じですが、ネイティブっぽく動かすという目的はなんとなく果たせていますね。
参考リンク
reportBackToIOS
のところはStackOverflowのこの質問を参考にしました。
ios - How can I reliably detect a link click in UIWebView? - Stack Overflow
クラウドでできるHTML5ハイブリッドアプリ開発 Cordova/Onsen UIで作るiOS/Android両対応アプリ (Monaca公式ガイドブック)
- 作者: 永井勝則,アシアル株式会社
- 出版社/メーカー: 翔泳社
- 発売日: 2015/02/18
- メディア: 大型本
- この商品を含むブログ (4件) を見る