Using -exportArchive instead of Package Application to export an IPA

After upgrading to Xcode 7, my command line build script to build and package ipas started to fail because of this error:

usage of --preserve-metadata with option "resource-rules" (deprecated in Mac OS X >= 10.10)!

--resource-rules has been deprecated in Mac OS X >= 10.10!

I did some research and learned that my method of exporting the ipa by using xcrun and the PackageApplication script uses --resource-rules and there is no way to get around it without actually updating the script itself. Since that seemed like a terrible way to resolve the issue, I did a little more research and found the proper way of exporting an ipa without using PackageApplication. Here is a before and after look at my build script.

Before: xcrun with PackageApplication

# Build the application
xcodebuild \
-scheme "${SCHEME_NAME}" \
-sdk "${TARGET_SDK}" \
-configuration Release build

# Package the application
/usr/bin/xcrun \
-sdk "${TARGET_SDK}" \
PackageApplication \
-v "${PROJECT_BUILDDIR}/${SCHEME_NAME}.app" \
-o "${BUILD_OUTPUT_DIR}/${APP_NAME}.ipa" \
--sign "${DEVELOPER_NAME}" \
--embed "${PROVISIONING_PROFILE}"

After: xcodebuild with -exportArchive

# Archive the application
xcodebuild \
-scheme "${SCHEME_NAME}" \
-sdk "${TARGET_SDK}" \
-archivePath "${PROJECT_BUILDDIR}/${SCHEME_NAME}.xcarchive" \
-configuration Release \
PROVISIONING_PROFILE="${PROVISIONING_PROFILE}" \
archive 

# Export the archive to an ipa
xcodebuild \
-exportArchive \
-archivePath "${PROJECT_BUILDDIR}/${SCHEME_NAME}.xcarchive" \
-exportOptionsPlist "${EXPORT_PLIST}" \
-exportPath "${BUILD_OUTPUT_DIR}"

The -exportArchive method does require a plist file with a few key values. The configuration changes depending on wether it is an enterprise or App Store ipa. This is what the plist files should look like:

Enterprise plist

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>method</key>
    <string>enterprise</string>
    <key>teamID</key>
    <string>**********</string>
    <key>compileBitcode</key>
    <false/>
</dict>
</plist>

App Store plist

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>method</key>
    <string>app-store</string>
    <key>teamID</key>
    <string>**********</string>
</dict>
</plist>

This method has been working great so far. Let's hope it survives the next Xcode upgrade!

References

Here are links to articles that helped me with this:

Building an indoor map with Mapbox

image

One of the first things I was tasked to build at the Met was a map for the Met App. Unfortunately, the Met App was first launched without an interactive map. Given that the Met is an incredibly large and complicated building, it comes as no surprise that users were practically screaming at us to include a map of the museum within the app. In order to quickly add this feature to the app, and to take advantage of an existing pixel coordinate system that we were using for the museum website that mapped gallery locations to our print map, we decided that the best way to move forward and produce an MVP map was to build the map using Mapbox which is built on top of the fantastic Leaflet open source library.

In this blog post, I will walk through some of the steps involved in building an interactive map using a raster image and Mapbox.js.

Generating map tiles

The first thing we have to do is generate map tiles that we can display using Mapbox.js. Mapbox provides us with a number of handy tools to style and customize maps. I used TileMill to generate and upload map tiles to be hosted on Mapbox.

In order to create a map layer with the image file of your map floor plan, the image has to first be converted to a GeoTIFF. This process essentially projects your floor plan image on to the world map to add spatial reference. To get started, we will need to download this:

Once you have installed the GDAL package, you can then add it to your PATH like so:

echo 'export PATH=/Library/Frameworks/GDAL.framework/Programs:$PATH' >> ~/.bash_profile
source ~/.bash_profile

We can now go ahead and use GDAL to translate our image into a GeoTIFF. The projection we will need to use is Google Web Mercator, which can be referenced by the code 'EPSG:3857'. We will also be specifying the bounds of our output file which represent the western, southern, eastern and northern limits of a web mercator map.

gdal_translate -a_ullr -20037508.34 -20037508.34 20037508.34 20037508.34 -a_srs EPSG:3857 first-floor.png first-floor.tif

The resulting GeoTIFF can then be added as a layer in TileMill. In your TileMill project, click on Add Layer and browse to the generated .tif file. Select 900913 as the SRS projection, then click Save and Style. Once you are happy with your new layer, you can click on Export and then Upload in order to upload this as a layer hosted on Mapbox.

Repeat this process for each floor of your building.

Adding floors to your map

To simulate toggling between different floors of the building, I took advantage of Leaflet's layer control and added each floor as a base layer. Note that the floor you want to see on first load has to be added to the map along with the layer control.

L.control.layers(
{
    'G': L.mapbox.tileLayer('ground-floor-layer-id'),
    '1/M': L.mapbox.tileLayer('first-floor-layer-id').addTo(map),
    '2/3': L.mapbox.tileLayer('second-floor-layer-id')
}, 
null, // no overlay layers to add
{ 
    position: 'topright',
    collapsed: false
}).addTo(map);

Setting bounds to your map

You will notice that by default, the floor map will keep repeating itself as you pan around similar to the expected functionality of the world map. In order to disable this, we will have to set bounds to the map object.

var southWest = L.latLng(-180,-180),
    northEast = L.latLng(180,180),
    bounds = L.latLngBounds(southWest, northEast);

map.setMaxBounds(bounds);

One additional thing we will need to do is when we create the tile layer like we did in the previous section, we can specify in the options to not load tiles outside the world width.

L.mapbox.tileLayer('ground-floor-layer-id', { noWrap: true });

Projecting pixel coordinates

The final piece is being able to project the pixel coordinate of a location on your floor plan image to a lat/long on the map. Thankfully, Leaflet provides us with a helper method that can do just that!

var latLng = map.unproject(L.point(x,y), zoomLevel);

Once we have the lat/long values, we can proceed with creating and adding markers to the map as usual.

References

Here are links to articles that helped me get started on this: