Swift Trimming Trailing Punctuation

Lately, I’ve been working on a project that requires some basic MLP text processing to be performed on device.  A component of our text processing involved removing any trailing punctuation, no matter the language.

I’ve found the below String Extension is a good balance between effectiveness, performance, and readability.  Especially if you are collecting input from users prone to excessive punctuation.

extension String {
func trimTrailingPunctuation() -> String {
return self.trimmingCharacters(in: .whitespacesAndNewlines)
.trimmingCharacters(in: .punctuationCharacters)
.trimmingCharacters(in: .whitespacesAndNewlines)
}
}
let example1 = "How are you???".trimTrailingPunctuation()
>>> How are you
let example2 = "Hi!!!!".trimTrailingPunctuation()
>>> Hi
let example3 = "Act limited time offer now!".trimTrailingPunctuation()
>>> Act limited time offer now

 

Stopping Tap Gesture from bubbling to child controls

The default behavior is for your tap or other gestures to bubble up to their child controls. This avoids the need to add a recognizer on all of your child controls. But this isn’t always the behavior you are looking for. For example imagine you create the below modal view. You want to add a tap gesture recognizer to the view itself so when your user taps the grey area it closes the modal. But you don’t want the gesture to be triggered when a tap is made on the content UIView of the modal.

modal-example

First adding the UITapGestureRecognizer

For our use case the first thing we need to do is add a UITapGestureRecognizer to the root UIView of the UIViewController. This will close our UIViewController when the grey area is tapped.

internal class ConfirmationViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let tap = UITapGestureRecognizer(target: self, action: #selector(self.handleTapOffModal(_:)))
tap.delegate = self
view.addGestureRecognizer(tap)
view.isUserInteractionEnabled = true
}
}

Limiting the scope of the Gesture

So that the UITapGestureRecognizer isn’t triggered when a tap is made to the content UIView we simply need to add protocol method to restrict the tap to the view it is associated with.

extension ConfirmationViewController: UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
return touch.view == gestureRecognizer.view
}
func handleTapOffModal(_ sender: UITapGestureRecognizer) {
dismiss(animated: true, completion: nil)
}
}

Putting it all together

Below is the full code associated with the modal UIViewController. If you are not familiar with how to create a modal UIViewController I would recommend checking out Tim Sanders tutorial here.

import UIKit
internal class ConfirmationViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let tap = UITapGestureRecognizer(target: self, action: #selector(self.handleTapOffModal(_:)))
tap.delegate = self
view.addGestureRecognizer(tap)
view.isUserInteractionEnabled = true
}
}
extension ConfirmationViewController {
@IBAction func cancel_click(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
@IBAction func confirm_click(_ sender: Any) {
print("Perform Confirmation action")
dismiss(animated: true, completion: nil)
}
}
extension ConfirmationViewController: UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
return touch.view == gestureRecognizer.view
}
func handleTapOffModal(_ sender: UITapGestureRecognizer) {
dismiss(animated: true, completion: nil)
}
}

Check if GestureRecognizer added

One of my new projects dynamically generates a UI based on a DSL entered by the user. This leads to a host of defensive coding challenges since there is multiple code paths that can add controls and their associated gesture recognizers.

I found it helpful to add guard statements that stop recognizers from being added twice. The same approach is used for controls, but that is a different topic.

The below utility function can be used to check if the provided recognizer is already in the recognizer collection associated with an object.

func containsGestureRecognizer(recognizers: [UIGestureRecognizer]?, find: UIGestureRecognizer) -> Bool {
if let recognizers = recognizers {
for gr in recognizers {
if gr == find {
return true
}
}
}
return false
}

The following code shows the utility function in action. All you have to do is incorporate containsGestureRecognizer into your guard statements.

