© Bruce Wade 2016

Bruce Wade, OS X App Development with CloudKit and Swift, 10.1007/978-1-4842-1880-8_3

3. Defining Our Data

Bruce Wade

(1)Suite No. 1408, North Vancouver, British Columbia, Canada

Now that we have our app’s rough prototype, we can figure out what data we need to store in our app. In this chapter we will look at the different features of our mockup and break them down into data categories and types. Finally, we will create the first pass of our app so that in the next chapter we can shift our focus to CloudKit.

The mockup we created is intentionally simple and lacks certain features so that we can focus on the basic flow of going from idea to complete app.

Taking a Closer Look at Our Mockup

Figure 3-1 shows our completed mockup.

A385012_1_En_3_Fig1_HTML.jpg
Figure 3-1. Mockup from where we left off in the last chapter

In looking at the mockup, it seems the information we need to keep track of includes the following:

  • Dog park details (Name, Location, Overview, Allows Offleash, Has Fresh Water, Is Fenced)

  • List of images

  • List of parks

From this we can determine we need at least two objects to get startedone object to hold an individual park, and one to hold the park’s images. For the list of parks we can just use the built-in list data structure.

Dog Park Data Types

Here are the park data types:

  • Name – String type

  • Location – Here we are also going to use a String; however, for a challenge you could change this to use the NSLocation type, which is also supported by CloudKit.

  • Overview – String type

  • Allows Offleash – Boolean

  • Has Fresh Water – Boolean

  • Is Fenced – Boolean

  • Images – List of Park Images

Creating Our Project in Xcode

We are going to use storyboards and Cocoa Bindings so as to write the minimal amount of code that will enable us to achieve our goals:

1. Open Xcode and create a new Xcode project.

2. Select “Cocoa Application,” which can be found in the OS X Application section. Click Next.

3. Enter DogParks (or a name of your choice) for the Product Name.

4. Enter your organization name in the Organization name field, or if you don’t have one enter your own name. (If you have set this up in the past it will be prepopulated for you.)

5. Make sure to select Swift as the programming language.

6. Check the box next to Storyboards, as we will be using them for our app.

7. Deselect all other checkboxes.

8. Click Next and pick the location where you want to save your app.

9. You can optionally select “Create Git Repository”; however, we will not be covering version control in this book.

Update the Main.storyboard

Now we have to make some changes to the storyboard to get the design we have in our mockup. The default storyboard has a single view controller connected to the window controller. Select the view controller and delete it. Now the window controller says “No Content View Controller.”

In the Objects Library either scroll through the objects to find the vertical split view controller or type it in the search filter at the bottom of the Objects Library. Drag the vertical split view controller to the position where the view controller you just deleted was. While you are at it, reorganize the two view controllers that are part of the vertical split view controller to be directly under it instead of to the right.

Next, we need to connect the window controller to the new vertical split view controller. Click on the window controller icon, then press Control and click and drag the icon to the vertical split view controller, and in the popup dialog select “Window Content.”

If you were to run the application now, something strange would happen. You might not see a window as you would expect. However, if you were to look closer you’d see the window is there, but is only a single gray line. This is because the vertical split view controller is dependent on autolayout. We will add the required constraints as we go.

Creating the Left Sidebar

Let’s start by taking a closer look at the sidebar from our mockup (Figure 3-2) to determine what objects Xcode provides that we can use to achieve our goals.

A385012_1_En_3_Fig2_HTML.jpg
Figure 3-2. Sidebar of the mockup

We can use a button image for the New Park button, a Search field, a Table View that contains an Image View, and two labels. We don’t have to worry about the close/minimize/maximize buttons, as Xcode handles this for us automatically.

Before we start dragging objects onto our view, we need to adjust the size of the sidebar to what we had in our mockup. To do this, select the view in the outline for the left-most view controller of the vertical split view controller (Figure 3-3).

A385012_1_En_3_Fig3_HTML.jpg
Figure 3-3. Selecting the view of the view controller

Next, in the Size Inspector, set the width of the view to 300 points (Figure 3-4).

