One of my favorite user experience interactions of Apple's official Podcast client is pull to refresh for new episodes. I love it so much it's the basic new episode transaction in SkipCast. There is something so pleasant about that action, not knowing if you will get a new episode but delighted when you do. It's a surprise every time, what's not to love?
I realize it's not for everyone though, so I plan on adding a background fetch mode to SkipCast. The fetch will work at app launch, and as a background activity. Care must be taken with the background method, as Apple's Watch Dog seeks out and kills processes if they run too long. Do this too many times and iOS will simply block your app from any background activity.
What method so you prefer? Drop me a line at info at skipcast dot net and let me know!
Just a quick update for all you loyal SkipCast fans, the very few but hopefully proud : ) I thank you from the bottom of my heart for sticking with me!
So it turns out I've had a hell of a time with the Watch app for this latest build, enough of a problem that I've had back to back app store rejections. As each submission usually takes around 2 weeks to process, it's been a painful waiting game.
I truly do apologize for this and as a result, have taken the rather drastic step of just removing the Watch app from the latest build. I'm pretty sure the issue is on my end, but from a technical perspective can't say for certain.
The basic issue is unfortunately I can't afford an Apple watch or, for that matter, an iPhone, which means all testing has been done using the Watch simulator. The first submission rejection was all me, in that I missed a bug that would cause a crash on the paired iPhone. The most recent one however, that's more complicated.
After the first rejection I took a long look at the Watch code and ran hours upon hours of tests, from stress to general usability. The result of that work was a much improved app, so to Apple for the first rejection: thank you, and I mean it.
The problem was the next build worked flawlessly for me, and so I was illegitimately stunned when it too, was rejected. It took all of a few seconds to see why. I loaded up the Watch extension app in the simulator and wow, what a mess. Slow and unresponsive, and would crash at almost every step of the way.
This was not the same app I had submitted. Yes the binary was the same, but it was acting completely different. Distressingly, to fix every issue I simply reset the Watch and iPhone simulators.
And so what the hell happened between when I submitted the app and tried after the first rejection? You got me, but at this point I'm tired of waiting to get this new build out. So, I remove the app so I can get the big fixes out first, I'll circle back and re-release ASAP.
The real kicker is even right now I'm at a loss as to how I should approach the Watch simulator. I've just launched the app+watch extension and, without any errors or explanation, the simulator fails to watch the Watch app. It just spins for a while and quits. Quit both simulators and relaunch from Xcode, now it works again.
This is not a knock on Apple, but a reality I'm dealing with. I can't afford real hardware, so I have no choice but to rely on the tools given to me. This is all fine and well until something like my second rejection happens. I'll keep at it folks.
I wanted to give a quick update and say hooray! Version 1.8.0 has been released to the app store and is awaiting approval. This version fixes a large number of bugs and annoyances, and I really, really hope is gets out soon.
This version is notable in that it's the first time I've used a network link conditioner for testing. For those not familiar, a link conditioner simulates various network conditions real-world users face. We have two versions. The first is an OS X preference pane item. When enabled we can select from various presets or define our own. This is used for testing within the simulator. iOS has an identical feature on-device in the Developer menu. The good news is considering how much networking SkipCast does I didn't have too many surprises, but I did find and fix a bug or two.
The first was in our Podcast Search preview window. Basically, SkipCast loads an audio preview and presents a play button on top of the Podcast artwork. This preview is conditional, we must have a valid stream of audio for it to work (a valid URL, in other words).
Apple's AVPlayer makes this a snap with key-value observing, where we attach an observer to the AVPlayer object and await status updates. The problem was locally (WiFi) this process happens very fast. We check for a valid preview URL and if one is found, attach our listeners. Later on, only if AVplayer loads a valid audio stream will we present the preview button.
Here's the bug: With the link conditioner set to simulate a slow 3G network there was an appreciable delay between attaching the observer and the AVPlayer stream being ready to play. As we only detach the listeners when the stream hits this ready status, we had a situation where an instance of AVPlayer would have an invalid listener if the user left the preview screen before this loading finished.
The fix for this was simple: toggle a boolean flag when we attach the listeners, and if viewWillDisappear is called when this flag is still true, remove the listener. The kicker is this bug was only found because of the link conditioner. I don't own an iPhone, just an iPod, which means I could never test these types of conditions - until now.
As a brief aside, SkipCast uses the low-level Core Audio framework for audio tasks, only reverting to AVPlayer for video playback, streaming, or if the user sets this in the options window. (AVPlayer is very efficient and faster on older devices like my iPod, especially for seeking). One strange annoyance I've had is when you use AVPlayer for the Podcast preview it changes the core functionality of the iOS Control Center. Specifically, it removes my skip forward and back buttons and replaces them with previous and next track. This change sticks even when we completely stop and reset avplayer and the control center.
One other bizarre side effect – and I have to admit it's kind of nice – is in this state we can now use the control center to scrub through our audio!
Of course for existing users I'm so sorry for the crashes you may have been experiencing. Some were inexcusable, such as a crash when we start a search in the episodes window, then leave and then tap back into the episodes window again. Luckily most have been more of the "non crash/logic" variety. A great example is how our episode count would be off for newly downloaded episodes in the main window.
Bottom line is this update is huge in terms of stability and I can't wait for Apple to release it into the wild.
I can't believe it took 2 months for me to remember this step. First, check this out:
Here's run #1 :
...and we'll call this Run #2:
See that massive drop in CPU on Run 2? That's after adding a single index on an integer column in my SQLite database. The difference is even more pronounced on real hardware, where I'm seeing a half second or more shaved off some queries.
It's funny. In the move to iOS development I've focused so heavily on Swift performance and optimizations I've forgotten one of the most basic: If your app uses a database, for goodness sakes add indexes!
It's hard to stress just how much I enjoy Podcasts. So much so I went ahead and built a Podcast client, the site of which you're on now (Hi!). Taken together, you'd expect raw passion and a great final product would give a fair shot at some measure of success – Unfortunately In this case we'd be wrong. Despite placing my best efforts into the organic growth of SkipCast by means of creating a great app and reaping the benefits of word of mouth, it simply hasn't happened.
By my count they're around 20 dedicated Podcast clients on the App Store. Your grocery store probably has over 60 kinds of breakfast cereal. The iOS Podcast market is, by all measures, not exactly crowded. It's not empty, but it's definitely not a vast sea of copy cats and clones. Despite this, the first three months of SkipCast's time on the App store saw just two purchases. I wish I was kidding. The real kick in the ribs is despite having only 20 competitors, I routinely find SkipCast 100 results down when you search the exact phrase "Podcast player". My effort aside, Apple needs to fix this.
It's tough out there, and sometimes raw dedication to your craft simply isn't enough. Enter RawVoice. Several months ago I had the idea of placing a donate link in SkipCast. I honestly can't think of a better "win win" feature, but like many things, an idea alone isn't enough. It needs to be standardized, well supported, and easy to implement. Luckily for me, the great people over at RawVoice had a similar idea, and within days we had a working draft of the new donate standard.
As a happy aside, the press received from this collaboration, along with a price drop to free, saw my first real up-tick in downloads. The numbers aren't huge, just over 300 over 3 days, but compared to what came before we're talking a percent increases in the thousands.
Let's hope I can find a way to delight these new users and attract far more.
The earliest sketches for SkipCast centered almost exclusively on the player interface, as that's what I found most lacking in the competition. The goal was to provide a uniquely quick way to skip around episodes; the reason, selfish: I'm an avid biker and runner, and the last thing I want to do is crash into something when searching for a tiny pause or skip button.
After spending the first few weeks getting up to speed with AVPlayer, file system work, and my database layer, the player screen was the first major UI project I took on. Two major obstacles here were Apple's Auto Layout and the general design sense of iOS 7.
Auto Layout because for a new iOS developer you have no clue what the hecks going on. Impenetrable constraints, a fiddly interface, "content hugging priority": it simply doesn't end. As Paul Hegarty notes in his wonderful Stanford iOS series, you simply need to play around with Auto Layout until it clicks.
The other major sticking point, oddly enough, was buttons. When iOS 7 was released back in September of 2013, Apple made a bold design choice: traditional buttons were out. Not the concept of a button of course, just the look. Up until iOS 7 buttons had always had borders. Now they didn't. Can something even be called a button if it has no border?
To build a iOS app without Auto Layout would have been easier. The price would be SkipCast not working out of the box with the iPad's wonderful new split screen view feature. The decision to not fight Auto Layout, despite it fighting me on many occasions, was absolutely the right one. The move to iOS 9 was a breeze.
In contrast, the decision to ditch border-less buttons was also correct. The player interface needed to have large buttons, but I simply couldn't get iconography or border less, Windows Phone style blocks to work.
I love the current player interface, and believe it fulfills its original, stated goal of being easy and safe for clumsy types like me.
The first committed build of SkipCast was on February 21, 2015. While true to its original vision, SkipCast has gone through a rather dramatic and continuous transformation. It's been an incredible experience.
Looking back at that first build, what's interesting for me is I can unequivocally say it absolutely stunk to be so green. I've been a web and C++ developer for years, what a shock it is to go back to being a complete novice. You see, SkipCast was my introduction into iOS programming.
The good news is at just over 32k lines of Swift code I think I'm finally getting the hang of this thing. I can write long blocks of code without error, the core concepts of iOS development no longer elude me. But in-between these two extremes were loads of compromises, tough decisions, and long, long hours of work. I hope to cover some of these difficult choices in the months that follow.
Of course I still have loads of features I'd like to add. From Deep links to Force Touch I've got my work cut out for me. I want to share these experiences as well.
It's been a great journey so far, and I hope you'll join me to see what comes next!