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.

Resetting the iOS Simulator

There seems to be several ways to reset or clear the iOS simulator ranging from pressing the “Reset Content and Settings..” button on the iOS simulator from deleting folder under ~/Library/Developer/CoreSimulator/DerivedData.

For me the easiest and faster approach for my workflow is to simply issue the below in terminal.

xcrun simctl erase all

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.

CaptiveNetwork Depreciated in iOS9

If you use Network SSIDs in your application you most likely have a breaking change or two in store for you in upgrading to iOS9.  Most likely you are using a block of code that looks something like the below to get the current SSID of the device.

#import <SystemConfiguration/CaptiveNetwork.h>

-(NSString*) findSSID {
#if TARGET_IPHONE_SIMULATOR
 	return @"simulator";        
#else
	NSString * informationUnknown = @"unknown";        
	CFArrayRef interfaces = CNCopySupportedInterfaces();
	if(interfaces == nil){
		return informationUnknown;
	}
	CFIndex count = CFArrayGetCount(interfaces);
	if(count == 0){
		return informationUnknown;
	}
	CFDictionaryRef captiveNtwrkDict =
	CNCopyCurrentNetworkInfo(CFArrayGetValueAtIndex(interfaces, 0));
	NSDictionary *dict = ( __bridge NSDictionary*) captiveNtwrkDict;
	CFRelease(interfaces);

	return (([dict objectForKey:@"SSID"]==nil)? informationUnknown :[dict objectForKey:@"SSID"]);
#endif
}

A quick look at the prerelease API documentation shows almost all of CaptiveNetwork’s properties and methods are depreciated.  This Apple developer forum post, highlights how you will now need to use the NetworkExtension framework’s new NEHotspotHelper class to access this information.

The below is how in iOS9 you can get an NSArray of all of the supported network interfaces.

#import <NetworkExtension/NetworkExtension.h>  

NSArray * networkInterfaces = [NEHotspotHelper supportedNetworkInterfaces];  
NSLog(@"Networks %@",networkInterfaces);  

The NEHotspotHelper class is part of the Hotspot improvements introduced in the NetworkExtension  framework.  There is a great WWDC video, “What’s New in Network Extension and VPN” which goes into detail on these new features and the associated breaking changes.

Unfortunately implementing NEHotspotHelper isn’t as easy as adding a conditional, you will need to add the new “com.apple.developer.networking.HotspotHelper”  entitlement in your project.  You will need to contact Apple to request this entitlement and complete a short questionnaire before this option will appear

Unlike most entitlements, you will need to contact Apple as outlined here to request access to the Network Extension entitlements for your team.  I’m still in the approval process so will see how easy it will be to switch to NEHotspotHelper.

Helpful Links:

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.

Spinning UIImageView using Swift

Recently I’ve been exploring Swift and really been enjoying both the syntax and extension points.  Although still getting my legs under me with Swift and interface builder it has been a great deal of fun. For a recent project, I had to provide a subtle progress cue when the application was synchronizing information.  This cue was accomplished by simply replacing the UIImage of our existing UIImageView, then rotating the UIImageView using CABasicAnimation.  The below shows the end result in action.

ImageViewSpin

Rotation Extension – UIView+Rotation.swift

A majority of the logic behind this rotation effect is encapsulated in the UIView+Rotation extension class shown below.  The following shows the full extension method.  You can also view the extension method on github here.

extension UIView {
    func startRotating(duration: Double = 1) {
        let kAnimationKey = "rotation"
        
        if self.layer.animationForKey(kAnimationKey) == nil {
            let animate = CABasicAnimation(keyPath: "transform.rotation")
            animate.duration = duration
            animate.repeatCount = Float.infinity            
            animate.fromValue = 0.0
            animate.toValue = Float(M_PI * 2.0)
            self.layer.addAnimation(animate, forKey: kAnimationKey)
        }
    }
    func stopRotating() {
        let kAnimationKey = "rotation"
        
        if self.layer.animationForKey(kAnimationKey) != nil {
            self.layer.removeAnimationForKey(kAnimationKey)
        }
    }
}

Start Rotating

The first step in starting the rotation animation is to replace the existing UIImage with the one we will be animating.  In our case we do this by loading an image from our Asset Catalog, as shown below.

syncImage.image = UIImage(named:”sync-spinning”)

Next, since we have the UIView+Rotation extension method added to your project, you simply have to call the startRotating method on your UIImageView to start animating the UIImageView.  The following shows how to call the startRotating extension method.

syncImage.startRotating();

In our example project, we have encapsulated this into the startSpinning shown below.

func startSpinning() {
    syncImage.image = UIImage(named:"sync-spinning")
    syncImage.startRotating()
}

Stop Rotating

The process of stopping the animation is similar to that of starting the rotation process.  First we stop the rotation animation of the UIImageView by calling the stopRotating extension method as shown below.

syncImage.stopRotating()

Next, we replace the UIImage with our initial non rotating image.  As shown below we do this by loading an image from the Asset Catalog.

syncImage.image = UIImage(named:”sync-not-spinning”)

In our example project, we have encapsulated this into the stopSpinning shown below.

func stopSpinning() {
    syncImage.stopRotating()
    syncImage.image = UIImage(named:"sync-not-spinning")
}

Ok, so show me the code

The following shows the code used to call both the start and stop rotating animations. For demonstration purposes, a delay is added to trigger the stop rotating event to illustrate the full animation life cycle.

func startSpinning() {
    syncImage.image = UIImage(named:"sync-spinning")
    syncImage.startRotating()
}

func stopSpinning() {
    syncImage.stopRotating()
    syncImage.image = UIImage(named:"sync-not-spinning")
}

func handleSyncTap(sender: UITapGestureRecognizer? = nil) {
    startSpinning()

    let dispatchTime: dispatch_time_t = dispatch_time(DISPATCH_TIME_NOW, Int64(3 * Double(NSEC_PER_SEC)))
    
    dispatch_after(dispatchTime, dispatch_get_main_queue(), {
        self.stopSpinning()
    })
}

A full working example of how to create a rotating UIImageView is available on github at https://github.com/benbahrenburg/SpinningImageView-Example

Walk Through

The following video tutorial walks through the UIImageView rotation example in great detail outlining everything from how the Storyboard UI elements are created through the UIGestureRecognizers added to trigger the rotation animations.

App Transport Security and LocalHost

With iOS9 and OS X 10.11 Apple has introduced App Transport Security which requires a secure connection be made for all NSURLSession connections.  While this encourages developers to use best practices for secure connections this can create interesting side effects if you are using an embedded web server.

If you are using an embedded web service like the excellent GCDWebServer project and target iOS9 you will see the following messages in your Xcode console:

WebServer booted: http://localhost:12344/
2015-07-18 20:34:07.372 myApp[615:6289] App Transport Security has blocked a cleartext HTTP (http://) resource load since it is insecure. Temporary exceptions can be configured via your app’s Info.plist file.

To work around this problem you need to create an NSExceptionDomains for localhost in your application’s Info.plist.  As of iOS9 beta 3 the following Info.plist entries allows your application’s UIWebView or WKWebView to use localhost to access your embedded web server.

<key>NSAppTransportSecurity</key>
<dict>
   <key>NSExceptionDomains</key>
   <dict>
       <key>localhost</key>
       <dict>
       	<key>NSTemporaryExceptionAllowsInsecureHTTPSLoads</key>
           <false/>        	
           <key>NSIncludesSubdomains</key>
           <true/>
           <key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
           <true/>
           <key>NSTemporaryExceptionMinimumTLSVersion</key>
           <string>1.0</string>
           <key>NSTemporaryExceptionRequiresForwardSecrecy</key>
           <false/>
       </dict>
   </dict>
</dict>

Adding a center button on your iOS Ti.UI.TabGroup

Remember the original “big center button” of the Instagram app?  Although this design language isn’t cutting edge anymore it still can be useful for a class of apps.  In this post we will explore how through a few lines of code you can create a center action button using the Titanium Alloy framework by Appcelerator.

View Code

To get started, first you need to create a Ti.UI.TabGroup with five Ti.UI.Tab’s.  You might notice that only four of the five Ti.UI.Tab’s are visible, but the hidden Ti.UI.Tab is needed to maintain spacing.  In the below snippet I’ve named and labeled each tab by number.  In our case, the third Ti.UI.Tab is the place-holder.

<Alloy>
	<TabGroup id="tbgroup">
		<Tab icon="KS_nav_views.png">
			<Window title="Tab 1">
				<Label>I am Window 1</Label>
			</Window>
		</Tab>
		<Tab icon="KS_nav_views.png">
			<Window title="Tab 2">
				<Label>I am Window 2</Label>
			</Window>
		</Tab>
		<Tab icon="KS_nav_views.png">
			<Window title="Tab 3">
				<Label>I am Window 3</Label>
			</Window>
		</Tab>		
		<Tab icon="KS_nav_views.png">
			<Window title="Tab 4">
				<Label>I am Window 4</Label>
			</Window>
		</Tab>	
		<Tab icon="KS_nav_views.png">
			<Window title="Tab 5">
				<Label>I am Window 5</Label>
			</Window>
		</Tab>			
	</TabGroup>
</Alloy>


Controller Code

Next alittle controller magic is needed.  Since Alloy does not allow for a Ti.UI.Button to be added to a Ti.UI.TabGroup in our view xml we need to do this programmatically in the controller.  The following snippet shows how a Ti.UI.Button named btnAction is created then added to the Ti.UI.TabGroup

var btnAction = Ti.UI.createButton({
	title:"I'm a button",
	backgroundColor:"#e7e7e8",
	width:85, height:55, bottom:0, borderColor:"#999"
});
 
$.tbgroup.add(btnAction);
$.tbgroup.open();

Since no position is provided, Titanium will automatically center the btnAction control within the boundary of the Ti.UI.TabGroup it is attached.  You just need to make sure the Ti.UI.Button you are attaching is larger then the Ti.UI.Tab place-holder in order to avoid incorrect touch events from firing.

What will we end up with?

The combination of the view and controller code creates the following screen with an oversized center action button.  Since these are just Titanium UI components you can style them and use event listeners as needed.

center-button-tabgroup