Apple Acquires Shazam

I still remember being at a WWDC Stump The Experts session where one of the first questions was what were the names of the songs playing before things kicked off, and a developer from Shazam stood up, and rattled all of them off.

There was a roar from the crowd, and he said he was from Shazam. And presumably all of us went and downloaded the app then and there.

https://www.macrumors.com/2017/12/11/apple-acquires-shazam/

Apple’s December 2 Springboard Crash Bug

It’s been interesting to watch this pop up tonight. It looks like a pretty serious bug in iOS 11:

https://www.macrumors.com/2017/12/02/ios-11-1-2-date-bug-crash-loop/

I can imagine what’s happen at Apple right now for the past few hours, engineers in there working and finding and solving the problem. Other folks discussing how to push it out to customers.

I wonder if they are also discussing tonight about pushing the button and releasing 11.2 tonight. I don’t think that’s in the cards if only for the size of that download, vs a super small patch for this one.

Today at work I spent most of the day doing a small firefight around how Touch ID / Face ID works in an app. I’ve come to the conclusion that’s a combination of a subtle bug, and customers expectations regarding how the feature is supposed to work. It felt like a small firefight to me – it was exciting and a little hairy at the same time.

But nothing on the scale of what’s happening at Apple tonight. Good luck Apple folk!

Halt And Catch Fire

I’ve really enjoyed Halt And Catch Fire over the past three years. Being part of the tech industry now for over 20 years, I’ve found myself really empathizing with these characters, and growing to love them.

The final season was great and heart wrenching at the same time. But the writers and actors did a fantastic job in my opinion. I shed more than one tear over the last 4 episodes.

If you haven’t seen the show, I highly recommend it. It’s a bit techie, but the show is really about relationships and people.

XOXO to Halt and Catch Fire.

Long after WKWebView Hello World – Part 1 – Cookie Handling

First of all, I want to thank my friend and colleague Lucien Dupont for inviting  me to contribute.

Recently our project transitioned web views written in UIWebView to WKWebView and Lucien suggested that I share some of my experiences with the larger community.  The intent here is not a tutorial on how to write web views using WKWebView, (there are tons of great, and free material out there) but suggest solutions on problems that we struggled with and hopefully help those with similar issues.

Before I begin, I am greatly indebted to the ton of postings in Stack Overflow and other web sites.  In particular please bookmark the following links whose authors really took the time to document the many tips they collected over the years:

https://github.com/ShingoFukuyama/WKWebViewTips

http://atmarkplant.com/ios-wkwebview-tips/

Finally my discussions assumes iOS 9 and above, Xcode 8.2.1, Swift 3, basic WKWebView API

Cookie Handling

Adding Cookies

Quick take: Cookies are added using Javascript and only after the document is loaded

Adding cookies to a WKWebView from the app is different from UIWebView.  For UIWebView, one can take advantage of the APIs in HTTPCookieStorage as follow:

let cookieStorage = HTTPCookieStorage.shared
let cookieProperties: [AnyHashable: Any] =
[HTTPCookiePropertyKey.domain: "mydomain",
HTTPCookiePropertyKey.path: "/",
HTTPCookiePropertyKey.name: "mycookie",
HTTPCookiePropertyKey.value: ",abc"]
let cookie = HTTPCookie(properties: cookieProperties as! [HTTPCookiePropertyKey : Any])

cookieStorage.setCookie(cookie!)
UserDefaults.standard.synchronize()

Add a cookie to the HTTPCookieStorage and viola, the cookie is added.

WKWebView, cannot take advantage of HTTPCookieStorage APIs to add the cookie.  Instead one has to use javascript evaluation to add the same cookie

let webConfiguration = WKWebViewConfiguration() 
myWKWebView = WKWebView(frame: .zero, configuration: webConfiguration)

let addCookieScript = "document.cookie = \'mycookie=abc;domain=mydomain;path=/\';"

...

// Call this only after document is loaded
myWKWebView.evaluateJavaScript(addCookieScript,completionHandler: { (result, error) in
            if completionHandler != nil {
               completionHandler(result, error)
            }
        })

Additionally, the above javascript evaluation will not be successful until a document is loaded!

Sharing Cookies

Quick Take: Use the same WKProcessPool to share cookies with other WKWebView

Since WKWebView is not able to take advantage of HTTPCookieStorage, you would need to create a common WKProcessPool to be used by WKWebViews that need to share cookies

let commonProcessPool : WKProcessPool = WKProcessPool()

let configuration1 = WKWebViewConfiguration()
configuration1.processPool = commonProcessPool
let webView1 :WKWebView = WKWebView(frame: CGRectZero, configuration: configuration1)

let configuration2 = WKWebViewConfiguration()
configuration2.processPool = commonProcessPool
let webView2 :WKWebView = WKWebView(frame: CGRectZero, configuration: configuration2)

Instruct WKWebView to add cookies once document is loaded

Quick Take: A javascript to add cookie can be set when the WKWebView is first initialized and can be instructed to run when the document is loaded

WKWebView can be instructed to add cookies once the document load and before any other scripts and logic are processed within the document.  This is especially helpful when some of the scripts are looking for certain cookies.  We can do this using the WKUserScript class and specify the injectionTime to .atDocumentStart (and if for some reason you need the script to be run at the end, then use .atDocumentEnd)

