Swift 2 By Example
上QQ阅读APP看书,第一时间看更新

The menu screen

Let's start implementing the first view, in which we can select the level of the game.

Implementing the basic menu screen

As we have planned to implement all of the UI in the code itself, we won't bother to touch the storyboard. We will proceed to open the View Controller class in order to implement the menu screen.

From the mockup, we can observe that there are three difficulty levels, each represented by a horizontally centered and vertically equidistant button.

First of all, we will define an enumeration to describe the difficulty.

Then, we implement the setup for the layout:

enum Difficulty {
    case Easy, Medium, Hard
}

This enum must be defined outside the class so that it will be accessible by all classes.

Just for the sake of readability, we group the methods required in order to implement a feature in a separated extension, leaving only the public functions and the variable definition in the main class. Although extensions were born for a different goal, which is to extend classes we don't have the source for, I've found that grouping together methods in an extension helps describe the goals of those methods. Matt Thompson, the creator of AFNetworking, the most used network library for iOS, used this approach in Alamofire at https://github.com/Alamofire/Alamofire:

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        setup()
    }
}

private extension ViewController {
    func setup() {
        view.backgroundColor = .whiteColor()

        buildButtonWithCenter(CGPoint(x: view.center.x, y: view.center.y/2.0), title: "EASY", color:.greenColor(),action: "onEasyTapped:")
        buildButtonWithCenter(CGPoint(x: view.center.x, y: view.center.y), title: "MEDIUM", color:.yellowColor(),action: "onMediumTapped:")
        buildButtonWithCenter(CGPoint(x: view.center.x, y: view.center.y*3.0/2.0), title: "HARD", color:.redColor(),action: "onHardTapped:")
    }

    func buildButtonWithCenter(center: CGPoint, 
    title: String, color: UIColor, action: Selector) {
    //
    }
}

Again, we are not yet relying on Auto Layout to establish relations between the components, so we pass the coordinates of each button to the initializer method. In the same method, we also pass the text to be presented as a caption and the background color.

You may also notice that Swift 2.0 can understand the scope of a class function given the type of the receiver: if a variable is UIColor, it isn't necessary anymore to add the class name before the function, like this:

let myColor: UIColor = UIColor.greenColor()

In Swift 2.0, you can just write it in this way:

let myColor: UIColor = .greenColor()

This will apply for the parameters of a function as well, making the code really clean.

The last parameter, called action, contains the name of the method inside View Controller that the button must call when pressed. The following implementation of buildButtonCenter() shows you how to create a button programmatically:

func buildButtonWithCenter(center: CGPoint, title: String, color: UIColor, action: Selector) {
    let button = UIButton()
    button.setTitle(title, forState: .Normal)
    button.setTitleColor(.blackColor(), forState: .Normal)

    button.frame = CGRect(origin: CGPoint(x: 0, y: 0), size: CGSize(width: 200, height: 50))
    button.center = center
    button.backgroundColor = color

    button.addTarget(self, action: action, forControlEvents: UIControlEvents.TouchUpInside)
    view.addSubview(button)
}

The last statement before adding the button to the view is the way to connect a callback to an event, the programmatic equivalent of creating a line connecting an event of the button to @IBAction using Interface Builder. This is a technique we saw in the previous chapter.

Because all the actions are logically tied together, we create another extension to group them:

extension ViewController {
    func onEasyTapped(sender: UIButton) {
        newGameDifficulty(.Easy)
    }

    func onMediumTapped(sender: UIButton) {
        newGameDifficulty(.Medium)
    }

    func onHardTapped(sender: UIButton) {
        newGameDifficulty(.Hard)
    }

    func newGameDifficulty(difficulty: Difficulty) {
        switch difficulty {
            case .Easy:
                print("Easy")
            case .Medium:
                print("Medium")
            case .Hard:
                print("Hard")
        }
    }
}

It is worth mentioning that this extension is not private, although it contains functions that are used only internally; the reason here is because these are functions called by the buttons when they are tapped, basically called by the Cocoa Runtime, which can access the functions only if they are internal or public.

Now, if we run the app by going to Product | Run, we can see that we have almost implemented the screen in the mockup, as you can see in the following screenshot:

Also, by tapping the buttons, we can verify that the button calls the correct function. We must see the correct message in the console when we press a button.

Although the screen is how we expected to implement it, it isn't very appealing, so before proceeding to implement the game in View Controller, we customize the color palette to make the UI prettier.

Creating a nice menu screen

Because the flat design has become very fashionable lately, let's go to http://flatuicolors.com in order to choose a few colors to decorate our components.

After choosing the colors, we extend the UIColor class:

extension UIColor {
    class func greenSea() -> UIColor {
        return .colorComponents((22, 160, 133))
    }
    class func emerald() -> UIColor {
        return  .colorComponents((46, 204, 113))
    }
    class func sunflower() -> UIColor {
        return .colorComponents((241, 196, 15))
    }
    class func alizarin() -> UIColor {
        return  .colorComponents((231, 76, 60))
    }
}

private extension UIColor {
    class func colorComponents(components: (CGFloat, CGFloat, CGFloat)) -> UIColor {
        return UIColor(red: components.0/255, green: components.1/255, blue: components.2/255, alpha: 1)
    }
}

With this extended palette, we can change the setup of View Controller:

func setup() {
    view.backgroundColor =.greenSea()

    buildButtonWithCenter(CGPoint(x: view.center.x, y: view.center.y/2.0), title: "EASY", color: .emerald(), action: "onEasyTapped:")
    buildButtonWithCenter(CGPoint(x: view.center.x, y: view.center.y), title: "MEDIUM", color:.sunflower(), action: "onMediumTapped:")
    buildButtonWithCenter(CGPoint(x: view.center.x, y: view.center.y*3.0/2.0), title: "HARD", color:.alizarin(), action: "onHardTapped:")
}

Inside the builder, let's change the title color to white:

func buildButtonWithCenter(center: CGPoint, title: String, color: UIColor, action: Selector) {
    //
    button.setTitleColor(.whiteColor(), forState: .Normal)
    //
}

The result, as shown in the following screenshot, is definitely prettier, reminding us of a real card table:

Now we can implement a proper newGameDifficulty() function for which we already wrote the empty implementation in the extension grouping the button callbacks:

func newGameDifficulty(difficulty: Difficulty) {
    let gameViewController = MemoryViewController(difficulty: difficulty)
    presentViewController(gameViewController, animated: true, completion: nil)
}

The function introduces a new View Controller, MemoryViewController, which will contain all the logic. Just to create the app build, let's make it empty.

Note

You can find the code at https://github.com/gscalzo/Swift2ByExample/tree/2_Memory_1_menu.