ASP.NET MVC 3

開發混合式原生與行動 Web 應用程式

Shane Church

下載代碼示例

您想要生成移動應用程式,但你摸不著的可用的設備和學習的 Api 的陣列。您應該選擇哪個移動平臺?蘋果 iOS (iPhone 和 iPad) 使用目標 C、 谷歌 Android 使用 Java,Windows Phone 使用 Silverlight,但這些選項中的每個具有不同的 API 和獨特的市場。選擇專注于一個特定技術堆疊可以離開 50%的市場份額 — — 或更多 — — 無法使用您的應用程式。如果您選擇嘗試支援所有這些平臺,您有至少三個不同的代碼庫以維持,大大增加您的開發和維護成本。

還有另外一個選擇:您可以生成移動 Web 應用程式,因為它可以在任何這些設備上查看。但這種方法也有一些障礙。為開發使用 HTML 和 JavaScript 的整個業務應用程式的最大障礙是訪問的缺乏如攝像頭、 GPS 或加速度計的許多本機設備硬體功能。

清楚地增長,那麼,如何做支援所有這些設備選項同時提供最佳的使用者體驗可能只會移動市場?在本文中,我將展示如何構建一個進行包裝,與本機應用程式中殼的移動 Web 應用程式中充分利用了這兩個領域的最佳的移動應用程式。

混合應用概念

混合式應用程式的基本概念是特定于設備的本機應用程式中殼中換行優化的移動 Web 應用程式。本機應用程式中殼主機配置為當外殼應用程式啟動時啟動特定的移動應用程式的 URL 的 Web 流覽器控制項。其他 UI 元素可以提供本機應用程式 shell 中,根據需要,但需要在 Web 流覽器控制項。當使用者導航網站所請求的 Url,然後偵聽本機 Web 流覽器控制項。當使用者請求需要的本機功能的特定的 URL 時,Web 流覽器控制項中斷導航事件,並轉而調用本機功能。如使用者完成了本機的過程,在應用程式導航回 Web 網站流量中的適當位置的 Web 流覽器控制項。

為了說明如何做到這一點,我就會走過我的經驗,為客戶建立與同事 EffectiveUI 應用程式。構建流程等症狀、 長凳、 消防栓市政資產維護工作訂單的數量,移動領域工人,在應用程式利用的流覽器支援的功能,以實現使用者的當前的位置和本機硬體資產的圖片,並將它們上傳到伺服器的訪問。圖 1 顯示已完成的應用程式的主功能表。


圖 1 已完成的應用程式主功能表

生成 Web 應用程式

當生成此移動應用程式,我跟在後面的多項建議從史蒂夫 · 桑德森條,"建立更好移動流覽體驗"(msdn.microsoft.com/magazine/hh288079) 2011 年 7 月發行的 MSDN 雜誌。在這篇文章中的建議,我學會了一路走來的幾件事情:

  • 優化最觸摸移動使用者在使用基於觸摸互動的 UI 元素。觸摸互動是本來就不太精確比基於滑鼠的相互作用在桌面上。所有的互動式元素如按鈕和功能表項目需要按比例大移動介面比在桌面體驗。
  • 為頻寬最移動設備的資源約束下,特別是當考慮頻寬優化您的移動視圖。不要強迫你的使用者才能使用你的網站下載的大圖像的數量。在移動設備上的使用者期望回應的介面,將很快就放棄您的網站或應用程式如果它不執行對他們的期望。
  • 使用 HTML5 和 CSS3 因為移動 Web 流覽器沒有長遺留下來的桌面流覽器,它們採用新興的 HTML5 和 CSS3 標準比桌面同行得更快。在許多情況下,移動流覽器已經遙遙領先桌面流覽器中執行這些功能。利用這個在您移動視圖,以減輕移動流覽器需要下載,讓流覽器做更多的文體渲染的負載。

