Category Archives: advice

Custom modally-presented UIViewController from another UIViewController

As a fast follow-up from the previous Custom UINavigationController Animated Transitions post, let’s say you have a UIViewController up and you want to transition to another UIViewController, but you are not using segues. The simple solution: present.

Create an animator like a fading animation controller

class FadeAnimator: NSObject, UIViewControllerAnimatedTransitioning {
    var duration: TimeInterval = 2.0

    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return duration
    }

    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        let containerView = transitionContext.containerView
        let toView = transitionContext.view(forKey: .to)!

        containerView.addSubview(toView)
        toView.alpha = 0

        UIView.animate(withDuration: duration,
                       animations: { toView.alpha = 1.0 },
                       completion: { _ in transitionContext.completeTransition(true) } )
    }
}

Extend your UIViewController as a transition delegate

Easy peasy: just extend the existing class, in this case to do a fade animation.

extension DetailViewController: UIViewControllerTransitioningDelegate {
    func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return FadeAnimator()
    }
}

Finally, instantiate and present!

Ready to show the next UIViewController? Let’s say you want to show a SubdetailViewController:

        let subdetailVC = SubdetailViewController()
        subdetailVC.view.frame = view.frame
        subdetailVC.transitioningDelegate = self
        present(subdetailVC, animated: true, completion: { })

The two bits of magic here are setting your “from” view controller as the transitioning delegate of the new view controller. Then you present with animations. The new controller will be presented modally over the old one.

One more thing: dismissing the new view controller

And when you’re ready to get rid of the new view controller, you can do it from inside it:

    dismiss(animated: true, completion: { })

That’s it!

Custom UINavigationController animated transitions

Thanks to the iOS Animation Tutorial: Custom View Controller Presentation Transitions article combined with A look at UIView Animation Curves (Part 3), it’s not that bad to implement custom transitions on UINavigationControllers. But of course, it’s sprinkling the right ingredients in the right proportions in the right files…

Create an Animator

Let’s start with the animation. If you create a file like SlideAnimator.swift and you put in it:

import UIKit

class SlideAnimator: NSObject, UIViewControllerAnimatedTransitioning {
    var duration: TimeInterval = 1.0

    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return duration
    }

    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        let containerView = transitionContext.containerView
        let toView = transitionContext.view(forKey: .to)!

        containerView.addSubview(toView)
        toView.frame = CGRect(x: 0, y: containerView.bounds.height, width: containerView.bounds.width, height: containerView.bounds.height)

        let timingFunction = CAMediaTimingFunction(controlPoints: 0/6, 0.8, 3/6, 1.0)
        CATransaction.begin()
        CATransaction.setAnimationTimingFunction(timingFunction)
        UIView.animate(withDuration: duration, animations: {
            toView.frame = containerView.frame
        }, completion: { _ in
            transitionContext.completeTransition(true)
        } )
        CATransaction.commit()
    }
}

That will take the “to view” that you’re going to and slide it up from the offscreen bottom and overlay the “from view”. In the above code, we do it in 1 second—tune to your liking. In animateTransition there’s a lot of juicy stuff in there:

  • The “to view” needs to be added to the containerView.
  • The transitionContext view needs to be added to the controller’s view.
  • We set the toView offscreen beyond the bottom.
  • We use a Bezier curve timing curve and add that to the CATransaction.
  • We do a usual UIView.animate and position the toView where we eventually want it.
  • The completion block notifies the transition context that it’s done.
  • And then we commit() to begin the animation.

Tell the UINavigationController to use custom transitions

(This does assume you are instantiating UINavigationController programmatically, and that it’s not your root view controller.) In the view controller that hosts your UINavigationController, set its delegate to itself, e.g.

class ViewController: UIViewController {
    var navVC: UINavigationController!
    ...

    override func viewDidLoad() {
        ...
        navVC = UINavigationController(rootViewController: tableVC)
        navVC.delegate = self
        ...
    }
}

Then it’s just a matter of extending that view controller to asking it for an animation controller depending on the operation (push or pop):

extension ViewController: UINavigationControllerDelegate {
    func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationControllerOperation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        switch operation {
        case .push:
            return SlideAnimator()
        default:
            return nil
        }
    }
}

Believe it or not, that is it!

Pssst. If you want to see how to go from UIViewController to UIViewController without a UINavigationController, you can do custom animations with present().

Creating custom UITableViewCell styles with dequeueReusableCell(withIdentifier:for:)

