Self-Explained Swift

// Self-Explained Swift #2
// Tools to make our code easier to manage and read.

import UIKit
import PlaygroundSupport

// Welcome to my second Self-Explained Swift. In this playground, we are starting with
// the code from the previous post but with some new changes and the old comments removed.
// If you feel like you don't understand something, go back and check it out.

// The idea I want to convey in this post is "Tool Creation". You can create many tools
// that can be reused throughout your app that will help cut down on coding mundane tasks
// such as view creation and common layout constraints.

// The "Tools" in this instance will be extensions. If you are not familiar with them,
// extensions allow you to bolt new functions on to existing types. Below I have added
// a few functions to assist us in initializing common UI elements, and generating common
// view layouts.

extension UIView { // Layout extension

    // This function will encapsulate the process of adding a view, enabling autolayout,
    // and configuring the common constraints
    func constrainTo(view: UIView) {

        // Turn on autolayout
        view.translatesAutoresizingMaskIntoConstraints = false

        // Because of the way we worded the function, view will be the parent and self,
        // the child. It might look a little strange here, but as you will see below, it
        // reads quite nicely in the call-sites.
        view.addSubview(self)

        // I was notified about the new NSLayoutAnchor system since my last post, this
        // makes alot nicer constraint building, so we use that here.
        view.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
        view.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
        view.leftAnchor.constraint(equalTo: self.leftAnchor).isActive = true
        view.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true

    }

}

extension UIStackView {

    // UIStackView has alot of things that are frequently changed from the defaults. This
    // new init overload will allow us to one-line most of it.
    convenience init(arrangedSubviews: [UIView],
                     axis: UILayoutConstraintAxis,
                     distribution: UIStackViewDistribution,
                     alignment: UIStackViewAlignment) {

        // Chain to the original initializer.
        self.init(arrangedSubviews: arrangedSubviews)

        // Set our custom properties here.
        self.axis = axis
        self.distribution = distribution
        self.alignment = alignment

        // Again, it's nice to hide this away, since we will always need it off anyways.
        self.translatesAutoresizingMaskIntoConstraints = false

    }

}

// Here we are going to make class functions to help create a sort of "theme" for
// our app.

// For the most part, this is just our previous button code, refactored into a
// class function. We provide function parameters to set things likely to be
// different per instance.

// We also want to set translatesAutoresizingMaskIntoConstraints, so we can completely
// remove that from our view controller code.

extension UIButton {

    class func standardAwesomeButton(title: String) -> UIButton {

        let button = UIButton()

        button.setTitle(title, for: .normal)
        button.translatesAutoresizingMaskIntoConstraints = false

        return button
    }

}

extension UILabel {

    class func standardAwesomeLabel(title: String) -> UILabel {

        let label = UILabel()

        label.font = UIFont(name: "Menlo", size: 14)
        label.textColor = .white
        label.text = title
        label.textAlignment = .center
        label.translatesAutoresizingMaskIntoConstraints = false

        return label
    }

}

class OurAwesomeViewController: UIViewController {

    lazy var titleLabel: UILabel = {
        return UILabel.standardAwesomeLabel(title: "Awesome")
    }()

    lazy var button: UIButton = {

        let button = UIButton.standardAwesomeButton(title: "Press Me")
        button.addTarget(self,
                         action: #selector(OurAwesomeViewController.buttonTest),
                         for: .touchUpInside)

        return button
    }()

    override func loadView() {

        super.loadView()

        view.backgroundColor = .blue

        // We are using our custom UIStackView Initializer, This will reduce quite
        // a bit of the duplicated code and make your call-sites much easier to read.
        let verticalLayout = UIStackView(arrangedSubviews: [titleLabel, button],
                                         axis: .vertical,
                                         distribution: .fill,
                                         alignment: .fill)

        verticalLayout.isLayoutMarginsRelativeArrangement = true
        verticalLayout.layoutMargins = UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20)

        // Call our new layout function, this encapsulates and simplifies the common
        // task of adding views and setting their constraints.
        verticalLayout.constrainTo(view: view)

    }

    func buttonTest(sender: UIButton) {
        view.backgroundColor = .red
    }

}

// Fire up our awesome view controller in a playground.
PlaygroundPage.current.liveView = OurAwesomeViewController()
PlaygroundPage.current.needsIndefiniteExecution = true

// As you can see, this greatly cleans up our layout code and makes it easier to
// manage. The View Controller is now ~43 lines of code and centralizes our styling.
// One forgotten property or function call could have caused your view to not render,
// but with this new setup, that code is now shared among other views and should be
// much easier to diagnose, and less likely to happen in the first place.

// Using these techniques, you can make your view controllers smaller, and simply
// theme creation. You could (if you wanted to) make several extensions for different
// styles of buttons, labels, or any sort of UI element. A change in any one would
// instantly be reflected across your app, with the only downside being the initial
// one-time setup.

