08 Mar 2016
Belle

Fun with Cocoapods and Swift

I’ve recently started learning how to use Swift, as I want to move into using Swift more in the future. I’m still well behind compared to my Objective-C understanding, but I’m catching up a lot faster than I’d expected to. I watched some of the Treehouse videos about Swift (I used Treehouse to get started with Objective-C a couple of years ago), which was helpful for going over some of the basic things I can already do in Objective-C: creating classes, subclassing, using properties, etc.

Once I had the basics of Swift down, I wanted to start writing code. I never fully understand programming principles or syntax until I get my hands dirty, so I set up a challenge to help me put Swift into practice: I’m recreating a small, simple project I’d been working on in Objective-C as an (almost) exact copy in Swift.

Littlelogs iOS timeline

The app is an iOS client for Littlelogs. The Objective-C version isn’t finished, but I have quite a few features that work already. It’s a good size for my first app in Swift, I think.

The Objective-C version consists of three tabs: two almost exactly the same, showing timelines of everyone’s public logs and the logs of everyone you follow. The third tab has a very simple form for creating and posting a new log. Each log in the timelines can be tapped to open a detail view showing comments and letting you post your own comment.

So far I have the two timeline tabs showing logs, so those screens are almost on par with the Objective-C version.

But apart from the basic layout and functionality, I have to implement all the small touches I’ve added to the Objective-C version, like success messages, and making the comment field stay above the keyboard while you’re typing into it.

The last two days I’ve been struggling to implement one of those small touches: making the keyboard dismiss when the user taps the background view.

Littlelogs iOS detail view

When you’re posting a comment on a log, there’s a small text view that sits on top of the keyboard. You type your comment in this box, but you can still see the log and other comments in the main view behind the keyboard. I want the user to be able to tap on the screen where the log and other comments are to dismiss the keyboard.

This is pretty common iOS app behaviour, but it isn’t standard. In the Objective-C version of my Littlelogs app I used a Cocoapod called AutoHideKeyboardControllers to implement this behaviour. This pod makes it really easy: all I have to do is add the pod to my project, make my view controller a subclass of AutoHideKeyboardControllers, and set my comment text view’s delegate to be my view controller.

I’m using a couple of other Cocoapods in this project, so I’d stumbled through working out how to use Cocoapods in a Swift project by now. But I couldn’t get AutoHideKeyboardControllers to work.

In my Objective-C project, I followed these simple instructions exactly, and the pod worked perfectly. After a lot of trial and error that didn’t get me anywhere, I dug into the pod’s code. I don’t do this very often, because I’m still so new to programming. Other people’s code tends to scare and confuse me, so I try to avoid it.

Thankfully, in this case the code was simple enough that I could understand exactly what it was doing. The file AutoHideKeyboardViewController.m, which is what I was subclassing with my own view controller, sets up a gesture recogniser in its viewDidLoad method. This recogniser kicks off a method whenever the background view is tapped, that dismisses the keyboard.

By adding some logs to various methods in this file, I figured out that viewDidLoad was never being called—thus, the gesture recogniser was never being added to my view, and my background taps weren’t being picked up.

Try as I might, I couldn’t figure out why viewDidLoad wasn’t being called. I’m still struggling to understand how to implement initialisers in my Swift classes, so it’s possible I’d screwed something up there. Or, perhaps there’s a limitation I don’t know about when trying to use an Objective-C Cocoapod file as the superclass for a Swift class.

As I was digging into this issue, I realised one option I had was to recreate what the pod was doing inside my project, rather than relying on an external library. Having seen how simple the code was, I figured I had a fairly good chance of being able to make that work, so that’s the route I took.

There were three main parts of the pod I needed to recreate.

Add a gesture recogniser to the background view

UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(viewBackgroundTapped:)];
tapGestureRecognizer.delegate = self;
[self.view addGestureRecognizer:tapGestureRecognizer];

Grab a reference to the comment text view when the user focused it (which makes the keyboard appear)

- (void)textViewDidBeginEditing:(UITextView *)textView
{
    _activeTextView = textView;
}

- (void)textViewDidEndEditing:(UITextView *)textView
{
    _activeTextView = nil;
}

Respond to a tap on the background view using the gesture recogniser, and call resignFirstResponder on the comment text view (which makes the keyboard disappear)

- (void)viewBackgroundTapped:(UITapGestureRecognizer *)tapGestureRecognizer
{   
    if ( _activeTextView )
    {
        [_activeTextView resignFirstResponder];
    }
}

Converting to Swift

The code above is from the pod, which you can find on GitHub. This only takes a handful of lines to implement in Swift, but they're scattered throughout my view controller class, so I'll show you all the pieces together here:

class BCLogView: UIViewController, UITextViewDelegate, UIGestureRecognizerDelegate {
    var commentField: UITextView = UITextView(frame: CGRectZero)
    var activeTextView: UITextView? = nil

    override func loadView() {
        // set up view here

        let tapRec: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: "backgroundTapped:")
        tapRec.delegate = self
        self.view.addGestureRecognizer(tapRec)
    }

    func textViewDidBeginEditing(textView: UITextView) {
        self.activeTextView = textView
    }
    
    func textViewDidEndEditing(textView: UITextView) {
        self.activeTextView = nil
    }

    func backgroundTapped(sender: UITapGestureRecognizer) {
        // Respond to a tap on the background view using the gesture recogniser
        
        if self.activeTextView != nil {
            self.activeTextView!.resignFirstResponder()
        }
    }
}

And that’s it! No external library necessary.