The trick with Swift always seems getting the right combination of object calls, the right naming, and wading through error messages. I have spent so much time on StackOverflow searching for “custom uitableviewcell” or “custom uitableviewcontroller” and “dequeuereusablecell indexPath fails”, and the Apple docs are as wordy as ever. Trying to use the dequeueReusableCell with the for:indexPath kept crashing my app with error messages like:

Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'unable to dequeue a cell with identifier CustomCell - must register a nib or a class for the identifier or connect a prototype cell in a storyboard'

After much frustration, this is the solution that seems to work the best:

Create a New Table Cell Class

Go ahead and create a new class with a XIB/NIB that inherits from UITableViewCell. You can do this programmatically, but sometimes visually working with autolayout constraints is just easier to grok. Make sure to bind all the labels and images and other UIView things from the NIB to the .swift class.

Create a New Table Controller

Create a new class inheriting from UITableViewController but no need to create a NIB unless you need. In the cellForRowAt method, you’ll want to dequeue as your custom table cell.

    let REUSE_IDENTIFIER = "YourCustomID"

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: REUSE_IDENTIFIER, for: indexPath) as! TableViewCell

        // Configure the cell...

        return cell
    }

Register the cell NIB on the tableView

Finally, the part that just killed me for the two days was how/when/where to register the NIB. And, the easiest is in the viewDidLoad of the table view controller:

    override func viewDidLoad() {
        super.viewDidLoad()

        tableView.register(UINib.init(nibName: "TableViewCell", bundle: nil), forCellReuseIdentifier: REUSE_IDENTIFIER)
    }

Notice that the register is on the tableView.

And that, is how you do it.

CSS3 columns demo

The addition to CSS3 removes so much pain from doing multicolumn layouts:

Dynamically deliver vertical action items with interoperable bandwidth. Appropriately drive client-centric systems for real-time process improvements. Proactively engineer client-centric technology and goal-oriented intellectual capital.

Competently enable virtual data before corporate initiatives. Authoritatively impact team building leadership skills without best-of-breed total linkage. Collaboratively unleash principle-centered opportunities and sticky e-tailers.

Assertively facilitate market positioning internal or “organic” sources with cross-platform methodologies. Conveniently leverage existing extensible sources after superior best practices. Dramatically empower high standards in interfaces vis-a-vis cross-media methods of empowerment.

Efficiently benchmark an expanded array of portals rather than prospective results. Monotonectally revolutionize interactive web-readiness without pandemic services. Dynamically envisioneer timely resources vis-a-vis cross functional ideas.

Authoritatively e-enable cooperative content whereas just in time relationships. Uniquely fashion low-risk high-yield users without vertical expertise. Dynamically facilitate just in time mindshare with sustainable testing procedures.

Collaboratively envisioneer prospective solutions after 24/7 channels. Rapidiously simplify mission-critical data via leveraged collaboration and idea-sharing. Proactively provide access to timely action items after real-time processes.

Dynamically deliver vertical action items with interoperable bandwidth. Appropriately drive client-centric systems for real-time process improvements. Proactively engineer client-centric technology and goal-oriented intellectual capital.

Competently enable virtual data before corporate initiatives. Authoritatively impact team building leadership skills without best-of-breed total linkage. Collaboratively unleash principle-centered opportunities and sticky e-tailers.

Assertively facilitate market positioning internal or “organic” sources with cross-platform methodologies. Conveniently leverage existing extensible sources after superior best practices. Dramatically empower high standards in interfaces vis-a-vis cross-media methods of empowerment.

Efficiently benchmark an expanded array of portals rather than prospective results. Monotonectally revolutionize interactive web-readiness without pandemic services. Dynamically envisioneer timely resources vis-a-vis cross functional ideas.

Authoritatively e-enable cooperative content whereas just in time relationships. Uniquely fashion low-risk high-yield users without vertical expertise. Dynamically facilitate just in time mindshare with sustainable testing procedures.

Collaboratively envisioneer prospective solutions after 24/7 channels. Rapidiously simplify mission-critical data via leveraged collaboration and idea-sharing. Proactively provide access to timely action items after real-time processes.

A good explanation is in the Mozilla docs. The main thing is this one property:

column-count: 3;

(Vendor prefixes required, unfortunately.)

Solving iPhone rendering issues: stopping scaling, restoring inertial scrolling

There’s nothing like wasting a Sunday morning searching the Internet for solutions to web page rendering issues on mobile devices. ūüôĀ

Stopping an iPhone from resizing web page text

