Sunday, December 22, 2013

BFGNotificationBar

Back when I started my first iOS app, RollPG (@rollpgapp), I created a drop-down notification bar system. This was later repurposed for Missives (@missivesapp) with some minor modifications. When implementing it for Workflows (@workflowsapp) I finally buckled down and updated it for iOS 7 and made a sharable library out of it.

See the BFGNotificationBar repository on GitHub for more information

Monday, December 16, 2013

Docset for XCTest

One thing that's been a pain is not having clean, readable documentation for Xcode 5's new XCTest framework. (Or, at least, none that I could find.) So, I broke out doxygen and generated a set. It's not the prettiest documentation, but it's functional. And can be nicely imported into Dash.

Download the .docset here (.tar.bz2)

Monday, September 23, 2013

Last Line(s) of a UITextView May Scroll Past the Bounds of the View

I ran across this oddity today that rears its head in UITextViews under iOS 7 (didn't occur under iOS 6). Essentially, the view allows text to be written past the bottom of the UITextView's frame causing text to be hidden by a keyboard and/or keyboard accessory. This problem doesn't manifest if you're typing and the text word wraps; it only appears after tapping Return (whether after text or by itself). The following snippet (which goes in your UITextViewDelegate) did the trick for me:

- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
    [textView scrollRangeToVisible:range];

    if ([text isEqualToString:@"\n"]) {
        [UIView animateWithDuration:0.2 animations:^{
            [textView setContentOffset:CGPointMake(textView.contentOffset.x, textView.contentOffset.y + 20)];
        }];
    }

    return YES;
}

The call to -scrollRangetoVisible mostly does the trick. When you start typing at the bottom of the UITextView, the text will be hidden, but will immediately scroll up to become visible. A touch odd, but bearable. However, if you just tap Return, the text will continue to be non-visible. Thus, we check for a newline, and if so, adjust the contentOffset for the view by an appropriate amount for your text (probably better to calculate the right size if you allow the user to choose a font and/or font size). The animation makes the adjustment smooth so the text view doesn't jerk each time text is changed.

Here's to hoping Apple gets this one fixed in the next point release or two. I'll be filing a Radar.

Update 2013-09-24

After working with the the original version some more, it had a number of shortcomings. This is the version I'm using now, which should be more robust and should properly account for font and font size changes.

- (BOOL)textView:(UITextView *)tView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
    CGRect textRect = [tView.layoutManager usedRectForTextContainer:tView.textContainer];
    CGFloat sizeAdjustment = tView.font.lineHeight * [UIScreen mainScreen].scale;
    
    if (textRect.size.height >= tView.frame.size.height - sizeAdjustment) {
        if ([text isEqualToString:@"\n"]) {
            [UIView animateWithDuration:0.2 animations:^{
                [tView setContentOffset:CGPointMake(tView.contentOffset.x, tView.contentOffset.y + sizeAdjustment)];
            }];
        }
    }
    
    return YES;
}

Update 2013-10-03

Kevin Hayes has updated my latest update to account for UITextViews where the view extends underneath the keyboard. He's detailed his update here: Autoscrolling UITextView in iOS7.

Additionally, I've found adding this bit (from the UITextViewDelegate) also helps things work more smoothly, as well:

- (void)textViewDidChangeSelection:(UITextView *)textView {
    [textView scrollRangeToVisible:textView.selectedRange];
}

Friday, September 6, 2013

Alfred Workflow: Rate Track and Play

Inspired by the following couple of tweets, I realized I had a similar problem. I tend to use smart playlists in iTunes to work through music to be rated. Since iTunes 11 was released, changing the rating of a track in a smart playlist where the track no longer belongs on the list causes the audio playback to stop.

Since I'm not a Keyboard Maestro user, I wanted to take advantage of the same workflow but in my favorite tool: Alfred. Wrote up some AppleScript to do the dirty work and embedded it all in an Alfred 2 workflow to trigger them with hotkeys and notify when the rating has been made. Happy listening and rating.

Download the Workflow

Friday, July 12, 2013

How Not to Round

Found this gem in some code (not mine!) today:
NSString *precisionTwoString = [NSString stringWithFormat:@"%.2f",
    ((minRequiredLabelSize.width + 16.0) /
    self.credentialInputTableView.frame.size.width)];

__labelProportion = [precisionTwoString floatValue];
Those two digits so meticulously "preserved?" Irrelevant. Obviously, ceil() was too complicated.

Tuesday, June 4, 2013

Byword 2.0

If you don’t have it, you need it. Byword 2.0 was released today for Mac and iOS with plenty of new goodies. Much better conflict management and, with an in-app purchase, the ability to publish to various blogging platforms.

For a full run-down, check out the MacStories review.

Monday, April 8, 2013

Alfred Workflow: Mail.app Message Colors

Like most automation junkies, I like to have quickly accessible commands and hotkeys to perform common functions. For me, my go-to tool of choice is Alfred.

Today's piece of automation was creating a quick workflow to set message colors in Mail.app. Color options are the standard ones that AppleScript supports. Simply, select the messages you want to colorize and either bring up Alfred and use "label <color>" or set the hotkeys for each color and cut out some typing.

Download the Workflow

Before anyone asks, I have used Mail Act-On before. It's a great product, but I generally find I don't use most of the features. For me, a simple workflow does all I need.

Tuesday, March 26, 2013

Save Icons and the Floppy Disk

Why do we still use the floppy disk as the icon to save? This has been hashed and rehashed a few times in the last few years, but there has yet to be a consensus on the topic. Some good attempts have been made, but overwhelmingly, people argue that the disk is ubiquitous and therefore hard to supplant. A strong argument in favor is the phone icon used on smartphones. A telephone handset? When's the last time you saw one of those?

That being said, I don't remember the last time I saw a floppy and anyone under 18 likely has never seen one other than in a toolbar in Microsoft Office. We don't necessarily save things to removable disks anymore. It could be to a hard disk, an SSD, a CD, a DVD, a network drive, a flash drive, an external disk, or somewhere in the "cloud." Heck, I can save a document to a cloud folder by printing the document. Save is not tied to media. In the case of iOS, disks have never existed on the platform.

Apple avoided the concept of what to do about the save icon by removing it and making save an automatic action you never need to worry about. But, there are times when you need an icon to indicate you want to save, such as when you want to "Save As…" or duplicate or "Save a Copy" of a file. Or, in my particular case, I'm working on an iOS app where the user has the option to save/keep/store his document if he or she wants to. Here, I need some sort of icon to put on a button (other than just using the word "Save" which doesn't fit the UI) that means, to the user, "I would like to save this one for later."

On one level, there's the concept of favorites or bookmarks, but these don't quite match the paradigm here. Down arrows generally mean "download" to me. Using a folder generally means "file" or "organize" (or, gasp, "Open…") to me. I, like others, will be wracking my brain on this one.

Do you, dear reader, have any ideas? Hit me up on Twitter or drop me an email. I'd love to hear your thoughts.

Thursday, March 14, 2013

The Demise of Google Reader

Unless you've been living under a rock for the past 24 hours or so, you've probably read or heard somewhere that Google is shutting down Google Reader on July 1, 2013. This, of course, has led to weeping and gnashing of teeth, an onslaught of comments on social media, and unending blog posts about what may or may not come next. (Of which this is one.)

I agree with Marco Arment that Google shutting Reader down will finally lead to innovation on the feed consumption front. A number of years ago, RSS was a great idea to syndicate sites and allow you to receive easy, periodic updates. When Google came along back in 2005 with Reader, it changed the landscape. Throughout this time, Reader was the red-headed stepchild in Google's portfolio and never really received the support it needed internally (see here for some inside scoop). In fact, the API was never even official, despite its ubiquity. One could argue that Google Voice is in a similar predicament at this time, as well.

All in all, this tweet summed it all up from a business perspective:

Don't believe there's much more that can be said there.

What's Next?

That's the big question. The last 24 hours have seen a veritable onslaught of product announcements from Feedly, Digg, and others. Reeder, the mainstays of feed readers on iOS and Mac tweeted this:

And another popular reader app for iPad, Mr. Reader, tweeted the following:

Recent approaches to the feed problem have taken the magazine/newspaper presentation (Flipboard, Pulp, The Early Edition) and others have attempted to help float the most important articles to the top (Feedly, Cream, Fever). These are all noble attempts, but they don't generally solve the problem, and often both rely on Google Reader and don't address the multi-device needs of today's users.

In other words, the future is open. The next few months will be a watershed of innovation. It's time for our feed consumption to move to the next level, whatever that is. In today's world where users move from Mac to Windows to iPhone to iPad to Kindle to even their TVs, the way we consume our lifestream needs to accommodate it. This is likely one of the reasons behind Google Reader's demise and likely one of the reasons Mountain Lion didn't even ship with a bundled app that could read RSS (removed from both Mail and Safari).

As I've been looking at the alternatives, I've found that there are plenty of good options, depending on your needs, but for my needs, nothing quite meets my workflow. Yet. Is my developer's brain working on it? Sure is. As are probably many others. I, for one, have no plans to act on my ideas unless they gel into something that's truly revolutionary (I'll accept semi-revolutionary). The market, as they say, is ripe.

In the meantime, I'll stick with Google Reader until the lights go out.

Friday, March 1, 2013

Across the line…

Been heads-down recently finishing up my next iOS app, Missives and just pushed 1.0.0b1 out to testers. Can now take a breather and look toward some interesting technical things to talk about. Stay tuned.

Wednesday, February 13, 2013

Linking Drafts, Day One, and TextExpander with Fill-in Expansion on iOS

Inspired by Federico Viticci (@viticci) of MacStories who has begun automating the bejeezus out of his iPad with Pythonista, I started looking at doing the same to reduce my dependency on needing my MacBook Pro around at all times.

As part of my automation, I set up a number of TextExpander 4 snippets to help me create recurring entries in my Day One journal. These snippets use a feature in TextExpander 4 that allows you to create fill-ins, or, essentially fill-in form elements you can use to replace pieces of your template.

When I tried to move this workflow to iOS I found out, to my chagrin, that TextExpander touch doesn’t support fill-ins (checking with @TextExpander on Twitter confirmed this, but I was unable to determine if it’s a shortcoming of iOS, the TextExpander touch SDK, or a limitation imposed by Apple). After racking my brain, I finally decided to buckle down and write the glue the process needed. After all, it’s just text parsing, right? I’m vaguely familiar with Python, though I’m an old school Perl guy–how hard can it be?

To make a long story short, I made it through. The script’s not my prettiest work, but it’s so far, definitely functional. You can grab the script here if you want to take a look at it. [Edit: Since releasing it, I’ve made a few changes to my own copy of the script. I’ve outlined those below.]

Gist


You can also download the code in a GitHub gist here. This should make it a bit easier to import into Pythonista using the gist download trick.

TextExpander/TextExpander touch


In order for this to work you need both TextExpander for Mac and TextExpander touch (for iOS). You’ll need to set up the snippets with fill-ins you want on the Mac and sync them to iOS. (Though, I suppose you could roll them yourself if you were masochistic.)

Once you have your snippets set up to your liking, you can set up the workflow.

Drafts


Drafts is the key to making this work. The latest major release introduced custom URL actions which are used to trigger the whole workflow. Drafts also includes the TextExpander touch SDK allowing it to take advantage of TextExpander snippet expansion (make sure snippet sharing it enabled in the settings). If you enter your shortcut text in a draft, it will immediately expand. If your snippet has fill-ins in it, you’ll see them unexpanded in your text enclosed in %’s.

To create the workflow, open Drafts’ settings and create a custom URL action as follows:
pythonista://Expander?action=run&argv=[[draft]]
This will send the current contents of your draft to Pythonista and run the Expander script stored within (you may need to tweak this if you’ve named the script something different). I saved my custom action as “Expand Fill-ins.” Once the rest of the steps are configured, you’re ready to go. All you need to do is expand your snippet in Drafts, select the Expand Fill-ins custom action, and your completed, filled-in, snippet will be posted to a new entry in Day One. The python script, when complete, posts a URL back to Drafts with the modified snippet and calls Drafts’ built-in Send to Day One action to complete the work.

Pythonista


If Drafts is the key to making this work then Pythonista is the lock. For the most part, the script takes care of all the havy lifting, but you will need to bring the script into Pythonista yourself (limitation of Apple’s App Store rules). The Pythonista site has some more details on this, but, personally, I opened the script in an editor (in my case, Textastic), copied out the text, and pasted it into a new script inside Pythonista.

When the script runs, it parses the TextExpander fill-in syntax and creates prompts for each on the Pythonista console where you can fill in the values. When you’ve completed the last one, the workflow continues as outlined above.

Limitations


Currently, the expander script does not support the “optional section” fill-in type. It does support the other three (single-line, multi-line, and popup menu). The script is also entirely console-based and, while it does some basic validation checking, it doesn’t provide a GUI or the ability to edit your fill-ins before committing them. (The code is available under a non-restrictive Creative Commons license, so an enterprising soul could take this to the next level.)

Day One


Day One is the final step in this process. All that’s needed is you have it installed and the Send to Day One action be enabled in Drafts.

Update


In my personal copy of the script, I’ve cleaned up the rogue semicolons (python doesn’t seem to care, but I do most of my work in Objective-C, JavaScript, Perl, and other C-based languages so it’s a habit) and added the following line right under ### MAIN ###:
console.clear()
You will also need to import console at the top of the script. This does nothing more than clear the Pythonista console before it starts prompting. I find that this makes for a cleaner and less-confusing experience.

Update 2


Smile has added fill-in support to the TextExpander touch SDK as of version 2.0. The change requires some additional work by each developer to implement it, so it may be some time before your favorite app has it available.