A385012_1_En_3_Fig4_HTML.jpg
Figure 3-4. Updating the sidebar’s width

Now we can drag a button image onto the sidebar. Set the button’s title to be “New Park” using the Attributes Inspector. Using the Size Inspector, set the width of the button to 258 points and the height to 50. Position the button 40 points from the top and 21 points from each side of the sidebar. To accomplish this, select the button on the storyboard and hold down the Option key (the same process as used in Sketch 3) with your mouse hovering over the parent view of the sidebar. Then use the arrow keys to move the button into place. Running the app now would still not show the window as you expected, but we are going to fix that next. If you are wondering where I am getting all these “magic” numbers from, I am taking them directly from our sketch mockup.

Now we must add constraints to our new button. With the button selected, use the “Align button” option to align it horizontally in the container (Figure 3-5).

A385012_1_En_3_Fig5_HTML.jpg
Figure 3-5. Alignment constraints

Xcode is going to complain about the Y position needing a constraint, so let’s fix that now. Use the Pin button to pin the New Park button 40 points from the top of the sidebar, 21 from left, and 21 from right. Also check the boxes for width and height; otherwise, the frame of the button could change to a size we don’t want (Figure 3-6).

A385012_1_En_3_Fig6_HTML.jpg
Figure 3-6. Pin constraints

Now there shouldn’t be any complaints from Xcode. Run the project, and if all goes well you will now see the window correctly. See Figure 3-7.

A385012_1_En_3_Fig7_HTML.jpg
Figure 3-7. The running app

Fixing the App’s Colors to Match Our Mockup

The current running app doesn’t match what we have in our mockup, especially the title bar and colors. We will fix that now. In order to change the title bar, we have to create our own subclass called NSWindow so we can override some settings.

Select the DogParks folder in the Navigator, then press Command + N. In the Template dialog, select “Source” under OS X and “Cocoa Class” as the template, then click Next. Name the class MainWindow, with subclass NSWindow, and make sure the language is set to Swift, then click Next. Choose the location you where you want to save the file, making sure DogParks is set in the Targets group, and click the Create button.

Now we want to override the initWithContentRect:styleMask:backing:defer: method (Objective-C format, as found in the Apple documentation), which allows us to change the style. We first need to make sure we call super.init so that all required initialization is completed before we make our changes. Next, we want to change the titleVisibility, titlebarAppearsTransparent, and styleMask. We also must implement the required init?(coder:) method to prevent getting an error in Xcode. Update the MainWindow.swift file with the code found in Listing 3-1.

Listing 3-1. Code for the Main Window
import Cocoa

class MainWindow: NSWindow {

