Category Archives: iOS

Loading an image from the iOS Library in PhoneGap

23 Aug 2016

While working through one of my personal projects I’ve figured out how to load an image from an iOS device’s Library. There are two steps – first use the Camera plugin to provide a UI for the user to select a file. The next is to take the file path the Camera plugin provides and use the File plugin to load it.

Requirements

This was tested via PhoneGap Build using the following setup:

  • CLI 6.3.0
  • iOS deploy target: 9.0.0
  • Camera plugin version 2.2.0
  • File plugin version 3.0.0

If you’re using PhoneGap Build this is what should be added to your config.xml





Using the Camera Plugin to Access the Library

It might seem counter intuitive to use the Camera plugin since it seems logical to first look at the File API to look for files… unlike the File API where you would need to write your own file browser and UI, the Camera Plugin uses native functionality and so makes it trivial to pick an image from a user’s Library. The Camera plugin will present a native UI to the end-user so that they can navigate their Library’s folder structure to locate the image they want to use and in the end provide a path to that image on the device.

This code will do what is described above:

   navigator.camera.getPicture(
      function (fileURI){
         console.log(fileURI);
         /* remove the comment below to use with
          * the rest of the code on this page
          */
         //convertPath(fileURI);
      },
      function (err){
         // fail
         console.log('fail',err);
      },
      {
         allowEdit: true,
         correctOrientation: true,
         destinationType: Camera.DestinationType.FILE_URI,
         sourceType: Camera.PictureSourceType.PHOTOLIBRARY,
         targetHeight: window.innerHeight,
         targetWidth: window.innerWidth
      }
   );

Literally copy and paste the above, here are the things to note about the configuration object:

  • allowEdit – this is a flag that tells the native Library picker UI to allow scaling/positioning of the resource that the user selects.
  • correctOrientation – as it implies, use the image in the correct orientation relevant to how the device is being held
  • destinationType – this is the part that tells the plugin to return the path to the image
  • sourceType – tells the plugin to display UI to allow the user to select the image from the library
  • targetHeight – the desired height of the image – iOS creates a temporary image and passes that path back to you based on any edits and the Height and Width settings. Here I just assume that you would want an image that is the size of the viewport.
  • targetWidth – see above

That’s it. Dead simple. Now we need to load up the file using the path that the Camera plugin returns which requires the use of the File plugin.

Using the File plugin to Load an Image

This part is trickier and the source of much frustration among developers – during my search for documentation there was no single source that explained how this should work. I was left to putting the parts together from various sources as the “official” documentation didn’t directly explain how to do it. Anyway, I’ll do the explaining here within the code comments.

In short, these are the steps that result in a Base64 serialization of the image from which you can do whatever you like:

  1. Convert the image path to a file Entry Object
  2. Pass the FileEntry Object to a function that converts it to a File Object
  3. Pass the File Object to a FileReader to read the file
  4. Handle the response containing the image data

Here is all of the code:

   /**
    * This takes a file:// URI and creates a file entry object. The operation is asynch,
    * so the resulting fileEntry object is passed to the success callback.
    * @type {Function}
    * @name convertPath
    * @param {String} fileURI - the file:// path to the resource
    * @return {} Returns nothing
    */
    function convertPath(fileURI){
        window.resolveLocalFileSystemURL(
            fileURI,
            function(fileEntry){
                getFileSuccess(fileEntry);
            }
        );
    }

   /**
    * This starts the read process via the file entry object. This is asynch, so the file is passed to the success callback
    * @type {Function}
    * @name getFileSuccess
    * @param {Object} fileEntry - the file entry object
    * @return {} Returns nothing
    */
    function getFileSuccess(fileEntry){
        fileEntry.file(
            readFile, // success
            function(err){ // failure
                console.log('Failed to get file.',err);
            }
        );
    }

   /**
    * This creates a file reader using the file object that is passed to it.
    * Note how similar this is to programatically creating an image and loading data into it.
    * @type {Function}
    * @name readFile
    * @param {Object} file - file object
    * @return {} Returns nothing
    */
    function readFile(file){
    	console.log('got file...',file);
        var reader = new window.FileReader();
        reader.oneerror = function(e){
            console.log('FileReader Error: ',e.target.result);
        };
        reader.onloadend = function(fileObject) {
            console.log('we have the file:',fileObject);
            console.log('the image data is in fileObject.target._result');
        };
        reader.readAsDataURL(file);
    }

