[Swift] Detect Touches on Attributed Text in UILabel

It’s always funny when you google “how do I…” and Google shows you a result that you actually wrote.

I wanted to solve this problem, and it looks like I did it years ago in Objective-C.

So, I re-wrote it in Swift that you can basically just copy-paste-use.  You’re welcome.

//  TappableLabel.swift
//  Created by Stephen O'Connor on 07.10.20.
//  MIT License.  You will send no lawyers here.  Have fun.
//  taken from here:  https://horseshoe7.wordpress.com/2015/12/10/detect-touches-on-attributed-text-in-uilabel/

import UIKit

typealias LabelLink = (text: String, link: Any?)

protocol TappableLabelDelegate: class {
    func didTapOnLink(_ link: LabelLink, in tappableLabel: TappableLabel)

extension NSAttributedString.Key {
    static let custom = NSAttributedString.Key("CustomAttribute")

class TappableLabel: UILabel {
    weak var delegate: TappableLabelDelegate?
    override init(frame: CGRect) {
        super.init(frame: frame)
    required init?(coder: NSCoder) {
        super.init(coder: coder)
    override func awakeFromNib() {
    private func commonInit() {
        self.textAlignment = .left // has to be left for this to work!
    private func addGestureRecognizers() {
        let tap = UITapGestureRecognizer(target: self, action: #selector(tappedLabel(_:)))
        self.isUserInteractionEnabled = true
    private func tappedLabel(_ tap: UITapGestureRecognizer) {
        guard let label = tap.view as? TappableLabel, label == self, tap.state == .ended else {
        let location = tap.location(in: label)
        processInteraction(at: location, wasTap: true)
    private func processInteraction(at location: CGPoint, wasTap: Bool) {
        let label = self
        guard let attributedText = label.attributedText else {
            return // nothing to do
        let textStorage = NSTextStorage(attributedString: attributedText)
        let textContainer = NSTextContainer(size: label.bounds.size)
        let layoutManager = NSLayoutManager()
        textContainer.lineFragmentPadding = 0.0
        textContainer.lineBreakMode = label.lineBreakMode
        textContainer.maximumNumberOfLines = label.numberOfLines
        let characterIndex = layoutManager.characterIndex(for: location,
                                                          in: textContainer,
                                                          fractionOfDistanceBetweenInsertionPoints: nil)
        if characterIndex < textStorage.length {
            log.info("Character Index: \(characterIndex)")
            let range = NSRange(location: characterIndex, length: 1)
            let substring = (attributedText.string as NSString).substring(with: range)
            log.info("Character at Index: \(substring)")
            if let labelLink = attributedText.attribute(.custom,
                                                        at: characterIndex,
                                                        effectiveRange: nil) as? LabelLink {
                log.debug("You \(wasTap ? "tapped" : "pressed") on \(labelLink.text) and the value is: \(String(describing: labelLink.link))")
                self.delegate?.didTapOnLink(labelLink, in: self)
    // will set the label's text to the given text argument, but for any callbackString it will search the text for that and embed it.
    func setText(_ text: String, withCallbacksOn callbackStrings: [LabelLink] = []) {
        self.text = text
        let attributedString = NSMutableAttributedString(string: text)
        let coreAttributes: [NSAttributedString.Key: Any] = [
            .foregroundColor : self.textColor!,
            .font: self.font!
                                       range: NSRange(location: 0, length: text.count))
        for labelLink in callbackStrings {
            let range = (text as NSString).range(of: labelLink.text)
            if range.location != NSNotFound {
                var additionalAttributes = coreAttributes
                additionalAttributes[.custom] = labelLink
                attributedString.setAttributes(additionalAttributes, range: range)
        self.attributedText = attributedString

An alternative to the #warning(…) tag in Xcode

Experienced old Objective-C developers like myself appreciated the #warning(...) tag in source code. It was a great way to remind yourself of things that might otherwise get lost in TODO comments that you forget to search for or your colleague is unaware of. Then it went away in Swift, and people like this guy came up with his own solution, which I liked for a while because it was truly customizable by keyword. Then Apple put #warning(...) back and that became less of an important thing to use.

…. Until I started working on Source Code that is to become a Cocoapod. For if you try to run pod lint spec MySpec.podspec, and there are compiler warnings, the validation process will fail.

So, return of the Run Script that goes through your Swift files and searches for keywords and adds warnings or errors as appropriate.

You can see the original post, or just create a run script (I think before you compile sources but I don’t think it matters), and add this (modify keywords as you like):

find "${SRCROOT}" \( -name "*.h" -or -name "*.m" -or -name "*.swift" \) -print0 | xargs -0 egrep --with-filename --line-number --only-matching "($TAGS).*\$|($ERRORTAG).*\$" | perl -p -e "s/($TAGS)/ warning: \$1/" | perl -p -e "s/($ERRORTAG)/ error: \$1/"

Why is FileManager so unforgiving?

This is more of a reminder for me as to how to do simple things.

Apple keeps modifying the FileManager API, but doesn’t actually make it obvious as to how to do simple things.  I have Data that I want to save somewhere.  You think it would be as easy as getting a path, then writing it.  Nope.

So here’s a recipe to show how to simply write some data somewhere, overwriting as you do it:

func testSerialization() {
        do {
            let data = try NSKeyedArchiver.archivedData(withRootObject: self.dates, requiringSecureCoding: false)
            let url = writeLocation()  // currently just the caches directory / SomeFolder / SomeFilename.dta
            let fm = FileManager.default
            if fm.fileExists(atPath: url.path) {
                try fm.removeItem(at: url)
            let folder = url.deletingLastPathComponent()
            try fm.createDirectory(at: folder, withIntermediateDirectories: true, attributes: nil)
            let success = fm.createFile(atPath: url.path, contents: data, attributes: nil)

            XCTAssertTrue(success, "Should have written the file!")
            XCTAssertTrue(fm.fileExists(atPath: url.path), "Should have written something here")

        } catch let error {
            XCTFail("Failed with error: \(error.localizedDescription)")


Multi-Threaded Core Data Solution

I’ve been wanting to revamp some old code that wasn’t performing as I like.  I had come up with something a bit too complicated involving NSOperationQueue, fetching remote data, parsing it all in the background, then saving that to my Core Data’s persistent store.

I always thought my solution a bit too complicated, and not sure it was entirely correct / robust.

I don’t know about you, but I regularly have 6-7 Google Chrome windows open with tons of tabs.  I have some articles that sit there for months that I don’t want to forget about.  One of which was written by Marcus Zarra, a prominent source of Core Data info.  He made it look so easy.

I’ve come to enjoy Core Data as a framework.  I think there is no other way at this point.  And I’m sure I’ve only scratched the surface of what it can do.  Along with mogenerator in your build pipeline, and especially the associated controller (NSFetchedResultsController) this framework is indispensable.

I basically took Marcus Zarra’s post and extended it to allow for doing data model work in the background, and also for making “scratch pad” contexts.  That is, consider editing forms but then the user hits “cancel”.  No rollbacks needed.  Just discard the “editor” context.  Or, consider data imports that run in the background, and you are on a view controller using an editor context.  You can even tell that editor context to update itself with those changes that occurred in the meantime.

Anyway, all a bit vague, so I’d like to just refer you to my repository that demonstrates what I’m talking about at http://github.com/horseshoe7

Please clone it and run it.  So a search for scenarioToExamine and change that value.

UPDATE:  I just found and read this article by Florian Kugler.  This solution above is an implementation of his so called “Stack #2”.  I really want to have a solution that’s going to be versatile and fast, so expect my repository’s implementation to change to be “Stack #3”.  Will update again after this happens.

UPDATE 2:  So I found my solution.  It’s in the Repo.  It’s called HSHybridThreeStack.  It has Marcus Zarra’s asynchronous background saving context, it has Florian Kugler’s separate context for speedy importing, it has main thread editing contexts that can optionally keep themselves updated to changes resulting from these import contexts, and an API that should be pretty straightforward.  You could refactor the HSCoreDataStack protocol and just incorporate it into one baseclass, but I kept it as such so that my various implementations were completely separate from one another.  So there is a lot of code repeated across implementations.  It is a Sandbox project after all and doesn’t represent an incredible approach to architecture.

“Invisible Container” – HSPassthroughView

I’d like to revisit an older post which deals with allowing subviews in a view hierarchy to be touchable, but making the parent view completely “non-interactive”.

Sometimes you need a ‘container view’ that can simplify Autolayout Constraint animations.  You want the view to be ‘invisible’ whilst allowing its subviews to behave normally. Being invisible, you may also want to tell it to inherit it’s superview’s color, so to reduce the need for layer blending (better performance)

I present the updated HSPassthroughView:

#import <UIKit/UIKit.h>

@interface HSPassthroughView : UIView

@property (nonatomic, assign) IBInspectable BOOL inheritsBackgroundColor;  // defaults to NO


@implementation HSPassthroughView

- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent *)event
    UIView *view = [super hitTest:point withEvent:event];
    if (view == self)
        NSLog(@"A view was asked for a view, but this is a QLPassthroughView.  If you see this message but expect a touch to be found, look here.");
        return nil;
    return view;  //ensure this view will never register a touch!
- (void)awakeFromNib
    if (self.inheritsBackgroundColor && self.superview) {
        self.backgroundColor = self.superview.backgroundColor;
- (void)willMoveToSuperview:(UIView *)newSuperview
    if (self.inheritsBackgroundColor) {
        self.backgroundColor = newSuperview.backgroundColor;
    [super willMoveToSuperview:newSuperview];

This helped solve a problem I had that was on StackOverflow.

UINavigationBar that Scrolls Away (Revised)

On this blog one of my most searched for posts is the one about a UINavigationBar that scrolls away.

That was written a while back.  I’ve recently had to revisit this topic again, so I thought I’d revise my implementation, which aims to remove many caveats and just make it easy to drop into your code and you’re done.

So I redid that code and made it slightly less hacky, and implemented it as a category on UIVIewController. If it doesn’t work for you the only thing I can think of are the new properties on UIViewController that pertain to layout. Mine had automaticallyAdjustScrollViewInsets to YES, and extend edges under top bars.

//  UIViewController+ScrollyNavBar.h
//  Created by Stephen O'Connor on 24/03/16.
//  MIT License

 How to use this:
 import this into your (probably) table view controller
 make sure you set HS_navigationBarLayerDefaultPosition in viewDidLoad:
 self.HS_navigationBarLayerDefaultPosition = self.navigationController.navigationBar.layer.position;
 optionally set HS_scrollingNavigationBarThresholdHeight if you want to be able to scroll a bit before 
 the nav bar starts scrolling with it.  A typical use case would be if you want the bar to start
 scrolling with your first table view section, and not with the tableViewHeader.
 in viewWillAppear:, call:
 - (void)HS_updateNavigationBarPositionUsingScrollView:(UIScrollView*)scroller
 and, assuming your table view controller is still the UITableView's delegate, it's also
 a scroll view delegate.  In -scrollViewDidScroll:, call:
 - (void)HS_updateNavigationBarPositionUsingScrollView:(UIScrollView*)scroller
 as well.

#import <UIKit/UIKit.h>

@interface UIViewController (ScrollyNavBar)

@property (nonatomic, assign) CGFloat HS_scrollingNavigationBarThresholdHeight;  // defaults to 0.  I.e. think about tableViewHeader's height
@property (nonatomic, assign) CGPoint HS_navigationBarLayerDefaultPosition;

- (void)HS_updateNavigationBarPositionUsingScrollView:(UIScrollView*)scroller;


Then then .m file:

// some theoretical knowledge here:  http://nshipster.com/associated-objects/

#import "UIViewController+ScrollyNavBar.h"
#import <objc/runtime.h>

@implementation UIViewController (ScrollyNavBar)

@dynamic HS_scrollingNavigationBarThresholdHeight;
@dynamic HS_navigationBarLayerDefaultPosition;

- (void)setHS_navigationBarLayerDefaultPosition:(CGPoint)HS_navigationBarLayerDefaultPosition {
                             [NSValue valueWithCGPoint:HS_navigationBarLayerDefaultPosition],

- (CGPoint)HS_navigationBarLayerDefaultPosition {
    return [(NSValue*)objc_getAssociatedObject(self, @selector(HS_navigationBarLayerDefaultPosition)) CGPointValue];

- (void)setHS_scrollingNavigationBarThresholdHeight:(CGFloat)HS_scrollingNavigationBarThresholdHeight {

- (CGFloat)HS_scrollingNavigationBarThresholdHeight {
    return [(NSNumber*)objc_getAssociatedObject(self, @selector(HS_scrollingNavigationBarThresholdHeight)) floatValue];

- (void)HS_updateNavigationBarPositionUsingScrollView:(UIScrollView*)scroller
    // get the navigation bar's underlying CALayer object
    CALayer *layer = self.navigationController.navigationBar.layer;
    CGFloat contentOffsetY = scroller.contentOffset.y;
    CGPoint defaultBarPosition = self.HS_navigationBarLayerDefaultPosition;
    CGFloat scrollingThresholdHeight = self.HS_scrollingNavigationBarThresholdHeight;
    // if the scrolling is not at the top and has passed the threshold, then set the navigationBar layer's position accordingly.
    if (contentOffsetY > -scroller.contentInset.top + scrollingThresholdHeight) {
        CGPoint newPos;
        newPos.x = layer.position.x;
        newPos.y = defaultBarPosition.y;
        newPos.y = newPos.y - MIN(contentOffsetY + scroller.contentInset.top - scrollingThresholdHeight, scroller.contentInset.top);
        layer.position = newPos;
        layer.position = defaultBarPosition;  // otherwise we are at the top and the navigation bar should be seen as if it can't scroll away.


Boom! Pretty straightforward.

Note however, that I don’t really recommend this functionality because it’s prone to error, and it’s sort of hacking UINavigationBar.  It has a few related consequences:

  • UITableView section headers will still ‘stick’ to the bottom of the now invisible navigation bar.
  • You must use a translucent UINavigationBar for this to really work properly.

Now, for the first of those two points, I don’t have a solution and anyone who does, please leave me something in the comments.

For the second one, if you read the API docs of UINavigationBar closely, you’ll see:

@property(nonatomic, assign, getter=isTranslucent) BOOL translucent


The default value is YES. If the navigation bar has a custom background image, the default is YES if any pixel of the image has an alpha value of less than 1.0, and NO otherwise.

So, with a little trickery, we can just change the Class on a UINavigationController to use a custom UINavigationBar class that sets an *almost* opaque image as the background image any time you change the barTintColor:

#import "UIImage+NotQuiteOpaque.h"

@interface HSNavigationBar : UINavigationBar

@implementation HSNavigationBar

- (void)awakeFromNib
    [self setBarTintColor:self.barTintColor];

- (void)setTranslucent:(BOOL)translucent
    NSLog(@"This HSNavigationBar must remain translucent!");
    [super setTranslucent:YES];

- (void)setBarTintColor:(UIColor *)barTintColor
    UIImage *bgImage = [UIImage HS_stretchableImageWithColor:barTintColor];
    [self setBackgroundImage:bgImage forBarMetrics:UIBarMetricsDefault];


And you’ll see here that the real trick is this category on UIImage that generates a stretchable image of the color specified, but it sets the top right corner’s pixel alpha value to 0.99. I assume that nobody will notice that slight translucency in one pixel at the top right of the screen.

@implementation UIImage (NotQuiteOpaque)

+ (UIImage*)HS_stretchableImageWithColor:(UIColor *)color
    UIImage *result;
    CGSize size = {5,5};
    CGFloat scale = 1;
    CGFloat width = size.width * scale;
    CGFloat height = size.height * scale;
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    size_t bitsPerComponent = 8;
    size_t bytesPerPixel    = 4;
    size_t bytesPerRow      = (width * bitsPerComponent * bytesPerPixel + 7) / 8;
    size_t dataSize         = bytesPerRow * height;
    unsigned char *data = malloc(dataSize);
    memset(data, 0, dataSize);
    CGContextRef context = CGBitmapContextCreate(data, width, height,
                                                 bytesPerRow, colorSpace,
                                                 (CGBitmapInfo)kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
    CGFloat r, g, b, a;
    UIColor *colorAtPixel = color;
    r = 0, g = 0, b = 0, a = 0;
    for (int x = 0; x < (int)width; x++)
        for (int y = 0; y < (int)height; y++)
            if (x == (int)width - 1 && y == 0) {
                colorAtPixel = [color colorWithAlphaComponent:0.99f];  // top right
            [colorAtPixel getRed:&r green:&g blue:&b alpha:&a];
            int byteIndex = (int)((bytesPerRow * y) + x * bytesPerPixel);
            data[byteIndex + 0] = (int)roundf(r * 255);    // R
            data[byteIndex + 1] = (int)roundf(g * 255);  // G
            data[byteIndex + 2] = (int)roundf(b * 255);  // B
            data[byteIndex + 3] = (int)roundf(a * 255);  // A
            colorAtPixel = color;
    CGImageRef imageRef = CGBitmapContextCreateImage(context);
    result = [UIImage imageWithCGImage:imageRef scale:scale orientation:UIImageOrientationUp];

    result = [result resizableImageWithCapInsets:UIEdgeInsetsMake(2, 2, 2, 2) resizingMode:UIImageResizingModeStretch];
    return result;


So, there it is. It works, yes, but not perfectly, and the whole thing feels a bit hacky. I wonder if there’s a better way to do this, or if in the future Apple will start to update this code. Seeing as we see collapsing address bars on WebView controllers like in Safari, perhaps they might abstract this further to support UINavigationControllers.

Cheat Sheet – IB_DESIGNABLE Views

Not so much a cheat sheet, but a general procedure to follow. Consider this class, a UILabel that draws an outline around it that you can customize and more importantly, view from Interface Builder:


@interface HSOutlinedLabel : UILabel

@property (nonatomic, assign) IBInspectable CGFloat outlineWidth;
@property (nonatomic, assign) IBInspectable UIColor *outlineColor;
@property (nonatomic, assign) IBInspectable CGFloat cornerRadius;

@property (nonatomic, assign) IBInspectable CGPoint padding;  // between the text and outline


and then the .m

#import "HSOutlinedLabel.h"

@implementation HSOutlinedLabel

- (instancetype)initWithCoder:(NSCoder *)aDecoder
    self = [super initWithCoder:aDecoder];
    if (self) {
        [self _commonInit];
    return self;

- (void)_commonInit
    // gets called first (initWithCoder:), before any of the settings in IB are used.
    self.layer.masksToBounds = YES;
    _outlineWidth = 0;
    _outlineColor = nil;
    _cornerRadius = 5;
    _padding = CGPointZero;
    self.layer.cornerRadius = _cornerRadius;

- (void)prepareForInterfaceBuilder
    // careful!  this will overwrite any settings in Interface Builder.
    // it's meant for setting values that perhaps aren't IBInspectable
    [super prepareForInterfaceBuilder];

- (void)awakeFromNib
    if (_outlineWidth > 0) {
        self.layer.borderWidth = _outlineWidth;
        if (_outlineWidth > 0) {
            self.layer.borderColor = _outlineColor ? _outlineColor.CGColor : NULL;
            self.layer.borderColor = NULL;

#pragma mark - Setters (Are used by Interface Builder)

// NOTE:  Interface Builder calls setValue:forKey: when you change
// an inspectable value.  If you are purely drawing your UIView subclass
// with -drawRect: then these changes update.
// However, if you see the setters below, I use the IBInspectables
// to ultimately wrap CALayer properties.  If you want your view to
// draw correctly,  you need to write accessors for any IBInspectable
// that triggers changes to any object that won't necessarily be done
// in drawRect:

- (void)setOutlineWidth:(CGFloat)outlineWidth
    if (_outlineWidth != outlineWidth) {
        _outlineWidth = outlineWidth;
        self.layer.borderWidth = outlineWidth;

- (void)setOutlineColor:(UIColor *)outlineColor
    if (_outlineColor != outlineColor) {
        _outlineColor = outlineColor;
        self.layer.borderColor = outlineColor.CGColor;

- (void)setCornerRadius:(CGFloat)cornerRadius
    if (_cornerRadius != cornerRadius) {
        _cornerRadius = cornerRadius;
        self.layer.cornerRadius = cornerRadius;

- (void)setPadding:(CGPoint)padding
    if (!CGPointEqualToPoint(_padding, padding)) {
        _padding = padding;
        [self invalidateIntrinsicContentSize];

- (CGSize)intrinsicContentSize
    CGSize size = [super intrinsicContentSize];
    size.width += 2 * _padding.x;
    size.height += 2 * _padding.y;
    return size;


I just meant this to be a starting point for your own work. And actually a reminder for myself.

UITableView with Animating Section Header

So, I’ve been scratching my head on an issue I’ve been having:

I want to have a UITableView with a section header, because I want it to stick to the top of the screen while scrolling.  The thing is, it’s supposed to have a search bar that expands when you enter “search mode”.

What you see in the video is my prototype.  Tapping a table cell is supposed to toggle this search mode on and off.  In the video above you see that that table cell layout updates and expands, but the section header seems to be “one click behind” and expands when the cell layouts push up because they think the header is shorter.

This behaviour happened via the standard posts on stack overflow.  Here, for example.

Now, perhaps my issue is because I also have a stretchy section header, which I based off of this post:

Anyway, I found the solution involved a slightly complicated solution, but once it’s in place, there’s little to know (i, ii, iii, …) and do (A, B, C, D, …) :

i)  I assume you will have one section on your table view
A) Define an associated view on your view controller, which will be your “expandableHeaderView” (more on the specifics later)
Screenshot 2016-03-17 14.03.04

B) Define your class to have such properties and methods:

@interface QLExpandableSectionHeaderTableViewController : UITableViewController

@property (nonatomic, assign) IBInspectable NSInteger expandableSectionHeaderInactiveHeight;  // defaults to 44
@property (nonatomic, assign) IBInspectable NSInteger expandableSectionHeaderActiveHeight; // defaults to 88

@property (nonatomic, strong) IBOutlet UIView *expandableHeaderView;

@property (nonatomic, readwrite, getter=isHeaderExpanded) BOOL headerExpanded;

- (void)setSectionHeaderHeightExpanded:(BOOL)expanded animated:(BOOL)animated;


@implementation QLExpandableSectionHeaderTableViewController

- (instancetype)initWithCoder:(NSCoder *)aDecoder
    self = [super initWithCoder:aDecoder];
    if (self) {
        _expandableSectionHeaderInactiveHeight = 44.f;
        _expandableSectionHeaderActiveHeight = 88.0f;
    return self;

- (void)viewDidLoad {
    [super viewDidLoad];
    if (!self.expandableHeaderView) {
        NSLog(@"WARNING: You haven't provided an associated header view you want to use when toggling section header height!");
        // this has to be clear, since animation doesn't quite work as desired.
        self.expandableHeaderView.backgroundColor = [UIColor clearColor];

- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
    if (section == 0) {
        if (self.isHeaderExpanded) {
            return self.expandableSectionHeaderActiveHeight;
        return self.expandableSectionHeaderInactiveHeight;
    return 0;

- (UIView*)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
    if (section == 0) {
        if (self.expandableHeaderView) {
            return self.expandableHeaderView;
            // will probably never get used
            UIView *header = [UIView new];
            header.backgroundColor = [UIColor redColor];
            header.frame = CGRectMake(0, 0, self.tableView.bounds.size.width, self.expandableSectionHeaderInactiveHeight);
            return header;

    return nil;

- (void)toggleSectionHeader:(UIButton*)button
    if (self.isHeaderExpanded) {
        [self setSectionHeaderHeightExpanded:NO animated:YES];
    else {
        [self setSectionHeaderHeightExpanded:YES animated:YES];

- (void)setSectionHeaderHeightExpanded:(BOOL)expanded animated:(BOOL)animated
    if (self.isHeaderExpanded && expanded) {
        return;  // nothing to do!
    self.headerExpanded = expanded;
    [self.tableView beginUpdates];
    [self.tableView endUpdates];
    //NSLog(@"%@", self.searchHeaderView.constraints);
    __block NSLayoutConstraint *headerHeightConstraint = nil;
    [self.expandableHeaderView.constraints enumerateObjectsUsingBlock:^(__kindof NSLayoutConstraint * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        // I determined this name by logging the self.searchHeaderView.constraints and noticed
        // it had a constant value that was one of my searchBar(In)activeHeight values.
        if ([obj.identifier isEqualToString:@"UIView-Encapsulated-Layout-Height"]) {
            headerHeightConstraint = obj;
            *stop = YES;
    CGFloat newHeight = expanded ? self.expandableSectionHeaderActiveHeight : self.expandableSectionHeaderInactiveHeight;
    headerHeightConstraint.constant = newHeight;
    // after the animation completes, the expandableHeaderView's frame doesn't change until a layout update
    // , which only happens after you start scrolling.  This will ensure it has the right size as well.
    CGRect frame = self.expandableHeaderView.frame;
    frame.size.height = newHeight;
    [UIView animateWithDuration:animated ? 0.3f : 0.0f
                         [self.tableView layoutIfNeeded];
                     } completion:^(BOOL finished) {
                         self.expandableHeaderView.frame = frame;

The code above will handle animation.  There is still some weirdness happening on the UIKit side of things, because the the header view’s subviews animate according to auto-layout, but the self.expandableHeaderView doesn’t visually change size until a layout update occurs.  And this only occurs once you start scrolling.  So we set that frame to the size it’s supposed to have, and that sorts it all out. As a result, we have to set the self.expandableHeaderView.backgroundColor = [UIColor clearColor];

And that solved the problem! It’s a bit hacky, but I found no other way to accomplish this that isn’t THAT ugly.

And here’s the result:

Storyboard Segues that don’t animate

It’s a bit of a hack as one can’t seem to set this easily in code, but perhaps you have a view controller hierarchy in your Storyboard where you want to guarantee a specific state on app launch, but your entry point is in some base-level container view controller (think HSApplicationViewController).

It would be silly on viewDidLoad to then trigger a bunch of segues, especially if these animate.  The App would start and the first thing you’d see would be transitions through a view controller hierarchy.

My solution for this started via this article on stackoverflow, and is basically just repackaged here.

I tend to keep a naming convention for segue identifiers.  They usually begin with “segue…” for forward segues, and “unwind….” for unwind segues.

Have a look at this: non-animated_segue

The idea is that you duplicate any segue, uncheck the animates box in the inspector pane and add a suffix to it, such as “…NoAnimation”.  So now you would have “segueSomeThing” and “segueSomeThingNoAnimation”, then when you implement your “prepareForSegue:” method, you have the same handler code for both:

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
  if ([segue.identifier hasPrefix:@"segueSomeThing"])
    // the key is "hasPrefix". You can even use a string constant for your animated variant
    // same destinationViewController, same setup. Just not animated.

And that’s it.  It does have a bit more management overhead as you have to keep track of hard-coded strings in your Storyboard in 2 places, but perhaps it’s worth it.