デザイナーへの愛を込めて:モバイルアプリケーションにWebフォームを埋め込む

多数の外部システムと連携しなければならないプロジェクト用のモバイルアプリケーションを開発する場合、必然的に、機知と工夫を示す必要がある状況が発生します。 特に、こうしたシステムの技術的特徴を考慮に入れて、設計者の思考のソフトウェアフライトを実装しようとすると、こうした状況が発生します。 この記事では、Money.Ru Moneyモバイルアプリケーションで作業する際に、このような問題をどのように解決するかを説明します。







そのため、パートナープロジェクトの外部WebページにはWebフォームが含まれています。 このページは、アプリケーションに組み込まれたブラウザーでは正常に機能しますが、外観はデザイン部門の美しさに関するアイデアと一致せず、内部は無機質に見えます。 デザイナーは、新しい美しい形を描き、「このように見えるはずです!」というコマンドを与えます。 誰もが独自のタスクを持っていますが、私たちの共通の目標は高品質のアプリケーションです。



私たちの仕事は明確です。 実装を取得します。 新しいデザインでアプリケーションにフォームを埋め込みます-複雑なことは何もありません。 しかし、Webフォームについてはどうでしょうか?



手っ取り早く、フォームを使用してページのロジックをプログラムで実装できます。 次に、「送信」ボタンのクリックをエミュレートするHTTPリクエストを生成し、それをUIWebViewに転送します。



ただし、このアプローチにはすべての単純さがあり、落とし穴があります。 フォームには簡単にCSRFトークン (ページをロードし、最終リクエストで渡すためにトークンを解析する必要があります)、サーバー側で頻繁に変更できる値のリスト(ロードおよび解析も)、および通常状態を操作することができますユーザーが入力したデータに応じて、1つ以上の非表示フォームフィールド(hello、JavaScript!)。 これはすべてタスクを十分に複雑にします、見つけませんか?



別の方法があります! そしてステージでは、観客の拍手の下で、マエストロクラッチが登場します。 何してるの?



すべてが非常に簡単です。 ユーザーの目から隠されたUIWebViewを取得し、そこにWebページをロードし、JavaScriptを使用してそのDOMオブジェクトを操作します。



簡単な例でこの手法を検討してください。 実験的なウサギとして、Habrのメインページの右上隅にある検索フォームを使用します。このフォームには、次のHTML表現があります。



<div class="search"> <form id="search_form" name="search" method="get" action="//habrahabr.ru/search/"> <input type="submit" value=""> <input type="text" name="q" x-webkit-speech="" speech="" tabindex="1" autocomplete="off"> </form> </div>
      
      







フォームは単純で、テキスト入力フィールドとボタンが1つだけ含まれているため、実験に理想的なオブジェクトです。

まず、Webフォームを管理するコントローラーを作成します。



 @interface MRWebViewController () <UIWebViewDelegate> @property (nonatomic, weak, readonly) UIWebView *webView; @property (nonatomic, strong, readonly) NSURLRequest *request; @property (nonatomic, assign) BOOL hasForm; // ... @end @implementation MRWebViewController { } // ... - (instancetype)initWithURLString:(NSString *)urlString { self = [super init]; if (self) { _request = [NSURLRequest requestWithURL:[NSURL URLWithString:urlString]]; } return self; } - (void)viewDidLoad { [super viewDidLoad]; [self createWebView]; self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; self.view.alpha = 0.0; } - (void)createWebView { UIWebView *webView = [[UIWebView alloc] initWithFrame:self.view.bounds]; webView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; webView.backgroundColor = UIColor.whiteColor; webView.scalesPageToFit = YES; webView.delegate = self; [self.view addSubview:webView]; _webView = webView; } // ... - (void)reload { self.hasForm = NO; self.view.alpha = 0.0; [self.webView stopLoading]; [self.webView loadRequest:self.request]; } // ... @end
      
      







コントローラーには、フォームとともにページをロードするUIWebViewと、ページをロードするためのリクエストを保存するために使用するNSURLRequestオブジェクトが含まれています。 ビューオブジェクトのautoresizingMaskプロパティを指定すると、このコントローラーを問題なく子View Controllerとして使用でき、 alphaプロパティでその可視性を制御できます。



プロジェクトの腸内のどこかにコントローラーオブジェクトを作成し、フォームを含むページを読み込みましょう。



 static NSString *kMRHabraURLString = @"http://habrahabr.ru"; MRWebViewController *controller = [[MRWebViewController alloc] initWithURLString:kMRHabraURLString]; [controller reload];
      
      







この場合、ページの読み込み結果は、コントローラーの対応するデリゲート関数によってインターセプトされます。 DOMオブジェクトの操作はjQueryで便利です。 したがって、ロードされたページに確実に存在するようにします。



 - (void)webViewDidFinishLoad:(UIWebView *)webView { if (!self.hasForm) { NSLog(@"Installing jQuery at %@", webView.request.URL.absoluteString); [self.webView stringByEvaluatingJavaScriptFromString:[MRScriptsFactory jqueryScript]]; self.hasForm = YES; } // ... }
      
      







Webページをロードするプロセスは非同期に発生し、ページが最後までロードされない場合がありますが、プログラムで実装されたネイティブフォームをユーザーに表示することを妨げるものは何もありません。 同時に、ネイティブフォームは、ユーザーから受信したデータを入力および検証する責任を負います。



ユーザーがネイティブフォームに入力し、その中の[検索]ボタンをクリックすると、コントローラーはメッセージsearchWithString:を受け取ります



 - (BOOL)searchWithString:(NSString *)searchString { BOOL result = NO; if (self.hasForm) { // ... NSString *actualString = [searchString stringByReplacingOccurrencesOfString:@"'" withString:@"\\'"]; NSString *script = [NSString stringWithFormat:[MRScriptsFactory fillFormScript], actualString]; NSString *scriptResult = [self.webView stringByEvaluatingJavaScriptFromString:script]; __autoreleasing NSError *error = nil; id object = [NSJSONSerialization JSONObjectWithData:[scriptResult dataUsingEncoding:NSUTF8StringEncoding] options:0 error:&error]; result = (!error && [object isKindOfClass:[NSDictionary class]] && [object[@"success"] boolValue]); // ... } return result; }
      
      







私たちの場合、 [MRScriptsFactory fillFormScript]を介して取得したスクリプトの形式は次のとおりです。

 (function ($, searchString) { var components = { $text : $("form#search_form input[type='text']"), $submit : $("form#search_form input[type='submit']") }; components.$text.val(searchString); components.$submit.click(); return JSON.stringify({ "success" : true }); })(jQuery, '%@');
      
      







スクリプトのソースコードからわかるように、フォームのテキストフィールドに検索文字列が入力され、プログラムでフォームボタンのクリックをエミュレートします。



UIWebViewでリクエストを実行した結果として取得されたデータの後続の処理は当初想定していなかったため、この例では、単にユーザーに「表示」します。



 - (void)webViewDidFinishLoad:(UIWebView *)webView { if (!self.hasForm) { // ... } else if (self.isScriptExecuting) { [UIView animateWithDuration:0.3 animations:^{ self.view.alpha = 1.0; }]; self.scriptExecuting = NO; // ... } }
      
      







このアプローチは、当社が長い間成功裏に使用してきており、十分に実証されています。 この例の完全なソースコードはこちらにあります。



質問がある場合、またはフォームの操作に関するベストプラクティスを共有したい場合は、コメントで議論することをお勧めします。



All Articles