SpeakerClock – Cocoanetics https://www.cocoanetics.com Our DNA is written in Swift Tue, 20 Jul 2021 19:57:16 +0000 en-US hourly 1 https://wordpress.org/?v=6.4.3 39982308 Accessibility in SpeakerClock 1.3.1 https://www.cocoanetics.com/2021/07/accessibility-in-speakerclock-1-3-1/ https://www.cocoanetics.com/2021/07/accessibility-in-speakerclock-1-3-1/#comments Tue, 20 Jul 2021 19:56:50 +0000 https://www.cocoanetics.com/?p=10725 You can now fully operate SpeakerClock with no or low vision. We gave SpeakerClock the full Accessibility treatment. In this article I describe some of the things I learned adding accessibility features to SpeakerClock, now that it is fully written in SwiftUI.

SpeakerClock’s UI is divided into 3 sections, the presets at the top, the LED timer as big as possible in the center and the phase “traffic light” at the bottom.

The main interaction gesture is to horizontally swipe over the LED digits to change the timer. This kind of gesture is not possible when VoiceOver is active because there you can pan over the screen to quickly get to the hot spots of interactive elements.

I solved this by having individually adjustable sub-elements for the minutes and seconds while the timer is not running. This way you can swipe vertically to adjust the minutes and seconds separately.

There are three kinds of standard gestures for Voice-Over which I made full use of:

  1. double-tap with single finger to select an element
  2. double-tap with two fingers to perform a “magic tap”
  3. draw a Z with two fingers to “escape”

I used #1 for the single-tap function of the preset and speaking phase buttons. #2 substitutes for the long press. The rationale is that you have to consciously tap with two fingers instead of one to modify the presets, as to prevent you from accidentally changing them.

In the regular flow of things, VoiceOver basically comments on the focused element and after a short pause also reads out the accessibility hint that tells the user what interactions are possible. I also used VoiceOver’s announcement notifications to give audio feedback on some interactions.

The cherry on top is that certain timer values get red out aloud. In the yellow and green phases you get a voice prompt every minute. The phase transitions get announced as well. In the red phase there is an announcement very 15 seconds, with the final 10 seconds being accompanied by beeps.

That felt like a reasonable amount of voice feedback for starters. I might add some configuration options at a later point.

In this video I am demonstrating all that we discussed.

Conclusion

I would say my implementation is 95% perfect. There are some edge cases still – which I cannot do much about – where VoiceOver will insist of speaking something that it wouldn’t need to. Unfortunately there is no way to tell Accessibility to “shut it” for certain times when there is something more important going on.

It cost me a lot of experimenting and the better part of a day to get to this stage. I am anxious to hear from actual users of SpeakerClock, in particular those who are visually impaired and might have use for a timer. And some regular users also asked about acoustic feedback. What sort of configuration options related to sounds might make sense?

]]>
https://www.cocoanetics.com/2021/07/accessibility-in-speakerclock-1-3-1/feed/ 2 10725
SpeakerClock 1.3.0 https://www.cocoanetics.com/2021/07/speakerclock-1-3-0/ https://www.cocoanetics.com/2021/07/speakerclock-1-3-0/#comments Sun, 18 Jul 2021 18:25:32 +0000 https://www.cocoanetics.com/?p=10714 I’ve been busy since I completely rewrote SpeakerClock in SwiftUI. That was version 1.2.0.

The App Store provides a concept called Universal Purchase, which is where purchasing an app on one device also unlocks it on all other supported platforms. In the previous version I added a Mac version. This update now adds the AppleTV version. Still a minor update, because the functionality is identical, yet all three versions benefit from improvements.

As on the other platforms I wanted to keep the UI identical, while optimising for the screen dimensions at hand. The main challenge was that contrary to the direct manipulation on the other platforms (with finger or mouse pointer) you are controlling focus on Apple TV with the remote.

