Wednesday, August 22, 2012

App rejected because Apple uses sandbox account when testing

I encountered this about 2 weeks ago when my newsstand app was rejected by Apple because "in-app purchases weren't working". I was quite shocked at the time because I have tested everything rigorously and it was definitely working for me during my own testing. So after further investigation inspecting the server logs, I see the following errors:

 INFO | Apple's response: {"status":21007}

The receipt status code of 21007 means "This receipt is a sandbox receipt, but it was sent to the production service for verification." You can see the complete list of auto-renewable verification status codes here. So it seems that when reviewing apps, apple could be using a sandbox iTunes account to purchase stuff in prod! This was something that I definitely did not expect and cater for in my production server code.

I wasn't 100% convinced about this but further research confirms this: "Always verify your receipt first with the production URL; proceed to verify with the sandbox URL if you receive a 21007 status code. Following this approach ensures that you do not have to switch between URLs while your application is being tested or reviewed in the sandbox or is live in the App Store.". You can see the full article here (faq #16).

That pretty much confirmed it. I was still quite bewildered that after 5 app approvals this is the first time that I have encountered this issue. I guess it depends on your luck what kind of tester you get when your app is being reviewed. Long story cut short the changes I made to my server code looks something like this:

public static Receipt GetReceipt(string receiptData)
{
    const int SandboxReceiptSentToProd = 21007;
    var receipt = GetReceipt(urlProduction, receiptData);

    // GOTCHA: During apple approval, apple could be using a sandbox itunes account to purchase stuff in prod!
    // We have to check if receipt comes back with status 21007, that means the tester had used a sanbox account and
    // that we have to post the receipt to sandbox url instead of production
    if (receipt.Status == SandboxReceiptSentToProd)
    {
        // send receipt to sandbox now
        receipt = GetReceipt(urlSandbox, receiptData);
    }

    return receipt;
}

I'm still not happy that I need to have a sandbox reference in my production code, but you just have to follow by Apple's rules. Hope this helps someone else!

Monday, August 20, 2012

CheckForIllegalCrossThreadCalls on MonoTouch 5.99

One of the first things I encountered after I upgraded to iOS6 (beta 4) and MonoTouch 5.99.2 was that my app would crash intermittently at random places. That got me quite worried, but a bit of googling reveals that this is because of a new feature in MonoTouch 6.0.

So the new version of MonoTouch will check for illegal cross thread calls and crash your app if its been naughty! The property UIApplication.CheckForIllegalCrossThreadCalls defaults to true for DEBUG ONLY. This is meant to educate the developers to fix the illegal cross thread calls.
If you are after a quick fix, you can do this in your AppDelegate FinishedLaunching method:

public override void FinishedLaunching (UIApplication app)
{
    UIApplication.CheckForIllegalCrossThreadCalls = false;

    // your other init code here ...
}

However beware that the correct thing to do will be to fix your actual code!

Tuesday, August 7, 2012

iOS 6 Auto Rotate and Orientation Changes on MonoTouch 5.99 (alpha)

In iOS6, auto rotate and orientation changes have changed quite significantly.

In a nutshell, these are the steps I took to upgrade my monotouch project to work with the new changes:


1. You will need to assign a root view controller to your main application window on FinishedLaunching. This is the crucial bit! I spent quite sometime figuring this out. Basically if you don't set your root view controller, you will get a warning in build output that looks like this: 

Application windows are expected to have a root view controller at the end of application launch

So if previously like me you have this in your FinishedLaunching(UIApplication app) method in main.cs:
window.AddSubview(mainVC.View);

Replace it with this:
window.RootViewController = mainVC;


2. Replace this:
public override bool ShouldAutorotateToInterfaceOrientation (UIInterfaceOrientation toInterfaceOrientation)

With these two lines:

public override bool ShouldAutorotate()
public override UIInterfaceOrientationMask SupportedInterfaceOrientations()


Obviously you will need to specify the orientation masks your view controller support in SupportedInterfaceOrientstions(). For example, if you support all orientations you might want to do this:

public override bool ShouldAutorotate()
{
return true;
}

public override UIInterfaceOrientationMask SupportedInterfaceOrientations()
{
return UIInterfaceOrientationMask.All;

}

That's my experience in getting auto rotation to work in ios 6 on MonoTouch 5.99. Check out the full release notes in the developer portal or if you don't have a login, you can check it out here.

Thursday, May 3, 2012

Background Image Downloading with MonoTouch.Dialog ImageLoader

MonoTouch.Dialog contains a very useful class called ImageLoader to perform background image downloading like iTunes and AppStore.

ImageLoader does not have a DefaultImage property though which I need in my app to display as a placeholder before the actual image gets downloaded or if the download failed. You will need to do a little extra work to assign a default image.