let addCookieScript="var cookieNames = document.cookie.split(\'; \').map(function(cookie) { return cookie.split(\'=\')[0] } );\nif (cookieNames.indexOf(\'mycookie\') == -1) { document.cookie=\'mycookie=abc;domain=mydomain;path=/\'; };\n"

let script = WKUserScript(source: addCookieScript, injectionTime: .atDocumentStart, forMainFrameOnly: false)

let config = WKWebViewConfiguration()
config.processPool = processPool

config.userContentController.addUserScript(script)

webView = WKWebView(frame: CGRect(x: CGFloat(0), y: CGFloat(0), width: CGFloat(50), height: CGFloat(50)), configuration: wkconf)

What about time sensitive cookie?

Quick Take:  Use a message handler to add time sensitive cookie

The above example works well if the value of your cookie stays the same.  Eg if the same web view is used to load another document which also needs the same cookies, then your work is done.  However, what if the value of the cookies changes eg if they are time sensitive, then using the above example could result in stale data.  Fortunately we can modify the example to callback to use a message handler to set the cookie with the latest value when the document loads

class MyWKWebViewController: NSObject, WKNavigationDelegate,  WKScriptMessageHandler {

override init() {
  // this script callback when the document load
  let script = WKUserScript(source: "window.webkit.messageHandlers.MyListener.postMessage(\"setCookiesForInitialLoad\");", injectionTime: .atDocumentStart, forMainFrameOnly: false)

  let config = WKWebViewConfiguration()
  config.processPool = processPool

// Note make sure the name of the message handler, in this case 'MyListener' is the same the one specified in the script
  config.userContentController.addUserScript(script)      conf.userContentController.add(self, name: "MyListener")

  webView = WKWebView(frame: CGRect(x: CGFloat(0), y: CGFloat(0), width: CGFloat(50), height: CGFloat(50)), configuration: config)

}

// MARK: Message Handler methods
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {

  let messageValue = message.body as! String;
  switch (messageValue) {
     case "setCookiesForInitialLoad":
          self.webView.evaluateJavaScript(self.addTimeSensitiveCookieScript, completionHandler: nil)
                break;
            default:
                break
   }
}

func addTimeSensitiveCookieScript() -> String! {
   let newValue : String = self.getSomeTimeSensitiveValue()
   let addCookieScript="var cookieNames = document.cookie.split(\'; \').map(function(cookie) { return cookie.split(\'=\')[0] } );\nif (cookieNames.indexOf(\'mycookie\') == -1) { document.cookie=\'mycookie=\(newValue);domain=mydomain;path=/\'; };\n"

    return addCookieScript
 }

Deleting Cookies

Cookies are not automatically deleted when the WKWebView is deleted, have to manually delete them

As with UIWebView, cookies are not deleted when they are deleted, in the case of WKWebView, once again, you are not able to delete cookies using HTTPCookieStorage APIs.  You can use the following (which will not only delete cookies but any cache data as well.  Note some cookies are needed for signing out etc, so do not delete cookies until they are used for the necessary clean up.

func deleteWebViewData(completion: @escaping () -> Void) {
  let dateFrom = NSDate(timeIntervalSince1970: 0)
  let websiteDataTypes = WKWebsiteDataStore.allWebsiteDataTypes()

  WKWebsiteDataStore.default().removeData(ofTypes: websiteDataTypes, modifiedSince: dateFrom as Date, completionHandler: {() -> Void in
  UserDefaults.standard.synchronize()
  completion()
  })
}

That’s all for now, hopefully its enough to chew for the time being.

I would like to leave you with a link to a slide show that Mobify put together about their experience with WKWebView:

Test Driven Development in iOS with Swift

At Intuit we work in 2 week sprints. Last sprint I decided to take a story about a crash happening in an image downloader class. After looking at it for a bit, I decided it would be a great candidate to rewrite using dispatch groups, and also in Swift. (It’s an internal goal to rewrite when we can in Swift).

At first it was rough going and slow. The write a test, run it, and fail felt far slower than just writing the class from scratch, and then debugging it inside the app.

But – I really wanted to give it a try, so I changed my layout in Xcode. Instead of having the source in my left source panel in Xcode, I decided to put the unit test file there. And then on the right side I split that into two panels: the new Swift downloader source, and the old Objective C source.

That changed my focus from writing the source, to writing the tests, and then making the tests pass. I know it sounds like a small thing, but for me it really worked.

I also decided to embrace the “slowness” of TDD. It gave me more time to think about what I was writing and how to make sure it covered all the cases.

In short, something I feel I could have finished in a few days (with no tests) took me 5 days (along with meetings, production support, etc). But I got into it and really enjoyed it. I’d recommend you give it a try!

Learning Swift, Part 1

I’ve been attempting to, in my spare time, do more Swift development. The hardest thing for me so far is struggling knowing that I could do something in two minutes than in Swift takes me 20-30 minutes to search for and explore an answer and get the code in the correct format. 

It would be easy to slip back to Objective C to just “get it done”. But I think Swift is important now to spend the time to start using and learning.