Tuesday, May 24, 2016

Swift and Objective-C

There have been a ton of discussions and think pieces written on this topic, but I wanted to get my thoughts out there in a longer form than 140 character blurbs.

Think pieces? Yes, think pieces. Almost every argument for dynamism or keeping Objective-C’s better parts are making one of the following assumptions:
  • Apple has no interest in dynamism in Swift.
  • Apple has no concept of what Objective-C can do and why it’s beneficial.
  • The Swift Core Team isn’t made up of some of the smartest people out there.
  • There are no plans to create a Swift-based version of the frameworks.
  • Objective-C is being phased out.
  • Swift only exists to make iOS/Mac apps.
And so on. Thing is, this is Apple. They kept Swift secret for years before they told us about it. There’s a longer-term plan at work, I’m certain of it.

Is Objective-C dead? Hardly. Is Swift complete? Again, hardly. Can they coexist? Certainly (though sometimes with a bit of pain). Is Objective-C going away? Doubt it. In fact, it would be utterly naive to think so. Dynamic features in Swift are coming, it’s been mentioned by People in the Know™. Native frameworks are coming, just not immediately (a stable API and a stable ABI are higher priority).

Last, but certainly not least, is that Swift is a cross-platform language. What’s good for the language isn’t necessarily what’s good for UIKIt or AppKit. This is a tightrope the Core Team must walk, figuring out how to manage the language, the open-source process, Apple’s plans, future-proofing, and tons of other factors. They are incredibly smart, but they are limited in number. This all can’t happen overnight and I’d argue strongly that it shouldn’t. For the next major release (or two), I’d expect a focus on stability.

Either way, I doubt your complaints aren’t being heard. There’s a lot of noise and there are people traversing the chaos. Relax. It’s coming. This is only possible because Objective-C is so stable and provides a solid foundation (pun intended). Your toolbox isn’t limiting you–it’s stronger than it has ever been.

Update: Chris Lattner on dynamic dispatch

Update: Wil Shipley on this whole thing

Tuesday, April 5, 2016

TextExpander and Subscriptions

I, like many of you, were caught off-guard today when it was announced that TextExpander from Smile’s latest version was moving from a traditional software model to a subscription service. I was perturbed, but since TextExpander is so integral to my workflow, I bit the bullet and upgraded things to the new version (for nearly $50/year). My reasoning was that I already had everything in place and configured and I required the iOS integration. And thus, I paid for a service I didn’t actually need or want.

There’s an argument to be made that TextExpander is utility software only and this type of subscription is a hard sell to users:


And, perhaps, Smile could have learned a bit more from how (wonderfully) AgileBits handled its recent upgrades for 1Password. The difference here is that password sharing between family members and organizations is a problem people have and 1Password found a way to solve it. The ability to share snippets between members of an organization and edit them on the Web? Not convinced this is a problem that needed solving.

Typinator


(I’m aware there are other options, but this is the one for which I’ve had experience and a paid license.)

In the past, I was a Typinator, rather than a TextExpander, user and I have an existing Typinator 5 license I can upgrade to version 6 for less than $15. The feature-gap between TextExpander and Typinator has closed (and is potentially non-existent). Advanced features such as fill-ins, snippets that run scripts, snippets that contain other snippets, snippets that perform calculations, etc. are all available now. This leaves the elephant in the room…

iOS Support


As a user, I’ve liked the fact that many of my apps support TextExpander so I can easily share snippets and functionality across my devices. As a developer, I’ve spent time adding TextExpander support to my apps for the same reasons. Over time, however, things have changed. Apple added keyboard Text Replacement (née Keyboard Shortcuts) back in iOS 5 with iCloud sync in iOS 6. In recent iOS updates, Apple has made it more difficult for apps to integrate with TextExpander now requiring each app to keep a copy of the shortcuts library and provide an interface to manually sync them from TextExpander. When custom keyboards were added in iOS 8, TextExpander added a keyboard to help solve this problem, but, in practice, this feature is less than ideal and falls (far) short of Apple’s system keyboard.

I’ve been thinking about how I use TextExpander on iOS and it generally matches this tweet by my pal Pedro:

The short answer? I don’t depend on TextExpander on iOS as much as I thought. Apple’s Text Replacement meets 90% of my needs (and works nearly universally) with synced Safari preferences, iCloud keychain, 1Password, Workflow, and a few choice extensions filling the remaining 10%.

Future Development


I haven’t yet made a decision on whether I will continue to support TextExpander in my apps going forward. Obviously, I’m not going to tear it out in a fit of rage from existing apps, but for something forthcoming like TextTool 2 where I haven’t yet integrated it, the decision is murkier.

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.