You can use the fileObject.target._result to populate the background of a div, for example:

$('#myDiv').css('background-image','url:(' + fileObject.target._result + ')');

Or insert it into a canvas:

   var image = new Image();
   var canvas = document.getElementById('canvas');
   var context = canvas.getContext('2d'); //retrieve context
   
   image.onload = function(){
      context.drawImage(this, 0, 0,_canvas.width, _canvas.height);
   }
   image.src = fileObject.target._result; // load the image data

It’s worth noting that of course you’ll need the appropriate styling for your DIVs if using the resulting image data as a background image. Also, if loading the data into a canvas your aspect ratio may be off – you’ll need to figure out how to scale the data to fit the canvas without distortion.

Create Mobile Provisions and P12 files without a Mac for Cloud Build Services

02 Aug 2016

There are many Windows developers who want to create iOS apps but do not want to make the investment into Apple hardware – one way around it is to virtualize OSX which works OK if your intent is to use Xcode. The other option is the situation where you are planning on using cloud services to build a hybrid app. The great thing about building in the cloud is that you don’t have to have Apple hardware to do so. A barrier that you’ll encounter very quickly is that you need two files to build in the cloud: 1) a Mobile Provision File and 2) a P12 file.

NOTE: Some cloud build services don’t submit the app for you (i.e., PhoneGap Build) while others do (i.e., Ionic’s app workflow). In the former case a Mac will be needed to upload your distribution IPA (your compiled app) to Apple. The instructions on this page will allow you to create files for development and production purposes without needing a Mac.

Being a Windows guy (and to be honest, an Android one as well) that didn’t help me much until I realized that I could do everything without a Mac using OpenSSL.

What follows are all the steps that anyone will need to be able to use a cloud build service to build a hybrid PhoneGap app for both development and distribution.

As a bonus this article wraps things up with instructions on how to use the Application Loader to upload and then submit your app to the App Store.

Install Open SSL

To begin, download and install these two pieces of software:

Install the Microsoft product first, then install OpenSSL.

Add OpenSSL to Your Environment Path

I tried to add the c:\OpenSSL-Win32\bin folder to my environment path but had some issues with it. In the end what I did was to execute everything within the bin folder (as described below) and created a naming convention so that all of the files that I created would stay grouped together at the top of the folder. Specifically, since I was creating everything for development purposes, I used the “_dev” prefix. When I was done it was easy to see all the files.

Well, that was my personal preference – you can come up with your own naming conventions…

Create a Certificate Signing Request

If you installed OpenSSL to its default location then navigate to:

  • C:\OpenSSL-Win32\bin

Once in that directory press SHIFT and RIGHT-CLICK in the Explorer window showing that directory. A menu will appear – click on “Open command window here”.

Your currently using the Windows command prompt, type the following and press “enter” to switch to the OpenSSL prompt:

  • openssl

You will get a warning message when you do this, below is an example of typing in the openssl command and the result:

C:\OpenSSL-Win32\bin> openssl
WARNING: can’t open config file: /usr/local/ssl/openssl.cnf
OpenSSL>

The warning is normal and can be ignored. Enter the following command to create a key substituting the name of the key for your desired key name.

  • genrsa -out mykey.key 2048

Next use this command to create your CSR (Certificate Signing Request), be sure to replace the email, name, etc with the proper values:

  • req -new -key mykey.key -out mycertificate.csr -subj “/emailAddress=your@address.com, CN=Joe Smith, C=US” -config “openssl.cfg”

All done, your CSR is in the bin directory. If you followed the above instructions verbatim then your file is called mycertificate.csr. Below is a screen capture of the above console steps.

openssl

Next you need to upload your CSR to the Apple Developer Portal.

Login, click “All” under the “Certificates” heading and then click the “+” (plus) button to begin the upload process. Click continue/next, etc until you see this screen:

cert_upload

Upload your CSR. If successful you will see the next image. If not then recreate your CSR and try again.

cert_ready

Click the “Download” button to download the “ios_development.cer” file <<< make note of this file as you will need it in a moment. Move this file to the c:\OpenSSL-Win32\bin directory if you are executing your OpenSSL commands from that location.

Add Devices