    override init(contentRect: NSRect, styleMask aStyle: Int, backing bufferingType: NSBackingStoreType, `defer` flag: Bool) {

        super.init(contentRect: contentRect, styleMask: aStyle, backing: bufferingType, `defer`: flag)
        self.titleVisibility = NSWindowTitleVisibility.Hidden
        self.titlebarAppearsTransparent = true
        self.styleMask |= NSFullSizeContentViewWindowMask


    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

The titleVisibility is a property used to control the window’s title and title bar buttons’ visibility. The available options for this property are NSWindowTitleVisibility.Visible or NSWindowTitleVisibility.Hidden.

The titleBarAppearsTransparent is a Boolean that tells the window whether to draw the title bar’s background or not.

Finally, styleMask is used to determine what kinds of control items are displayed. The available options are shown in Listing 3-2.

Listing 3-2. Available Options for styleMask
var NSBorderlessWindowMask: Int { get }
var NSTitledWindowMask: Int { get }
var NSClosableWindowMask: Int { get }
var NSMiniaturizableWindowMask: Int { get }
var NSResizableWindowMask: Int { get }
var NSTexturedBackgroundWindowMask: Int { get }
var NSUnifiedTitleAndToolbarWindowMask: Int { get }
var NSFullScreenWindowMask: Int { get }
var NSFullSizeContentViewWindowMask: Int { get }

Now, let’s go back to Main.storyboard and update the window so it uses our subclass. Select the window, then update the class to MainWindow in the Identity Inspector (Figure 3-8).

A385012_1_En_3_Fig8_HTML.jpg
Figure 3-8. Class set to our custom MainWindow

Let’s build and run the app to see what we were able to change. We see the title bar is a lot closer to what we had in our mockup (see Figure 3-9). Next, we will update the sidebar’s color to match what we created in Sketch 3.

A385012_1_En_3_Fig9_HTML.jpg
Figure 3-9. Running app with updated title bar

Repeat the steps you just followed to create a custom class. Name the class ParkListViewController and create subclass NSViewController. Make sure Swift is selected as the language and that the checkbox for creating a NIB is unchecked.

Sketch provides us with easy access to a hex value for the color; however, the CGColorRef that we will be using does not provide a way to use hex codes, so we are going to create two helper methods.

Open the AppDelegate.swift file. At the bottom, outside of the class definition, add the following function. We are putting this in the AppDelegate only, because we will be using it in multiple places (Listing 3-3).

Listing 3-3. Helper Methods for Creating a Color from a Hex Value
func CGColorCreateWithHexValues(red red: Int, green: Int, blue: Int) -> CGColor {
    assert(red >= 0 && red <= 255, "Invalid red component")
    assert(green >= 0 && green <= 255, "Invalid green component")
    assert(blue >= 0 && blue <= 255, "Invalid blue component")


    return CGColorCreateGenericRGB(CGFloat(red) / 255.0, CGFloat(green) / 255.0, CGFloat(blue) / 255.0, 1.0)
}


func CGColorCreateFromHex(netHex: Int) -> CGColor {
    return CGColorCreateWithHexValues(red: (netHex >> 16) & 0xff, green:(netHex >> 8) & 0xff, blue:netHex & 0xff)
}

This will allow us to easily set colors using hex values, which are the standard, especially if you have experience with web development.

Let’s go back to ParkListViewController and use one of our new methods to set the background color of the sidebar. We are going to have to override awakeFromNib and set the view.layer?.backgroundColor. Update your class to look like the following (Listing 3-4).

Listing 3-4. Initial ParkListViewController
import Cocoa

class ParkListViewController: NSViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do view setup here.
    }


    override func awakeFromNib() {
        if self.view.layer != nil {
            let color : CGColorRef = CGColorCreateFromHex(0xF5F7F7)
            self.view.layer?.backgroundColor = color
        }
    }
}

We first check to make sure that the view.layer has actually been created. Next, we use our new method to create a color from the hex value we get from Sketch. In Sketch, when you click on the color box inside the popup, you see the hex value. Just make sure when using that value with this method you append it with 0x (Zero), so in our case it is 0xF5F7F7 (see Figure 3-10).

A385012_1_En_3_Fig10_HTML.jpg
Figure 3-10. The color picker

Finally, we set the view.layer?.backgroundColor to the new color we just created. If you build and run the application now, you will see the sidebar with the new color (Figure 3-11).

A385012_1_En_3_Fig11_HTML.jpg
Figure 3-11. The app with the updated sidebar color

Next, we need to make a final change to our button by updating the text size and font color. We are able to use Interface Builder to update the font type and size; however, we are unable to update the font color for the NSButton. This means we are going to have to write some custom code to accomplish this.

First, we must create an outlet for our button. Open the Main.storyboard and select the sidebar, then open the Assistant Editor. Finally, press Control and click and drag the sidebar to the ParkListViewController.swift file above the viewDidLoad method, then release your mouse. In the dialog, make sure “outlet” is selected and name it newParkButton.

Update the viewDidLoad method so it looks like the code in Listing 3-5.

Listing 3-5. Code to Update the New Park Button Title, Font, and Size
override func viewDidLoad() {
        super.viewDidLoad()


        let style = NSMutableParagraphStyle()
        style.alignment = .Center


        newParkButton.attributedTitle = NSAttributedString(string: "New Park", attributes: [ NSForegroundColorAttributeName : NSColor.whiteColor(), NSParagraphStyleAttributeName : style, NSFontAttributeName: NSFont(name: "Helvetica Neue", size: 20)!])
    }

