Our BlogTips, Tricks, and Thoughts from Cerebral Gardens

Mix and Match Swift 3 & Swift 4 Libraries with CocoaPods

With Xcode 9, it’s possible to mix and match Swift 3 and 4 libraries together. In the build settings for the project you set the version of Swift to use by configuring the SWIFT_VERSION setting. You’re able to override the project setting at the target level, thereby building some targets with Swift 3, and others with Swift 4.

If you’re using CocoaPods as your dependancy manager, there’s an issue when mixing and matching.

As you know, when using CocoaPods, you end up with an Xcode workspace that contains your main project (SampleProject for our example here), and a Pods project. Whenever you run pod install or pod update, CocoaPods will set the SWIFT_VERSION for all targets to be whatever your main project is set to, or it will fallback to Swift 3 if the main project doesn’t have the SWIFT_VERSION specified.

This means Xcode will try and compile all targets with the same version of Swift, regardless of what version of Swift is actually needed. There’s no built-in way for you to specify the version of Swift to use for each pod you’re including. There is a way for the pod maintainer to specify the version needed in the podspec (they need to set pod_target_xcconfig = { 'SWIFT_VERSION' => '4.0' } see XCGLogger.podspec for an example), but I’ve found it’s rare at this time for it to be set (hopefully this post will help change that).

Even if the pod sets the version of Swift to use, we run into a problem when Xcode resolves the setting. Xcode will prefer the target’s direct setting over the podspec’s suggestion, and since pod update always sets a direct setting on the target, the pod spec’s suggestion is never used (not surprising it’s rarely set).

The solution is to add a post_install script to the end of your podfile:

Let's examine this script.

It’s a post_install script so CocoaPods will execute the script after is has updated all of the included pods and updated the project file.

The script starts by looping through the build configurations of the pod project and setting the default Swift version to 4.0 (lines 2-5).

Then it loops through all of the project’s targets (lines 7-19). It checks the target name against a known list of targets (line 8) and sets each of the configurations for matching targets to Swift 3 (lines 10-12). If the target isn’t in the known list, the script unsets the Swift version (lines 15-17), which will allow the pod to set the version itself using the pod_target_xcconfig setting we noted above. If the pod doesn’t set the version, Xcode will use the default Swift version we set at the start.

You will need to tweak the script for your project, specifically to set your default Swift version, and then to add the targets that require a different version on line 8.


If you’ve found this article helpful, please subscribe to the RSS feed

It would be awesome if you’d download our new app All the Rings. It’s free and we really think you’ll like it!

  380 Hits

It’s Time to Transition from The App Store to the App Mall

With the recent announcement of some App Store changes, and WWDC just days away, I figured I’d better write about an idea I had before it’s too late. I’ll keep this much shorter than the version that’s been floating around in my head.

I would suggest that Apple release their grip on the App Store, and start allowing other stores on iOS/tvOS which would, essentially create an App Mall. Open it up so that anyone can create a store. These will be distinct apps developed like any other third party app, clearly branded to avoid user confusion with Apple’s App Store. I envision stores created by brands you already know: TouchArcade, 148Apps, AppShopper, Google, Microsoft, Panic, OmniFocus, RelayFM (for sponsored apps) etc; as well as new ones that will appear.

These stores would be akin to radio stations. If a person likes Rock and Roll, they tune in to a Rock and Roll station. If they prefer Jazz, they listen to a Jazz station. Every once in a while you listen to something different. We’ll have stores that focus on pro apps, stores for games, a store for writers, developers, parents etc. Users will come to know and trust the curators of their favourite stores. This plan delegates some of the curation of apps out to the community where it can be handled much better (just because of sheer numbers). It doesn’t take away from Apple’s App Store curation, rather it enables a method to better group apps and aid with app discovery. Instead of trying to fit 2+ million apps into 25 categories, there will be another layer on top to help sort.

One huge side effect to this plan is that Apple would have more control over it’s own App Store. They will be able to delist a tonne of bad apps, and stop adding new bad apps by raising the criteria that allows apps to be listed in the official App Store. If an app is ugly as sin, riddled with spelling errors, etc, they can refuse to include it in their store, just as Saks Fifth Avenue can refuse to stock substandard products. Right now, Apple has a set of rules, and if your app follows those rules it should be allowed in the store. Ugly apps should never be featured anyway, but they still come up and clutter the search results, they still show up in the “Customers Also Bought” section. With my new plan, those apps won’t show up at all. It’s my belief that Apple has to generally accept any app that follows their rules, or else they’ll start to run afoul of anti-competition laws. Since there is no other way to sell apps to users with iOS/tvOS devices than through the Apple App Store, if they reject apps based on religious beliefs, politics, bad UI, etc, they are preventing other companies from operating, and could get into trouble.

It would sort of be like Panasonic selling a radio, and then saying no Justin Bieber songs can be played on them. How long would it be before Panasonic was dragged into court by the Department of Justice? So my point here is that because Apple would be allowing developers to list their apps in other stores, they’ll be free to be more selective in their own store.

None of this affects app review, signing, pricing or privacy BTW. All apps would still go through review (though it would be more for weeding out malware or buggy software). Apps would still be signed by the developers and installed from Apple’s servers. Just as the TestFlight app can install apps that aren’t in the App Store, third party stores would also be able to use an API to trigger app installs (securely of course, apps wouldn’t be able to install other apps without the user’s explicit permission). The price of an app would be the same, and the payment would still be handled by Apple. So privacy is preserved as Apple would still be the only one to know who the customer is. Apple could still take their 30% (or now 15% in some cases, hopefully more cases soon). The third party store developer would be compensated via the already existing affiliate program. Or depending on the store, they may charge the developer for a listing, just as grocery stores charge food producers for the valuable space on the end of the aisle.

The goal of this idea is to help with app discovery. By opening up the App Store in this way, it empowers the developers in our community to help solve this major problem that’s really hurting the platform, without compromising the security or privacy of the platform that users have come to expect.

Copyright

© dave

  2125 Hits

The First Essential Swift 3rd Party Library To Include In Your Project

As we all scramble to learn this fantastic new language Apple gifted to us at WWDC 2014, we're coming across new ways of doing things, either because the new way is better, or because the old way is no longer possible.

One of the main features that Swift has taken away, is the C preprocessor. That's what enabled #define's to work. A common #define used is for debug logging, to include useful info with every line.

#define DLog(...) NSLog(@"%s(%p) %@", __PRETTY_FUNCTION__, self, [NSString stringWithFormat:__VA_ARGS__])

This lets us go from this:

NSLog(@"Simple Message");
2014-06-08 05:38:54.649 TestApp[35062:60b] Simple Message

to this:

DLog(@"Simple Message");
2014-06-08 05:38:54.649 TestApp[35062:60b] -[TSTAppDelegate application:didFinishLaunchingWithOptions:](0x10961f2e0) Simple Message

We now get a lot more info in our log messages and can see where in our code the log message came from without having to type it in for every line.

In Swift, we lost this functionality. The reason this was traditionally done with a #define, and not a function or class, is so that we can use the __PRETTY_FUNCTION__, __FUNCTION__, __FILE__, and __LINE__ macros. The preprocessor replaces those macros with their actual values. For example, __FILE__ will always contain the filename where the macro is placed for example. If you were to use them in a logging function, the macros would always contain the information of the logging function, not the calling function, rendering them useless.

This looked like it was going to be a major inconvenience in Swift so I filed a radar about it: http://openradar.appspot.com/17170702. After playing with Swift for a while, I've discovered a solution. I've built a library you can use in your projects.

Introducing XCGLogger. My first open sourced third party library, that I think will be essential to add to your project.

The source can be found on GitHub here: https://github.com/DaveWoodCom/XCGLogger, with basic instructions on how to use it.

At a glance, it will change your logs from this:

Simple message

to this:

2014-06-09 06:44:43.600 [Debug] [AppDelegate.swift:40] application(_:didFinishLaunchingWithOptions:): Simple message

By writing code like this:

log.debug("Simple message")

instead of this:

println("Simple message")

A few things to note:

  1. Swift is brand new and in a state of flux, so it — and any libraries using it — will be subject to frequent changes.
  2. This is my first released library, so please let me know what you think and please share any ideas for improvement.
  3. There is a bug in Xcode 6 when using __FUNCTION__. XCGLogger uses a workaround for now, but will remove that once the bug is fixed. See: http://openradar.appspot.com/17219684
  4. This library is intended for use in Swift projects. There's no Objective-C compatibility included in it at this time. If it's a requested feature, it can be added.