/// Example adding it the first time
let recognizer = UITapGestureRecognizer(target: self, action: #selector(self.handleTap(_:)))
view.addGestureRecognizer(recognizer)
/// Test if gesture already added, if added return else add the recognizer
guard containsGestureRecognizer(recognizers: view.gestureRecognizers, find: recognizer) == false else { return }
/// Add the recognizer
view.addGestureRecognizer(recognizer)
view raw example.swift hosted with ❤ by GitHub

Although really an edge case this was a fun bit of code to try out.

Logging Exceptions to Google Analytics Using SwiftyBeaver

There has been an avalanche of Swift based logging frameworks lately.  After trying several, I found SwiftyBeaver was both flexible and simple enough to move all of my apps onto.  Similar to most of the other logging frameworks, SwifyBeaver is extensible. But, unlike many others, there is no real magic injected into the framework making it easy to read and hopefully maintain.  This is best demonstrated by how compacted it’s plugin system is.  You really only need to write you business specific information, the rest is handled for you.

I use Google Analytics for behavior tracking and basic exception tracking today.  Although all of my Google Analytics code is centralized exception reporting is handled explicitly.  With the move to SwiftyBeaver I wanted to see how reporting exceptions to Google Analytics could be handled automatically as part of my logging strategy.  To accomplish this I created the SwiftyBeaver Google Analytics Exception Logger, available here.  Just as the very long name indicates this plugin will automatically post error information to Google Analytics.

Before you start

The Google Analytics Exception plugin requires that you first install SwiftyBeaver and Google Analytics.  You can find information on how to do this below.

After both SwiftyBeaver and Google Analytics have been installed you need to copy the plugin into your project. Instructions for doing this are available here.  You’re now ready to start configuring logging in your project.

Creating your Logger

Creating a logger in your project is extremely simple. Typically you will want to add the below to the top of your AppDelegate.swift so you can use logging throughout your project.

import SwiftyBeaver
let logger = SwiftyBeaver.self

Adding the Google Analytics Logger Destination

Now that you have created your SwiftyBeaver logger you need to add destinations.  Without adding any destinations SwiftyBeaver wont actually do anything.  For this example I’m going to add two destinations. The first will be the built-in Console destination which simply writes to the Xcode console.

let console = ConsoleDestination()
logger.addDestination(console)

Next we’ll add the Google Analytics Exception Logger.  When creating the GABeaver plugin you must added your Google Analytics key.  This will be used when reporting Exceptions.  You can also specify the reporting threshold.  This parameter controls the minimum logging level that should be reported to Google Analytics as an exception. By default this is set to only report error levels or greater.  If you wanted to report warnings or higher you could simply provide a threshold of warning and the plugin will automatically send both warnings and errors.

Below illustrates how to add the Google Analytics Exception plugin with the default settings.

let gaErrorLogger = GABeaver(googleAnalyticsKey: "your GA Key")
logger.addDestination(gaErrorLogger)

Optional Configurations

By default only Error Log messages will be sent to Google Analytics.  You can change this by setting the Threshold property on the logger.

For example, you can record all message with a level of WARNING or great by doing the following:

gaErrorLogger.Threshold = SwiftyBeaver.Level.Warning

You can also configure the logger to write to the console each time a message is logged. To enable console logging, set the printToConsole property to true as shown below:

gaErrorLogger.printToConsole = true

More Information

Additional information is available on github at SwiftyBeaver-GA-Exception-Logger.

SwiftyBeaver

Using OpenURL to launch links in a UIWebView

UIWebView in great for displaying HTML formatting information in your app.  In many cases you might be displaying fragments you didn’t create opening the door for links your user might action.  By default your embedded UIWebView will follow the link which can lead to an odd user experience.  There are several options for handling this including opening the SFSafariViewController.  Since a majority of text I will be displaying doesn’t have links I will just be using openURL to open the link in Safari. With the back button introduced in iOS9 this simplistic user experience can be added in just a few lines of code. The below walks through what is needed to implement this approach.

Adding the delegate

First the UIWebViewDelegate Protocol needs to be added to your controller.   This allows us to override the necessary loading content methods.

import UIKit

class ViewController: UIViewController, UIWebViewDelegate {

    @IBOutlet weak var webView: UIWebView!
    let html = "<div>Lorem ipsum dolor sit amet..."

}

Next in your viewDidLoad override method set the delegate method of your UIWebView to the controller, ie self.

override func viewDidLoad() {
    super.viewDidLoad()
    self.webView.delegate = self
    self.webView.loadHTMLString(html, baseURL: nil)
}

Loading Content

The final step is to add the webView:shouldStartLoadWithRequest:navigationType: method. This will be called whenever content is loaded or a navigation action such as clicking on a link is called.

func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool {

    if navigationType == UIWebViewNavigationType.LinkClicked {
        UIApplication.sharedApplication().openURL(request.URL!)
        return false
    }
    
    return true
}

You will notice we are checking if the navigationType  is LinkClicked  and then using openURL to open the link in Safari.  It is important to note that if this is a link we return false. This will stop the UIWebView from loading the link content.  Otherwise we always return true so the HTML passed to the UIWebView will load as expected.

The Result

With our delegate and content loading method in place Safari will load each time the user taps on a link in your UIWebView.  The following shows this snippet in action.

UIWebViewOpenUrl

A gist with the complete code is available here.

UIWebView Background Experiments

UIWebView is one of the most useful controls in UIKit and has almost a limitless amount of options you can configure.   My use case isn’t all that unique I’m simply displaying snippets of HTML email messages in a full screen UIViewController.  The need to set the background color of the UIWebView is the only thing that makes this more than just a simple drag and drop in Interface Builder.  Although this can all be done in 2-3 lines of code I thought it would be fun to share the combinations.

The Starting Snippet…

The code we are using for this experiment couldn’t be simpler consisting of a UIWebView that takes up most of the screen. We’ll use this snippet for our background experiments.

import UIKit

class ViewController: UIViewController {
    @IBOutlet weak var webView: UIWebView!
    let html = "<div>Lorem ipsum dolor sit amet...</div>"
    override func viewDidLoad() {
        super.viewDidLoad()
        self.view.backgroundColor = UIColor.lightGrayColor()
        self.webView.delegate = self
        self.webView.loadHTMLString(html, baseURL: nil)
    }
}

UIWebView Default Behavior

Let’s start with our default behavior.  When you overscroll you can see the linen background.  For my use case I want to replace the background linen with a solid color.

UIWebView-Default

 

UIWebView White Background

By adding two lines of code, the background linen has been replaced with a white background.

self.webView.opaque = true
self.webView.backgroundColor = UIColor.whiteColor()

UIWebView-White-Background

UIWebView Underlying UIView

Since the background UIView for our UIViewController is lightGrayColor we can set the backgroundColor of the UIWebView to clearColor to have our overscroll exposure the underlying UIView.  This is a nice option if you have a background image or treatment you want to expose to the user when they overscroll.

self.webView.opaque = true
self.webView.backgroundColor = UIColor.clearColor()

UIWebView-Show-Background

UIWebView Transparent Background

By changing opaque to false we show the background view under the contents of the UIWebView.  This is outside my use case but can see where this could be a useful technique for overlaying text.

self.webView.opaque = false
self.webView.backgroundColor = UIColor.clearColor()

UIWebView-Transparent-Background

 

Gist to the example code is available here.

A simplistic approach to using Google Analytics in your Swift UIViewControllers

I use Google Analytics to collect usage statistics across both my web and mobile applications.  There are a few great AngularJS and other libraries that track page usage automatically when a controller is initialized.  Using these concepts I wanted to create my own similar approach in Swift.  The end result of this is the creation of a simple BaseController which makes an analytics call when viewDidLoad is called.

class BaseController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        Analytics().screenView(self.controllerName)
    }

}

Deconstructing this simple one line method call you will notice there are an extension method called controllerName passed into a static method on the analytics struct.  The controllerName references the below extension method and simply is used to get the name of the UIViewController so that we can track usage by the screen name.  Below is a snippet with the code for this extension method.

extension UIViewController {

    var controllerName: String {
        return NSStringFromClass(self.classForCoder).componentsSeparatedByString(&amp;quot;.&amp;quot;).last!
    }

}

The analytics struct is an anti corruption layer that I’ve added to my app to wrap the underlying Google Analytics code.  This also provides a layer of abstraction which helps combat the fact that Xcode randomly makes the Google Analytics Objective-C references invalid.  The Analytics struct is used in two places in my app. First it is initialized in my app delegate, then it is used within my BaseController to record feature usage.

Before we get to the Analytics struct code below is the snippet using in my app delegate used to create a default tracker with our Google Analytics key.

Analytics.createTracker(Analytics.GAToken.QAKey.rawValue)

Once initialized the Analytics struct method screenView is used on each viewDidLoad call to record that a UIViewController has been viewed.

import Google

public struct Analytics {

    enum GAToken: String {
        case QAKey = "UPDATE-WITH-QA-KEY"
        case ProdKey = "UPDATE-WITH-PROD-KEY"
    }

    public static func createTracker(key: String) {
        GAI.sharedInstance().trackerWithTrackingId(key)
        GAI.sharedInstance().logger.logLevel = GAILogLevel.None
        GAI.sharedInstance().trackUncaughtExceptions = true
    }