So my round LED buttons have an additional white circle if they are in focus. That didn’t look nice for the LED digits however. So there I used the LED digits shadow to give it a white glow. While the timer is thus focussed you can adjust it by swiping horizontally on your Apple TV remote.

I wanted to avoid having the white glow being active while the timer is running, it would soon be annoying to the user. So if you start the timer via tapping on the remote, I place focus back on the currently active preset.

The second big change for the Apple TV concerned the user guide which is shown via the button with the question mark. Since there is no direct manipulation of a scroll view I experimented with having the individual paragraphs be focusable. But that didn’t work out well.

In the end I discarded the ScrollView and instead put the individual user guide sections on separate tabs. Since there was a lot of empty space around the text, I added some eye candy. Some of which is actually interactive.

Finally, the hidden bonus feature which possibly only developers would care about concerns the multiple layers of the app icon. The icons are all generated with original live user interface elements from the app. The non-TV icons were quite simple to do like that. On TV however an app icon has multiple layers which are shown three-dimensionally floating in an awesome parallax effect.

To achieve this for SpeakerClock I divided the multiple layers such that you have the inactive LED bars at the back, the active LED elements in the middle and the traffic light in the front.

Next up I’ll be looking into a Watch app. Although there it might make sense to wait for the iOS 15 feature where you can continue to update the screen every second. Also you should be able to use the watch as remote control for SpeakerClock running on any other platforms.

If you have any ideas or requests or questions, please feel free to contact me by email.

]]>
https://www.cocoanetics.com/2021/07/speakerclock-1-3-0/feed/ 1 10714
Rewriting SpeakerClock in SwiftUI https://www.cocoanetics.com/2021/07/rewriting-speakerclock-in-swiftui/ https://www.cocoanetics.com/2021/07/rewriting-speakerclock-in-swiftui/#comments Sun, 11 Jul 2021 12:37:29 +0000 https://www.cocoanetics.com/?p=10704 When I started out developing iOS apps, 11 years ago I put several apps on the App Store. Since they became fewer and fewer as the income from them didn’t warrant updating them. Amongst those my most successful one was iWoman, which I sold in 2015. My second-most-valuable (in terms of revenue) remained my beloved SpeakerClock, the last app standing.

I had left SpeakerClock online for the main reason that it kept producing like an average of $100 per month, even without me doing anything on it. For that reason, I didn’t want to make it free, but rather put it to a relatively high price tag of $5. There is also an In-App-Purchase of another $5. I figured “why kill the cow while it still produces some tasty milk”.

The other side effect of these price tags was that – I believe – only people who really wanted what the app was offering would actually purchase it. My philosophy with this speaking timer was to have the biggest LED digits possible, with the functionality that supports the speaking style of TED Talks, which historically have defaulted to a maximum length of 18 minutes.

Some crashes introduced by new iOS versions caused me to do small bug fixing releases (for iOS 3 in 2010, iOS 5 in 2011, and 2017 for iOS 10). Also, looking back at the release notes of those versions, I had made this exact promise:

“We have totally modernised the code base so that we can bring you some exciting new features in the next major release”

But I didn’t lie with this statement, a “next major” release would have been version 2.0. But I didn’t ever dare to turn the version number up that high. I only increased the third digit of the version number.

Apple did force me to do a new build eventually, when they cracked down on apps which weren’t updated in too long a time. And the most recent update they did themselves, when the Apple certificate had expired and they re-signed my app on their servers without me doing anything.

Enter SwiftUI

Over the last few months, I have grown very fond of SwiftUI. Being a developer on Apple platforms for more than a decade made me quite tired of having to keep writing the same MVC code countless times. And that would only get you like standard functionality, nothing truly exciting. So I jumped at the chance when one of my clients asked me to implement a new iOS Widget in SwiftUI, in the fall of 2020. Apple had turned to SwiftUI as the only way you could create such widgets because of SwiftUIs ability to produce and preserve a static view hierarchy which the system could show to the user at certain points in a timeline without substantial power usage.