Because we are going to be overriding the button’s attributedTitle we must set everythingincluding alignmentin code. We use an NSMutableParagraphStyle to handle setting the alignment to Center. Next, we create a new NSAttributedString with the title “New Park” and use it to set the color and paragraph style, and we use the font attribute name to set the font and the font size.

To learn more about attributed strings, review the Attributed String Programming Guide for details about which attributes you can use. View the “Standard Attributes” section of the guide here: https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/AttributedStrings/AttributedStrings.html

Run the app to see the New Park button with the size and style that match our mockup. See Figure 3-12.

A385012_1_En_3_Fig12_HTML.jpg
Figure 3-12. App with updated button text and style

A final optional task for the button is to set an alternative image background that is to be used when the button is clicked. By default, it will just darken the current light blue.

It is important to note that sometimes when running the app you will only see the sidebar. This is because we have not yet set the autolayout constraints for the details area of our app. We will handle this in a later section of this chapter.

Adding the Search Box

Next, let’s add the Search box . However, we will not be adding the search functionality at this stage. Before we do anything, let’s adjust the height of our sidebar to match what we have in our mockup. Select the Sidebar View, then set the height to 500 points in the Size Inspector.

Find the Search field in the Object Library and drag it under the New Park button. In the Size Inspector, set its size to a width of 258 points, but notice that we cannot adjust the height. If we want to adjust the height, we must do so with a constraint. For this example we will leave the default height to acknowledge that once in a while a specific design feature from a mockup might not fit with what Xcode offers.

Position the Search box 18 points below the button and 21 points from both the left and right sidebar edges. (Hold down the Option key to see the guides.)

In the Attributes Inspector for the Search field, set the placeholder text to “Search for a Park.” Next, we are going to set up the constraints for the Search field. In the Pin popup, select the top (18), left (21), right (21), and height checkboxes. Finally, click “Add constraints.”

When running the app, you should see the output in Figure 3-13. Notice that the height doesn’t match what we set in the storyboard. This is because we don’t have any constraints set for the height. We will fix that in the next section when we add our Table View for the parks list.

A385012_1_En_3_Fig13_HTML.jpg
Figure 3-13. App with Search field

Implementing the Parks List

Drag a Table View from the Object Library and place it under the Search field. Make sure the top of the Table View is 18 points below the Search field. Next, drag the resize widgets to make the Table View take up the remaining bottom portion of the sidebar, and ensure that it is snug against both sides. In the Size Inspector, you should see a Table View width of 300 and height of 352.

In the Document Outliner, expand both the Bordered Scroll View and the Clip View, then select the Table View. In the Attributes Inspector, set the columns to 1 and uncheck the “Headers” checkbox. Select the Scroll View and then select the first option for the Border Type. With the Scroll View still selected, use the Pin dialog to add constraints for Top, Left, Right, Bottom, Height, and Width, then click “Add constraints.”

Select the Table View in the Document Outliner again and set its background color to F5F7F7—the same color we used for the sidebar. If you now run the app you will notice the height of the sidebar has changed, and if you set the color correctly you shouldn’t even notice the table (Figure 3-14).

A385012_1_En_3_Fig14_HTML.jpg
Figure 3-14. App with a Table View you can’t see

Next, we are going to adjust the size of our prototype cell. We will also add an Image View and another label. First, in the Document Outliner, drill down and select Table Cell View, then in the Size Inspector set its height to 72 points.

Let’s now update the settings and location for the label that is currently in the cell. Select the Table View Cell and, still in the Size Inspector, change its height to 24 and its width to 217, then set x to 70 and y to 36. Next, change the font size to 20 in the Attributes Inspector. Change the label’s title to Park Name as well.

Press the Option key and click and drag the Park Name label down to create a duplicate of it. In the Attributes Inspector, change the font size to 16. In the Size Inspector, set x to 70, y to 12, width to 217, and the height to 19. Change the label’s title to Park Location.