Since Swift is brand new, there are a lot of different ways to accomplish the same thing. Over time, some best practices and patterns will emerge. I've used what I think are good practices and patterns in this library and hopefully they'll be helpful for developers as we work to establish what's best. For example, how to store static tokens for dispatch_once calls, shared instances, and global variables etc.

How does this work when I said earlier that using __FUNCTION__ and its friends in a function only gives you the information in the function instead of where it's called? Well, that's the secret sauce I discovered last week. If you set the default value of a parameter in a function to one of the macros, the compiler fills in that value at the calling location giving us the effect we need. Giving us access to the correct macro values inside the function that's called.

If you find this library helpful, you'll definitely find these other tools helpful:

Watchdog - monitors Xcode® and automatically cleans up stale cache files

Slender - cleans up Xcode projects, removing duplicate and/or unused assets

Briefs - powerful app prototyping, lets you and your clients try before you build

Follow me on Twitter @DaveWoodX


This post is part of iDevBlogADay, a group of indie iPhone development blogs featuring two posts per day. You can keep up with iDevBlogADay through the web site, RSS feed, or Twitter.

Copyright

© sabby

  14955 Hits

Every iOS and Mac Developer Needs a Watchdog

Today, Cerebral Gardens introduces Watchdog for Xcode. Watchdog is a helpful utility for iOS and Mac OS X developers that monitors Xcode cache files (DerivedData) and cleans out stale files before they interfere with your builds.

If you’ve been building apps in Xcode for a while, you will see the value in Watchdog instantly as you are familiar with the weird errors that can happen with Xcode. If you’re new to using Xcode, you may not have run into these issues yet, but eventually you will and that’s when Watchdog will save immense time and frustration.

A Watchdog user will no longer see these weird issues:

  • Old images that you've replaced, still showing up in your app.
  • The DerivedData folder growing continuously over time, often taking up 10+ gigabytes of space.
  • Constants/Defines not updating in the app after you've changed them in the code.
  • Localization file changes not being seen.
  • Phantom breakpoints and/or breakpoints stopping on the wrong line.
  • Xcode refusing to run a build on your device, only reporting something obscure like: "Error launching remote program: No such file or directory"

Sometimes the cause is related to your version control system updating files without Xcode noticing. Sometimes it’s random. Regardless, the result is the same: a bad build, time wasted, a frustrated developer, or even worse, an annoyed customer.

These errors can be mind numbing. Let Watchdog be your guard against these errors so you never again have your time wasted.

Watchdog gives you truly clean builds, saves time, and your sanity. It guards, protects and, most importantly, prevents.

Download Watchdog for Xcode

Copyright

© sabby

  13196 Hits

App.net – Get In Early

I'm sure by now most of you have heard of App.Net. A new microblogging type service that aims to be much more. At first glance it looks a lot like Twitter, but it is much better in a few fundamental ways.

The first thing you'll notice is that you have 256 characters per 'post' instead of Twitter's 140. It is very liberating. Think in terms of 140 characters, and then actually spell out all of the words, use punctuation and clearly articulate your point.

Ok, who's kidding? That's actually the second thing you'll notice. First you'll notice that you have to pay for an account! WHHHHHAAAAAAATTTT? Pay? Money? For a social network? Aren't they all free? - No, social networks such as Twitter, Facebook, Linked In, and pretty much most of the others (excluding Ping1) are not free. You don't pay with money, but you pay with time, attention, and the loss of personal privacy. With most social networks, you are not the customer, you are the product being sold2. With App.Net, you are the customer, and so you're the one paying with money. A normal member account is $50 per year. Which, is arguably cheap compared to Twitter. WHHHHHAAAAAAATTTT?

Let's take a quick look using some rough guesstimates comparing just 1 metric that you use to pay for your Twitter account: Time. Twitter is now starting to sell a lot of ads (promoted tweets) on its network. They've even started to push into selling a high volume of small accounts to users such as you and me, not just to the big guys. They've been giving away $100 starter accounts to promote their ad service. So, here's where we start to guess some numbers, we'll attempt to guess low in order to give Twitter the benefit of the doubt.

  • Assumption 1: on an average day, you see 15 tweets that are ads (promoted tweets, spam tweets, etc).
  • Assumption 2: it takes you 2 seconds to read/recognize and discard/move on.
  • Assumption 3: your time is worth more than $20/hour.

This means, over the course of a year, you're wasting 365 days * 15 tweets * 2 seconds = 10950 seconds = 182.5 minutes = 3 hours. For a monetary cost of over $60/year. And I know my time is worth a lot more than $20/hour.

This example ignores the cost you pay with your lack of privacy; Twitter and Facebook both track you as you move from site to site on the Internet, even when you're logged out!3

Two other benefits of App.Net that I won't get into detail on are: conversation views — much improved as you can see the whole thing, instead of having it forked into uselessness. It's a platform, not just a Twitter clone. Other apps and services that aren't possible with Twitter are being built right now on top of App.Net.

So, I encourage you to get on board with app.net, own your content, be the customer, and if you act pretty fast, get the name you always wanted....

Follow me on App.Net as @davewood and @cerebralgardens.

Footnotes:

1. No one uses Ping so there's no cost in time. (back)

2. Original quote via Gruber (back)

3. Original research (back)


This post is part of iDevBlogADay, a group of indie iPhone development blogs featuring two posts per day. You can keep up with iDevBlogADay through the web site, RSS feed, or Twitter.

Copyright

© sabby

  6943 Hits

WWDC 2013: Ideas For Expansion

WWDC 2012 sold out in less than 2 hours, a record that had been easily predicted by many. Tickets went on sale at about 8:30 Eastern time and were sold out before most people on the west coast had even woken up. The demand for tickets was obviously extremely high, and the supply was limited to about 5000. How can Apple solve this for next year?

Of course, the first question is, does Apple even want to solve this. I believe they do. They have information they want to put into the hands (and heads) of developers, as many developers as possible. That's why they release the videos shortly after the event. That's why they've had the free Tech Talks in various cities. So yes, Apple does want to get the information out to many developers as they can. So it's to their advantage to increase the supply of tickets for WWDC.

One idea that often comes up when this topic is discussed is to have two events. Perhaps keep the first one in San Francisco and have another a couple of weeks later somewhere in Europe. The SF one would have the main Keynote for press, just like the present; the Europe one would skip the keynote but have all of the same sessions. This would temper the expectations of the press, who may be disappointed if the event were held months later without additional product announcements. The SF one would still be called WWDC, but would now actually be 'Western World Developers Conference', the Europe one would be EWDC, 'Eastern World Developers Conference'. The downside of this scenario for Apple is the increased cost, not just the direct costs of hosting an event in Europe, but the time involved in tying up Apple's engineers for an additional week (or two including travel/prep etc).

My personal suggestion is to keep it as one event, but increase attendance to about 10,000. The most common argument against this idea is that Apple likes to have a roughly 1:5 engineer to developer ratio, and they don't have enough engineers available to maintain that ratio if they double the developer attendance. I've only been to one WWDC, so this could be inexperience talking, but to me, the number of engineers there was almost irrelevant. Each session had 1-5 engineers on stage, but it didn't matter whether there were 500, 1000, or 2500 people in the audience, only the size of the room affected how many people could attend. Where the number of engineers matters is in the labs (which are more like Q&A sessions than labs). So I have an idea to increase the usefulness of the labs for everyone, while at the same time increasing the efficiency so that the same number of engineers can support 10,000 attendees.

The idea is to have attendees submit the questions that they intend to ask an engineer in a lab, to a special WWDC lab email address (along with their project source if applicable). These questions will be prescreened by Apple engineers (or even interns) way before WWDC. Some questions will be simple enough that an email response will be enough to answer them. For the rest, an appointment at WWDC can be scheduled with an engineer that can actually answer the question for the developer. In a lot of cases there will be duplicate questions that can be answered in a group session. This plan will reduce the need to have such a high ratio of engineers to developers while increasing the value of engineer and developer meetings. No more lining up to talk to an engineer that doesn't know any more about the problem you're trying to solve than you.

The only other logistic is how to fit an extra 5000 people ino the sessions. My plan there would be to expand to Moscone North and/or South, and make all of the rooms bigger. Same number of sessions, same number of engineers, just a larger audience. I heard developers this year talk about how long the lines were to get into each session, and that they weren't that long in previous years. But the consensus seems to be that Apple was just way better organized this year than in previous years and that the lines just looked longer than a massive mob of unorganized people. My point here is that Apple did a great job of moving the 5000 attendees around this year. If they increase the time between sessions a little, it should be possible to move 10,000 people around the 3 buildings efficiently.

Whatever the plan, I can't wait to attend WWDC 2013.

If you enjoy reading my blog, please follow me on Twitter, and/or like Cerebral Gardens on Facebook.


This post is part of iDevBlogADay, a group of indie iPhone development blogs featuring two posts per day. You can keep up with iDevBlogADay through the web site, RSS feed, or Twitter.

  24243 Hits

What a Week! WWDC 2012 Edition

I've been at WWDC for the last week. This was my first WWDC but it certainly won't be my last. For my iDevBlogADay post I'm going to try and share some of the things I learnt over the last week. Nothing that's covered by the NDA of course.

1) It was great to finally meet some of the big wigs in the community. Drinking beer with Jeff LaMarche and the other MartianCraft guys. Hanging out with the Empirical Development guys that I've been working with for most of the last year was awesome. (Empirical is hiring BTW, so check us out if you're looking for work). And getting to pitch Party Doodles to Eli Hodapp (of Touch Arcade) and Victor Agreda, Jr of (TUAW) in person was amazing. I'm sure it helped that Apple basically used Party Doodles as an example of how to do an AirPlay game correctly.

2) Probably the biggest shock to my system was the amount of walking involved. As someone who normally sits at a desk for 12+ hours a day, it was a major change to walk back and forth from my hotel 2 or 3 times each day. Why 2 or 3 times you ask, depending on whether or not I took my laptop to the sessions and wanted to drop it off at my hotel before dinner/socializing etc, or based on meetings with various people I had scheduled between sessions.

3) In most cases, you do not need to take your laptop with you to the sessions or labs. I had a completely incorrect assumption of what the labs were. Labs should be considered more like Q&A sessions with Apple engineers. They are not planned tutorials or anything scripted. They're just a chance to ask a question, sometimes with someone who may have helped build the system you have a question about. The only time having your laptop with you is probably if you need to show an engineer your code during your Q&A (lab) session.

4) For the labs, my own experience was pretty dismal in this regard. I had a few questions to ask about various topics, and each time, the engineer(s) I was talking to had no more information to provide on the issues. That being said, I heard of some people that had much more successful visits to the labs.

5) The actual sessions where amazing. Some covered brand new information about iOS 6 or Mountain Lion, while others covered older information that you might have missed. Sometimes you see something presented that's been available for a while that you just hadn't seen and you think "this will save me hours". When the session videos are released, make sure you watch as many as you can. Even if you think you already know about a topic. There are always extra little tips that are invaluable.

6) When you attend a session in person, please use some common decency and follow these four rules:

  1. When sitting down, move to the center of the row, don't 'end cap' the row by sitting in the first seat. Most sessions fill the entire room and when everyone has to fill in rows by jumping over a person sitting in the first seat, it's pretty annoying.
  2. Wait until the speaker has finished talking before running out to the next session. We all have to get to the next session at the same time, give the speaker the respect they deserve by letting them finish.
  3. Do not use a MiFi device. They jam the provided WiFi and in some cases prevented even the presenters from being able to demonstrate what they had planned.
  4. Take your trash with you. If you bring in a drink, lunch etc, just take the garbage with you when you leave and drop it in the garbage bin or recycling etc. I think they teach this in kindergarden but it appears some people missed that day.

7) Related to #2 above, the choice of hotel is important. The closer the better (or at least the less walking you have to do). But there are other issues. I only have experience with the one I stayed at this year, Parc 55 Wyndham, but I'm pretty sure I won't be staying there again next year. The room was nice, clean etc, most of the staff were nice and helpful. My issues with the hotel were

  1. the network is awful. Wifi or wired, it wasn't strong enough to keep iChat connections alive. And they charge $15/day ($50/week).
  2. the included breakfast only includes pastries, you can add eggs and bacon for $25!
  3. the elevators are extremely slow, taking up to 10 minutes to go up and down.
  4. the TVs are locked down and prevent you from adding your own input, no connecting Apple TV or your laptop for example. That made testing some changes to Party Doodles impossible.

8) Since I'm Canadian and our roaming fees are insane, I wanted to pick up a local SIM card in order to be able to use data whenever I needed. I have an unlocked phone so it should have been easy. Eventually I went AT&T, $50 for unlimited voice and text, and $25 for 1G that they said wouldn't work on an iPhone and that they wouldn't refund the cost if I couldn't get it to work. After putting in the SIM card, it took all of about 30 seconds to switch the APN using the site: http://www.unlockit.co.nz/. The AT&T network has been great the whole week (Keynote excluded, but nothing was working there).

9) J.J. Abrams. Wow. He was a guest speaker for the Friday lunch session. And boy was his talk amazing. For one, he was by far the most entertaining speaker of the week, granted his content makes it easier, blowing up stuff is more exciting by itself than NSManagedObjects being accessed by the wrong NSManagedObjectContext. But his way of presenting was great, it almost felt like it was just me and J.J. in the room and he was telling me stories from his life. It was very interesting to hear how certain ideas/shows came to be due to other events in his life, in much the same way we move from app to app where the first app may inspire the idea for the second app. I'd love to go into more detail here, but it seems even this talk is covered by the NDA. J.J., if you're reading this (maybe Google Alerts brought you here), I just want to say thanks for the awesome and inspiring presentation.

10) One last point. Since it was my first WWDC, I wasn't sure when I should be here, so booked my flight for Saturday to Sunday. Getting here on Saturday worked out well, gave me some time to get to know the area and meet up with people for drinks etc. But next year I'll leave Friday night or Saturday morning. There wasn't much happening on Saturday or Sunday as most people have already left.

I'd say WWDC (I'm not yet cool enough to be able to call it "dubdub") was a great success this year. I can't wait for next WWDC 2013! It'll sell out super fast again next year, so be prepared...

If you haven't already, please download my free game Party Doodles, like us on Facebook, and if you like to hear me ramble, follow me on Twitter.


This post is part of iDevBlogADay, a group of indie iPhone development blogs featuring two posts per day. You can keep up with iDevBlogADay through the web site, RSS feed, or Twitter.

  10350 Hits

Introducing Party Doodles, And The Lessons It Has To Share

Last Wednesday we released our newest game, Party Doodles. A unique picture guessing game that uses AirPlay mirroring to create a party game experience unseen before now on the App Store. It has been in the works since AirPlay mirroring was announced (yes, almost a year from concept to release).

I believe, and app reviewers (here, here, and here) seem to agree, that Party Doodles is the first of it's kind on the App Store. The tag line for the game is 'Made for iPad, designed for Apple TV' because while you can play without Apple TV, the game is really meant to be played on the Apple TV.

The easiest way to describe the game, is to compare it to Pictionary. A group of friends break into two teams, each person takes a turn drawing clues (on the iPad) while their teammates try and guess what it is (watching on the big screen TV) before time is up. If time runs out, the other team can steal the points. I added some strategy to the game by having 3 difficulty levels baked into each turn, the person doodling can pick an easy topic to win 1 point, a medium topic for 2 points, or a hard topic for 3 points. If you're behind the other team it gives you a chance to catch up, or if you're winning, a chance to get even further ahead!

The game really is a lot of fun, and I'm not just saying that cause I made it. I had people actually come over to my house and ask to play it. And it's not uncommon for players to break out into uncontrollable laughter.

Anyway, enough plugging the game, please download it, it's free, and give it a try.

On to what lessons the game has for us, as developers....

First, it's important to know that progress on the game was sometimes slow as I was working full time at a normal job when I first started working on the game. Even when I went full time indie, client projects usually take precedence and consume the majority of my time. I recently looked back at my source commits and noticed several months where I hadn't committed a single line.

A lot of things happened over the course of the development of the game. One of which was that Draw Something came out and became a massive hit. There was a definite 'Oh shit' moment at that point. But it didn't take long to come to terms with the fact that the games are significantly different. I never intended Party Doodles to have mass market appeal anyway. It's definitely for a niche market since the requirements to play properly are high; you need:

  1. an iPad
  2. an Apple TV
  3. actual friends in the same room1

The goals for Party Doodles were to create a unique experience doing something that no other app on the store was doing yet (I expect more will come along shortly), to try the freemium model out, to create a fun game that people can really enjoy playing together, and to get a little attention for the game itself since it is the first of it's kind on the App Store, not an easy feat nowadays.

I've definitely learned a lot while developing this game and plan to share a bunch of tips/tools and code over the next few blog posts. Today's tip is a short one though (since I used up so much space detailing the game in the first place).

When releasing an app with In-App purchases, make sure you test the purchase functionality with the live app from the App Store as soon as it's available. (Use a promo code to download the app after it's approved and before it's released if need be). There are some slight differences between the Production IAP system and the sandbox we can get to test with before submission. In my case, I added receipt verification as suggested by Apple (using PHP on the server, with code I'll likely share in a later post) and the verification process would fail on production requests only due to a minor difference in data sent back from Apple. That caused the first few purchases to be rejected. Not a good user experience at all! Even Apple's reviewers test the app in the sandbox and thus missed my production only bug.

Fortunately I was able to fix it fast since it was just server side code and not in-app code that needed to be changed.

Ok, lots more to come in future articles so stay tuned. In the meantime, please download the game, and like us on Facebook.

1As a french review (Google Translation) of the game pointed out, you also need a TV which they said makes my free game very expensive. I hadn't considered that someone would add the TV to the cost of my game...


This post is part of iDevBlogADay, a group of indie iPhone development blogs featuring two posts per day. You can keep up with iDevBlogADay through the web site, RSS feed, or Twitter.

Copyright

© sabby

  7795 Hits

Improving Your Image Workflow

No doubt you're excited about the new iPad being released next Friday. Me too! I've been hoping for an iPad with a Retina display since the days of the original iPad. In celebration, here are a few tips to make creating your images a little easier. These of course work for iPhone too.

{emailcloak=off}First, it goes without saying (but I'll say it anyway), you need to create the high res version of the image to start with. I use Adobe's Illustrator and Photoshop for most of my graphics. (Actually, my Wife uses Illustrator mostly, while I import her stuff into Photoshop for any pixel level tweaks and to ensure sizes are consistent etc). For the most part, that means I have images in Photoshop with one image per layer. I use one .psd file for all images of the same size in the app.

When I have a part of the app that needs multiple images, say for a menu, each menu item will be on it's own layer, if there are separate overstate images, they also get their own layer.

Even Steven

Make sure your canvas width and heights are even numbers. If you have an odd number you'll get slight cosmetic errors and off by 1 pixel effects.

Mass Export

Name the layer with the desired filename for that image. Eg: btnMenuItem1@2x etc. Make sure you include the @2x part.

Now you can use Photoshop's Export Layers as Files script to quickly create PNG's of all the images in your .psd file. Leave the File Prefix blank, and make sure you uncheck the 'Trim Layers' option which is always defaulted to on or some of your image sizes will be altered and could end up odd.

Honey, I Shrunk The Files

Next you're going to need to do some setup.

Setup Step 1: We're going to use the awesome ImageMagick tools, so you'll need to install them. The quickest way is to use MacPorts, follow the instructions here.

Setup Step 2: Copy the following script to somewhere in your path. I use ~/bin, but other options will work.

~/bin/1x (Download):

#!/bin/bash

## Copyright (C) 2012 Cerebral Gardens http://www.cerebralgardens.com/
##
## Permission is hereby granted, free of charge, to any person obtaining a copy of this
## software and associated documentation files (the "Software"), to deal in the Software
## without restriction, including without limitation the rights to use, copy, modify,
## merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
## permit persons to whom the Software is furnished to do so, subject to the following
## conditions:
##
## The above copyright notice and this permission notice shall be included in all copies
## or substantial portions of the Software.
##
## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
## INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
## PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
## HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
## CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
## OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

CONVERT=/opt/local/bin/convert
IDENTIFY=/opt/local/bin/identify

if [ $# -lt 1 ] ; then
	echo "Usage: ${0} filename@2x.ext"
	exit
fi

for ARG in "$@"; do
	FILEPATH=`dirname "${ARG}"`
	FILENAME=`basename "${ARG}"`
	FULLNAME=${FILEPATH}/${FILENAME}

	PHOTOSHOPTEST=${FILENAME#_[0-9][0-9][0-9][0-9]_}

	if [ "${PHOTOSHOPTEST}" != "${FILENAME}" ] ; then
		mv "${FULLNAME}" "${FILEPATH}/${PHOTOSHOPTEST}"
		SOURCEFILE=${PHOTOSHOPTEST}
	else
		SOURCEFILE=${FILENAME}
	fi

	IMAGENAME=`expr "${SOURCEFILE}" : '\(.*\)@2x.png'`
	EXTENSION=`expr "${SOURCEFILE}" : '.*@2x.\(png\)'`
	DESTFILE=${IMAGENAME}.${EXTENSION}
	FAILNAME=.
	BLANKS="                                                  "

	if [ "${DESTFILE}" != ${FAILNAME} ] ; then
		${CONVERT} "${FILEPATH}/${SOURCEFILE}" -resize 50% "${FILEPATH}/${DESTFILE}"

		FILENAME_LENGTH=${#IMAGENAME}
		SOURCE_WIDTH=`${IDENTIFY} -format "%W" "${FILEPATH}/${SOURCEFILE}"`
		SOURCE_HEIGHT=`${IDENTIFY} -format "%H" "${FILEPATH}/${SOURCEFILE}"`
		DEST_WIDTH=`${IDENTIFY} -format "%W" "${FILEPATH}/${DESTFILE}"`
		DEST_HEIGHT=`${IDENTIFY} -format "%H" "${FILEPATH}/${DESTFILE}"`

		echo "${IMAGENAME} - @2x : ${SOURCE_WIDTH}x${SOURCE_HEIGHT}"
		echo "${BLANKS:1:${FILENAME_LENGTH}}   @1x : ${DEST_WIDTH}x${DEST_HEIGHT}"

	else
		echo "Skipping: " "${FULLNAME}"
	fi
done

Once you're setup, and after running the export script, you'll have a series of files in the export folder with names like _0000_btnMenuItem1@2x.png, _0001_btnMenuItem2@2x.png etc. Drop to a terminal in that folder (Go2Shell works great for this). And run the script you created above for each file you've exported. You can do this one file at a time:

1x _0001_btnMenuItem2\@2x.png

or for a collection of files at once,

1x *

What this script does, is chop off the _xxxx_ part of the filename (if it's there), then create a @1x version of the image with the appropriate filename. It will skip any file that doesn’t match the filename@2x.ext format.

The script will also print out the original image dimensions along with the new image dimensions as a quick way to ensure you don't have an odd sized image. (It's ok for the @1x image to have odd dimensions).

Size Apparently Does Matter

Take a look at the sizes of your final images. Apple recommends using PNG files, but they're not always the best solution. Some images just do not compress well as PNG files and are better suited as JPG files (assuming you don't need alpha transparency). For example, an @2x image in my upcoming iPad game was over 5 mb in size as a PNG and just 305 kb as a JPG. There is no discernible difference in the way the file looks or behaves as a JPG, but 10 million users (fingers crossed) will each save 6 mb of space on their iPads (the @1x version went from 1.3 mb to 85 kb).

Get Your App To The Gym

Check out Slender in the Mac App Store. Run your project through it and it'll catch any odd size images, or extra images you're no longer referencing in your app. It can even automatically remove the extra images from your project for you (saving a backup first of course).

On a completely unrelated note, I've launched a Facebook page for Cerebral Gardens tonight. It would be swell (if you use Facebook) if you'd head over there and do the Like thing. - Thanks!


This post is part of iDevBlogADay, a group of indie iPhone development blogs featuring two posts per day. You can keep up with iDevBlogADay through the web site, RSS feed, or Twitter.

  8055 Hits

Enumeration of NSMutableArray's Follow Up

Today I'm going to follow up on some interesting comments to my previous tip regarding enumerating NSMutableArray's.

I had suggested to make a copy of the mutable array before enumerating, in order to prevent another thread from modifying the array you're working on and causing a crash.

It was mentioned in the comments that making that copy is itself an enumeration and so we were just moving the crash point. So, I did some digging.

// array is an NSMutableArray
- (IBAction)button1TouchUp:(id)sender
{
    DebugLog(@"Populating Array");
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
    ^{
        for (int i = 0; i < 10000000; ++i)
        {
            [array addObject:[NSNumber numberWithInt:i]];
        }
        DebugLog(@"Done Populating Array: %d", [array count]);
    });
}

- (IBAction)button2TouchUp:(id)sender
{
    DebugLog(@"Enumerating Array");
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
    ^{
        DebugLog(@"Copying Array");
        NSArray *copyOfArray = [array copy];
        DebugLog(@"Done Copying Array");

        long long x = 0;
        for (NSNumber *number in copyOfArray)
        {
            x += [number intValue];
        }
        DebugLog(@"Done Enumerating Array: %d", [copyOfArray count]);
    });
}

Note: if you test this, do so in the simulator, a device will generate a memory warning and shutdown the test app.

The idea here is that by touching button1, we create a large array in a thread, and touching button 2, we enumerate that array. The array is large enough that it gives you time to ensure you touch button 2 while button 1's thread is still populating the array. If you didn't create the copy of the array before enumerating it button 2, the app will crash. Using the copy of pattern I mentioned in the last post, prevents that crash as expected.

Taking this a little further, the test code shows that when you take a copy of the mutable array, you get a copy of it at in its current state. If something is altering the array when you make the copy, you could get a copy of the array in an unusable or unexpected state. If you were adding or removing elements for example, you might have half of the elements in your copy while expecting all of them, or none of them.

If you need to ensure changes to an array are completed in full before another thread accesses it, you can use the @synchronized directive. The modified code looks like:

- (IBAction)button1TouchUp:(id)sender
{
    DebugLog(@"Populating Array");
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
    ^{
        @synchronized(array)
        {
            for (int i = 0; i < 10000000; ++i)
            {
                [array addObject:[NSNumber numberWithInt:i]];
            }
            DebugLog(@"Done Populating Array: %d", [array count]);
        }
    });
}

- (IBAction)button2TouchUp:(id)sender
{
    DebugLog(@"Enumerating Array");
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
    ^{
        @synchronized(array)
        {
            DebugLog(@"Copying Array");
            NSArray *copyOfArray = [array copy];
            DebugLog(@"Done Copying Array");

            long long x = 0;
            for (NSNumber *number in copyOfArray)
            {
                x += [number intValue];
            }
            DebugLog(@"Done Enumerating Array: %d", [copyOfArray count]);
        }
    });
}

The @synchronized directive creates a lock on the object for you.

A few points to be aware of:

  1. you need to use @synchronized around all places where you need to lock the object.
  2. the code will block until the object can be accessed, so if thread A is using the object thread B will pause until thread A is done with it, so be sure not to create a block on the main thread.
  3. if you define a property on a class as atomic, by omitting the non-atomic keyword, the synthesized accessors will include synchronization code and simply the code for you, but you still need to be aware of the blocking issues.

As always, I love reading your comments. Especially when you point out my mistakes, since that's the fastest way to learn more.

My next post will be New Years Eve, and if the stars are aligned, I'll have a special treat for you!


This post is part of iDevBlogADay, a group of indie iPhone development blogs featuring two posts per day. You can keep up with iDevBlogADay through the web site, RSS feed, or Twitter.

  11524 Hits

A Simple Tip To Avoid Crashes

As you may be aware, I went full time indie a couple of months ago. I've been working almost as many hours as I can stay awake for clients, and spending whatever extra time I can find on a new game that I hope will be finished and submitted to the App Store before my next blog! I can't wait to tell you guys all about it.

With all this work though, I've been slacking in the blog department. Today I start to correct that. Here's a quick tip for today that may save you from a few crashes tomorrow.

It's a common sight to see something like this in Objective-C, where you're enumerating through an array (that's mutable), and doing something to each of the objects:

// self.objects is an NSMutableArray
for (NSObject *object in self.objects)
{
    [object doSomething];
}

If your app is multithreaded, you'll get a crash if another thread adds to or removes from the array at the same time this loop is being processed. Depending on timing, you might not see this bug hit until the app is being used by users.

Instead, anytime you're going to run through an array like this, make a copy first and enumerate the copy. Now you're multithreaded safe.

NSMutableArray *copyOfObjects = [self.objects copy];
for (NSObject *object in copyOfObjects)
{
    [object doSomething];
}
[copyOfObjects release]; copyOfObjects = nil;

Here's another common pattern you'll see, when you're enumerating the array in order to remove certain elements from it. Because you know you can't remove objects from the array as you're running through it, you create another array to store the the objects you want to remove and then remove them in a second step:

NSMutableArray *objectsToRemove = [[NSMutableArray alloc] init];

// self.objects is an NSMutableArray
for (NSObject *object in self.objects)
{
    if ([object shouldBeRemoved])
    {
        [objectsToRemove addObject:object];
    }
}
[self.objects removeObjectsInArray:objectsToRemove];
[objectsToRemove release]; objectsToRemove = nil;

Using the copyOf pattern above, you can remove the objects in a single step, since you're not actually enumerating the same array anymore:

NSMutableArray *copyOfObjects = [self.objects copy];
for (NSObject *object in copyOfObjects)
{
    if ([object shouldBeRemoved])
    {
        [self.objects removeObject:object];
    }
}
[copyOfObjects release]; copyOfObjects = nil;

Now you have nice, clean, efficient, crash free code.


This post is part of iDevBlogADay, a group of indie iPhone development blogs featuring two posts per day. You can keep up with iDevBlogADay through the web site, RSS feed, or Twitter.

  6758 Hits

RIP Steve Jobs (1955-2011)

RIP Steve Jobs

Steve,

Thanks for everything you've done to improve this world.  Thanks for giving us a platform with which to build a business, a career, and a life that's truly fulfilling.

You'll be missed.

Tags:

Copyright

© sabby

  2809 Hits

A Year From Now

Hello again iDevBlogADay readers!

For those that are unaware, Cerebral Gardens has been pretty much a one man shop since it's inception. Well, more like an eighth of a man shop since I've had a full time job the entire time. Earlier this month, I came across a quote:

A year from now you may wish you had started today.
-Karen Lamb

After reading it, I thought to myself, 'self, I wish I had started more than a year ago actually', and so I knew it was time.

As of October 1st, Cerebral Gardens will become a whole man shop! Plus a woman of course to keep me focused; someone has to crack the whip, and she's way more organized than I.

We have some exciting products that we'll be able to finish and bring to market soon. Plus, I'll be able to spend a bit of time writing more blogs for iDevBlogADay (ones more relevant to you than this one was).

If you're the type of person that likes to be inspired by quotes, you can check out a simple app I built a while ago to test Apple's push notifications. Carpe Diem. Message me if you'd like a promo code, I'll give them out in the order requested.

  5834 Hits

Add The Power of Dropbox to Every App

Ok, maybe not every single app, but if your app has any sort of data, then yes, you should add Dropbox support to the app. Certainly if the app is a tool of some sort, you understand that the user values the data that has been generated; but even if the app is a game, the user still values that data! If they've spent 5 hours to complete a tonne of levels, it's nice to have that accomplishment backed up, or available on a second device. This is where Dropbox comes in.

If you've been living under a rock and don't know what Dropbox is, it's a service that allows users to create a special folder on their computer that automatically syncs itself to the Dropbox servers and then any other computers also using the same account. This provides the user with a cloud based backup service (with a basic version control too). Using the official Dropbox iOS, Android and Blackberry apps, the user can access any of the files in their Dropbox account on the go. Sounds simple, and it is. So simple that everyone should be using it (especially since the basic service is free).

So how can you use this magical service in your apps? Dropbox provides an SDK for developers that lets you access a user's Dropbox account (with their permission of course). Now it's up to you to implement a solution to use it.

Before I go into detail on using the SDK (a future post), I'm going to suggest a directory hierarchy that we all follow. Remember that the root folder of the Dropbox is actually a directory on the user's computer that they're using. We don't want to clutter that folder with all kinds of stuff the user doesn't understand, they're likely to delete it.

One of the first apps I used that was Dropbox enabled was 1Password (An awesome app by the way). Their solution was to create a file called

.ws.agile.1Password.settings

in the root folder that only contained the location of their actual data. This lets users store their data wherever they want (provided it's still under the main Dropbox folder), and 1Password can still find it. There's a problem with this though, when you have lots of apps installed, all using a similar plan, you're going to have a tonne of junk in the root folder. Not user friendly at all. Not to mention, that while Mac OS X will hide a file starting with a dot by default, some of your users might not have switched from Windows yet and Windows will show the 'hidden' file in the folder, just waiting to be deleted.

My proposal, is that we create a folder in the Dropbox root called

.apps

as a central repository for third party app data. From there you use a reverse domain name system similar to your bundle id, but instead of dots, use new directories, and only use lowercase. So keeping with the 1Password example, they would store their data in

.apps/ws/agile/1password/

This will be consistent for their iPhone, iPad, Android, Mac OS X and Windows clients. All of their 1Password apps will be able to find the data regardless of system being used. Any data that is system specific can be stored further down the hierarchy.

This system should keep everything clean, organized and out of view of the user. Windows users will still see the .apps folder but at least it's just one folder and it's name should make it's meaning clear to most users.

Be aware that because you're now backing up your user's data to their Dropbox, they can now easily browse (and even modify) that data if they're so inclined. So make sure you protect this data if it shouldn't be seen or modified. For example, if you're game has 10 levels and you have to complete them in order to advance to the next one, don't record level completion in a simple format that lets the user effectively just check off that they've completed a level. If reading the data is ok (high score list etc), you can still use a simple format to store the info, just add some sort of validation to ensure the file hasn't been modified. A hash file will probably be fine for most simple cases (use some salt that you're keeping secret).1

Due to the rules change for iDevBlogADay, I am being rotated out for some fresh blood so this is my last official #idevblogaday post until I rotate in again. However, I will continue to blog on a regular basis at my main site: www.cerebralgardens.com. Some upcoming posts will involve how to actually do the syncing above. I have created a few components that I plan to release as Open Source and will go into detail on how to use them. If you're interested in reading more, you'll want to follow me on Twitter @CerebralGardens.

Thanks to everyone for reading my words so far, and especially to Miguel @mysterycoconut for running the whole iDevBlogADay thing.

See you next week!

Update (added this paragraph that was accidentally cut during my editing phase): Why bother using Dropbox in your app when iTunes backs everything up during a sync anyway? Well there are lots of reasons:

  • Not everyone syncs on a regular basis; shocking I know
  • If a user deletes an app, it deletes the data too, next sync, the backup is deleted too, if the user decides to put your app back on their phone, they have to start from scratch
  • Using Dropbox allows multiple devices to access the same data, play a game on your iPad, and continue later on your phone
  • It may be your app, but it’s the users data, so making it easy for them to access is a good thing

1 Note that the above point about checking for modified data files etc is vital even if you're not syncing to Dropbox, it's trivial for users to access/modify your data files through jailbreaking or even iTunes backups. Another issue is that the Dropbox folder is exposed to unknown other apps that might try to use the data contained within maliciously, scan for emails to spam, or account passwords etc. Those apps could be Windows malware etc so definitely encrypt sensitive data.


This post is part of iDevBlogADay, a group of indie iPhone development blogs featuring two posts per day. You can keep up with iDevBlogADay through the web site, RSS feed, or Twitter.

  16799 Hits

iDevBlogADay Reader Survey Results

The iDevBlogADay survey I posted was available for 3 weeks, and received 109 entries. Thanks to all those who filled it out, hopefully this information will be of use and/or interest to the community. Here's the info:

Q1: Please select the description that best applies to you regarding your mobile development experience.





Q2: Please select the description that best applies to you regarding server side development.





Q3: Please select the description that best applies to you regarding server administration.





Q4: Please select the description that best applies to you regarding your source code.





Q5: Which of the following are you interested in learning more about?





Q6: Which do you prefer?



 

Due to upcoming changes in the way iDevBlogADay is going to operate, I may be rotated out of active duty shortly in order to make room for some new blood. If you've enjoyed my previous posts, and would like to read my upcoming ones that will make use of the above info, please follow me on Twitter: @CerebralGardens.


This post is part of iDevBlogADay, a group of indie iPhone development blogs featuring two posts per day. You can keep up with iDevBlogADay through the web site, RSS feed, or Twitter.

  4950 Hits

Will iOS 4.3 Change The App Store Ecosystem?

All indications point to the imminent release of iOS 4.3, if not this week, then certainly by next week. As always with new releases, a host of new features will be included, not just for users, but also for developers.

The rumour is that 4.3 will introduce subscription pricing options to the app store. This is in response to newspaper and magazine publishers looking for better pricing options for daily, weekly and/or monthly editions. But, who says subscriptions need to be limited to publications. Apple's Terms of Services don't. More on that in a bit.

One of the challenges of iOS development is earning enough revenue to make a living. It's been said that there are two app stores, App Store A, where you sell apps with mass market appeal, hoping to generate a lot of revenue in a very short time, and App Store B where you sell apps that target a narrower audience, aiming to generate a steady revenue stream for years.

App Store A is often compared to buying a lottery ticket, and has just recently made an appearance in Dilbert.

Dilbert.com

App Store B is considered to be the more attainable, long term success strategy. Since you're aiming at a narrower target market, it helps to be able to generate recurring revenue from your users.

I've long wanted to try charging a monthly or yearly fee to use an app, something that will support the development process after the initial sale. Every other software platform allows you to charge for upgrades in order to generate some recurring revenue from your installed user base. A few apps on the App Store have phased out the original version and released a new version, with a new charge. There a few problems with this approach, a) users who buy the app just before the switch, kind of get screwed, b) there's no easy way for a user to transfer their data from version 1 to version 2 (it can be done, but you have to build in a solution, unlike normal app upgrades), c) not all users of version 1 will even know version 2 is available.

Another option is to add new features and charge for them though in-app purchases, then a user can decide if the new features are worth the extra money to them, if not, no harm, they keep using the app as they bought it.

Back to the new subscription option. With subscription pricing there will be a new opportunity to generate recurring revenue from your user base. And to the user, it will be a well defined, easily understood method. $x per time period. Just like paying your monthly phone bill, or a yearly magazine subscription. You can cancel at any time, or keep paying and take advantage of all new features as they're released. For developers, you now have recurring revenue. Earnings to enable you to continue to maintain and support the app, while still feeding your kids.

There's no indication yet what the options will be from Apple, but it's a good guess that the same pricing tiers we're using now will apply, and that you'll be able to select from a variety of time periods, likely: weekly, bi-weekly, monthly, quarterly, yearly. As an example, you'll be able to charge $0.99 a quarter, instead of a one time fee of $2.99. If the user doesn't like your app, they save money. If they like the app, and continue to use it, you'll break even after 3 quarters, and earn more for the entire length of time the user uses your app.

The largest obstacle I foresee moving to this model will be the blow back from customers. The current app ecosystem has bred a sense of entitlement where users (not all, but a lot), feel they deserve an app, all future updates, full support etc, all for the low cost of $0.99. For most developers, and most apps, this isn't sustainable. Using a subscription will help solve this. As more of us developers begin to use this model, customers will begin to accept it and most likely actually prefer it, since they'll know exactly what they're getting and for how much, and their apps will constantly improve at no additional cost. And, all things considered, they'll still be getting their apps at a ridiculously low price.

Update: Apple officially released the information on subscriptions today. Subscription term lengths are: weekly, monthly, bi-monthly, quarterly, bi-yearly or yearly. Thanks Apple for giving developers more opportunity to become profitable!


iOS 4.3 is currently available as a beta and thus is under an NDA. Nothing I discuss here is covered under the NDA to my knowledge as there's been no official word about subscriptions other than the now public iTunes Terms and Conditions. If you feel I have disclosed something in the NDA, please let me know and I'll edit the post accordingly.

This post is part of iDevBlogADay, a group of indie iPhone development blogs featuring two posts per day. You can keep up with iDevBlogADay through the web site, RSS feed, or Twitter.

Copyright

© sabby

  7956 Hits

iDev Tools

Last week I posted a short survey for developers to fill out, in order to get a gauge of the type of posts you're interested in. I had planned to post the results of the survey this week, but the number of responses doesn't yet seem large enough to be accurate. So I'm going leave the survey up for another week, please if you've got 2 minutes, fill it out (if you haven't already).

For the regular post today, I'm going to cover some awesome developer tools that can make your life easier. These are listed in no particular order.

AppViz: http://www.ideaswarm.com/products/appviz/

AppViz is a tool for automatically downloading your sales, trends and financial reports from iTunes Connect, as well as the reviews for your apps. The app works by scrapping the information from the web so it breaks whenever Apple changes something at iTC, but, IdeaSwarm, the creators, are insanely fast at analyzing the changes and pushing out an update to accommodate. This is by far the best $30 I've spent, and to be honest, I don't know of a single developer with published apps, that isn't already using AppViz. So I guess this tip is for newbies. Get this app now, you'll love it.

MajicRank: http://majicjungle.com/majicrank.html

MajicRank by David Frampton (of Chopper 2 fame) is the tool to use to track how your apps are doing in the top 200 lists. It scans all regions, so you can see results in countries you can't normally access. It's similar to AppViz in that it scraps info from the web and so it can break, but it rarely does. MajicRank is free so there's no excuse not to use it.

TestFlight: http://testflightapp.com/

I mentioned TestFlight a couple of weeks ago but they're worth adding to this list too. They've created a great service so far, it's almost too easy to use! There are still some kinks to work out, but note that they moved quickly to resolve the security issue I pointed out in the last article.

DropBox: http://dropbox.com/

DropBox is a fantastic cloud storage system for users. It lets anyone backup important files or easily transfer files to another place/person effortlessly. They provide an SDK that lets you add DropBox support to your apps so that you can let your users backup and/or transfer their app data easily.

Amazon AWS: http://aws.amazon.com/

Amazon Web Services actually consists of several great tools. The two most popular so far seem to be S3 (another cloud storage system, but intended for companies not users), and SimpleDB (a simple database in the cloud). If your app is data intensive, there doesn't seem to be a better solution than Amazon for hosting that data. They now have a free starter plan too so you can try it out at no risk.

Matt Gemmell: http://mattgemmell.com/

Mr. Matt Legend Gemmell isn't really a dev tool, but he does make a few that you can use in your apps. The most popular ones are MGTwitterEngine (an implementation of the Twitter API) and MGSplitViewController (a replacement for the iPad's UISplitViewController that gives you more control/options). Check them out here: http://mattgemmell.com/source

Accessorizer: http://www.kevincallahan.org/software/accessorizer.html

I found out about Accessorizer through Jeff LaMarche who said it was one of his favourite apps. I can see why. The app is a source code generator. You teach it your coding style, and then it pumps out code for you. No, it won't build your next app for you, but it will save you tonnes of repetitive typing setting up all your class properties etc. If you value your time, Accessorizer will save a lot of it for you.

TouchXML: https://github.com/TouchCode/TouchXML

TouchXML is a library for reading in and parsing XML files. If you store your app's data and/or configuration in XML files, this is a far faster way of getting that info than using libxml. You can quickly extract exactly what you need from XML in just a few lines of code. Truly indispensable. And if you'd prefer JSON over XML, there's a TouchJSON available too.

Hopefully something above will help you or a friend on you next project. Now, please don't forgot to fill out the short survey. It's only 6 questions. Thanks! :)


This post is part of iDevBlogADay, a group of indie iPhone development blogs featuring two posts per day. You can keep up with iDevBlogADay through the web site, RSS feed, or Twitter.

  9711 Hits

A Different Numbers Post

For this week's iDevBlogADay post, I'm going to give you some numbers. A different set of numbers than usual, not sales numbers (unfortunately they've not been interesting enough to publish yet); I'm going to give you the number of visits to my other iDevBlogADay posts so far.

I love being a part of the iDevBlogADay rotation, it's forcing me to write and communicate more with the iOS community (I'm usually a very quiet person). I appreciate the opportunity to be able to give a little something back. To that end, I want to maximize the reach and the impact of the little that I do get to write. So, following the numbers, I'm going to have a tiny survey for you. I'll publish the results next week and then use the information gained to create a series of posts that should interest and help, the most people.

Here are my numbers so far (all stats current as of Jan 30th, 2011).

Dec 26 2010 Discussion - Apple's App Store Policy Against Name Squatting 695
Jan 02 2011 Discussion - Reducing App Store Piracy 1028
Jan 09 2011 Open Source, the GPL, and the App Store 740
Jan 16 2011 iDev Tips & Tricks 1947
Jan 23 2011 TestFlight your Apps 980

The most successful post (judging solely on web traffic) was the Tips & Tricks post. By far, nearly double the traffic of the second most popular post about reducing piracy. The Tips post had a tonne of people tweeting the link.

My theory, is that the Tips post had 9 pretty different tips, and so there was 9 times as many chances for a topic to be interesting to someone. That's why it was linked to more often than the others. The second, regarded piracy, a topic almost every developer is concerned with.

I have no idea if the stats I'm seeing are similar to others, I only have about 200 followers on Twitter, which is pretty low. But then again, I don't say much, so it's not surprising.

So, please help me by answering the 6 questions in this survey so that I can write on topics that are the most interesting to you!

Thanks!


This post is part of iDevBlogADay, a group of indie iPhone development blogs featuring two posts per day. You can keep up with iDevBlogADay through the web site, RSS feed, or Twitter.

  5040 Hits

TestFlight your Apps

You've been working on your new app for ages and it's finally ready for beta testing. Prior to iOS 4.0, it was a considerable pain in the neck to get a build on to your testers devices. You needed to package up your apps and ad hoc profile, send them to each tester, and then they needed to use iTunes to install the profile and app on their device. It seemed like an art more than a science getting an app installed, never mind an updated build later. It was a very clunky system, and it didn't always go smoothly.

With iOS 4.0, Apple made it much easier to install test apps on a device. It's now possible to install profiles and apps without going through iTunes. Using XCode's Build and Archive option, you can create an .ipa file that embeds the ad hoc profile. Just put the .ipa file on a web server, (or your public Dropbox folder), and send the link to your testers. They can install the build by clicking the link in mobile Safari.

You're still required to collect your users UDID's, add their devices to your developer account and create an ad hoc profile that includes their devices.

Last week, TestFlight, a new service for distributing your test builds to your beta testers, went live. TestFlight promises to revolutionize the way developers beta test their apps, and after testing it out for a bit, I'm pretty sure they're going to do just that.

The free service, found at testflightapp.com, allows a developer to invite people to become a beta tester. The tester creates an account through the web site in Mobile Safari on their device, and then they register their device with the system. This process involves installing a configuration profile (different from an ad hoc profile, but listed in the same area on the device's settings). This gathers the device UDID and reports it back to the server. A tester can register more than one device. The tester, and her device(s) then show up in the developer's account. As a developer, you can export the UDID's of all your testers and import them into Apple's iOS Provisioning Portal. One issue I found here though, is that if you attempt to import a file of UDID's that contain a record that you've already added to the Portal, it will reject the entire file, instead of just ignoring that record.

Once you've imported all the new UDID's into the iOS Provisioning Portal, you can update your ad hoc profile to include all of your testers. Then, use it to create a test build of your app. You still need to use XCode's Build and Archive Option, and then use it's sharing wizard to create the .ipa file (all in all, a simple process, more details here: http://iphonedevelopment.blogspot.com/2010/05/xcode-32-build-and-archive.html).

Next, you upload your .ipa file to the TestFlight web site, and select the testers you'd like to notify of the new build. TestFlight emails each of them a link to a page that lets them install the build easily.

TestFlight lets you see how many times the .ipa file was downloaded (though, not by whom for some reason). I had one person delete it from their phone and redownload it, and it counted as another download. So a list of 5 people, with 5 downloads, doesn't really mean they all downloaded it. But it will likely be close.

I tested this entire process with some extremely non-technical people, not one had an issue. They were all able to create their accounts, register their devices, and install my test app with ease.

If you need more beta testers, TestFlight offers a recruitment tool. You can start recruiting random people through Twitter, your web site, or wherever. It's just a link you post. When people sign up to be a beta tester for you, you can accept or reject them based on whatever criteria you like.

Overall, I'm very impressed with the initial service offering TestFlight has. And, remember, it's entirely free (for now) for developers. They are charging for enterprise accounts.

I do have to point out one concern I have with TestFlight however. During my limited testing of the service over the last couple of days, I uploaded a few builds, sent them out to some testers, including myself, and then deleted the builds. When I went back to one of my test devices, I still had the link open in Safari to do the install, for a build that I had deleted in the TestFlight dashboard. So, I tested, and tried to install the build, that should have been deleted. It installed perfectly. This is my concern, because I had deleted the build! Which means, that TestFlight, is not actually deleting the bits of the builds you tell it to delete, it's just removing them from your dashboard. Whether or not you consider this acceptable is up to you (and your personal level of paranoia) . But remember, that when you upload your .ipa to TestFlight, you're letting unknown people view and test your ideas (yes, the TestFlight people can view and run your .ipa files, if they so choose).

If you are concerned, you can use Hockey to manage your beta installs. It's not as easy to manage as TestFlight, but you con trol everything on your servers so it's arguably more secure.

You can even mix and match some of the TestFlight features (UDID collection, recruitment etc), and then deliver the actual builds via Hockey.

Paranoia aside, TestFlight is excellent so far, and will change the way you deliver your test builds to your testers. Down the road, they plan to support additional features, such as adding analytics so you can view what your testers tested and for how long.


This post is part of iDevBlogADay, a group of indie iPhone development blogs featuring two posts per day. You can keep up with iDevBlogADay through the web site, RSS feed, or Twitter.

  18757 Hits

iDev Tips & Tricks

For today's iDevBlogADay post, I'm going to cover some useful tips and tricks. These are presented in no particular order, and are pretty much unrelated to each other. Hopefully you'll find some, or all of them useful.

1. Regarding the upcoming iPad 2

Reports are starting to surface that the next version of the iPad will support a retina type display. Apple will no doubt repeat what they did with the iPhone 4 and double the resolution (4 times the number of pixels). This makes it easy to support old apps on the new device by employing pixel doubling.

But, you can start preparing for this now! For every iPad image you use, include a higher resolution (double sized) image with the @2x suffix. And for icons, include a 144x144 (double 72x72) icon. I've included a 144x144 icon in all my iPad apps since the iPhone 4 was announced, betting on Apple doubling the iPad resolution. It's cheap to do, and if the predictions are wrong, there's no harm in having an unused icon.

As a sub tip, you should include the following sizes for your icons: 144x144, 114x114, 72x72, 58x58, 57x57, 50x50, 29x29

2. self.var vs var

In your classes, when use the following syntax:

DWClass.h

@interface DWClass : NSObject {
    NSObject *myObject;
}

@property (nonatomic, retain) NSObject *myObject;

DWClass.m

@synthesize myObject;

You're telling the compiler that your new class DWClass will have a property called myObject, and that it should create setMyObject (setter) and myObject (getter) methods to access that property. And that those methods should handle your retain/release cycles for you. Any other objects that need to interact with your myObject property, will do so by like this:

dwClass.myObject // (assuming dwClass is an instance of DWClass)

And this will actually call the appropriate setter/getter for the myObject property, which in turn interacts with the myObject instance variable of the dwClass.

Inside the DWClass however, you can access the property like this:

self.myObject

And the same setter/getter methods are used.

Inside the DWClass, you can also access the myObject instance variable directly just by referencing it. DO NOT do this. If you do that, you're not using the setter/getter methods, which means you're not automatically handling the retain/release calls. This is a surefire way to create hard to find bugs in your code. Plus, there are other problems with doing this. If down the road, you need to do something special whenever that property is accessed or updated, so you ditch the @synthesize and create your own setter/getter methods, you're now going to miss even more than the retain/release calls, you're going to miss whatever else you've added.

This entire tip also applies to non-object properties. Even if you're using a standard int as a property, always use the self.variable syntax to access it. It's just good practice and will save you headaches down the road.

3. Keeping your secrets, secret

Often when you're accessing other services, Twitter, Dropbox, your own servers etc, you may need to store passwords, API keys, etc in your code. It's dead easy to just have a constant like this: @"MySuperSecretKey" and be done with it. If you do that though, you may as well post the secret on your web site for all to see. Since, it's trivial for a bad guy to extract your secret from the compiled code they download from the App Store after your release. This is a bad thing. In the case of Twitter for example, some spammer could put your secret key into a rogue app that spam blasts users. Every one of those tweets will say it was sent by your app, and your legit app will be blocked pretty quickly. Your users will be locked out until you submit a fix and have it approved by Apple, say goodbye to two weeks of sales, not to mention, all the bad reviews you'll receive for selling a non-functioning app.

So, keep your secrets secret, use some encryption inside of your app to encrypt your keys etc. It doesn't have to be complex as the bad guy usually just looks for low hanging fruit (unless they are specifically targeting you). You can use one of the many encryption libraries available, or even roll your own if you're so inclined.

4. Ensure the App Store knows you support multiple languages when you do so

This tip comes from a mistake I made in version 1.0 of the Cruze app. The app supported English and French from the get go, but it was done by detecting the language of the user via code and loading the correct set of files for the primary language. This worked great and was way less work than using Apple's recommended method of localization for every nib etc.

One problem though, once the app was released to the store, the only supported language listed in iTunes was English. Because I used my own language detection iTunes Connect didn't detect French. I fixed this in 1.5 by using Apple's localization on a small dummy text file. One that I don't actually use for anything in the app, but that is enough to trigger the language detection tools Apple uses.

Update: see Ovogame's comment below for an even better way to fix this issue.

5. AppName_Prefix.pch

There's a file in your XCode project named AppName_Prefix.pch (where AppName, is your app's name). This file is included at the top of every source file in your project. It's a great place for you to store any constants you need across your app.

6. A Better NSLog()

A common method of debugging is to add NSLog() calls throughout your code. The messages are echoed to the screen as the code runs and you can see what's happening, giving you hints as to what's causing bugs. When you're finished however, and you want to do your final build, all of those NSLog() calls remain in your final build and all of those strings are available for anyone looking through your binaries to see. Who knows what secrets you might disclose.

Instead of using NSLog(), I use DebugLog(). This is a tweak of a function I’ve seen others use, based on the answer here: http://stackoverflow.com/questions/300673/is-it-true-that-one-should-not-use-nslog-on-production-code.

Add this to your AppName_Prefix.pch file:

#ifndef DebugLog
#ifdef DEBUG
#define DebugLog( s, ... ) NSLog( @"<%p %@:(%d)> %@", self, \
	[[NSString stringWithUTF8String:__FUNCTION__] lastPathComponent], __LINE__, \
	[NSString stringWithFormat:(s), ##__VA_ARGS__] )
#else
#define DebugLog( s, ... )
#endif // DEBUG
#endif // DebugLog

Now, replace all of your NSLog() debugging statements with DebugLog(), and define DEBUG in your debug configuration (sub tip 2: go to your Project Info, Debug configuration, search for Preprocessor Macros, add DEBUG).

After this, use DebugLog() all you like, and the strings are skipped over in your Release and Distribution builds. You also have the added bonus of having the function name and line number included with all debug statements now, making it clear what's generating the messages.

7. *.dSYM files

Whenever you build your app, XCode will output the *.app files, as well as a *.dSYM file. For Debug and Release builds, you can just toss/ignore the *.dSYM file. But for your distribution build, that you're going to submit to the App Store, make sure you keep the .dSYM file. You'll need this later, to analyze crash reports. I'll go into more detail on this in a later post, just know you need to keep the files. For the impatient, you can read more on this here: http://www.anoshkin.net/blog/2008/09/09/iphone-crash-logs/

8. Push Notification Certificates

If you have an app that uses push notifications, you need to generate a certificate with Apple, one of the first steps, is to create a Certificate Request file (CertificateSigningRequest.certSigningRequest), that you send to Apple. Keep this file. When your certificate expires, you'll need to request a new one. You can reuse the same Certificate Request file and skip the first few steps.

9. [object release]; object = nil;

With NSObjects, when you're done with them, you call release to reduce the retain count and let the system know you no longer need it. When that retain count hits 0, the system free's the object and releases the memory allocated back for use later.

It is common practice, to set the object to nil after you call release. This prevents a possible crash later, if you attempt to call a method on an object that has been freed. That's because if you try to call a method on a nil object, the system just ignores it, no error, all is good (or is it). If you don't set the object to nil, then it will still point to the memory address that was allocated for that object. There's a chance that object is still there (if something else was using it and increased the retain count for example). In that case, calling a method on that released object, will still work. But if that memory had actually been released calling the method would cause a crash.

This is why developers often set the object to nil, to prevent that crash in the case of a bug. But, this doesn't fix the bug, it just hides it from you. So, I subscribe to the second school of thought you shouldn't set the object to nil. If you have a bug, let the app crash while you're developing and you'll be able to find and fix that bug. When you no longer have any crashes, you'll know you're (closer to being) bug free, and that you haven't just masked your bugs.

Wow

This was a much longer post that I had thought it would be. Thanks for sticking through. Hopefully I've helped you prevent a problem in the future. Being part of the iDevBlogADay group is great, and it's truly forcing me to write more; I'd love to hear some feedback from some of you on what you think about the style, content, etc of my posts so far. Are they a benefit to the community? What can I do to improve them etc? I can be contacted via email (easy to guess), or through a contact form here: http://www.cerebralgardens.com/contact


This post is part of iDevBlogADay, a group of indie iPhone development blogs featuring two posts per day. You can keep up with iDevBlogADay through the web site, RSS feed, or Twitter.

  10533 Hits