An Interactive UITextView with RxSwift

Recently I once again had need for a text view in which any arbitrary word could be marked as a button. This certainly isn't the first time I've needed such a view, but it is the first time I've wanted one with `RxSwift` support. I did a cursory search but didn't find anything simple enough which did what I wanted, so I built one. I've made it a gist but for ease I'll also paste it below. Hopefully you'll find it helpful.

//
//  InteractiveTextView.swift
//  Views
//
//  Created by James Valaitis on 05/03/2018.
//  Copyright © 2018 VIPR Digital. All rights reserved.
//

import RxSwift
import UIKit

//  MARK: Interactive Text View
/**
A text view with support for tapping on certain words.
 */
open class InteractiveTextView: UITextView {
    //  MARK: Properties
    private let interactiveAttribute = NSAttributedStringKey("com.fixr.interactiveAttribute")
    private typealias Callback = () -> ()
    private var callbacks = [String: Callback]()
    //  MARK: Initialization
    public override init(frame: CGRect, textContainer: NSTextContainer?) {
        super.init(frame: frame, textContainer: textContainer)
        setup()
    }
    public required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setup()
    }
}
//  MARK: Configuration
private extension InteractiveTextView {
    func setup() {
        isUserInteractionEnabled = true
        let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
        addGestureRecognizer(tapRecognizer)
    }
}
//  MARK: Interactions
public extension InteractiveTextView {
    /**
    Observes any tap made on the first instance of the provided text found within the `attributedText`.
    - Parameter text:    The portion of text to be monitored.
    - Returns:    An observable for any tap made on the text. An error will be returned if the text could not be found within `attributedText`.
     */
    func tap(onString text: String) -> Observable<Void> {
        guard let range = attributedText.string.range(of: text) else { return .error(InteractiveTextViewError.wordNotFound) }
        let updatedText = NSMutableAttributedString(attributedString: attributedText)
        updatedText.addAttributes([interactiveAttribute: text], range: NSRange(range, in: attributedText.string))
        attributedText = updatedText
        return .create { [weak self] observer in
            guard let `self` = self else { return Disposables.create() }
            self.callbacks[text] = { observer.onNext(()) }
            return Disposables.create { self.callbacks[text] = nil }
        }
    }
}
private extension InteractiveTextView {
    @objc func handleTap(_ recognizer: UITapGestureRecognizer) {
        //    location of tap in myTextView coordinates and taking the inset into account
        var location = recognizer.location(in: self)
        location.x -= textContainerInset.left;
        location.y -= textContainerInset.top;

        //    character index at tap location
        let characterIndex = layoutManager.characterIndex(for: location, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)

        //    if index is valid then do something
        guard characterIndex < textStorage.length else { return }

        // check if the tap location has a certain attribute
        guard let observedText = attributedText.attribute(interactiveAttribute, at: characterIndex, effectiveRange: nil) as? String,
            let callback = callbacks[observedText] else { return }

        callback()
    }
}
//  MARK: Interactive Text View Error
public enum InteractiveTextViewError: Error {
    ///    The provided word could not be found within the text.
    case wordNotFound
}

2017 Hip Hop

2017 was a good year for Hip Hop. I haven't listened to everything, but I've made an effort to check out some new people. I'm not going to do a Top 5, or some sort of categorised "best of" compilation. Instead I'd like to shout some artists and albums that I feel have been exceptional in 2017.

saturation-ii.jpg

BROCKHAMPTON

I'd feel like an idiot if I didn't mention this boy band. Hitting with a fire first album, SATURATION, back in June it seemed like they came out of nowhere and were noteworthy out of the gate. Just two months later they drop the follow up, SATURATION II, which already commented on some of what people found special about their presence in this genre.

As if two great albums in a single year wasn't enough, mere weeks before 2017 drew to a close SATURATION III was released complete with the bouncy, funky production and co-ordinated vocals the group had given us up until that point.

Shout out to these bars (SATURATION II - Junky):

"Why you always rap about bein' gay?" 'Cause not enough niggas rap and be gay

100000x100000-999.jpg

J.I.D - The Never Story

Watch this guy; he's going to blow up. His "The Never Story" was one of my favourite projects this year and demonstrates incredible talent beyond what one would usually expect from a first studio album. The promise in this 27 year old is not to be underestimated.

4eva-Is-A-Mighty-Long-Time-Artwork-1.jpg

Big K.R.I.T. - 4eva is a Mighty Long Time

I'd happily just call this my favourite album of the year, or rather, my favourite two albums. KRIT could have dropped either side of this double album and it would have been "stop what you're doing and listen to this right now", but for anything with 22 tracks (20 excluding skits) to be this consistently incredible - it goes past "wow" and into "how?!".

It kicks off with a song called "Big K.R.I.T." which starts slow before transitions smoothly into the high energy rapping and from there we get carry on with the absolute bangers; "Confetti" and "Big Bank" (featuring none other than the other King of the South himself, T.I.). I would love to go through each track and sing it's praises, but I'll finish by touching on the second disc wherein we get a more introspective and personal KRIT, exemplified by his choice to give the first song his family name "Justin Scott". Please listen to this, if nothing else of what I mention.

final.jpg

IDK - IWASVERYBAD

Another rapper that I hadn't previously heard but massively impressed me with his debut album. The journey over the 12 songs was incredibly well executed. The succinct set up of his mother's exasperation played well with the empathy created for IDK in his adoration of his Mum as the album rounds out. Each track demonstrates passion and serious skill.

eminem-revival-2017-album-29-2160x3840.jpg

Eminem

Thought I'd just take some time to mention that I didn't hate Revival, I rather enjoyed it. I also still massively believe in this guy and hope that the severe criticism of his latest albums lights even more of a fire under his arse. If the Chloraseptic Remix is anything to go by, Shady has a lot more to give us.

I'll admit to being a massive Stan by the way. As far as I'm concerned Em is probably the G.O.A.T. and is certainly my favourite artist of all time. That being said, Encore was straight trash, so I do know a bad album when I hear it.

cunninlynguists-rosa

Cunninlynguists - Rose Azura Njano

When people don't know about these guys it's hurts me a little inside. They have to be one of the most solid Hip Hop trios still going and each of their projects has been one of my favourites of it's released year.

This album touches on the political climate of America in a manner which feels simultaneously gentle and forceful.

Measure Function Performance in Swift

It's simple.

At the start of the function add:

let start = mach_absolute_time()

and at the end of the function add:

let endNano = mach_absolute_time() - start
let endSec = TimeInterval(endNano) / TimeInterval(NSEC_PER_SEC)
print("\(#function) - \(endSec)")

For example:

override func viewDidLoad() {
    let start = mach_absolute_time()
    super.viewDidLoad()
    …
    //    do other stuff
    …
    let endNano = mach_absolute_time() - start
    let endSec = TimeInterval(endNano) / TimeInterval(NSEC_PER_SEC)
    print("\(#function) - \(endSec)")
}

Easy.

MapKit Tutorial: Clustering in iOS 11

With the iOS 11 announcement one of the things that most excited me was the MapKit updates, the highlight of course being clustering. Let's look at an implementation now.

Read More