// That's it for #2, next time, we will adjust the architecture to move your app-logic
// out of the ViewControllers as well.

Download This Playground

Check out the previous post: Self-Explained Swift #1


I Added Some New "Stuff"

I’ve added a new Page. It’s called “Stuff”.

The idea is that I will put random pieces of code that I have been tinkering with in there for you to check out if you are interested.

The quality and usefulness of the “Stuff” will vary, so use them at your own risk.

Click or Tap here to check it out.


New Year, New Website!!!

Check it out, I finally got around to building a new website.

Features

  • HTTPS
  • PhileCMS
  • Piwik Analytics (data is owned by me and not Wordpress)
  • 100% Custom Theme (might still need some work, but it’s good for now)
  • Mobile Responsive

I am so excited, and I’m looking forward to writing more this year.

Stay Tuned!


Self-Explained Swift

// Self-Explained Swift #1
// AutoLayout... Programmatically

import UIKit // Can't do a layout post without UIKit
import PlaygroundSupport // So we can have this class run in a playground

// I will demonstrate a clear concise way to add elements, separate
// layout concerns, and configure your UI... All without Storyboards.

class OurAwesomeViewController: UIViewController {

    // For each of our UI Elements, we are going to make a lazy
    // var property, and an in-line initializer.

    // Because these are lazy, the variables will call their in-line initializer
    // once they are ready to be added to the view hierarchy. Ideally, I would
    // like these to be lazy let so that you wont accidentally change them
    // once they are on-screen, but as of Swift 3 it is not supported.

    // We could just do let, but with a let, it won't let you wire up selectors
    // to self because self is not ready at initialization.
    lazy var titleLabel: UILabel = {

        // Initialize our new label with the default initializer
        let label = UILabel()

        // Always disable this, otherwise you will get layout errors
        // in the debug log. I am not even sure why this defaults to
        // "true" anymore as you will never need it.
        label.translatesAutoresizingMaskIntoConstraints = false

        // We can set fonts
        label.font = UIFont(name: "Menlo", size: 14)

        // Set some text color (note, we are not going for design awards here)
        label.textColor = .white

        // And of course, we can set the text
        label.text = "Awesome"

        // Center our text
        label.textAlignment = .center

        return label
    }()

    // Buttons are fun
    lazy var button: UIButton = {

        // Initialize
        let button = UIButton()

        // Disable this stupid "feature"
        button.translatesAutoresizingMaskIntoConstraints = false

        // Set a button title
        button.setTitle("Press Me", for: .normal)

        // Let's also wire up a button action
        button.addTarget(self,
                         action: #selector(OurAwesomeViewController.buttonTest),
                         for: .touchUpInside)

        return button
    }()

    // This is where you want to build your layout code. This is called by UIKit
    // when your view is being prepared to be put on the screen.
    override func loadView() {

        // Make sure you call super if you plan to use the default view
        // provided by UIKit, if you don't need it, make sure you set
        // self.view to be something
        super.loadView()

        // Customize the view
        view.backgroundColor = .blue

        // StackViews will make your life much easier. The will automatically
        // manage the layout of their owned views and expose some properties
        // to tweak that layout without manually managing potentially dozens
        // of constraints. StackViews can also be nested and given margins, this
        // can allow for quite a bit of flexibiliy AND ease. IMHO, this is much
        // better than manually building all of your constraints.
        let verticalLayout = UIStackView(arrangedSubviews: [titleLabel, button])

        // again, we never need this
        verticalLayout.translatesAutoresizingMaskIntoConstraints = false

        // Make it vertical, and tweak the distribution and alignment
        // feel free to play with these to get a feel for how this works.
        verticalLayout.axis = .vertical
        verticalLayout.alignment = .fill
        verticalLayout.distribution = .fill

        // If you want to have some margins on your StackView, you can enable it like this.
        verticalLayout.isLayoutMarginsRelativeArrangement = true
        verticalLayout.layoutMargins = UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20)

        // Lets create some constraints to keep our StackView layout in check.
        // This is essentially boilerplate code and you might want to create
        // a micro library to wrap up these common tasks. I will show mine in
        // another post later.
        let topConstraint = NSLayoutConstraint(item: verticalLayout,
                                               attribute: .top,
                                               relatedBy: .equal,
                                               toItem: view,
                                               attribute: .top,
                                               multiplier: 1,
                                               constant: 0)

        let bottomConstraint = NSLayoutConstraint(item: verticalLayout,
                                                  attribute: .bottom,
                                                  relatedBy: .equal,
                                                  toItem: view,
                                                  attribute: .bottom,
                                                  multiplier: 1,
                                                  constant: 0)