Before you create a Mobile Provision: In order to be able to install your app on to development devices you need to register them in the developer portal. Again, within the Developer portal click the “All” link under the “Devices” category and then enter the desired name of the device and its UDID.

Create a Mobile Provision file

This time in the Apple Developer Console click “All” under “Provisioning Profiles” then:

  1. Click the “+” icon
  2. Select the appropriate type of provision – in my case it is “iOS App Development”
  3. Click “Continue”
  4. Select the appropriate App ID
  5. Click “Continue”
  6. Select the appropriate Certificate
  7. Click “Continue”
  8. Select the development devices that you want to work with your profile
  9. Click “Continue”
  10. Give your profile a name
  11. Again, click “Continue”…

The provisioning profile has been created, download it and keep it in a safe place as you will need to upload it to your cloud build service (1 of 2 files that you need for that purpose).

P12 Certificate

Along with the Mobile Provision file the P12 (also known as a PFX) is provided to cloud build services to build your iOS app. A P12 is a combined format that holds both the private key and the certificate.

Before you can create the P12 file you have to convert the certificate that Apple provides you after you’ve uploaded the CSR to a PEM file.

To convert the Apple-provided “CER” file (previously referred to as the “ios_development.cer” file) to a PEM:

  • x509 -in ios_development.cer -inform DER -out developer_identity.pem -outform PEM

Where:

  • ios_development.cer is the CER file you downloaded from Apple
  • developer_identity.pem is the desired name of your PEM file

When you issue the above command you will have a “PEM” file in your bin folder. Next you’ll convert that to a P12/PFX using this command:

  • pkcs12 -export -inkey mykey.key -in developer_identity.pem -out my_p12.p12

Where:

  • mykey.key is your key file
  • developer_identity.pem is the PEM file created in the previous step
  • my_p12.p12 is the desired name of your P12 file

Note that:

  • You will be asked for the “Export Password” when creating the P12
  • You will be asked to verify the password

Look for your P12 in the bin folder. That file along with your Mobile Provision file are the files your cloud build service will need to compile your apps. Also, take this moment to write down the password you used for your P12!!


Creating an App Store Production Certificate & Provisioning Profile

PhoneGap Build does a great job of creating IPA’s that you can side-load via iTunes to your i-device. Eventually you’ll be ready to submit your app to the App Store. To do so you repeat the steps that you took to create all the needed files for development, just be sure to choose options relevant for submission to the App Store.

As an aside, Ionic’s workflow now includes direct submission of your apps to the Apple App Store – no Mac needed! In the scenario described in this blog post, however, you **do** need a Mac to submit your app. I recommend finding a friend with a Mac instead of shelling out $$$ to buy one. Or if you’re the adventurous type go ahead and check out Ionic.

Back to the topic at hand – you will *** REPEAT *** all the steps above and only change a couple of things. You will choose options specifically for distributing your app. These two differences are described in the next two sections.

Create App Store Distribution Certificate

As before, follow the same process for creating the Certificate. There is one key difference – you will choose the “App Store and Ad Hoc” option as noted in these steps:

  1. Log into the Apple Developer Portal
  2. Click “Certificates, Identifiers & Profiles”
  3. Click the “+” icon
  4. The “What Type of Certificate do you need?” screen appears. Under the “Production” heading choose “App Store and Ad Hoc
  5. The next screen is titled “About Creating a Certificate Signing Request (CSR)”. Click the “Continue” button
  6. The next screen is titled “Generate Your Certificate” – on this page you will upload the CSR that you created earlier. Go ahead and upload and the Distribution Certificate will be created. Download it and keep it in a safe place.

Create a Distribution Provisioning Profile

Again, you’ve already done this for your development files – repeat them here but choose “App Store” where appropriate:

  1. Log into the Apple Developer Portal
  2. In the left column under “Provisioning Profiles” click on “Distribution”
  3. Click the “+” icon
  4. This page is titled “What type of provisioning profile do you need?” – choose “App Store“, then click “Continue”
  5. The next screen is the “Select App ID” screen. Choose the bundle identifier relevant to your app then click “Continue”
  6. The next screen is titled “Select certificates” – choose the certificate that you created previously. Click the “Continue” button
  7. Next is the “Name this profile and generate” screen – give the profile a meaningful name then click “Continue” to generate your Distribution Provisioning Profile

Build the Production IPA in the Cloud