    private func getTracker() -&amp;gt; GAITracker {
        return GAI.sharedInstance().defaultTracker
    }

    public func screenView(name: String) {
        let tracker = getTracker()
        tracker.set(kGAIScreenName, value: name)
        let builder = GAIDictionaryBuilder.createScreenView()
        tracker.send(builder.build() as [NSObject : AnyObject])
    }
}

Although there are more comprehensive Analytics (Google or other) approaches I’ve found this straightforward approach provides the basics in away that works for me.

Helpful Links:

  • gist with source code available here
  • Google Analytics on CocoaPods here
  • Google Analytics for iOS resources

Swift handling the back button title

For my current project we’ve got a design requirement that all screens have a simple “Back” button instead of the default text that iOS provides when pushing a new controller.

Back-Button

I found that Swift’s inheritance model combined with extensions methods make this simple to implement in a re-usable fashion.  To start this approach, I implemented a based controller which I called  BaseController that manages the title of the backBarButtonItem.  This is automatically handled by just inheriting from the BaseController.  In the below example we create a new NotificationController using this pattern.

import UIKit

class NotificationController: BaseViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
    }

}

The BaseController only has two lines of code.  The first creates a string with the title of the back button. The second calls an extension method that manages the back button title information.

import UIKit

class BaseViewController: UIViewController {

    override func viewDidLoad() {

        super.viewDidLoad()

        let backTitle = NSLocalizedString("Back", comment: "Back button label")
        self.addBackbutton(backTitle)

    }

}

The heavy lifting is performed in the UIViewController extension.  Using the addBackbutton method to add a new UIBarButtonItem or update the current one’s title.  If a new UIBarButtonItem is added the backButtonAction method is added to dismiss the controller on press.

import UIKit

extension UIViewController {

    func backButtonAction() {
        self.dismissViewControllerAnimated(true, completion: nil)
    }