Next, we will add an Image View to hold our park image. In the Object Library, find and drag an Image View to the left of the two labels. In the Size Inspector, update the view’s x value to 12, y to 12, width to 48, and height to 48. We are going to want to set a placeholder image for now to give us an idea of what our cell will look like. Find an image of your choice and drag it into the Assets.xcassets so we can easily access it from the Attributes Inspector. I will be using the Ambleside picture. I set the Image setting to ambleside.jpg and changed the scaling to “Axes Independently.” When finished, your sidebar should look like that in Figure 3-15.

A385012_1_En_3_Fig15_HTML.jpg
Figure 3-15. Shows the prototype Table View cell

If you were to run the application now, you still would not see anything showing up for the park lists. This is because we have just created a prototype cell. Later, we will use bindings to actually display our list. For now, let’s move on to the Detail View.

Setting Up the Detail View

To get started, we need to create another subclass like we did for the sidebar. Create a new Cocoa Class named DetailViewController that inherits NSViewController, and make sure the option to create a NIB is not checked, and of course that the language is set to Swift.

The first thing we will take care of is the background color so that it matches our mockup. This requires us to do exactly what we did with the sidebar, with the exception, of course, of the color used. Override awakeFromNib and set the layer’s background color to 0xFFFFFF. When finished, DetailViewController.swift should contain the code shown in Listing 3-6.

Listing 3-6. Code for the Initial DetailViewController
import Cocoa

class DetailViewController: NSViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do view setup here.
    }


    override func awakeFromNib() {
        if self.view.layer != nil {
            let color : CGColorRef = CGColorCreateFromHex(0xFFFFFF)
            self.view.layer?.backgroundColor = color
        }
    }
}

In the Main.storyboard, update the view controller that will be used for the park details so its class is set to DetailViewController in the Identity Inspector. When you run the project your window should now look like Figure 3-16.

A385012_1_En_3_Fig16_HTML.jpg
Figure 3-16. Detail view controller with a white background

In the Size Inspector for the detail view controller, set both the width and height to 500.

Let’s take a look at the detail part of our mockup for a refresher of what we are trying to accomplish (Figure 3-17).

A385012_1_En_3_Fig17_HTML.jpg
Figure 3-17. Park details section of our mockup

Based on this screenshot, we are going to need a Delete button, two text fields, a wrapping text field or Text View, three checkboxes, a button for adding an image, a button for deleting an image, and finally a Collection View to hold our images.

Let’s start by dragging two text fields onto the detail view controller. Set the first text field (the one on the left) to be 40 points from the top, and make sure it snaps to the blue guideline on the left, which will place it 20 points from the left. Using the Size Inspector, set its width to 256 points.

Select the second text field and make sure it too is 40 points from the top. Set it to have a width of 192 and to rest against the right guideline. If you run the app now, you will see that the textbox on the right is cut off; to fix this we must add some constraints.

Select the first text field and use the Pin menu to add constraints for the top, left, right (distance to the second text field), width, and height. Select the second text field and use the Pin menu to add constraints for the top, left (distance to the first text field), width, and height. Finally, set the placeholder text for the first text field to “Park Name”, and the placeholder text for the second text field to “Park Location”. Now run the app; you will see the window correctly showing the text fields. See Figure 3-18.

A385012_1_En_3_Fig18_HTML.jpg
Figure 3-18. Park details section with park name and location text fields

Drag a Text View from the Object Library and place it 18 points below the name/location text fields. Use the Pin menu to add constraints for top, left, right, and height.

Drag three checkboxes under the Text View. Set all the states to off. Change the title of the first one to “Allows Offleash”, the second one to “Has Fresh Water”, and the final one to “Is Fenced”. You are going to have to use the resizing widgets on the storyboard to make sure all the text shows. Position the first checkbox 20 points below the Text View. Set the second one 10 points below the “Allows Offleash” checkbox, and set the last one 10 points below the “Has Fresh Water” checkbox.

Pin each of the checkboxes one by one using constraints for top, left, and width.