從我的客戶創建此應用程式時的技術要求之一是展示網站的桌面和移動視圖之間共用控制器邏輯。這一要求是通用的許多客戶,應由開發人員也會受到青睞,因為它極大地簡化了構建應用程式的過程,支援桌面和移動使用者。ASP。NET MVC 3 提供了基於請求的元素例如,請求的流覽器,同時仍共用控制器和模型之間的多個視圖的視圖切換的能力。它還允許開發人員很好地控制在網站上為每個不同的平臺,意味著,開發人員只需要一次建立的業務邏輯,然後調整為每個平臺的演示文稿的經驗。圖 2 顯示決定哪個視圖提供的實用程式函數。

圖 2 實用程式,用於決定哪個視圖的禮物

private ActionResult SelectView(string viewName, object model,
  string outputType = "html")
{
  if (outputType.ToLower() == "json")
  {
    return Json(model, JsonRequestBehavior.AllowGet);
  }
  else
  {
    #if MOBILE
      return View(viewName + "Mobile", model);
    #else
      if (Request.Browser.IsMobileDevice)
      {
        return View(viewName + "Mobile", model);
      }
      else
      {
        return View(viewName, model);
      }
    #endif
  }
}

實用程式函數允許我去見共用相同的代碼決策有關哪個視圖向基於傳入請求的使用者的要求。 如果傳入的請求是請求而不是 HTML 的 JSON 的腳本,控制器可以還回應適當地使用相同的業務邏輯和模型類僅僅適當地設置輸出類型參數。 我還使用尋找移動的條件編譯符號的預編譯器語句啟用調試使用我的桌面流覽器的移動視圖。 啟用此使用其他生成的目標,"移動,"在 ASP 中的。NET MVC 3 專案,並且它允許我跳過 Request.Browser.IsMobileDevice 的檢查,在桌面調試配置中,大大提高了我在生成和調試應用程式的移動版本的辦事效率。

在生成應用程式的同時我還用不同的母版頁移動和桌上出版本的網站。 母版頁的桌上型電腦和移動的版本是解決平臺之間的演示文稿中的差距明顯不同。 移動母版頁包含我的移動特定的 CSS 檔和簡化的佈局結構,以減輕對單個視圖使用 jQuery 移動框架標記和語法的發展。

所有現代移動平臺允許設備 GPS 無線電來確定使用者的當前的位置,通過 HTML5 全球資訊網協會 (W3C) 地理定位 Api 訪問。 在布蘭頓 Satrom 的文章,"整合地理定位到 Web 應用程式"中詳細討論了地理 Api 的使用 (msdn.microsoft.com/magazine/hh580735) 在 2011 年 12 月出版。 雖然這篇文章討論如何使用 HTML5 JavaScript polyfill 支援本身不支援 HTML5 地理定位 Api 的流覽器上的位置,最新的移動流覽器支援 HTML5 地理定位 Api 以本機方式,所以 polyfill 技術很可能沒有必要。 您應該考慮的設備和流覽器您要吸引而您是要評估使用 polyfill 技術的必要性。 一件事來注意專門為 Android 是您需要確保在地理定位調用 enableHighAccuracy 參數設置為"true",以成功地訪問 GPS 功能在 Android 模擬器中,如中所示圖 3

圖 3 地理定位使用 HTML5

if(navigator.geolocation) {
navigator.geolocation.getCurrentPosition(function (position) {
$("#map_canvas").GoogleMap("addMarker", {
id: "device_location",
latitude: position.coords.latitude,
longitude: position.coords.longitude,
description: "Current Location",
iconImageUrl: '@Url.Content("~/Content/images/my-location-dot.png")',
callback: function () {
$("#map_canvas").GoogleMap("panToLocation", {
latitude: position.coords.latitude,
longitude: position.coords.longitude
});
}
});
}, function (error) {
}, {
enableHighAccuracy: true
});
}

使用 jQuery 移動服務

JQuery 移動框架根據專案的 Web 網站是"統一的基於 HTML5 的使用者介面系統所有流行的移動設備平臺上,"(jquerymobile.com)。 它包含多個觸摸優化視窗小部件,並極大地簡化了建設移動 Web 應用程式的外觀和感覺像移動的本機應用程式的任務。 jQuery 移動可以添加到您的 ASP。NET MVC 3 專案通過使用 NuGet 套裝軟體管理器介面的 NuGet 或從套裝軟體管理器主控台,通過運行命令"安裝套裝軟體 jquery.mobile"。這將 jQuery 移動 JavaScript 和 CSS 檔添加到專案中。 您仍然需要對 jQuery 移動 JavaScript 和 CSS 檔的引用添加到您的移動母版頁,如圖所示,在圖 4

圖 4 移動母版頁

    <!DOCTYPE html>
    <html>
    <head>
      <title>@ViewBag.Title</title>
      <meta name="viewport" content="width=device-width,
        initial-scale=1.0, user-scalable=no, height=device-height" />
      <meta http-equiv="Content-type" content="text/html; charset=utf-8">
      <link href="@Url.Content("~/Content/eui_assets/css/reset.css")"
        rel="stylesheet" type="text/css" />
      <link href="@Url.Content("~/Content/jquery.mobile-1.0.min.css")"
        rel="stylesheet" type="text/css" />
      <link href="@Url.Content("~/Content/mobile.css")"
        rel="stylesheet" type="text/css" />
      <script src="@Url.Content("~/Scripts/jquery-1.7.1.min.js")"
        type="text/javascript"></script>
      @RenderSection("PreJQueryMobileInit", false)
      <script src="@Url.Content("~/Scripts/jquery.mobile-1.0.min.js")" 
        type="text/javascript"></script>
      <script type="text/javascript">
        $('a[data-ajax="false"]').live('click', function (event) {
          if (!$(this).hasClass("camera-link")) {
            $.mobile.showPageLoadingMsg();
          }
        });
      </script>
      @RenderSection("Head", false)
    </head>
    <body class="eui_body" id="@ViewBag.BodyID">
      @RenderBody()
    </body>
    </html>

jQuery 移動不會進行一些重大修改其中任何一個 jQuery 的開發人員很熟悉的模式。 引用 jQuery 移動文檔:

您學習 jquery 的第一件事是調用 $(document).ready() 函數內部的代碼,所以一切都將執行,一旦載入 DOM。 然而,jquery 移動、 [AJAX] 用於載入到 DOM 的每個頁面的內容,如您導航和 DOM 準備好處理程式僅執行第一頁。 每當載入並創建一個新頁面,請執行代碼,您可以將綁定到 pageinit 事件。

我為了初始化地圖,當頁面過渡成通過 AJAX 的視圖包含谷歌地圖控制項的應用程式中使用 pageinit 事件,內部的所有頁。

移動母版頁的附加功能是 @ RenderSection("PreJQueryMobileInit", false) 線,所示圖 4,它允許您在頁面上執行之前初始化移動的 jQuery 腳本。 在示例應用程式綁定到 mobileinit 事件,所以我可以設置自訂的回檔,jQuery 移動 listview 篩檢程式行為完成後使用此功能。 我也添加到要添加到清單視圖原型的 filterCompleteCallback 方法,以內置清單篩選已完成時獲得通知的 jQuery 流動圖書館的兩行代碼。 這讓我刷新以匹配篩選清單中的地圖上的匹配的項。 要添加到 jQuery 移動 listview jQuery 移動應用的標記 ; 任何前所需的回呼函數 中所示的 mobileinit 事件處理常式中執行代碼圖 5

圖 5 綁定到 mobileinit 事件

if(navigator.geolocation) {   
  navigator.geolocation.getCurrentPosition(function (position) {
    $("#map_canvas").GoogleMap("addMarker", {
      id: "device_location",
      latitude: position.coords.latitude,
      longitude: position.coords.longitude,
      description: "Current Location",
      iconImageUrl: '@Url.Content("~/Content/images/my-location-dot.png")',
      callback: function () {
        $("#map_canvas").GoogleMap("panToLocation", {
          latitude: position.coords.latitude,
          longitude: position.coords.longitude
        });
      }
    });
  }, function (error) {
  }, {
    enableHighAccuracy: true
  });
}
@section PreJQueryMobileInit {
  <script type="text/javascript">
    $(document).bind("mobileinit", function () {
      $.mobile.listview.prototype.options.filterCompleteCallback = function () {
        // Note that filtercompletecallback is a custom
        // addition to jQuery Mobile and would need to be updated
        // in future revisions.
// See comments in jquery.mobile-1.0.js with SSC 09/12/2011
        var ids = [];
        var $visibleItems = $("#js-work-orders-list").find(
          "li:not(.ui-screen-hidden");
        for (var i = 0; i < $visibleItems.length; i++) {
          var item = $($visibleItems[i]).find("p");
          ids.push(item.text().substr(item.text().indexOf('#') + 1));
        }
        ids.push("device_location");
        $("#map_canvas").GoogleMap("hideAllMarkersExceptList", ids);
      }
    });
  </script>
}

jQuery 移動利用重大新功能 HTML5 中的頁眉和頁腳的標記和資料等 * 屬性。 資料角色屬性確定了應附加到給定元素的行為。 例如,在 MapMobile.cshtml 中的視圖圖 6,我有兩個 div 定義與資料角色 ="頁"屬性。

圖 6 MapMobile.cshtml 標記

    <div data-role="page" id="map_page" data-fullscreen="true"
      data-url="map_page" data-theme="a">
      <header data-role="header" data-position="fixed">
        <a href="@Url.Action("Index", "Home")" data-icon="home"
          data-direction="reverse">Home</a>
        <h1>Map Demo</h1>
        <a href="#" data-icon="back" id="js-exit-street-view"
          class="ui-btn-hidden">Exit Street View</a>
      </header>
      <div data-role="content" class="main-content">
        <div id="map_canvas" style="width:100%;height:100%"></div>
      </div>
      <footer data-role="footer" data-position="fixed"
        data-id="fixed-nav" data-theme="a">
        <nav data-role="navbar">
          <ul>
            <li><a href="#map_page" class="ui-btn-active
              ui-state-persist">Map</a></li>
            <li><a href="#items_page">Work Orders</a></li>
          </ul>
        </nav>
      </footer>
    </div>
    <div data-role="page" id="items_page" data-url="items_page" data-theme="a">
      <header data-role="header" data-position="fixed">
        <a href="@Url.Action("Index", "Home")" data-icon="home"
          data-direction="reverse">Home</a>
        <h1>Map Demo</h1>
      </header>
      <div data-role="content" class="main-content">
        <div class="list-container">
          <ul data-role="listview" id="js-work-orders-list" data-filter="true">
          @foreach (MapItem item in Model.Items)
      {
          <li class="work-order-id-@item.ID">
            <a href="@Url.Action("Details", "Home", new { id = item.ID })"
              data-ajax="false">
              <h3>@item.Issue</h3>
              <p>Work Order #@item.ID</p>
            </a>
          </li>
        }
          </ul>
        </div>
      </div>
      <footer data-role="footer" data-position="fixed"
        data-id="fixed-nav" data-theme="a">
        <nav data-role="navbar">
          <ul>
            <li><a href="#map_page" data-direction="reverse">Map</a></li>
            <li><a href="#items_page" class="ui-btn-active
              ui-state-persist">Work Orders</a></li>
          </ul>
        </nav>
      </footer>
    </div>

此屬性告訴 jQuery 移動這些 div 的每個應視為單獨的頁上移動設備和它們之間的過渡沒有出現在流覽器中的頁面導航使用 AJAX。這將產生中的螢幕擷取畫面所示的效果圖 7。JQuery 的移動 Web 網站提供建議和更多的細節如何使用每一資料-* jQuery 移動上下文中的屬性。


圖 7 通過 AJAX 頁面之間轉換

建設本機移動應用殼

本機應用程式中的貝殼的每個發展中的基本模式設計的應用程式只包含一個全螢幕的 Web 流覽器控制項。此控制項內我捕獲使用者請求一個新的頁面時,將觸發的事件,比較反對應調用本機功能的已知 Url 清單請求的 URL。這是應用的發生在本機應用程式中殼中的基於 Web 的"魔術"的地方。此應用程式而言,我匹配在網站中的 URL 是"首頁/圖像"來調用本機相機功能。在使用者工作訂單詳細資訊頁面上時,他將看到一個照相機圖示在螢幕的右上角,如中所示圖 8。按一下此圖示將調用本機的相機。


圖 8 調用本機的相機功能

Windows Phone

Windows Phone 使用 Silverlight 所有的本機功能。在某些方面,這使得 Windows Phone 支援的移動的 Web 開發人員熟悉 ASP 的最簡單的平臺。NET。基本的 XAML 佈局,本機應用程式命令列程式很簡單,如下所示:

<Canvas x:Name="LayoutRoot" Background="Black" Margin="0">
  <phone:WebBrowser HorizontalAlignment="Left" Name="webBrowser1" 
    Navigating="webBrowser1_Navigating" IsScriptEnabled="True"
    IsGeolocationEnabled="True"
    Background="Black" Height="720" Width="480" />
</Canvas>

要注意這裡是 IsScriptEnabled 設置的關鍵專案為 true — — 因為,預設情況下,在 Windows Phone 中的 Web 流覽器控制項不會啟用腳本 — — 和我正處理導航事件。

MainPage.xaml.cs,在所示圖 9,我處理 webBrowser1_Navigating 事件。如果導航 URL 匹配的我正在尋找的 URL,挑的工作令我一起工作,同時取消 Web 流覽器導航調用本機 CameraCaptureTask ID。之後,使用者將圖片帶相機,photoCaptureOr­SelectionCompleted 方法調用。在這裡,我將圖片上載到 Web 伺服器使用相同的 HTTP 形式會使用該 Web 網站,如果我提交的載于檔上載輸入的按鈕的表單的郵政行動。在圖片上傳完成後,upload_FormUploadCompleted 調用時,返回到 Web 應用程式流的使用者。

圖 9 Windows Phone MainPage.xaml.cs

public partial class MainPage : PhoneApplicationPage
{
  CameraCaptureTask cameraCaptureTask;
  BitmapImage bmp;
  string id = "";
  string baseURL = "http://...";
  // Constructor
  public MainPage()
  {
    InitializeComponent();
    cameraCaptureTask = new CameraCaptureTask();
    cameraCaptureTask.Completed +=
      new EventHandler<PhotoResult>(photoCaptureOrSelectionCompleted);
  }
  private void webBrowser1_Navigating(object sender, NavigatingEventArgs e)
  {
    // Catch Navigation and launch local camera
    if (e.Uri.AbsoluteUri.ToLower().Contains("home/image"))
    {
      id = e.Uri.AbsoluteUri.Substring(e.Uri.AbsoluteUri.LastIndexOf("/") + 1);
      cameraCaptureTask.Show();
      e.Cancel = true;
    }
  }
  void photoCaptureOrSelectionCompleted(object sender, PhotoResult e)
  {
    if (e.TaskResult == TaskResult.OK)
    {
      byte[] data = new byte[e.ChosenPhoto.Length];
      e.ChosenPhoto.Read(data, 0, data.Length);
      e.ChosenPhoto.Close();
      Guid fileId = Guid.NewGuid();
      Dictionary<string, object> postParameters = new Dictionary<string, object>();
      postParameters.Add("photo", new FormUpload.FileParameter(
        data, fileId.ToString() +
        ".jpg", "image/jpeg"));
      FormUpload upload =
        new FormUpload(baseURL + "Home/UploadPicture/" + id, postParameters);
      upload.FormUploadCompleted +=
        new FormUpload.FormUploadCompletedHandler(upload_FormUploadCompleted);
      upload.BeginMultipartFormDataPost();
    }
  }
  void upload_FormUploadCompleted(object source)
  {
    webBrowser1.Navigate(webBrowser1.Source);
  }
  private void PhoneApplicationPage_Loaded(object sender, RoutedEventArgs e)
  {
    webBrowser1.Navigate(new Uri(baseURL));
  }
}

與基於 Web 的版本的 Android 或 iOS 相比的谷歌地圖或必應地圖控制項進行交互時,Windows Phone 有一些不同的行為。 因為的互聯網資源管理器 9 方式移動流覽器沒有傳遞給的 JavaScript 引擎通過捕獲觸摸、 刷卡和捏的手勢,基於 Web 的地圖不能縮放或泛用手勢和必須使用縮放或平移控制項提供的地圖。 由於此限制,今後加強對這一專案將調用本機必應地圖控制項 Windows Phone 互動式地圖功能在要求的位置上,然後回到 Web 應用程式,不需要互動式地圖功能的螢幕上。

Android 系統

Android 的 Java 代碼由我的同事肖恩 · 克裡斯,寫的類似于 Windows Phone 的代碼。 以下 XML 佈局定義為 Android 的 web 視圖控制項的全螢幕佈局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
    <WebView android:id="@+id/webView"
      android:layout_width="match_parent" 
      android:layout_height="match_parent"></WebView>
</LinearLayout>

在 EffectiveUIActivity.java 內, 所示圖 10、 倚仗的重寫設置重寫 onLoadResource 的 WebViewClient 和 shouldOverrideUrlLoading 方法要在 Windows Phone 中使用,如果搜索相同的匹配字串的 web 視圖控制項的發現、 創建與相機活動和取消導航。 該代碼還會覆蓋 onGeolocationPermissionsShowPrompt 壓制的使用者將會產生每次允許訪問 GPS 位置的 web 視圖控制項的許可權運行應用程式時的提示。 執行相機活動後,onActivityResult 函數張貼到 Web 伺服器使用相同的方法,如前面的示例中 Windows Phone 的圖片,然後將使用者返回到 Web 應用程式流。

圖 10 Android EffectiveUIActivity.java

    public class EffectiveUIActivity extends Activity {
      /** Called when the activity is first created.
    */
      WebView webView;
      String cameraId;
      static String baseURL = "http://...";
      @Override
      public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        webView = (WebView)findViewById(R.id.webView);
        webView.getSettings().setJavaScriptEnabled(true);
        webView.getSettings().setGeolocationEnabled(true);
        webView.setVerticalScrollbarOverlay(true);
        webView.loadUrl(baseURL);
        final EffectiveUIActivity activity = this;
        webView.setWebViewClient(new WebViewClient(){
          @Override
          public void onLoadResource(WebView view, String url) {
            super.onLoadResource(view, url);
            if(url.contains("Home/Image")){
              activity.createCamera();
            }
          }
          @Override
          public boolean shouldOverrideUrlLoading(WebView view, String url){
            String match = "Home/Image/";
            int i = url.indexOf(match);
            if(i>0){
              cameraId = url.substring(i+match.length());
              activity.createCamera();
              return true;
            }
            return false;
          }
        });
        webView.setWebChromeClient(new WebChromeClient(){
          @Override
          public void onGeolocationPermissionsShowPrompt(
            String origin, GeolocationPermissions.Callback callback) {
            super.onGeolocationPermissionsShowPrompt(origin, callback);
            callback.invoke(origin, true, false);
          }
        });       
      }
      public void createCamera(){
        Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
        startActivityForResult(intent, 2000);
      }
      @Override
        public void onActivityResult(int requestCode, int resultCode, Intent data) {
          if (resultCode == Activity.RESULT_OK && requestCode == 2000) {
            Bitmap thumbnail = (Bitmap) data.getExtras().get("data");
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            thumbnail.compress(CompressFormat.JPEG, 75, bos);
            byte[] imagebytes = bos.toByteArray();
            ByteArrayBody bab = new ByteArrayBody(imagebytes, "image/jpeg",
              UUID.
    nameUUIDFromBytes(imagebytes).toString()+".jpg");
            HttpClient client = new DefaultHttpClient();
            HttpPost post = new HttpPost(baseURL+"Home/UploadPicture");
            MultipartEntity reqEntity =
              new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE);
            reqEntity.addPart("photo", bab);
            try {
              reqEntity.addPart("ID", new StringBody(cameraId, "text/plain",
                Charset.forName( "UTF-8" )));
            } catch (UnsupportedEncodingException e1) {
                // TODO Auto-generated catch block
                e1.printStackTrace();
              }
              post.setEntity(reqEntity);
              try {
                HttpResponse response = client.execute(post);
                BufferedReader reader = new BufferedReader(
                  new InputStreamReader(
                  response.getEntity().getContent(), "UTF-8"));
                String sResponse;
                StringBuilder s = new StringBuilder();
                while ((sResponse = reader.readLine()) != null) {
                  s = s.append(sResponse);
                }
              } catch (ClientProtocolException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
              } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
                }
                webView.loadUrl(webView.getUrl());
              }
            }
    }

iOS

IOS 的目標 C 代碼也由我的同事肖恩 · 克裡斯,寫的也類似于用於 Windows Phone 和 Android。內所示的 WebCameraViewController.m 圖 11,UIWebView 控制項執行的 shouldStartLoadWithRequest 方法做所請求的 URL 上相匹配的模式。如果 URL 字串匹配,返回"否"將取消導航代碼,並調用本機 UIImagePickerController。這允許使用者從照片庫中選取一個圖像或板載的相機拍攝新圖片。選擇圖片後, 代碼然後張貼圖片回 Web 伺服器使用 ASIFormDataRequest 庫 (ASIHTTPRequest/allseeing-i.com) 才回正常應用程式流返回 UIWebView。

圖 11 iOS 代碼

- (void) choosefromCamera {
    UIImagePickerController *picker = [[UIImagePickerController alloc] init];
    picker.delegate = self;
    picker.mediaTypes = [NSArray arrayWithObjects:(NSString*)kUTTypeImage, nil];
    if ([UIImagePickerController
      isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
      picker.sourceType = UIImagePickerControllerSourceTypeCamera;
    }else{
      picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
    }
    [self presentModalViewController:picker animated:YES];
}
- (void)imagePickerController:(UIImagePickerController *)picker   
    didFinishPickingMediaWithInfo:(NSDictionary *)info {
    UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage];
    NSData *jpg = UIImageJPEGRepresentation(image, 0.3);
    [picker dismissModalViewControllerAnimated:YES];
    [picker release];
    NSString *url =
      [NSString stringWithFormat:@"%@:7511/WorkOrders/UploadPicture", baseURL];
    ASIFormDataRequest *request =
      [ASIFormDataRequest requestWithURL:[NSURL URLWithString:url]];
    [request addData:jpg withFileName:[
      NSString stringWithFormat:@"%@.jpg", [self GetUUID]]
      andContentType:@"image/jpeg" forKey:@"photo"];
    [request addPostValue:cameraId forKey:@"ID"];
    [request setDelegate:self];
    [request setDidFinishSelector:@selector(imageUploaded:)];
    [request setDidFailSelector:@selector(imageUploaded:)];
    [request startSynchronous];
    [webView reload];
}
-(void) imageUploaded:(ASIFormDataRequest *)request {
    NSString *response = [request responseString];
    NSLog(@"%@",response);
}
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(
  NSURLRequest *)request
    navigationType:(UIWebViewNavigationType)navigationType {
    NSURL *url = [request URL];
    NSString *str = [url absoluteString];
    NSRange range = [str rangeOfString:@"WorkOrders/Image/"];
    if (range.location != NSNotFound) {
      cameraId = [str substringFromIndex:range.location+17];
      [cameraId retain];
      NSLog(@"%@", cameraId);
      [self choosefromCamera];       return NO;
    }else{
      return YES;
    }
}

移動體驗的優雅降級

那如果移動 Web 網站的使用者不使用本機應用程式 shell 訪問相機?在此方案中,它是重要的使用者體驗的優雅降級。優雅降級是生成應用程式,這樣,它繼續正常工作,即使在查看與比優化軟體的概念。這並不意味著每個功能的工作是在完全相同的方式,或甚至看起來類似于預定的經驗,但它的目的是確保所有使用者的基本目標仍然可以有即使使用者不獲得最佳的體驗。

要啟用此應用程式中的優雅降級,我建立了 ASP。NET MVC 3 控制器和視圖圖像捕獲 URL,"首頁/圖像,"通過提供一個簡單的檔的本機應用程式中殼捕獲上載表單中所示圖 12。此表單允許使用者不使用增強型手機殼來完成相同的任務,將圖片添加到一個工作秩序,雖然他們得不到集成的經驗。在表單發送到相同的控制器操作使用的本機應用程式中的貝殼,鼓勵所有不同的平臺和視圖之間的代碼重用。


圖 12 簡單檔上載表單的優雅降級

顯著的成本優勢

混合應用方法可以提供獨特的本機應用程式,兩者在長期和短期的顯著的成本優勢。JQuery 移動等工具縮小可用性的差異,可能導致巨大的業務優勢本機設備訪問不是必需的。

與增殖像野火一樣的移動設備,您有幾個選擇,當希望構建移動應用程式:

  • 生成本機應用程式為每個平臺您想要支援這有明顯的優勢,提供最佳的使用者體驗和每個平臺的性能,同時允許訪問所有設備的本機功能和應用程式商店的行銷力量。缺點,但是,是它可能是建立和維護,因為它將需要一個單獨的代碼庫為您希望支援每個平臺的昂貴得多。此外,每個新版本的應用程式要求該應用程式重新提交給應用程式商店。
  • 生成移動 Web 應用程式本有的優勢在於開發、 啟動和更新所有平臺、 最簡單、 最便宜的選項,但使用者體驗可以受到缺乏對本機硬體功能的訪問。缺乏獲得應用程式商店還可以危害您的應用程式,推進市場化的應用程式,您的所有通過。
  • 建立混合本機和移動 Web 應用程式本是,討論的方法,其中提供了獨特的本機應用程式,為每個平臺和移動 Web 應用程式的本機硬體訪問缺乏發展的成本高的固體折衷。此選項還提供應用程式商店,增加您的應用程式的訪問。

請務必注意這些方法都沒有是生來就比其他人 — — 他們都有自己的長處和弱點。全面的成本效益分析的每個選項將有助於確定哪一條路是適合您的使用者與您的業務。作出這一決定時,時,重要的是考慮使用者體驗、 先期開發成本和日常維護成本,以及更多微妙的因素,如行銷和使用者通過。

對於許多業務應用方案,我主張列入的移動 Web 或混合應用程式,如額外的努力,建立獨特的本機應用程式,每個移動平臺應該大於商業效益。商業方案需要仔細檢查移動 Web 或混合應用程式部署的範圍內。

移動應用程式將留在這裡,作為計算陣列的移動體驗遠離傳統的桌面體驗輪班越來越重要。當您想要構建應用程式移動的空間中,記得妥協不總是一個骯髒的字眼,並可能會導致這兩個領域的最佳產品。

Shane Church 是在科羅拉多州丹佛的 EffectiveUI 技術負責人 他一直在 microsoft 開發。重點是 ASP.NET 框架。自 2002 年以來的網和微軟移動計算技術。 他的博客就位於 s church.net。您可以瞭解有關在 EffectiveUI effectiveui.com

多虧了以下的技術專家審查這篇文章:博士。 James McCaffrey