Log into PhoneGap Build, add your new files (select “Add a key” in the iOS “key” drop down list) and do a build against it. Since the files you are providing are distribution files you will automatically get a distribution IPA as a result. Download the production-ready distribution IPA to the Mac that you will use to submit your app to the App Store.

Upload Your Distribution-Ready App to the App Store

On the Mac make sure that Xcode is installed then start the Application Loader – in the screen shot below I typed “Application Loader” into the finder to locate the application.

submit_to_app_store_1

Enter your Apple ID and Password:

submit_to_app_store_2

Click “Choose” and browse for your Distribution-ready IPA file:

submit_to_app_store_3

Once your app is checked for conformance to submission rules you will see the “Deliver Your App” window. Click the “Next” button to submit the app.

submit_to_app_store_4

The “Deliver Your App” window appears where you can see the progress of your app while it is being submitted to the App Store. To view details of the process click the “Activity” button. Otherwise, just sit back and be patient.

submit_to_app_store_5

The deliver your app screen appears, click “next” to deliver your app. The process took about 2 or 3 minutes for my app to get through the submission process before I was finally greeted with this screen:

submit_to_app_store_6

Click the “Next” button to see the “Thank You” screen:

submit_to_app_store_7b

Submit Your App to the App Store

You must add a new app to the iTunes Connect Portal, including all the needed information, screen captures, icon art, etc before you can submit your app to the App Store. Once you have that done, then you can submit your app using the following steps.

  1. Log in to iTunes Connect.
  2. Click “My Apps”
  3. Click your app to view its details
  4. Click on “Prepare for Submission”
  5. Click “Build” to select your build
  6. Select the build from the list that appears
  7. Click the “Save” button
  8. Click “Submit for Review”

Angular and Scrolling Content

15 Oct 2015

I’m currently enrolled at Thinkful.com in the Angular course. I’m well over half-way through and have been applying Angular to my work projects with great success. One thing that I needed to do was to have scrolling views in my hybrid apps while having other areas of the app remain static. My future goals involve using Ionic – which handles scrolling easily enough, and I suspect, in the exact same way I’ve done it here – but I’m a logical step-by-step kind of guy so for now I’m content with learning Angular and rolling my own UI. That being the case I thought to search for the “Angular” way of getting views to scroll and tried a few of the custom directives available on the Internet.

The easiest to set up was ng-scrollable which technically worked but lacked the finesse of the vaunted iScroll. I also tried angular-iscroll but without much success. I reverted to the usually reliable iScroll but Angular didn’t want to play nicely with it. I then reverted to ng-scrollable thinking I could revisit it and proceeded with my other layout duties only to find that ng-scrollable injected too much crap into the DOM which broke a previously working flex-box-based layout. Attempts to rectify the layout proved to be a waste of time. So, I removed ng-scrollable and instead pursued a native / pure css solution.

The Technique

So lets get back to what I wanted – native-like smooth scrolling with inertia. The answer can be found via CSS by creating a class with the following properties (your wrapping container must have a set width / height, and a setting of “overflow:hidden” on the wrapper will kill scrolling):

.scrollable {
    overflow-x: hidden; // Control how to clip the container's content.
    overflow-y: scroll;
    -ms-overflow-style: -ms-autohiding-scrollbar; // configure overflow scrolling behavior for IE 10
    -webkit-overflow-scrolling: touch; // The magic happens here
}

Each of the above styles are configured as follows:

  • -webkit-overflow-scrolling
    • auto – Non-inertia scrolling. Scrolling stops as soon as your finger no longer touches the screen.
    • touch – Use inertia-based scrolling, the faster you flick the scrollable content the faster it scrolls but continues to a deaccelration scroll whne your finger stops touching the screen. I find this to be the desired behavior as its closest to native on Mobile.
  • overflow-x, overflow-y – These properties specify how to treat overflowing content via one of the following values:

    • visible – Indicates the content is not clipped, it may be rendered outside the content box.
    • hidden – Indicates the content is clipped and no scrollbars are provided.
    • scroll – Indicates the content is clipped and desktop browsers use scrollbars whether or not any content is clipped. For hybrid application development the scrolling behavior mimics native scrolling where scroll bars don’t appear unless you interact with the scrollable content. On Android at least the scrollbar overlaps content so you should add padding to account for it. On desktop the scrollbars use up additional space within the scrolling wrapper.
    • auto – Depends on the user agent – meaning that you should expect the native behavor for the overflowing content.