Nothing is more frustrating as a designer/developer to see a neatly laid out web page resize its font size when you don’t expect it. I thought this line of HTML would have solved the problem:

<meta name="viewport" content="width=device-width, initial-scale=1.0">

No. Apparently you have to also add this CSS in:

body { -webkit-text-size-adjust: 100%; }

Adding inertial scrolling back to overflow:auto or overflow:scroll DIVs

It is a weird feeling to have a graceful UI somehow feel “stuck” when inertial scrolling gets disabled. Apparently setting the CSS property overflow to¬†auto, scroll, scroll-x,¬†or¬†scroll-y will turn it off. This is how you put it back:

-webkit-overflow-scrolling: touch;

Ugh.

 

Resizing (to fill) and cropping images with Paperclip :convert_options

It has been quite a while since I’ve used the Paperclip gem by ThoughtBot. I have a very simple model called¬†Photo which has an¬†image¬†attribute acting as the attachment:

class Photo < ActiveRecord::Base
  attr_accessible :image
  has_attached_file :image,
    styles: {
      thumbnail: '200x200^',
      preview: '800x800^'
    },
    convert_options: {
      thumbnail: " -gravity center -crop '200x200+0+0'",
      preview: " -gravity center -crop '800x800+0+0'"
    },
  default_url: "/images/:style/missing.png"
end

For some reason I couldn’t remember how to resize images to fit neatly within a square, but after fiddling around this totally works:

  • 200×200^: This sets the output geometry to 200×200 pixels but the shortest side will be 200 pixels.
  • convert_options: This adds the extra options to:
    • -gravity center: First center the image. It’s important this is first!
    • -crop ‘200×200+0+0’: Then crop the image into a 200×200 square with 0,0 as the offset.

Underscore.js’s debounce() is a great way to kill that double-click

Your mission: make sure your users don’t double-click a form button, but also don’t forever deny them future clicks.

This is a throttling problem really. And recently I was working on a UI element that was just being clicked way too often, my suspicion being the users were double-clicking it. I was going to do what I normally do: set a timeout on the click handler that would prevent more than 1 click being fired during a time period. But that just ends up making me write more code—I’d rather use a library!

Turns out that I’m using Underscore.js a little bit more these days and there is a built-in utility called debounce. It pretty much does exactly what I want it to do. As a comparison, there’s a similar function called throttle, but that still fires at least one more click event. Check this example out:

Mac OS X Mail uses “Sent Messages” folder, not “Sent”

Oh, Mac OS X…

It is pretty. But it has so many quirks. I suppose if you’ve been a long-time Mac fan these things don’t feel that weird to you, but I have to say that I’ve just come across the most non-obvious way of changing an application’s settings.

A few months back I took the plunge and made Mac OS X Mail my primary mail app. I figured all I had to do was hook up the accounts like I did on my iPhone and iPad: pick an account type, enter some credentials, and that was it. Generally-speaking, that is true. But I started running into this problem:¬†sent messages from OS X Mail weren’t showing up in my Sent mail folders on my iPhone and iPad.

I still have lots of IMAP mail accounts. When you add those IMAP accounts to an iPhone or iPad you can go to the advanced server settings and choose where you want the Sent messages to go: on the phone/tablet or to a folder on the server. And when you pick the folder on the server you normally would pick “Sent”. Not so for OS X Mail!

OS X Mail creates a folder called “Sent Messages” and there is¬†no indication anywhere that’s the case. If you look at the Mailboxes bar there is a Sent category which expands to show you all the sent-mail folders for all accounts you’ve connected. And if you open up your IMAP account folders you might see a Sent folder but no Sent Messages folder. As you send messages out your Sent Messages folder gets all those new sent messages.

(And, no, there is no indication in the Preferences for Mail that Sent Messages is the destination sent mail folder.)

How do you fix this? A post back in 2004 has the answer:

  • Open Mail
  • Navigate to the Sent folder of an IMAP account
  • Click the name to highlight it
  • From the Mailbox¬†menu choose Use This Mailbox For, then choose Sent

If you look at your IMAP folders now you will see the Sent Messages folder listed.

How horrible.

Grepping with more context

I wanted to get some more context while grepping some code. Turns out there are options -A and -B that give you n number of lines after/before the matched pattern. But that can still leave you a little lost, so why not add some color?

% grep some_pattern -B 3 -A 3 --color some_file
blah blah blah
blah blah blah
blah blah blah
blah some_pattern blah
blah blah blah
blah blah blah
blah blah blah