        let leftConstraint = NSLayoutConstraint(item: verticalLayout,
                                                attribute: .left,
                                                relatedBy: .equal,
                                                toItem: view,
                                                attribute: .left,
                                                multiplier: 1,
                                                constant: 0)

        let rightConstraint = NSLayoutConstraint(item: verticalLayout,
                                                 attribute: .right,
                                                 relatedBy: .equal,
                                                 toItem: view,
                                                 attribute: .right,
                                                 multiplier: 1,
                                                 constant: 0)

        // Now add our view...
        view.addSubview(verticalLayout)

        // And our constraints.
        view.addConstraints([topConstraint, bottomConstraint, leftConstraint, rightConstraint])
    }

    // Here is our test function to be called when our button is tapped.
    func buttonTest(sender: UIButton) {
        // Nothing fancy here, we will just change the background color.
        view.backgroundColor = .red
    }

}

// Fire up our awesome view controller in a playground.
PlaygroundPage.current.liveView = OurAwesomeViewController()
PlaygroundPage.current.needsIndefiniteExecution = true

// Layout in-code might look a bit daunting, but it allows you to become more familiar with
// the UI elements, their placement, and their properties. It improves change tracking in git
// and personally, I find this much easier to reason as opposed to the black magic of
// Interface Builder.

// BONUS CONTENT!!!

// You might want to know how to use this in an actual Xcode Project, rather than Playgrounds.

// First, in your project file, you might have a "Main Interface" configured, this is normally
// your first storyboard to load. Just open your project file and clear it out.

// Next

// This should be similar to your default AppDelegate. However, the only lines you need to
// change are in the applicationDidFinishLaunchingWithOptions function.
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    func application(_ application: UIApplication,
                     willFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {

        // Let's create a new window. Every app needs one to start.
        // We will set its frame to be the same size of the screen.
        window = UIWindow(frame: UIScreen.main.bounds)

        // Set the window's rootViewController to be the
        // ViewController you want to start with.
        window?.rootViewController = OurAwesomeViewController()

        // This will push it on to the screen.
        window?.makeKeyAndVisible()

        // Unless you have some major failure during this function, you should
        // return true here to let your application know it's ready to go.
        return true
    }
}

// And that's it for my first post, let me know via twitter (@WestonHanners) if you like this
// format and want to see more.

// Keep an eye out for my next post where we will write a few extensions to make the code
// above much easier to manage and read.

Download This Playground


Easy, Automatic Server Mocking for iOS Testing

What do you do when you need to run UI tests on an integration server for an app that requires a VPN to reach its web service?

This was a question that I needed an answer for earlier this week. My first thought was to try to stub out the service calls with dummy data and bundle it with my app, but this was going to need a lot of work to maintain and I am pretty lazy. After an hour or so of research, I came upon mitmproxy. Here is the solution…

Project Configuration

First set up a new project configuration and call it something like “Mock” In your app’s target build settings, find the section called “Other Swift Flags”. Twirl down the arrow and add “-D MOCKING” next to your mocking configuration. This works similarly to preprocessor macros in Objective-C, and will allow you to set up code that will only be compiled when it is defined. Hopefully you are using NSURLSession for your API calls, find the place where you initialize it and set its configuration like this.

#if MOCKING

let proxyDict: [AnyHashable: Any] = [
    "HTTPSEnable"   :1,
    "HTTPSProxy"    :"localhost",
    "HTTPSPort"     :8080,
    "HTTPEnable"    :1,
    "HTTPProxy"     :"localhost",
    "HTTPPort"      :8080
]

URLSession.configuration.connectionProxyDictionary = proxyDict

#endif

This will redirect your http and https requests made with that session through your local proxy.

Install and Configure mitmproxy

Now you are ready to install mitmproxy. brew install mitmproxy. After installing, run it once from the terminal to make sure everything works. This will also install a certificate into ~/.mitmproxy.

Clone this repository somewhere.

This tool will install the certificate into your simulators. Run this command:

./iosCertTrustManager.py -a ~/.mitmproxy/mitmproxy-ca-cert.pem

You will have to enter “y” for any of the simulators you want to use. Now comes the moment of truth. Run mitmproxy again and start-up your app. Requests should appear in the window.

Recording

SUCCESS!!!

Start Recording

Finally we can start recording, when you are ready, run this command.

mitmdump -w output_file_name

You can name the output file whatever you want. Once it is running, do any actions you want to record in the Simulator, and they will be saved to that file.

Playback

Press CTRL+C when you are done. Now when you want to replay the responses you saved, run this command.

mitmdump -S saved_file

If your server is uses https, you can prevent mitmdump from trying to check the certificates during replay by adding the option “–no-upstream-cert”. You will now be able to run your app, even if you have no internet connection. This works especially well for UI testing since your requests are not likely to differ between runs. …and that’s it. I hope you find this useful, and if you have any questions, feel free to poke me on twitter.