My client was happy about the result and so I was tasked with the next level of SwiftUI development. I needed to implement a watchOS app, also entirely in SwiftUI. Development was quite similar to the widget, but this time I also needed to deal with user interaction and communication with the iOS counterpart app. That all took some a few months more than the widget, but again increased my SwiftUI skills tremendously.

After having delivered the watch app, I had a little extra time available to do something for myself. I do have some other ideas for apps, but my thoughts turned to SpeakerClock. I figured that this highly custom UI would lend itself nicely to be implemented in SwiftUI.

Paths in Shapes

The most important asset in the legacy code was the drawing of the big red LED digits and how they arrange themselves in portrait versus landscape, in a nice animation. So my first SwiftUI view was one that had a Path element with the SwiftUI commands adding the path elements to make up the individual bars of the LED. My first error here concerned using a GeometryReader to determine the dimensions of the path. The LED digits have a fixed aspect ratio and the drawing coordinates are based on those.

struct LEDDigit: View
{
   var digit: Int? = nil
	
   var body: some View
   {
      GeometryReader { proxy in
         let (w, h) = proxy.unitSize

         // top horizontal line
         Path { path in
            path.move(to: CGPoint(x: 24 * w, y: 7 * h))
            path.addLine(to: CGPoint(x: 60 * w, y: 7 * h))
            path.addLine(to: CGPoint(x: 62 * w, y: 10 * h))
            path.addLine(to: CGPoint(x: 57 * w, y: 15 * h))
            path.addLine(to: CGPoint(x: 24 * w, y: 15 * h))
            path.addLine(to: CGPoint(x: 21 * w, y: 10 * h))
            path.closeSubpath()
         }
         .activeLEDEffect(when: [0, 2, 3, 5, 7, 8, 9].contains(digit))
         ...
}

While this produces the correct output, it causes the individual Paths to animate separately when rotating the device. I solved this problem by moving the individual path’s code into a Shape where I am adding the bars only based on whether I am looking for the active or inactive LED elements. The path(in rect: CGRect) function hands us the required size, so we don’t a GeometryReader any more.

struct LEDDigitShape: Shape
{
   var digit: Int? = nil
   var isActive: Bool
	
   func path(in rect: CGRect) -> Path
   {
      let w = rect.size.width / 73
      let h = rect.size.height / 110
		
      var path = Path()
		
      // top horizontal line
		
      if [0, 2, 3, 5, 7, 8, 9].contains(digit) == isActive
      {
         path.move(to: CGPoint(x: 24 * w, y: 7 * h))
         path.addLine(to: CGPoint(x: 60 * w, y: 7 * h))
         path.addLine(to: CGPoint(x: 62 * w, y: 10 * h))
         path.addLine(to: CGPoint(x: 57 * w, y: 15 * h))
         path.addLine(to: CGPoint(x: 24 * w, y: 15 * h))
         path.addLine(to: CGPoint(x: 21 * w, y: 10 * h))
         path.closeSubpath()
      }
      ...
}

This is used such:

struct LEDDigit: View
{
   var digit: Int? = nil
	
   var body: some View
   {
   ZStack
   {
      LEDDigitShape(digit: digit, dot: dot, isActive: false)
         .activeLEDEffect(isActive: false)
      LEDDigitShape(digit: digit, dot: dot, isActive: true)
         .activeLEDEffect(isActive: true)
   }
}

The two members of the ZStack draw all the inactive LED elements behind the active LED elements. It still needed to be two Shapes because one shape can only have a single drawing style. The inactive elements are simply filled in a gray. The active elements are filled with red and have a red glow around them simulating some radiance.

With this approach a digit is always drawn in its entirety which lends itself to smooth resizing.

Layout and Orientation Woes

The next step was to aggregate multiple LED digits and lay them out over the screen with different positions for landscape and portrait orientations, with a smooth animation when you rotate the device.

I have basically two layouts:

  1. Hour digits, Colon, Minute digits (in a HStack)- in horizontal layout with the outer sides touching the safe area insets
  2. A VStack of Hour digits and Minute digits – in vertical layout

Sounds easy, but my attempts with HStacks and VStacks failed miserably. At the beginning of the rotation animation the digits would always get a very small frame expanding into the final one.

I can only imagine that somehow the SwiftUI layout system doesn’t remember that those are the same views. So I tried giving them static identifiers and I also tried geometry matching. But I couldn’t shake these animation artefacts. There must be some piece missing in my understanding about view identity.

In the end I came back to doing my own layout inside a GeometryReader, setting frame’s width/height and appropriate offsets (i.e. translation) for individual elements. This works very nicely and also lets me have a separate animation for the opacity of the colon.

The colon sticks to the right side of the hour digits and disappears in portrait layout. By sorting view modifiers in a certain way I was able to get this effect that the colon fades in with a slight delay.

var body: some View
{
   GeometryReader { proxy in
			
   let digitSize = self.digitSize(proxy: proxy)
   let colonSize = self.colonSize(proxy: proxy)
   let centeringOffset = self.centeringOffset(proxy: proxy)
   let isLandscape = proxy.isLandscape
			
   let timerSize = self.timerSize(proxy: proxy)
			
   Group
   {
      LEDNumber(value: model.countdown.minutes)
      .frame(width: digitSize.width * 2, height: digitSize.height)
      .animation(nil)
				
      LEDColon()
      .frame(width: colonSize.width, height: colonSize.height)
      .offset(x: digitSize.width * 2, y: 0)
      .animation(nil)
      .opacity(isLandscape ? 1 : 0)
      .animation(isPadOrPhone ? (isLandscape ? .easeInOut.delay(0.2) 
                              : .easeInOut) : nil)
				
      LEDNumber(value: model.countdown.seconds)
      .frame(width: digitSize.width * 2, height: digitSize.height)
      .offset(x: isLandscape ? digitSize.width * 2 + colonSize.width : 0,
              y: isLandscape ? 0 : digitSize.height)
      .animation(nil)
   }
   .offset(x: centeringOffset.width,
           y: centeringOffset.height)

You can see that I am specifically disabling animation with .animation(nil) for the most parts because I found that the animation otherwise is always out of sync with the rotation resizing animation. The LED colon on the other hand has its own animation with an additional delay of 0.2 seconds.

The second reason why I explicitly disabled animations is because on the Mac version those animations would lag behind the resizing of the app’s window. This resizing also switches between both layouts depending on how you drag the window corner, sort of like “responsive design” as we have seen on HTML web pages. More on Mac things further down below.

Multi-Modal Buttons

Another challenge that had me try multiple approaches concerned the preset buttons (top left) and traffic light buttons (center bottom). These buttons have a different function for a single tap (select) versus a long press (set).

The main problem is that you cannot have a simple .onLongPressGesture because this prevents the normal taps from being handled. One approach is to have a .simultaneousGesture for the long press, but then the tap action is executed right (i.e. “simultaneous”) after the long press action if you lift the finger over the button. The other approach is to use a .highPriorityGesture which again disables the built-in tap.

I ended up with the following approach which uses the gesture mask to selectively disable the long press gesture if there is no long press action and to disable the tap gesture if a long press was detected.

struct LEDButton<Content: View>: View
{
   var action: ()->()
   var longPressAction: (()->())?
   @ViewBuilder var content: ()->Content
	
   @State fileprivate var didLongPress = false
	
   var body: some View
   {
      Button(action: {}, label: content)  // must have empty action
      .contentShape(Circle())
      .buttonStyle(PlainButtonStyle())   // needed for Mac
      .simultaneousGesture(LongPressGesture().onEnded({ _ in
         didLongPress = true
         longPressAction!()
         didLongPress = false
      }), including: longPressAction != nil ? .all : .subviews)
      .highPriorityGesture(TapGesture().onEnded({ _ in
         action()
      }), including: didLongPress ? .subviews : .all)
   }
}

This approach uses a custom TapGesture in tandem with the LongPressGesture. A @State variable keeps track of the long press. We do need to reset didLongPress to false or else all subsequent taps would continue to be ignored. I found that I don’t need a dispatch async for putting it back to false.

I believe that the reason for that is that the first setting of the variable causes the body to be updated and thus the including: to disable the tap gesture while in progress. Thus the tap doesn’t fire upon releasing the long press. Good to know: The .all enables the gesture and the .subviews disables a gesture.

Contrary to other approaches I have seen on the internet this approach preserves the standard behavior of Button for highlighting, While you press a custom button like this, it makes it slightly transparent.

A Mac Version – For Free?

The huge promise of SwiftUI is that you would get a Mac version of your app for little extra work, effectively “for free”. So I decided to put this to the test also produce a macOS version. I set the targeted devices to iPhone, iPad, Mac and chose the “Optimize Interface for Mac” because that sounded to me like the better result.

This optimized mode caused some issues for my custom buttons, because they got replaced with empty round rects destroying my custom look. You can prevent this modification by adding .buttonStyle(PlainButtonStyle()).

Apart from this my code really did run as a native Mac app quite nicely. Behind the scenes though it is all Mac Catalyst. As I understand it, that means UIKit is still at the helm, on Mac just a macOS version of it.

I left the code signing settings alone as I wanted to have users be able to install the Mac and iOS versions with the same purchase. This “universal purchase” is enabled by having the same bundle identifier for both versions.

Some very minor tweaks were required for adjusting some minimum and maximum button sizes. There is a bug on macOS that stumped me for a while. Only on Mac I found that when I tapped in certain spots in my app this would cause gestures to stop working. Then when I triggered a new layout by resizing the window, everything returned back to normal.

My workaround for this was to attach the Pan Gesture (for setting the timer) only to the LED digits. This way there is no interference and all buttons continue to work normally. The system might get confused by having too many conflicting gestures on top of each other.

A side-effect of the Mac version is that you start to attach keyboard shortcuts to buttons. This was also a reason why I wanted to get Button to work with tap and long press as opposed to making a custom view that is not a button.

let title = "\(index+1)"

PresetButton()
.keyboardShortcut(KeyEquivalent(title.first!), modifiers: [.command])

This way you can trigger the preset buttons also with COMMAND plus number. And not just for the Mac app, but that works for iPads with attached keyboard as well.

That got me thinking, that maybe it would be great to allow the space bar to stop/start the timer, like we are used to from video players. For that purpose I have an empty completely black button behind the LED digits:

Button(action: { model.isTimerActive.toggle() },
       label: {
          Rectangle()
          .foregroundColor(.black)
          .frame(width: timerSize.width, height: timerSize.height)
          .onTapGesture(count: 2) { model.restoreGreenTime() }
       })
.keyboardShortcut(.space, modifiers: [])
.buttonStyle(PlainButtonStyle())

This button allows me to add a keyboard shortcut for space to act the same as a tap. Curiously having a two-tap gesture attached to the Rectangle() poses no problem.

I submitted the Mac build right after the one for iOS but initially got a shocking rejection:

The user interface of your app is not consistent with the macOS Human Interface Guidelines. Specifically:

We found that the app contains iOS touch control instructions such as tap and swipe.

The reason for that was that I put back the help screen with a text I had previously written with iOS in mind. I needed to replace mentions of swiping with dragging and instead of tapping you are clicking. I have hard coded the text and formatting for now and with and #if I can switch the text between a version for Mac and one for iOS.

Group
{
   Text("Setting the Timer")
   .font(.headline)
   .padding(.bottom, 5)
						
#if targetEnvironment(macCatalyst)
   Text("To adjust the timer, click on the LED digits and drag horizontally.")
   .font(.body)
   .padding(.bottom, 5)
#else
   Text("To adjust the timer swipe left and right.")
   .font(.body)
   .padding(.bottom, 5)
#endif					
}

Once I had made those changes the Mac app was approved very quickly.

Conclusion

I’ve experienced first hand how I can rewrite an app in SwiftUI and the great pleasure that can be had from deleting all your crufty Objective-C code when doing so.

SwiftUI is my new love and this way my app is no longer a “child from another mother”. This restores some enthusiasm in me to actually finally really add some long-promised “exciting new features”. For starters I am thinking of having a watchOS companion app which shows the timer and allows you to remote control it. Another idea might be to store my presets on iCloud so that they are the same on all my devices.

I would love to hear from you what you think about the process of re-implementing parts of apps or even whole apps in SwiftUI.

]]>
https://www.cocoanetics.com/2021/07/rewriting-speakerclock-in-swiftui/feed/ 25 10704
SpeakerClock 1.1.3 https://www.cocoanetics.com/2012/11/speakerclock-1-1-3/ https://www.cocoanetics.com/2012/11/speakerclock-1-1-3/#respond Tue, 20 Nov 2012 13:31:13 +0000 http://www.cocoanetics.com/?p=7268 We have totally modernized the code base of our talk timing app SpeakerClock so that we can bring you some exciting new features in the next major release.

Changes

  • ADDED: Launch images for all devices and resolutions
  • ADDED: iOS 6 and iPhone 5 support
  • FIXED: Preset values not getting saved if the app gets terminated
  • FIXED: Presets might not be showing after purchase

The update has been submitted to Apple and is waiting for review.

Update Nov 27th: The update has been approved by Apple.

]]>
https://www.cocoanetics.com/2012/11/speakerclock-1-1-3/feed/ 0 7268
SpeakerClock 1.1.2 https://www.cocoanetics.com/2012/03/speakerclock-1-1-2/ https://www.cocoanetics.com/2012/03/speakerclock-1-1-2/#respond Tue, 06 Mar 2012 15:42:01 +0000 http://www.cocoanetics.com/?p=6068 This release fixes the crash on iOS 5 that I had in all my apps using the version of DTAboutViewController from before iOS 5 was released. I’m sorry for having procrastinated so long on updating SpeakerClock, but I had forgotten about this issue until a user reminded me of it via e-mail.

The update has been submitted to Apple and we hope to have it available on the app store in about 5 days.

Update March 10th: … and it’s approved and available for download.

]]>
https://www.cocoanetics.com/2012/03/speakerclock-1-1-2/feed/ 0 6068
SpeakerClock 1.1 https://www.cocoanetics.com/2010/06/speakerclock-1-1/ https://www.cocoanetics.com/2010/06/speakerclock-1-1/#respond Sun, 06 Jun 2010 22:39:33 +0000 http://www.drobnik.com/touch/?p=2664 My previous article covering an iPhone-to-Universal migration inspired me so much that I spent two days on getting the HD-upgrade for SpeakerClock ready to ship.