I’m not too concerned with IE in my hybrid app development but here are the configuration options none-the-less:

  • -ms-overflow-style – Sets the scrolling behavior for overflowing elements in IE (copied from MS).

    • auto – Indicates the element inherits its -ms-overflow-style from its parent element
    • none – Indicates the element does not display scrollbars or panning indicators, even when its content overflows. Unlike overflow: hidden, elements with -ms-overflow-style: none can still be scrolled via touch panning, keyboard, or mouse wheel.
    • scrollbar – Indicates the element displays a classic scrollbar-type control when its content overflows.
    • -ms-autohiding-scrollbar – Indicates the element displays auto-hiding scrollbars during mouse interactions and panning indicators during touch and keyboard interactions.

Notes

If using this on Android / PhoneGap be sure to install the Crosswalk plugin so that you don’t have to worry about compatibility with older webkits.

Also, it is worth noting that sometimes it wont work unless you apply the webkit-specific styling directly to the element like so:

<div id="comeContent" style="-webkit-ovrflow-scrolling: touch;">...

Also worth noting is that if the content of the scrolling element changes that you may need to force the content heights to be recalculate on iOS by inserting a psuedo-element using a simple calc() function to determine its new height:

#comeContent:before {
  content:'';
  width: 1px;
  float: left; // remove from the document flow
  height: calc(100% + 1px); // force the recalculation of the container's height
  margin-left: -1px; // remove from view
}

Other methods of forcing a screen redraw may work as well.

Preventing Webview Bounce and Keyboard Layout Shift

11 Apr 2015

Using libraries over frameworks means that there are few things that you have to do yourself. Preventing webview bounce – as can be seen in mobile Safari – is one of them. This is especially true since you are most likely using a scrolling library such as iScroll. Webview-bounce and iScroll don’t play well together. This issue turns out to be an easy one to solve. simply add this to your project:

document.ontouchmove = function(e){e.preventDefault()};

The next irritation for us web app developers is how the keyboard moves the screen around when it appears. It will move your layout to ensure that the focused input element is visible. The caveat is that iOS will not return the layout to its previous position if the user touches the layout to put away the keyboard instead of tapping the “hide keyboard” button. Even the “hide keyboard” button may not return the view to normal.

This is also easily solved, add this to your project:

var repositioner = null;
$('body').on('blur','input,area',function(){
  repositioner = setTimeout(function(){window.scrollTo(0,0;},200);
});
$('body').on('focus','input,area',function(){
  clearTimeout(repositioner);
});

Why the setTimeout? Well, lets say we only set a listener for the blur event. When an input element gains focus iOS will display the keyboard and shift the entire view up. With the keyboard still visible you could touch and thereby give focus to other input elements. But, giving focus to a different element blurs the previously focused element. This causes the view to bounce up on the blur event and then back down on the focus per what iOS feels like doing. Basically, the view bounces up and down as you touch form elements (this wont happen if you use the iOS keyboard’s built-in input tabbing).

By giving mobile Safari a 200 milisecond timeout we prevent the bouncing screen when touching input elements. Once the user puts away the keyboard the timer is quick and repositions your web app without a perceptible delay.

Mobile Device Detection using the UserAgent

26 Jun 2013

It used to be that you could use css media queries on mobile devices due to the limited screen resolutions available. We knew, for instance, that 480 x 320 was the iPhone’s screen resolution so you could do a media query based on those dimensions and deliver a phone layout, or the 1024×768 resolution was a tablet – which is funny because the iPhone 5 is 1136×640 – so, what is your media query going to do then? Even today designers are making assumptions about the break points for their media queries and there continues to be debate on the topic. I wonder why all the fuss when as time moves on the subject only just gets more confusing and debatable as higher resolutions are no longer solely in the realm of tablets. Today the gamut of resolutions varies to such an extent that you cannot assume that resolution x is a phone or resolution y is a tablet.

IMO if you’re trying to figure out which style sheet to apply you need to first know the sort of mobile device you’re dealing with as that gives you a better idea about the relative physical size of the device to begin with.

The way to do this is via the navigator object’s userAgent property. For iOS things are pretty simple – look for the presence of “iOS” and you know you are dealing with an Apple device. Look for “iPhone” and “iPad” and you know if you have an Apple phone or an Apple tablet.

For Android its mostly just as simple. I say mostly because not every manufacturer sticks to Google’s requirement that the word “Mobile” be present in all Android phone-specific UA strings. Even with that caveat this makes it possible for us to discern an Android phone from an Android tablet.

To take things a step further, you’ll also need to know the Android version numbers as you may find it neccessary to code against specific versions of Android – even within the minor versions – in order to make your app work as desired (or degrade as desired, as the case may be).

So then below you will find my take at this – it is a function that creates an object that contains everything that you may want. At the very least it contains everything that I personally find helpful.

Here’s the code:

...
    var _deviceInfo = {
        ios:false,
        android:false,
        iphone:false,
        ipad:false,
        androidTablet:false,
        androidPhone:false,
        isPhone:false,
        isTablet:false,
        isDesktop:false,
        pixeldensity:1,
        androidVersion:{full:null,major:null,minor:null,revision:null}
    };
	
    (function(){
        var ua = navigator.userAgent.toLowerCase();
        var pf = navigator.platform.toLowerCase();
        var iosTest = /mac.+mobile/gi;
        var andTest = /android/gi;
        var iPhoneTest = /iphone/gi;
        var iPadTest = /ipad/gi;
        var mobile = /mobile/gi;
        var desktopWin = /win/;
        var desktopOSX = /mac/;
        var deviceHeight = window.innerHeight;
        var deviceWidth = window.innerWidth;
        var androidUAVer = /[android\s](\d\.\d\.\d){1}/;
		
        if (iosTest.test(navigator.userAgent)){
            _deviceInfo.ios = true;
        }
        if (andTest.test(navigator.userAgent)){
            _deviceInfo.android = true;
        }
        if (iPhoneTest.test(navigator.userAgent)){
            _deviceInfo.iphone = true;
            _deviceInfo.isPhone = true;
        }
        if (iPadTest.test(navigator.userAgent)){
            _deviceInfo.ipad = true;
            _deviceInfo.isTablet = true;
        }
        if (ua.indexOf('android') != -1 && ua.indexOf('mobile') == -1){
            _deviceInfo.androidTablet = true;
            _deviceInfo.isTablet = true;
        }
        if (ua.indexOf('android') != -1 && ua.indexOf('mobile') != -1){
            _deviceInfo.androidPhone = true;
            _deviceInfo.isPhone = true;
        }
        if(desktopWin.test(pf) || desktopOSX.test(pf)){
            _deviceInfo.isDesktop = true;
        }
        if (_deviceInfo.android){
            var andVer = ua.match(androidUAVer)[1];
            var parts = andVer.split('.');
            _deviceInfo.androidVersion.full = andVer;
            _deviceInfo.androidVersion.major = parts[0]; 
            _deviceInfo.androidVersion.minor = parts[1];
            _deviceInfo.androidVersion.revision = parts[2];
        }
        if (window.devicePixelRatio){
            _deviceInfo.pixeldensity = window.devicePixelRatio;
        }
    })();
...

Ideally you drop the above within your app’s namespace, but once you have it incorporated within your project you can use the _deviceInfo object to learn about the device in question. Take a look at the object declaration at the top – its obvious what you can get out of it.

Note that the isPhone and isTablet properties are just generic “phone” and “tablet” flags and work for both iOS and Android. If you want to get specific, then use the platform specific properties where iphone and ipad are obviously those devices and androidTablet and androidPhone are what their titles infer.

An example application of this is writing device-specific style sheets into your Web app (add break-points for the specific device if you’re so inclined):

...
    (function(){
        var _d = your_name_space.getDeviceInfo();
        if (_d.isTablet && _d.ios){
            document.write('');
        }
        if (_d.isTablet && _d.android){
            document.write('');
        }
        if (_d.iphone){
            document.write('');
        }
        if (_d.isPhone && _d.android){
            document.write('');
        }
    })();
...

Together with responsive layouts the above should serve you well.

Another way to make use of this script is for device-specific feature enablement such as in this case where I’m setting application-wide features in jQuery Mobile:

...
    var _d = your_name_space.getDeviceInfo();
    $.extend( $.mobile,{
        defaultDialogTransition: (_d.android ? 'none' : 'pop'),
        defaultPageTransition: (_d.android ? 'none' : 'fade'),
    });
...