First a little background on how ImageLoader works. ImageLoader caches downloaded images automatically (defaults to 50 images) so calling ImageLoader.DefaultRequestImage will either return a null if the image is not cached or the actual UIImage object if the image is cached. In the former case ImageLoader will queue a download request for the image and then call the interface method UpdatedImage when the download succeeds.

What happens when the download failed though? ImageLoader does NOT call UpdatedImage if the download failed. So you can't assign the your default image there because nothing will be done.

Instead, you should assign your default image immediately after the first call to DefaulRequestImage.

var cover = ImageLoader.DefaultRequestImage(uri, this);

if(cover == null)
{
_ImageView.Image = DefaultImage;
}
else 
{
_ImageView.Image = cover;
}

If the image is cached, your ImageView will display the real image. If it's not the default image will be displayed. In the UpdatedImage callback method, you should just do the following:

#region IImageUpdated implementation
public void UpdatedImage (Uri uri)
{
_ImageView.Image = ImageLoader.DefaultRequestImage(uri, this);
}
#endregion

In the case where download succeeded, this will get called and your default image will be replaced with the real image. If download failed, this method won't get called and your default image stays.

I will post a full sample soon.

You can check the official doco here:
http://docs.xamarin.com/ios/tutorials/MonoTouch.Dialog#Background_Image_Loading

And the actual code for ImageLoader.cs can be found here:
https://github.com/migueldeicaza/MonoTouch.Dialog/blob/master/MonoTouch.Dialog/Utilities/ImageLoader.cs

Monday, April 23, 2012

Monotouch upload error after renaming output assembly

I discovered today the hard way that renaming your iOS project assembly in MonoDevelop under Project Options/Built/Output can result in the following error when you try to upload your app to your iOS device:
Installation failed: AMDeviceInstallApplication returned: 0xe8008001
The solution to this is make sure your output assembly is named the same as your application! For example if my application is called HelloWorld, then in your output folder you should have HelloWorld.app and HelloWorld.exe. If these two are not named the same then you will get the above error.


So lesson learnt.. don't be a smart arse renaming stuff that works!

Saturday, March 31, 2012

Upgrade to Lion for MonoTouch users

These were the steps I took to upgrade from Snow Leopard to Lion to make MonoTouch work with ios sdk 5.1:
1. Install Lion.
2. Check system updates (will install 10.7.3 Lion update if you are not already on it. My usb stick that was purchased from apple store has 10.7.2).
3. Install XCode 4.3 from Mac AppStore (will require your apple login, and will give you the option to delete Xcode 4.2 which I did).
4. Check Monodevelop updates (will install 2.8.6.5 containing XCode 4.3 compatibility).
5. Re-install MonoTouch http://download.xamarin.com/Installer/Monotouch/monotouch-full.dmg This will download MonoTouch 5.2.5 (or whatever the latest version is when you read this blog).
6. Update Versions to latest version if you are using Versions for source control. I use Versions to connect to tfs using svnbridge. More info on svnbridge: http://svnbridge.codeplex.com/
7. Open terminal and run:
sudo xcode-select -switch /Applications/Xcode.app/
to set xcode path to make Versions work properly.
8. Install command tools from xcode/preferences/Downloads

Upgrade OpenFlowSharp to work with retina display

For those of you using OpenFlowSharp for your MonoTouch apps for iOS and is wondering why it is not displaying retina resolution images the following is the fix to make it work:

In ImageUtils.cs line 81, replace:
UIGraphics.BeginImageContext(size);

with:

if (UIScreen.MainScreen.RespondsToSelector(new Selector("scale")))

{

UIGraphics.BeginImageContextWithOptions(size, false, UIScreen.MainScreen.Scale);

}

else

{

UIGraphics.BeginImageContext (size);

}


You'll need the if block to check for "scale" because it is only supported on iOS 4 and above.

iOS sdk 5.1 CATiledLayer Bug - Draw gets called twice for every tile

Ok I've been banging my head as to why the CATiledLayer Draw(RectF rect) method is being called twice resulting in memory crashes in my monotouch app in sdk 5.1. This used to work in 5.0.1 with no problems i.e. tiles are rendered only once as it should. However once I upgraded to Lion 10.7.3 and ios sdk 5.1, this bug appears and each tile seems to be rendered twice. I can tell from a simple Console.WriteLine(rect) in the Draw method which outputs the coordinates of the tile being drawn.

The reason I discovered this is because my app started to crash with SCGen checked (MonoDevelop iPhone Build/Advanced/Runtime Options). This bug is confirmed in apple's official devforum: https://devforums.apple.com/message/638091

Note: you will need to have a login to view the post.

In the meantime, the workaround is to NOT use SCGen for production build (I am already NOT using it for prod builds anyway, I am only using it for dev builds because it is still experimental) and build against 5.0.1 if you still can.

Hopefully Apple will release a fix soon.

FInally...

Finally I have decided to share with the world all my frustrations and discoveries to do with the development world..