Changes

  • Universal app, runs on iPhone and on iPad in native resolution
  • Portrait Mode, now all orientations are supported
  • Now screen flashes in the appropriate color when threshold to yellow or red is passed
  • In-App Purchase for 5 presets instead of one
  • Lot’s of minor usability improvements

I didn’t know if Apple would approve an iPad-ready app if it did not support all orientations. So I implemented a mode for portrait orientations for iPad and retrofitted it for iPhone as well.

A big thank you goes to Erica Sadun who inspired me to take on these changes. When I met her on the Voices That Matter Conference in Seattle she played around with SpeakerClock as if it where the coolest of all toys, making me really proud of it. But I cringed seeing the iPhone-app on her iPad so I definitely had to make a HD-version. I dedicate this one to Erica.

I also added the information on how to start and stop the timer to the instructions. Some people did not understand that you can do so by simply tapping the screen.

Also this version marks the end of the “free trial” for SpeakerClock 1.0. As soon as it goes live I will raise the price. So if you want to get it for free, then better get it now. If you liked 1.0 then the new + button is your chance to show your appreciation, it will give you 5 instead of 1 preset. A preset saves the timer starting value, the yellow and the red threshold values.

Personally I use SpeakerClock whenever I’m recording YouTube videos. A couple of times I went over the maximum of 10 minutes and my handy timer prevents this for good. Here’s a demo of the old and new features.