Now, drag a Collection View to the bottom of the Detail View. Set the height to 164 points. Finally, position the Collection View 20 points from the bottom, left, and right of the Detail View. Pin the Collection View to the top (“Is Fenced” checkbox), right, left, and bottom using constraints.

Unfortunately, there is a bug in Xcode with Collection Views and how Collection View items are created on the storyboard. If you try to run the application now, you will receive an error: “Unknown segue relationship: Prototype” (Figure 3-19).

A385012_1_En_3_Fig19_HTML.jpg
Figure 3-19. Prototype Collection View item error

Delete the segue between the Collection View and the Collection View items. This will get rid of the error, and when you run the app you will see the output shown in Figure 3-20.

A385012_1_En_3_Fig20_HTML.jpg
Figure 3-20. Current app with Details View progress

Let’s add our last three buttons and then move on to fixing our Collection View. Drag two push buttons above the Collection View. Set the title of the button on the right to “Delete Image(s)”, and set the title of the button on the left to “Add Image(s)”. Set the position of the Delete Image(s) button to be 20 points from the right edge of the Detail View and 12 points above the Collection View. Set the position of the Add Image(s) button to be 12 points to the left of the Delete Image(s) button and 12 points above the Collection View. Using the Pin menu, add constraints for the Delete Image(s) button for the right, bottom, and left. Next, add constraints for the Add Image(s) button on the right and bottom.

Drag one more button and place it above the Park Location text field. This will be used to delete the park entry. Set the title to “Delete Park”. (This is changed from the initial mockup to make it clear what this button does). Pin the button to the top, right, and bottom using constraints. When you are finished, your view should look like Figure 3-21.

A385012_1_En_3_Fig21_HTML.jpg
Figure 3-21. Park details section buttons for adding/deleteing images and deleting the park

Fixing the Collection View Item

First, we need to set the Storyboard ID in the Identity Inspector for the Collection View item to collectionViewItem. Next, let’s adjust the size of the Items View where images will appear. Click inside the Collection View item or use the Document Outliner to select the view, then use the Size Inspector to change the width and height to 90.

Drag an Image View inside the Collection View item and use the size widgets to make the Image View fill the entire Collection View item area. Pin the Image View to the top (0), right (0), left (0), and bottom (0).

Open the DetailViewController.swift file. We are going to update the viewDidLoad function to link the Collection View to the Collection View item. We are also going to need an outlet to the Collection View. Update your DetailViewController to look like Listing 3-7.

Listing 3-7. Code Connecting Collection Prototype Item to Collection View
import Cocoa

class DetailViewController: NSViewController {

    @IBOutlet weak var parkImagesCollection: NSCollectionView!

    override func viewDidLoad() {
        super.viewDidLoad()


        // don't forget to set identifier of collectionViewItem
        // from interface builder
        let itemPrototype = self.storyboard?.instantiateControllerWithIdentifier("collectionViewItem")
            as! NSCollectionViewItem
        self.parkImagesCollection.itemPrototype = itemPrototype
    }


    override func awakeFromNib() {
        if self.view.layer != nil {
            let color : CGColorRef = CGColorCreateFromHex(0xFFFFFF)
            self.view.layer?.backgroundColor = color
        }
    }
}

If you try to run the app now, you will get an error because we have not connected the parkImagesCollection outlet to the view controller. Let’s do that now. Open the main storyboard, open the Document Outliner, then select the collection view controller on the storyboard. This will then show the Bordered Scroll View. Expand this until you can see Collection View. Then press Control and click and drag from the detail view controller to the Collection View. In the popup window, select parkImagesCollection (Figure 3-22).

A385012_1_En_3_Fig22_HTML.jpg
Figure 3-22. Connecting Collection View to parkImagesCollection outlet

Now you should be able to build and run the app without any problems. At this point we have our prototype running in Xcode.

Conclusion

This chapter enabled us to analyze the data we are going to need, which we will start working with in the next chapter. Then we went through the process of integrating our mockup in Xcode to create a prototype. In the next chapter, we will shift our focus over to CloudKit, where we will implement more functionality into our app as we learn about it.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset