App Store Review Times – Automation?

Recently Apple shortened their review times from about 7 days to about 1 day.

You can follow the latest review times at appreviewtimes.com
appreviewtimes

How is Apple doing this? It could be new leadership. It could policy changes. It could be more people. While it may be all of these, I think it has something to do Automation.

Each year Apple has been getting better with their testing tools. In iOS 9, they made a big improvement with UI Testing tools. Xcode now allows you to record your UI Tests. Basically click a record button and then navigate your app in the Simulator. It’s a bit more complicated than that, but almost that easy.

What if Apple modified their testing tools, so the App Store Review team could record their review sessions. Once they have a recording for an app, they can run that recording on new submissions. If it passes, they approve the App. If it fails, they give the app a quick look through and fix the UI test.

They could have started recording session months in advance and then all of a sudden turned it on. If an App hadn’t been approved in a while or it was a new app, the first review would take longer. But after that, it would be fast.

I have no idea if this is what they are really doing, just fun speculation. I hope we’ll learn more at WWDC 2016.

Filed under iOS

Fastlane Frameit Tips

Here are few tips I learned when setting up Fastlane Frameit.

When I first ran fame it, I got this error:

$ frameit white

iconv: /Users/doug/src/my-ios-project/fastlane/screenshots/en-US/title.strings:1:252: incomplete character or shift sequence
[16:37:51]: Could not get title for screenshot ./iPhone6-0.png. Please provide one in your Framefile.json

iconv: /Users/doug/src/my-ios-project/fastlane/screenshots/en-US/title.strings:1:252: incomplete character or shift sequence
[16:37:52]: Could not get title for screenshot ./iPhone6-1.png. Please provide one in your Framefile.json

iconv: /Users/doug/src/my-ios-project/fastlane/screenshots/en-US/title.strings:1:252: incomplete character or shift sequence
[16:37:53]: Could not get title for screenshot ./iPhone6-2.png. Please provide one in your Framefile.json
ç
iconv: /Users/doug/src/my-ios-project/fastlane/screenshots/en-US/title.strings:1:252: incomplete character or shift sequence
[16:37:55]: Could not get title for screenshot ./iPhone6-3.png. Please provide one in your Framefile.json
√
iconv: /Users/doug/src/my-ios-project/fastlane/screenshots/en-US/title.strings:1:252: incomplete character or shift sequence
[16:37:56]: Could not get title for screenshot ./iPhone6-4.png. Please provide one in your Framefile.json

As the documentation clear states: “.strings files MUST be utf-16 encoded (UTF-16 LE with BOM).”

I fixed this by saving the file ast UTF-16 in TextWrangler.
textwrangler_utf-16

Then I ran into a strange error:

$ frameit white
[16:42:10]: `mogrify -gravity Center -pointsize 55 -draw text 0,0 'Text 1' -fill #000000 /var/folders/rf/yz3fgzq17sbgkjcxfq_x6lh40000gn/T/mini_magick20160324-45310-1yizv31.png` failed with error:
mogrify: unable to read font `(null)' @ error/annotate.c/RenderFreetype/1153.
mogrify: unable to read font `(null)' @ error/annotate.c/RenderFreetype/1153.
mogrify: non-conforming drawing primitive definition `text' @ error/draw.c/DrawImage/3165.

[16:42:12]: `mogrify -gravity Center -pointsize 55 -draw text 0,0 'Text 2' -fill #000000 /var/folders/rf/yz3fgzq17sbgkjcxfq_x6lh40000gn/T/mini_magick20160324-45310-qp00up.png` failed with error:
mogrify: unable to read font `(null)' @ error/annotate.c/RenderFreetype/1153.
mogrify: unable to read font `(null)' @ error/annotate.c/RenderFreetype/1153.
mogrify: non-conforming drawing primitive definition `text' @ error/draw.c/DrawImage/3165.

[16:42:13]: `mogrify -gravity Center -pointsize 55 -draw text 0,0 'Text 3' -fill #000000 /var/folders/rf/yz3fgzq17sbgkjcxfq_x6lh40000gn/T/mini_magick20160324-45310-eu9yaq.png` failed with error:
mogrify: unable to read font `(null)' @ error/annotate.c/RenderFreetype/1153.
mogrify: unable to read font `(null)' @ error/annotate.c/RenderFreetype/1153.
mogrify: non-conforming drawing primitive definition `text' @ error/draw.c/DrawImage/3165.

[16:42:15]: `mogrify -gravity Center -pointsize 55 -draw text 0,0 'Text 4' -fill #000000 /var/folders/rf/yz3fgzq17sbgkjcxfq_x6lh40000gn/T/mini_magick20160324-45310-1ie72eo.png` failed with error:
mogrify: unable to read font `(null)' @ error/annotate.c/RenderFreetype/1153.
mogrify: unable to read font `(null)' @ error/annotate.c/RenderFreetype/1153.
mogrify: non-conforming drawing primitive definition `text' @ error/draw.c/DrawImage/3165.

[16:42:16]: `mogrify -gravity Center -pointsize 55 -draw text 0,0 'Text 5' -fill #000000 /var/folders/rf/yz3fgzq17sbgkjcxfq_x6lh40000gn/T/mini_magick20160324-45310-ji9gyl.png` failed with error:
mogrify: unable to read font `(null)' @ error/annotate.c/RenderFreetype/1153.
mogrify: unable to read font `(null)' @ error/annotate.c/RenderFreetype/1153.
mogrify: non-conforming drawing primitive definition `text' @ error/draw.c/DrawImage/3165.

I came across a forum thread that said I needed to install ghostscript. So I installed Ghostscript with Homebrew like:

$ brew install gs
==> Installing dependencies for ghostscript: libtiff, little-cms2
==> Installing ghostscript dependency: libtiff
==> Downloading https://homebrew.bintray.com/bottles/libtiff-4.0.6.el_capitan.bottle.tar.gz
######################################################################## 100.0%
==> Pouring libtiff-4.0.6.el_capitan.bottle.tar.gz
🍺  /usr/local/Cellar/libtiff/4.0.6: 259 files, 3.4M
==> Installing ghostscript dependency: little-cms2
==> Downloading https://homebrew.bintray.com/bottles/little-cms2-2.7.el_capitan.bottle.tar.gz
######################################################################## 100.0%
==> Pouring little-cms2-2.7.el_capitan.bottle.tar.gz
🍺  /usr/local/Cellar/little-cms2/2.7: 16 files, 1M
==> Installing ghostscript
==> Downloading https://homebrew.bintray.com/bottles/ghostscript-9.18.el_capitan.bottle.tar.gz
######################################################################## 100.0%
==> Pouring ghostscript-9.18.el_capitan.bottle.tar.gz
🍺  /usr/local/Cellar/ghostscript/9.18: 709 files, 61M

Then it ran successfully:

$ frameit white
[16:45:21]: Added frame: '/Users/doug/src/my-ios-project/fastlane/screenshots/en-US/iPhone6-0_framed.png'
[16:45:23]: Added frame: '/Users/doug/src/my-ios-project/fastlane/screenshots/en-US/iPhone6-1_framed.png'
[16:45:25]: Added frame: '/Users/doug/src/my-ios-project/fastlane/screenshots/en-US/iPhone6-2_framed.png'
[16:45:28]: Added frame: '/Users/doug/src/my-ios-project/fastlane/screenshots/en-US/iPhone6-3_framed.png'
[16:45:30]: Added frame: '/Users/doug/src/my-ios-project/fastlane/screenshots/en-US/iPhone6-4_framed.png'
Filed under iOS

Managing where rows can be moved to in UITableView

In an app I was working on, I needed to prevent users from moving rows from section 1 to section 0. After reading the docs, I found Apple has just the API: Reordering Table Rows.

Here’s how I used the API. If the proposed destination section is 0, then return the current indexPath. As a result the user will be unable to draw a row from section 1 to section 0.

func tableView(tableView: UITableView, targetIndexPathForMoveFromRowAtIndexPath sourceIndexPath: NSIndexPath, toProposedIndexPath proposedDestinationIndexPath: NSIndexPath) -> NSIndexPath
{
    
    if proposedDestinationIndexPath.section == 0 {
        return sourceIndexPath
    }
    
    return proposedDestinationIndexPath;
}

My problem was simple, but this API could be used for all kinds of reordering problems.

Xcode 7 crashing when toggling Assistant editor

I had a Xcode project which was crashing whenever I tried to close the Assistant editor.  Here is what I did to fix it.

  1. Close the project in XCode
  2. Delete the following files using the Terminal.  Where “myproject” is the name your project.
rm -rf myproject.xcworkspace/xcuserdata

rm -rf myproject.xcodeproj/xcuserdata

rm -rf myproject.xcodeproj/project.xcworkspace

If you don’t feel comfortable using the Terminal, you can also right click on the file and choose “Show Package Contents” and delete the files.

If you don’t use CocoaPods, you may not have a myproject.xcworkspace

This solution may fix other Xcode crashing issues.

Migrate NSUserDefaults to App Groups – Swift

I needed migrate my NSUserDefaults to using App Groups. I came across this post Migrating to App Groups, which was doing exactly what I wanted in Objective-C. Here’s the version I rewrote in Swift:

func migrateUserDefaultsToAppGroups() {
	
	// User Defaults - Old
	let userDefaults = NSUserDefaults.standardUserDefaults()
	
	// App Groups Default - New
	let groupDefaults = NSUserDefaults(suiteName: "group.myGroup")
	
	// Key to track if we migrated
	let didMigrateToAppGroups = "DidMigrateToAppGroups"
	
	if let groupDefaults = groupDefaults {
		if !groupDefaults.boolForKey(didMigrateToAppGroups) {
			for key in userDefaults.dictionaryRepresentation().keys {
				groupDefaults.setObject(userDefaults.dictionaryRepresentation()[key], forKey: key)
			}
			groupDefaults.setBool(true, forKey: didMigrateToAppGroups)
			groupDefaults.synchronize()
			print("Successfully migrated defaults")
		} else {
			print("No need to migrate defaults")
		}
	} else {
		print("Unable to create NSUserDefaults with given app group")
	}
	
}

Gist

Filed under iOS

Errors while installing APK from Android shell

I ran into 2 error while trying to install an APK from the Android shell. Note the apk is on the Android filesystem and I am installing it from the shell, not using adb.

The first error I got was: INSTALL_FAILED_INVALID_URI

$ pm install myApp.apk
	pkg: myApp.apk
Failure [INSTALL_FAILED_INVALID_URI]

This is because you need to give the full path to the apk, like:

$ pm install /sdcard/myApp.apk

The next error I got was INSTALL_FAILED_UID_CHANGED:

$ pm install /sdcard/myApp.apk
	pkg: /sdcard/myApp.apk
Failure [INSTALL_FAILED_UID_CHANGED]

I was installing over an existing app. The first thing I tried was to uninstall the app, but that didn’t work. I found that removing the data directory for the app solved my problem:

$ rm -rf /data/data/com.my.app.myApp/

Cocoapods – Generated duplicate UUIDs

I recently added another target to my Podfile to support tvOS. Here is an example of what the Podfile looks like:

source 'https://github.com/CocoaPods/Specs.git'
use_frameworks!

target 'AppiOS' do
        platform :ios, '9.0'
        pod 'AFNetworking', '3.0.0-beta.1'
end

target 'AppTV' do
    platform :tvos, '9.0'
    pod 'AFNetworking', '3.0.0-beta.1'
end

I got this really long error complaining of duplicate UUIDS. Here is a shortened version:

[!] [Xcodeproj] Generated duplicate UUIDs:

PBXFileReference -- 
.... /Products/AFNetworking.framework

From what I understand, this deterministic UUIDs is a harmless warning and can be disabled by running the following command in the terminal. Then run pod install again.

$ export COCOAPODS_DISABLE_DETERMINISTIC_UUIDS=YES

Downgrading Cocoapods

I recently had some issues with a pre-release version of Cocoapods. To fix it, the suggestion was to downgrade Cocoapods to a previous version. It wasn’t obvious how to do that, but this is what I learned.

First you can figure out which version of Cocoapods you are on with the command:

pod --version

You can also see all the version of Cocoapods you have installed with this command:

sudo gem list cocoapods

Next uninstall Cocoapods. If you have multiple version, you will have the choice of uninstalling all or a specific version.

sudo gem uninstall cocoapods

Finally you can install the specific version with this command:

sudo gem install cocoapods -v 0.39.0.beta.3

Apple Watch Workout App

I’ve been using the Apple Watch Workout App since the end of April. While I really like it, it has a few things which could be better. Here are my thoughts on the Apple Watch 1.0 Workout App.

When you start a workout, you get to choose what type of workout you are doing. For my case I primarily do an Outdoor Run or Outdoor Cycle.

IMG_5432

Then you can choose between an Open Goal, a Distance Goal or a Calorie Goal. Fo my case, I always do an Open Goal.
IMG_5433

You hit the start button and it counts down from 3. I’m not sure why it needs to do this. When I start, I want to start, I don’t want to wait 3 seconds.

Then you are shown a pageable screen. You can page between: Elapsed Time, Speed/Pace, Distance, Calories, and Heart Rate.

IMG_5316 IMG_5317 IMG_4974 IMG_4973

 

Apple got one thing right here.  They always show the time.  I’ve used Run Keeper on the Pebble and it drove me crazy that the time was not visible.  This is a watch, the time should always be visible.

The workout app is almost like a watch face in that it stays the current app while you are working out.  This is nice because it would be a pain to keep on reloading the Workout app.

Consolidated Metrics View

The first thing I’d like to see is a page that has all of these metrics on it. Why should I have to page through all the metrics separately when I could view them at one time.  The text would need to be smaller, but it could all fit.  One problem I have is when working out and my hands get sweaty, it’s impossible to register swipes and taps on the watch.  Sometimes I need to stop and dry my hands off so I can switch between pages.  I would be nice to see them all at one glance.

Notifications

Often while I’m working out, a notification will come in.  And for whatever reason, I don’t have time to check it at that second.  If I don’t check quick enough, it disappears.  To view it, I have to go back to the watch face and swipe down.  This is a lot of steps. Why can’t the workout app be like the watch face.  Put a red dot at the top when their are unread notifications and allow me to swipe down to view them.

Glances

For the same reason, why not give me glances too.  I often want to swipe up to use glances to change a song or some other action.

Loosing Contact

This really annoys me.  I often glance down at my watch and see it back on the watch face with the screen locked.  I probably hit a bump on my bike and it lost contact from my skin long enough to lock.  When this happen, sometimes is continues to track my workout.  Most times it pauses my workout.  Then when you unlock the phone, it loads the workout app and and the spinner just spins.  You have to exit the app and reopen the app.  Try doing this on your bike.  It’s impossible.  I have to stop and make my watch happy again.  The watch should be smart enough.  If I’m in the middle of a workout, please be more forgiving about losing contact with my skin.   I’d even be happy with a preference that I have to accept to make my watch less secure to make this happen.  Or at least notify me when I’ve lost contact and it’s locked.  It’s frustrating to be doing a workout and loose a couple of miles to this.

Sweat

As I mentioned before, the watch is almost impossible to use with sweaty hands.  I’m not sure what can be done about this from a software perspective, except to give me more things that require me to touch my watch less.

Pause While Driving

One thing that Apple got right, is that is pauses the app if you are doing a workout and drive in the car. I’ve done this many times.  I’m out for a run somewhere I had to drive to.  When I finish I get back in the car and drive home.  When using Runkeeper, this would result in extra mileage on my run.  The Apple Watch app stops recording.  This is brilliant.

I’m hoping that Apple Watch 2.0 Software or the next hardware version will fix some of these issues.