SpeakerClock 1.1. has been submitted to Apple for approval.

UPDATE: … has been approved.

UPDATE 2: … has a nasty bug where it would crash on all iPhones with an OS Version less than 4.0. So I already submitted an 1.1.1 update.

UPDATE 3: The 1.1.1 hotfix is now available on the app store.

]]>
https://www.cocoanetics.com/2010/06/speakerclock-1-1/feed/ 0 2664
SpeakerClock 1.0 https://www.cocoanetics.com/2010/03/speakerclock-1-0/ https://www.cocoanetics.com/2010/03/speakerclock-1-0/#comments Tue, 02 Mar 2010 07:11:34 +0000 http://www.drobnik.com/touch/?p=2185 I like to watch the TED Talks, it’s always something novel and instructive and makes me believe that the world is generally moving towards a brighter future led by a handful of rather bright fellows.

Now one thing these guys do extremely well is to give a TALK. Through experimentation it was found that at that length the speaker is forced to condense his message and be as clear as possible to get his point across. This constraint is enforced by the famous TED speaker LED clock. (It’s actually a countdown and not a clock, but people seem to prefer using the word “clock” over “countdown”)

This is a countdown at the edge of the stage which at a glance shows you what your remaining speaking time is. Also there is a traffic light of sorts. Shortly before the end of the time a green light switches to yellow to signal that you have to start wrapping up your message. Red means that it’s time for the closing remarks.

Obviously there are dedicated devices out there which aim to fill exactly the same need of visualizing a speakers time constraint. And of course there are a couple of iPhone apps providing this functionality. My second choice of the name of my app was taken by Talk Timer. Yet another is Speech Timer Free which provides the traffic light and the Premium version of it even allows for exporting of your speaking log.

I might continue to wish I were a great and inspirational speaker, but in the meantime one thing that I CAN do is make such a countdown clock for iPhone. I just had to do it, because the thought of the clock kept popping up in my head and kept distracting me from other projects.

My goal for SpeakerClock was this:

  • emulate the famous TED clock as closely as possible
  • use big red LED numbers (for which I had invented DTLEDNumberView)
  • allow for all customization and setting via touch gestures, all on the main screen
  • use the second page solely to showcase DTAboutViewController

Version 1 uses the maximum size possible of the digits that is available in landscape mode. Because of this you can see the digits from several meters away which is necessary if you want to position it so that you can move freely while giving your speech. To maximize the size of the clock I had to move minutes and seconds closer together and wrap the traffic lights underneath.

I made it a special point to finish the app within a single day and so I left out several things which I can put it if there is any interest in this app at all. The art of 1.0 is to concentrate on the required core features and leave some of your brilliant ideas for future versions. Here are some ideas still on my mental drawing board:

  • German localization (and other major languages) – language is not critical to understand usage of the clock all texts are on the instructions and about pages
  • Multiple Presets – might be an idea for a freemium upgrade
  • Recording of speaking logs, summing up your total speaking time, exporting, sharing …

I made a YouTube video to demonstrate the app:

I sent the app to Apple yesterday. SpeakerClock will be available on the app store initially for free to get user feedback.

UPDATE: 2 days after submission SpeakerClock is now available on the app store.

]]>
https://www.cocoanetics.com/2010/03/speakerclock-1-0/feed/ 7 2185