    func addBackbutton(title: String) {
        if let nav = self.navigationController,
            let item = nav.navigationBar.topItem {
            item.backBarButtonItem  = UIBarButtonItem(title: title, style: UIBarButtonItemStyle.Plain, target: self, action:
                #selector(self.backButtonAction))
        } else {
            if let nav = self.navigationController,
                let _ = nav.navigationBar.backItem {
                self.navigationController!.navigationBar.backItem!.title = title
            }
        }
    }
}

The full code is available as a gist here.

Using IANA Time Zones with NSTimeZone

As part of a recent project I’ve been spending more time then I’d like with time zones.  In my case a variety of dates and time zone details are provided by a remote service. Having spent the last few years solving this type of problem with moment.js I was excited to see that NSTimeZone contained many of the same features.

The time zone information is provided in the IANA time zone database format.  I was really existed to see that you can create a new NSTimeZone using an IANA time zone name.  Below is an example using the Kuala Lumpur time zone.

let timeZone = NSTimeZone(name: "Asia/Kuala_Lumpur")
//What is the abbreviation
timeZone?.abbreviation
//Get the description with everything
timeZone?.description

So my first experiment was to parse the IANA time zones into a Swift String Array so that I could write a few Playground tests.  For this blog post we will show a few of the time zone names, instead of the full 417 item array.

let IANAtimeZones = ["Europe/Andorra","Asia/Dubai","Asia/Kabul",...]

* You can see the full array here

Next I found that the NSTimeZone method knownTimeZoneNames provides a list of all time zones iOS supports out of the box.  This gives me an easy way for me to later compare the IANA time zone database with those supported by iOS.  I use the below to get a list of those time zones known by iOS.

let iOStimeZones = NSTimeZone.knownTimeZoneNames()

To make writing any comparison testing alittle easier we create a method called tzExists.  This will allow us to compare a provided time zone name against all of those known by iOS.  In your production code you will want to cache the knownTimeZoneNames array for performance reasons.

func tzExists(name : String) -> Bool {
    return NSTimeZone.knownTimeZoneNames().contains(name)
}

Now that we have everything setup a simple loop can be used to compare our IANA time zones with those known to iOS.

for tz in IANAtimeZones {
    if(!tzExists(tz)) {
        print(tz + " not available")
    }
}

Oddly enough there are differences.  It could be I have an incomplete version of the IANA time zone database as iOS has 10 more known time zones then my IANA test harness.  Even with these differences the straight forward usage of NSTimeZone solves my problem perfectly.

Additional Resources:

  • Playground : A playground which shows the above objects, tests, and methods is available here.
  • Gist : The playground is available as a gist here.

How to tint an UIImageView Image

I continue to explore a few of the basics around using UIImageView and Swift to create interface elements.  In this post I will discuss how you can apply a tint to an UIImageView image.  The example I will use to show the image tint is to toggle on and off the selected state of a favorite “star” icon as shown below.

demo

There are many techniques for applying a tint to an image.  In this example, I’ll discuss the two approaches I like to use.

Just add two lines of code…

The easiest way to apply a tint to an UIImageView image is to set the imageWithRenderingMode of the image to AlwayTemplate and then apply a tint.  This only takes two lines of code to get the desired results.  The below snippet shows how to turn the UIImageView image blue.

myImageView.image = myImageView.image!.imageWithRenderingMode(UIImageRenderingMode.AlwaysTemplate)
myImageView.tintColor = UIColor.blueColor()

Or use an extension

Coding your image tint operations inline as discussed above works for most cases.  If you have a large number of image elements or different use cases you might want to explore using an extension method.  An extension method allows you to encapsulate the tint operating and make your code more readable.  For example, the snippet below turns the UIImageView image blue in just one line.

myImageView.tintImageColor(UIColor.blueColor())

To accomplish this we added the UIImageView+ImageTint extension to our project.  This extension simply encapsulates our inline code from above in a convenience method named tintImageColor.  The full extension method is shown below.

extension UIImageView {
    func tintImageColor(color : UIColor) {
        self.image = self.image!.imageWithRenderingMode(UIImageRenderingMode.AlwaysTemplate)
        self.tintColor = color
    } 
}

You can also find the extension method on Github here.

An example of both…

I’ve put together an example project ImageViewTintImage-Example here that walks you through how to apply both of these techniques to star and unstar UI elements.