In this Metal video tutorial, you’ll earn how to add shading and highlights to your models with diffuse and specular lighting.
The post Video Tutorial: Beginning Metal Part 12: Diffuse and Specular Lighting appeared first on Ray Wenderlich.
In this Metal video tutorial, you’ll earn how to add shading and highlights to your models with diffuse and specular lighting.
The post Video Tutorial: Beginning Metal Part 12: Diffuse and Specular Lighting appeared first on Ray Wenderlich.
Learn how to use a simple CSS boilerplate library called Skeleton to beautify your Vapor web pages.
The post Screencast: Server Side Swift with Vapor: Beautifying Pages with Skeleton appeared first on Ray Wenderlich.
The GameplayKit framework provides you with powerful tools to help you design modular and scalable games with minimal effort.
One of the best uses for GameplayKit is providing artificial intelligence for your games. Since GameplayKit is game-engine agnostic, it works with your games whether you’re using SpriteKit, SceneKit or even UIKit to power your development journey.
In this tutorial, you will learn how to incorporate an artificial intelligence into an existing game using GameplayKit.
This tutorial assumes you’re familiar with Swift and SpriteKit. If you need a refresher of Swift, check out our beginner series. If you are new to SpriteKit check out this tutorial.
Get ready to defeat pesky zombies — in a classic game of Tic-Tac-Toe.
Download the starter project. This project is already organized into the following folders:
Build and run your game; it’s fully playable in its current state.
Time to bring some “life” into this game!
Quite simply, an artificial intelligence (AI) is an algorithm that rationally analyzes data to make decisions. In video games, AI is used to bring realistic behavior to non-player characters. The best AIs will blur the line between non-player characters and human players.
The goal of any AI is to give the appearance of intelligence. However, if you gave the AI knowledge of all the rules of the game, it could easily win almost every match. Therefore, to avoid creating an undefeatable monster, it’s a good idea to introduce intentional errors.
A good example of restricting AI to make the game fair is the logic in first-person shooters (FPS). You wouldn’t want to play a game where the non-player characters have perfect aim and always know where you are.
The star of the show is the GKStrategist
protocol. This protocol defines the general logic to decide which moves are the best ones to play.
Since this is a general purpose protocol, you can use it as a foundation to create your own custom strategist that will decide on moves according to your game’s needs. However, GameplayKit provides two specific strategist classes based on the GKStrategist
protocol you can use in your games:
GKMinmaxStrategist
class ranks every possible move to find the best one. The best move is the one that results in the AI player winning the game, or the one that minimizes the chances of other players winning. However, since this strategist runs through every possible move to assign it a rank, the performance toll increases as the game increases in complexity.GKMonteCarloStrategist
class chooses its moves by making a probabilistic guess of a move that could likely result in the AI player winning the game. This is a more efficient approach and is likely to choose a good move, but not always the best one. Even in complex games, this strategist can still perform well since it doesn’t need to rank every move from best to worst.For this game, you’ll be using the GKMinmaxStrategist
. Given that Tic-Tac-Toe is a straightforward game with a limited set of outcomes, calculating the rank of any particular move is a trivial task.
For a strategist to work its magic, it has to know the structure of your game. That’s where the game model comes into play.
The game model is a representation of your gameplay that GameplayKit can understand. GameplayKit uses your game model to do the following:
Your model keeps track of all the players in the game. To GameplayKit, a player is simply a class that conforms to the GKGameModelPlayer
protocol. This protocol is very easy to implement, as it only requires the player class to have a unique playerId
property so your model can distinguish between players.
A move is simply a class that implements the GKGameModelUpdate
protocol. According to this protocol, this class must contain a value
property so it can assign the move a score based on its calculation. You’re free to design the rest of your move class according to your game’s needs.
Since this is TicTacToe, your model will consist of a 3×3 board and 2 players: the brain and the zombie. Your move class will keep track of the position of the player. You will make the AI only smart enough to determine if the next move will win the game.
Start off by opening Player.swift from the Model folder and add the GKGameModelPlayer
protocol to the class declaration:
class Player: NSObject, GKGameModelPlayer { |
You’ll see that Xcode warns you that Player
doesn’t conform to the GKGameModelPlayer
protocol.
To fix this, add the playerId
property to the variable declarations below the value
and name
variables:
var playerId: Int |
Scroll down to the init(_:)
method and assign playerId
an initial value:
playerId = value.rawValue |
Your player class is now ready to be used by GameplayKit.
Open Move.swift and replace the contents with the following:
class Move: NSObject, GKGameModelUpdate { enum Score: Int { case none case win } var value: Int = 0 var coordinate: CGPoint init(_ coordinate: CGPoint) { self.coordinate = coordinate } } |
Because this is the move class, you first declare conformance to the GKGameModelUpdate
protocol. You will use the Score
enum to define the score of a move based on whether it results in a win. Then, you provide a value
property to help GameplayKit rate a move. You should never modify this property yourself during gameplay. Finally, the coordinate property is a CGPoint
that contains the location on the board.
Now you can put these pieces together in your model class. Open Board.Swift and add the following class extension to the bottom of the file:
extension Board: GKGameModel { // MARK: - NSCopying func copy(with zone: NSZone? = nil) -> Any { let copy = Board() copy.setGameModel(self) return copy } // MARK: - GKGameModel var players: [GKGameModelPlayer]? { return Player.allPlayers } var activePlayer: GKGameModelPlayer? { return currentPlayer } func setGameModel(_ gameModel: GKGameModel) { if let board = gameModel as? Board { values = board.values } } } |
GKGameModel
requires conformance to NSCopying
because the strategist evaluates moves against copies of the game. The players
property stores a list of all the players in the match and the activePlayer
property keeps track of the player in turn. setGameModel(_:)
lets GameplayKit update your game model with the new state after it makes a decision.
Next, add the following below setGameModel(_:)
:
func isWin(for player: GKGameModelPlayer) -> Bool { guard let player = player as? Player else { return false } if let winner = winningPlayer { return player == winner } else { return false } } |
This method, which is part of the starter project, determines whether a player wins the game via winningPlayer
. It loops through the board to find whether either player has won and, if so, returns the winning player. You then use this result to compare it to the player that was passed onto this method.
Next, add this code right below isWin(for:)
:
func gameModelUpdates(for player: GKGameModelPlayer) -> [GKGameModelUpdate]? { // 1 guard let player = player as? Player else { return nil } if isWin(for: player) { return nil } var moves = [Move]() // 2 for x in 0..<values.count { for y in 0..<values[x].count { let position = CGPoint(x: x, y: y) if canMove(at: position) { moves.append(Move(position)) } } } return moves } func apply(_ gameModelUpdate: GKGameModelUpdate) { guard let move = gameModelUpdate as? Move else { return } // 3 self[Int(move.coordinate.x), Int(move.coordinate.y)] = currentPlayer.value currentPlayer = currentPlayer.opponent } |
Here’s what’s going on in the code above:
gameModelUpdates(for:)
tells GameplayKit about all the possible moves in the current state of the game.apply(_:)
after each move selected by the strategist so you have the chance to update the game state. After a player makes a move, it is now the opponent’s turn.At the bottom of the extension add this new method:
func score(for player: GKGameModelPlayer) -> Int { guard let player = player as? Player else { return Move.Score.none.rawValue } if isWin(for: player) { return Move.Score.win.rawValue } else { return Move.Score.none.rawValue } } |
The AI uses score(for:)
to calculate it’s best move. When GameplayKit creates its move tree, it will select the shortest path to a winning outcome.
That’s it — the model is done. Now your brain is ready to come alive!
This is where everything comes together! You’ll use the model you defined to drive the strategist.
Open Strategist.swift and replace the contents of the struct with the following:
struct Strategist { // 1 private let strategist: GKMinmaxStrategist = { let strategist = GKMinmaxStrategist() strategist.maxLookAheadDepth = 5 strategist.randomSource = GKARC4RandomSource() return strategist }() // 2 var board: Board { didSet { strategist.gameModel = board } } // 3 var bestCoordinate: CGPoint? { if let move = strategist.bestMove(for: board.currentPlayer) as? Move { return move.coordinate } return nil } } |
Inside of this struct:
GKMinmaxStrategist
with a maxLookAheadDepth
of 5. The look ahead depth is the constraint you give a strategist to limit the number of future moves it can simulate. You also provide a random source to be the deciding factor when the strategist selects multiple moves as the best move.CGPoint
representing the strategist’s best move. The bestMove(for:)
method will return nil
if the player is in an invalid state or nonexistent.Now only one thing remains: adding the strategist to the game.
Switch to the GameScene.swift file located inside the Game folder and add the following to the properties section near the top:
var strategist: Strategist! |
With this property you keep a reference to the strategist used by the game.
Next, add following inside didMove(to:)
towards the bottom, above resetGame()
:
strategist = Strategist(board: board) |
Here you initialize the strategist with the model driving the game.
Next, add the following at the bottom of resetGame()
:
strategist.board = board |
This ensures that every time you reset the game you also provide the strategist with a fresh game model.
Scroll down to the Touches section and add this new method right above:
fileprivate func processAIMove() { // 1 DispatchQueue.global().async { [unowned self] in // 2 let strategistTime = CFAbsoluteTimeGetCurrent() guard let bestCoordinate = self.strategist.bestCoordinate else { return } // 3 let delta = CFAbsoluteTimeGetCurrent() - strategistTime let aiTimeCeiling = 0.75 // 4 let delay = max(delta, aiTimeCeiling) // 5 DispatchQueue.main.asyncAfter(deadline: .now() + delay) { self.updateBoard(with: Int(bestCoordinate.x), y: Int(bestCoordinate.y)) } } } |
In this function you do the following:
Now you only have to call this method when it’s the AI’s turn. To do this, add the following to the bottom of updateGame():
if board.currentPlayer.value == .brain { processAIMove() } |
Build and run. Now the game has a mind of its own!
If you play the game fast enough, you’ll soon notice that you can make the move for the AI. That’s not good.
To prevent this, add the following at the beginning of handleTouchEnd(_:with:)
:
guard board.currentPlayer.value == .zombie else { return } |
Build and run. Now the game will work as expected. Great job!
You can find the final project for this tutorial here.
Now you’re able to take this new knowledge you learned from this GameplayKit tutorial and add AI to your own games!
To further your knowledge of GameplayKit, check out Apple’s developer videos covering GameplayKit and its advances over time.
As an extra challenge, I encourage you to try and build more intelligence into the AI. Try adding detection of the opponent being one move away from winning and give a blocking move a higher rank. You’d only need to extend the Board.swift class and the enum in Move.swift.
We’d love you see what you can come up with. Join the discussion below to comment, ask questions or share your ideas!
The post GameplayKit Tutorial: Artificial Intelligence appeared first on Ray Wenderlich.
Update note: This macOS NSTableView tutorial has been updated to Xcode 8 and Swift 3 by Warren Burton. The original tutorial was written by Ernesto García.
Table views are one of the most ubiquitous controls in macOS applications, with familiar examples being Mail’s message list and Spotlight’s search results. They allow your Mac to represent tabular data in an attractive way.
NSTableView
arranges data in rows and columns. Each row represents a single model object within a given data collection, and each column displays a specific attribute of a model object.
In this macOS NSTableView tutorial, you’ll use a table view to create a functional file viewer that will bear a striking resemblance to Finder. As you work through it, you’ll learn a lot about table views, such as:
Ready to create your first table view? Read on!
Download the starter project and open it in Xcode.
Build and run to see what you’re starting with:
You have a blank canvas from which you’ll create a cool file viewer. The starter app already has some of the functionality you’ll need to work through this tutorial.
With the application open, choose File > Open… (or use the Command+O keyboard shortcut).
From the new window that pops up, choose any folder you want and click the Open button. You’ll see something like this in Xcode’s console:
Represented object: file:///Users/tutorials/FileViewer/FileViewer/ |
This message shows the selected folder’s path, and the code in the starter project passes that URL to the view controller.
If you’re curious and want to learn more about how things are implemented, here’s where you should look:
ViewController
class and is where you’ll spend some time today. It’s where you’ll create the table view and show the file list.Open Main.storyboard in the Project Navigator. Select the View Controller Scene and drop a table view from the Object Library into the view. There’s a container in the view hierachy named Table Container all ready for you.
Next, you need to add some constraints. Click the Pin button in the Auto Layout toolbar. In the popup that appears, set the all the edge constraints as follows:
Be sure to set Update Frames to Items of New Constraints, then click Add 4 Constraints.
Take a moment to have a look at the structure of a newly created table view. As you probably gathered from its name, it follows typical table structuring:
If you’re familiar with UITableView
on iOS, you’re treading familiar waters, but they’re much deeper here in macOS. In fact, you might be surprised by the number of individual UI objects in the object hierarchy that make up an NSTableView
.
NSTableView
is an older and more complex control than a UITableView
, and it serves a different user interface paradigm, specifically, where the user has a mouse or trackpad.
The main difference with UITableView is that you have the possibility of multiple columns and a header that can be used to interact with the table view, for example, ordering and selecting.
Together, NSScrollView
and NSClipView
, respectively scroll and clip the contents of the NSTableView
.
There are two NSScroller
objects — one each for vertical and horizontal scrolling across the table.
There are also a number of column objects. An NSTableView
has columns, and these columns have headers with titles. It’s important to note that users can resize and reorder columns, though you have the power to remove this ability by setting its default to disabled.
In Interface Builder, you’ve seen the complexity of the view hierarchy of the table view. Multiple classes cooperate to build the table structure, which usually ends up looking like this:
These are the key parts of an NSTableView
:
NSTableHeaderView
. It’s responsible for drawing the headers at top of the table. If you need to display a custom header, you can use your own header subclasses.NSView
or NSTableCellView
subclass, and its responsibility is to display the actual data. And guess what? You can create custom cell view classes to display the content however you’d like.NSTableViewColumn
class, which is responsible for managing width and behavior of the column, such as resizing and repositioning. This class is not a view, but a controller class. You use it to specify how columns should behave, but you don’t control the visual styles of the columns with it because the header, row and cell views have got things covered.Note: There are two modes of NSTableView. The first is a cell-based table view called an NSCell
. It’s like an NSView
, but older and lighter. It comes from earlier days of computing when the desktop needed optimizations in order to draw controls with minimal overhead.
Apple recommends using view-based table views, but you’ll see NSCell
in many of the controls in AppKit, so it’s worth knowing what it is and where it comes from. You can read more about NSCell
in Apple’s Control and Cell Programming Topics
Well, now that was a nice little jog into the basic theory behind table view structure. Now that you’ve had all that, it’s time to go back to Xcode and get to work on your very own table view.
By default, Interface Builder creates a table view with two columns, but you need three columns to display name, date and size file information.
Go back to Main.storyboard.
Select the table view in the View Controller Scene. Make sure that you select the table view and not the scroll view that contains it.
Open the Attributes Inspector. Change the number of Columns to 3. It’s as simple as that! Your table view now has three columns.
Next, check the Multiple checkbox in the Selection section, because you want to select multiple files at once. Also check Alternating Rows in the Highlight section. When enabled, this tells the table view to use alternating row colors for its background, just like Finder.
Rename the column headers so the text is more descriptive. Select the first column in the View Controller Scene.
Open the Attributes Inspector and change the column Title to Name.
Repeat the operation for the second and third column, changing the Title to Modification Date and Size, respectively.
Note: There is an alternative method for changing the column title. You can double-click directly on the header on the table view to make it editable. Both ways have exactly the same end result, so go with whichever method you prefer.
Last, if you can’t see the Size column yet, select the Modification Date column and resize to 200. It beats fishing around for the resize handle with your mouse. :]
Build and run. Here’s what you should see:
In its current state, the table view has three columns, each containing a cell view that shows text in a text field.
But it’s kind of bland, so spice it up by showing the icon of the file next to the file name. Your table will look much cleaner after this little upgrade.
You need to replace the cell view in the first column with a new cell type that contains an image and a text field.
You’re in luck because Interface Builder has this type of cell built in.
Select the Table Cell View in the Name column and delete it.
Open the Object Library and drag and drop an Image & Text Table Cell View into either the first column of the table view or the View Controller Scene tree, just under the Name table view column.
Now you’re whipping things into shape!
Every cell type needs an assigned identifier. Otherwise, you’ll be unable to create a cell view that corresponds to a specific column when you’re coding.
Select the cell view in the first column, and in the Identity Inspector change the Identifier to NameCellID.
Repeat the process for the cell views in the second and third columns, naming the identifiers DateCellID and SizeCellID respectively.
The table view currently knows nothing about the data you need to show or how to display it, but it does need to be looped in! So, you’ll implement these two protocols to provide that information:
NSTableViewDataSource
: tells the table view how many rows it needs to represent.NSTableViewDelegate
: provides the view cell that will be displayed for a specific row and column.The visualization process is a collaboration between the table view, delegate and data source:
numberOfRows(in:)
that returns the number of rows the table will display.tableView(_:viewFor:row:)
for every row and column. The delegate creates the view for that position, populates it with the appropriate data, and then returns it to the table view.Both methods must be implemented in order to show your data in the table view.
Open ViewController.swift in the Assistant editor and Control-drag from the table view into the ViewController
class implementation to insert an outlet.
Make sure that the Type is NSTableView and the Connection is Outlet. Name the outlet tableView
.
You can now refer to the table view in code using this outlet.
Switch back to the Standard Editor and open ViewController.swift. Implement the required data source method in the ViewController
by adding this code at the end of the class:
extension ViewController: NSTableViewDataSource { func numberOfRows(in tableView: NSTableView) -> Int { return directoryItems?.count ?? 0 } } |
This creates an extension that conforms to the NSTableViewDataSource
protocol and implements the required method numberOfRows(in:)
to return the number files in the directory, which is the size of the directoryItems
array.
Now you need to implement the delegate. Add the following extension at the end of ViewController.swift:
extension ViewController: NSTableViewDelegate { fileprivate enum CellIdentifiers { static let NameCell = "NameCellID" static let DateCell = "DateCellID" static let SizeCell = "SizeCellID" } func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { var image: NSImage? var text: String = "" var cellIdentifier: String = "" let dateFormatter = DateFormatter() dateFormatter.dateStyle = .long dateFormatter.timeStyle = .long // 1 guard let item = directoryItems?[row] else { return nil } // 2 if tableColumn == tableView.tableColumns[0] { image = item.icon text = item.name cellIdentifier = CellIdentifiers.NameCell } else if tableColumn == tableView.tableColumns[1] { text = dateFormatter.string(from: item.date) cellIdentifier = CellIdentifiers.DateCell } else if tableColumn == tableView.tableColumns[2] { text = item.isFolder ? "--" : sizeFormatter.string(fromByteCount: item.size) cellIdentifier = CellIdentifiers.SizeCell } // 3 if let cell = tableView.make(withIdentifier: cellIdentifier, owner: nil) as? NSTableCellView { cell.textField?.stringValue = text cell.imageView?.image = image ?? nil return cell } return nil } } |
This code declares an extension that conforms to the NSTableViewDelegate
protocol and implements the method tableView(_:viewFor:row)
. It’s then called by the table view for every row and column to get the appropriate cell.
There’s a lot going on the method, so here’s a step-by-step breakdown:
make(withIdentifier:owner:)
. This method creates or reuses a cell with that identifier. Then it fills it with the information provided in the previous step and returns it.Next up, add this code inside viewDidLoad()
:
tableView.delegate = self tableView.dataSource = self |
Here you tell the table view that its data source and delegate will be the view controller.
The last step is to tell the table view to refresh the data when a new directory is selected.
First, add this method to the ViewController
implementation:
func reloadFileList() { directoryItems = directory?.contentsOrderedBy(sortOrder, ascending: sortAscending) tableView.reloadData() } |
This helper method refreshes the file list.
First, it calls the directory
method contentsOrderedBy(_:ascending)
and returns a sorted array with the directory files. Then it calls the table view method reloadData()
to tell it to refresh.
Note that you only need to call this method when a new directory is selected.
Go to the representedObject
observer didSet
, and replace this line of code:
print("Represented object: \(url)") |
With this:
directory = Directory(folderURL: url) reloadFileList() |
You’ve just created an instance of Directory
pointing to the folder URL, and it calls the reloadFileList()
method to refresh the table view data.
Build and run.
Open a folder using the menu File > Open… or the Command+O keyboard shortcut and watch the magic happen! Now the table is full of contents from the folder you just selected. Resize the columns to see all the information about each file or folder.
Nice job!
In this section, you’ll work with some interactions to improve the UI.
When the user selects one or more files, the application should update the information in the bottom bar to show the total number of files in the folder and how many are selected.
In order to be notified when the selection changes in the table view, you need to implement tableViewSelectionDidChange(_:)
in the delegate. This method will be called by the table view when it detects a change in the selection.
Add this code to the ViewController implementation:
func updateStatus() { let text: String // 1 let itemsSelected = tableView.selectedRowIndexes.count // 2 if (directoryItems == nil) { text = "No Items" } else if(itemsSelected == 0) { text = "\(directoryItems!.count) items" } else { text = "\(itemsSelected) of \(directoryItems!.count) selected" } // 3 statusLabel.stringValue = text } |
This method updates the status label text based on the user selection.
selectedRowIndexes
contains the indexes of the selected rows. To know how many items are selected, it just gets the array count.Now, you just need to invoke this method when the user changes the table view selection. Add the following code inside the table view delegate extension:
func tableViewSelectionDidChange(_ notification: Notification) { updateStatus() } |
When the selection changes this method is called by the table view, and then it updates the status text.
Build and run.
Try it out for yourself; select one or more files in the table view and watch the informative text change to reflect your selection.
In macOS, a double-click usually means the user has triggered an action and your program needs to perform it.
For instance, when you’re dealing with files you usually expect the double-clicked file to open in its default application and for a folder, you expect to see its content.
You’re going to implement double-click responses now.
Double-click notifications are not sent via the table view delegate; instead, they’re sent as an action to the table view target. But to receive those notifications in the view controller, you need to set the table view’s target
and doubleAction
properties.
Note: Target-action is a pattern used by most controls in Cocoa to notify events. If you’re not familiar with this pattern, you can learn about it in the Target-Action section of Apple’s Cocoa Application Competencies for macOS documentation.
Add the following code inside viewDidLoad()
of the ViewController
:
tableView.target = self tableView.doubleAction = #selector(tableViewDoubleClick(_:)) |
This tells the table view that the view controller will become the target for its actions, and then it sets the method that will be called after a double-click.
Add the tableViewDoubleClick(_:)
method implementation:
func tableViewDoubleClick(_ sender:AnyObject) { // 1 guard tableView.selectedRow >= 0, let item = directoryItems?[tableView.selectedRow] else { return } if item.isFolder { // 2 self.representedObject = item.url as Any } else { // 3 NSWorkspace.shared().open(item.url as URL) } } |
Here’s the above code broken out step-by-step:
tableView.selectedRow
value equal to -1.representedObject
property to the item’s URL. Then the table view refreshes to show the contents of that folder.NSWorkspace
method openURL()
Build and run and check out your handiwork.
Double-click on any file and observe how it opens in the default application. Now choose a folder and watch how the table view refreshes and displays the content of that folder.
Whoa, wait, did you just create a DIY version of Finder? Sure looks that way!
Everybody loves a good sort, and in this next section you’ll learn how to sort the table view based on the user’s selection.
One of the best features of a table is one- or two-click sorting by a specific column. One click will sort it in ascending order and a second click will sort in descending order.
Implementing this particular UI is easy because NSTableView
packs most of the functionality right out of the box.
Sort descriptors are what you’ll use to handle this bit, and they are simply instances of the NSSortDescriptor
class that specify the desired attribute and sort order.
After setting up descriptors, this is what happens: clicking on a column header in the table view will inform you, via the delegate, which attribute should be used, and then the user will be able sort the data.
Once you set the sort descriptors, the table view provides all the UI to handle sorting, like clickable headers, arrows and notification of which sort descriptor was selected. However, it’s your responsibility to order the data based on that information, and refresh the table view to reflect the new order.
You’ll learn how to do that right now.
Add the following code inside viewDidLoad()
to create the sort descriptors:
// 1 let descriptorName = NSSortDescriptor(key: Directory.FileOrder.Name.rawValue, ascending: true) let descriptorDate = NSSortDescriptor(key: Directory.FileOrder.Date.rawValue, ascending: true) let descriptorSize = NSSortDescriptor(key: Directory.FileOrder.Size.rawValue, ascending: true) // 2 tableView.tableColumns[0].sortDescriptorPrototype = descriptorName tableView.tableColumns[1].sortDescriptorPrototype = descriptorDate tableView.tableColumns[2].sortDescriptorPrototype = descriptorSize |
This is what this code does:
sortDescriptorPrototype
property.When the user clicks on any column header, the table view will call the data source method tableView(_:sortDescriptorsDidChange:)
, at which point the app should sort the data based on the supplied descriptor.
Add the following code to the data source extension:
func tableView(_ tableView: NSTableView, sortDescriptorsDidChange oldDescriptors: [NSSortDescriptor]) { // 1 guard let sortDescriptor = tableView.sortDescriptors.first else { return } if let order = Directory.FileOrder(rawValue: sortDescriptor.key!) { // 2 sortOrder = order sortAscending = sortDescriptor.ascending reloadFileList() } } |
This code does the following:
sortOrder
and sortAscending
properties of the view controller, and then calls reloadFileList()
. You set it up earlier to get a sorted array of files and tell the table view to reload the data.Build and run.
Click any header to see your table view sort data. Click again in the same header to alternate between ascending and descending order.
You’ve built a nice file viewer using a table view. Congratulations!
You can download the completed project here.
This macOS NSTableView tutorial covered quite a bit, and you should now feel much more confident in your ability to use table views to organize data. In addition, you also covered:
There is a lot more you can do with table views to build elegant UI for your app. If you’re looking to learn more about it, consider the following resources:
If you have any questions or comments on this tutorial, feel free to join the discussion below in the forums!
The post macOS NSTableView Tutorial appeared first on Ray Wenderlich.
In this beginning Metal tutorial video, you’ll create a Breakout clone using a scene graph and lighting techniques.
The post Video Tutorial: Beginning Metal Part 13: Creating a Game, Part 1 appeared first on Ray Wenderlich.
In this beginning Metal tutorial video, you’ll create a Breakout clone using a scene graph and lighting techniques.
The post Video Tutorial: Beginning Metal Part 14: Making a Game, Part 2 appeared first on Ray Wenderlich.
In this closing video from Beginning Metal, you’ll review what you’ve learned, and get some hints on where to go from here.
The post Video Tutorial: Beginning Metal Part 15: Conclusion appeared first on Ray Wenderlich.
I hope everyone had a great Thanksgiving. For me, it was a chance to relax and be thankful to build apps on the best platform ever!
You all weren’t relaxing too much though. My inbox is full of apps from your fellow raywenderlich.com readers. I’ve downloaded them all and picked a few to share with you.
This month we have:
Keep reading to see the latest apps released by raywenderlich.com readers like you.
We all have a favorite weather app, and my new favorite is Partly Sunny.
Partly Sunny shows the most important information quickly for each location you care about: highs, lows, current temperature, and conditions. Thanks to its integration with Dark Sky, it can also give great information for the near future. For example, it might tell you it will rain in 12 minutes, or snow will continue for 35 minutes.
When you tap a location, you can get a very detailed forecast. You’ll see the entire day hour by hour graphed with temperatures and conditions. Even more information is available like visibility distances, humidity, wind speed, sunrise and sunset times, and more. You can also swipe up past the hourly charts to see a chart for the next 7 days with temperature ranges and expected conditions. Each day also has a detailed view.
Partly Sunny is packed with even more features. It has a rain graphs that show upcoming rain based on strength. You can graph hourly wind speed estimations, humidities, precipitation and more. It even has a dark theme for those that really love a little less white.
If you’re looking to try a new weather app, give Partly Sunny a try. If you’re not looking for a new weather app, give it a try anyway, it just might change your forecast. ;]
MyRedBag is the one stop shop for recipes and grocery lists.
MyRedBag lets you create and save recipes in an easy to manage index. You can even search recipes from other users to find new ideas for your kitchen. You can also easily add the ingredients from any recipe to your grocery list. MyRedBag even lets you schedule your meals in a meal planner. Then make sure you have a grocery list for everything you need to make it happen.
You can share your list with friends and family to make shopping a group exercise. And you can share your recipes with the community so others can taste your special creations.
You can even find stores in your area and submit grocery orders for delivery or pickup based on availability for the stores in your area.
MyRedBag is the one and only Recipe and Shopping app you should ever need.
iMonstickers were painstakingly designed to help you express emotions… if your emotions involve vampires, ghosts, or mummies. :]
iMonstickers has a sticker for everything. It’s got sad stickers, surprise stickers, scared, shy, and more! iMonstickers were great for Halloween, but we all have a little monster in us year round. ;]
hearEQ will help you learn, train, and perfect your ear for adjusting an equalizer.
The ability to adjust an equalizer effectively can transform music. Whether you’re a sound engineer, musician, or just an avid listener, a well tuned equalizer can take music to the next level.
hearEQ will help you understand how each frequency band can affect the music. You’ll be able to recreate popular effects or just balance music. hearEQ uses your favorite songs on your device to create custom ear training exercises that will teach you about individual frequency bands and then help you hone in your ear as you practice tuning. hearEQ supports both standard 10-band and 30-band equalizers like you’d find in iTunes or even professional boards.
There is also a quiz mode playing your song with random adjustments and letting you listen and guess the applied equalization. You’ll get 10 questions covering ten frequencies and a score to finish. As your score goes up, you should notice an improved ability to hear the difference in the music. Was the music sounding a bit hollow? Probably a 1000Hz cut. Was it too muddy? Probably a 500Hz boost.
hearEQ is great for beginners looking to get into sound mixing. But experts also appreciated the change to clearly control and quiz themselves to improve their sensitive ears.
The Battle of San Jacinto was an important battle in Texas history. This app brings the book “The Battle of San Jacinto” by James W. Pohl to life on the iPad and iPhone.
The original text is enhanced with pictures from the battle sights today as well as illustrations of how the site looked before. There are panoramic photos of the areas today. You can pan around with your finger or even move your iPad around to feel like you’re there. Zoom in for complete details.
There are also maps of the battlegrounds and surrounding area from the Southwestern Historical Quarterly by Jeffrey D. Dunn. You can get an idea of what the landscape was when the battle took place and how landmarks influenced the outcome.
San Jacinto is a fantastic historical reference and immersive learning tool.
Can Knockout is a classic carnival game right on your iPhone or iPad so you can play anytime.
Can Knockout shows a stack of cans and lets you flick a baseball to knock them down. You only get so many baseballs so you’ll have to aim carefully.
Each time you knock down a stack, you’ll get a chance at a new stack in a different challenging layout. As you work through levels you’ll even encounter some exploding cans.
Can Knockout is definitely addicting. My highscore is 55 if you care to try and beat it. ;]
Whether you are already great at playing the saxophone or are just getting started, iPlaySax is the app for you.
iPlaySax is an all around companion to saxophone musicians everywhere. The highlight of the app is sheet music specifically written and arranged for the app. They cover a variety of skill levels and styles. You can play the sheet music as is if you’d like and even record yourself as you play.
Each piece is also recorded by a real saxophone professional, so you can also play along or even play a duet! Each part was recorded separately so you can select what you’d like to play and the app will fill in the rest.
There are also instructional pieces. There is a finger position chart for beginners or anyone needing a refresher for individual notes. There are video tutorials teaching you anything from breathing techniques to finger exercises. You can buy additional music right in the app or also check out some free music by sharing the app with friends.
iPlaySax is also completely free! So its definitely worth checking out if you’re a saxophone player.
Ever wonder what might be going on in town? Maybe you’re visiting a new city or just at home looking for something to do. Events Near You will help you find something to do nearby anytime.
From garage sales to concerts to pickup basketball, ENY will show you a map of stuff going on around you. You can easily filter the map based on what interests you using keywords. Once you find something you like you’ll see various information like number of attendees, times, descriptions, and directions.
ENY will also let you save keywords for events that interest you often. When a new event with that keyword is posted, you’ll be notified if you’re nearby when it starts. A great way to get keyed into the local events without even looking for them.
Best of all, ENY isn’t only useful if everyone in your community uses it. It draws from some other popular services like Meetup. So even if you’re the only ENY user in your town right now, you’ll still find some events. And it will only get better as you invite your friends. :]
Some of us are always on the hunt for a new word game. This month, one fell right into my lap. :]
Blob.IT has a bit of a different system. Each level has a time limit and fixed number of letters. Letters will highlight and you must create the hidden word from the highlighted letters.
Not just any word will do. Blob.IT has hand selected words. So it won’t be enough to come up with “tone” if the scrambled highlighted word is “note.” So you’ll need to dig deep sometimes. The time limit is for the whole bank of letters, so you don’t have long for each word. Keep going as long as you can before the time runs out.
Blob.IT has GameCenter support with both achievements and leaderboards so you can see how you stack up. You can even challenge your friends to multiplayer.
Hell birdie is lost in our world and needs your help to get back to his dark home.
Hell Birdie is definitely challenging. Its a bit similar to flappy bird but with some interested tweaks. You tap to bounce, but taps on the left of the screen bounce you left and taps on the right bounce you right. Rather than flying side to side, you’re trying to slowly advance up through obstacles. The combination or upward movement and limited movement controls, makes it an easy to game to pick up but difficult to master.
Hell Birdie is our addicting game of the month. The perfect mix of simple mechanics with challenging gameplay makes it hard to just put down. You always want to get just a little… bit… further! ;]
Each month, I really enjoy seeing what our community of readers comes up with. The apps you build are the reason we keep writing tutorials. Make sure you tell me about your next one, submit here.
If you saw an app your liked, hop to the App Store and leave a review! A good review always makes a dev’s day. And make sure you tell them you’re from raywenderlich.com; this is a community of makers.
If you’ve never made an app, this is the month! Check out our free tutorials to become an iOS star. What are you waiting for – I want to see your app next month.
The post Readers’ App Reviews – November 2016 appeared first on Ray Wenderlich.
Learn how to make a simple web app to list, create, and delete objects using Vapor, a popular server side swift framework.
The post Screencast: Server Side Swift with Vapor: Making a Web App appeared first on Ray Wenderlich.
Good news — we’ve released an update to 3D Apple Games by Tutorials, Second Edition!
This update adds several resources, icons, utility packages and 3D models to the downloadable book package. Previous editions might not have had all of these directories included in the download, so we’ve repackaged the entire collection of resources.
Here’s the list of resources we’ve added, by chapter:
This free update is available today for all 3D Apple Games by Tutorials customers.
Hat tip to all our forum members and readers who emailed in to help diagnose and correct this issue!
The post 3D Apple Games by Tutorials Update Now Available! appeared first on Ray Wenderlich.
Welcome to another installment of our Top App Dev Interview series. Each interview in this series focuses on a successful mobile app or developer and the path they took to get where they are today. Today’s special guest is Jesse Squires.
Jesse is a Swift guru, technology writer and speaker. He is best known for running the Swift Weekly Brief and is currently an iOS developer at Instagram.
When he’s not busy working at Instagram, Jesse tours the world giving talks at conferences and meetup groups, including FrenchKit, Swift Summit & Realm. Jesse also contributes to many open-source projects.
Can you please describe the process of working within Instagram, and how that compares to a similar process when you are an indie developer?
Working as an indie developer, you have to do much more than just be a developer. You’re also a product manager, beta tester, designer and everything else.
Within a company, it’s all about utilizing many people’s skills. For example, we have entire teams looking after each aspect of the build, and over 500 million monthly active users constantly using the app providing feedback. It really boils down to resources – this is key. The more resources you have the fewer hats you have to wear.
My most recent work at Instagram (that’s public) was IGListKit and more broadly incorporating the framework across the app. I have been helping Ryan Nystrom move the project forward and engage with the community on Github.
If I was to find a bug at Instagram, can you describe the process of the work? For example Elaboration, Development, Testing. How does it flow through the scrum process?
We work in a very self-driven way. There is no formal scrum process. If you find a bug, you usually just fix it right away or create a task and hand it off to whomever “owns” that area of the codebase if you aren’t familiar with it. For other bug reports, you just prioritize/triage them like you would elsewhere.
We have a similar process when adding new features it consists of ideation, design, and prototyping — with collaboration across Product, Design, and Engineering. Once we figure out what we want to build, we’ll break up the work into tasks and start building.
There’s also a strong “dog-fooding” culture at Instagram/Facebook so employees are always using the latest internal builds of the app and providing feedback.
I note that Instagram push releases almost every week if not more. What’s the process to be able to achieve this?
It’s a pretty straightforward process. We work on master
and cut a release
branch every 2 weeks. The release
branch “soaks” for 2 weeks and we cherry pick commits for bug fixes as needed, then submit to the App Store. The majority of this is automated.
I think the key is that there are no exceptions — we cut and ship every 2 weeks. If your work isn’t finished, you just wait until the next cut — but we never miss or delay a cut.
We don’t use Jenkins to manage this; we use Facebook’s CI infra. But, for all intents and purposes it works the same way as something like Jenkins, Travis-CI, etc.
For source control, we use mercurial and phabricator. If you aren’t familiar with these, the analogous tools would be git and GitHub.
What’s your development workflow like?
Our development workflow is pretty common:
You are highly active in the open source community. What benefits have you seen from doing that?
I attribute most of my success to open source. This definitely isn’t true for everyone, though. I was lucky enough to start a project that became really popular. And I was privileged enough to have time to get involved in open source in the first place. Not everyone has that opportunity.
In the beginning it was about everything I learned:
With a popular open source project, you receive a ton of feedback — positive and negative. All of it is valuable and all of it helps you grow and learn.
You are well known for curating the Swift Weekly Brief, a newsletter about the latest developments on swift.org. What made you decide to start this newsletter?
This happened by accident. I was so interested in open source Swift when it was announced, and I wrote a few blog posts on my personal site. The community seemed to enjoy and appreciate them, so I decided to move it to a dedicated site and make it “a thing”.
It takes multiple hours a week to put a newsletter together, but I usually do it in small chunks as I go so it never seems like it takes too much of my time. For the blog posts, GitHub activity and mailing lists — it’s pretty much what I’m reading and interested in already. I would be doing much of this on my own, so I figured I might as well share it with the community as I go.
It’s certainly beneficial for me personally, too. I already want to stay up-to-date with everything that’s going on with Swift, so this commitment to publish the newsletter ensures that happens.
Recently you decided to open up the Swift Weekly Brief to external contributors, with the goal of making it a community newsletter, rather than your own. What made you decide to do this, and how can people contribute?
As I mentioned, open source has benefited me in numerous ways, and I want to give others an opportunity to contribute — if they want. There’s no reason a newsletter like this can’t be run and managed like a “regular” open source project, like AFNetworking. Bringing other peoples’ experiences and perspectives to any project makes it significantly better in my experience.
On the other side, part of that is me wanting a break! :] As you may expect, some weeks I’m busier than others so it’s really nice to have some help if I don’t have time to put the newsletter together. There have also been some weeks where I’m on vacation and going “off the grid”.
Luckily, Brian Gesiak, JP Simard, and Bas Broek have all stepped up numerous times to contribute and put together entire issues. It gets other people involved, it allows me to take a week off, and the community still gets their weekly dose of Swift news.
For anyone that wants to contribute, everything you need to know is here. The entire process for publishing is open source. Just open an issue on GitHub for the week that you would like to write the newsletter and we can discuss!
What advice would you give to any bloggers out there?
When I look at successful blogs, I see programmers and writers writing about the things they know about — the things they are interested in and passionate about. Don’t write what you think people want to hear, write about what you know and what you like. If you do this, the result will be good articles that people want to read (perhaps with a bit of practice, too). So that’s step 1 — good content.
Building and growing an audience is difficult, but I think it’s best for this to evolve organically. There’s no magic formula, so don’t try to force it — you’ll likely end up disappointed.
One thing that’s effective for me is conferences. Speaking at a conference is really just an extension of my blog — a conference talk is like an “in-person” blog post. When you speak at a conference, you can tell people about your blog. If you aren’t speaking at conferences yet, you should try! Start by speaking at a local meetup, then try submitting abstracts to conferences.
What’s the best feature you love in Swift 3.0 and how do you see the future of Swift going?
The huge collection of API and syntax refinements has made a huge difference. Once you get over the painful migration, these improvements really make Swift a pleasure to use.
As far as features, pretty much everything you need to know is on GitHub. But more broadly, I think Swift is on its way to being the dominant language on Apple’s platforms. It will take years, but eventually I think Objective-C will begin declining more rapidly. The day that Apple releases a pure Swift framework — that will be the turning point. I’m not sure when that will happen, but we know that Apple isn’t shy about abandoning old technologies.
For other platforms, it’s difficult to predict. IBM is pushing for Swift on the server, and there’s now an official Sever APIs Project. Once server-side Swift matures, my bet is that it will take off. There’s so much enthusiasm for Swift on the server.
As far as Android goes, I doubt Swift will ever be a first-class language. For that to happen, Google would need to invest in it and I don’t see that happening.
You have now ported most of your open source libraries to Swift, how did you go about doing this?
I migrated to Swift 3 in two distinct steps:
I did this so anyone using my projects could stay on Swift 2.3 if needed while allowing the projects to continue moving forward. I made the decision not to maintain any older versions of Swift for any projects because I simply don’t have time.
What advice would you give to somebody who wants to start building with Swift?
I would definitely say do it! Personally, I think I write code faster and better with Swift.
However, you should be aware that large apps have some large pain points. Uber, Lyft, LinkedIn and others have written posts and given talks about this, so check those out and decide if it’s worth the trade-off.
Especially if you are starting with Swift 3, I think there’s a strong argument to use Swift despite the current shortcomings in tooling. The Swift team are aware and working hard on solving these problems.
And that concludes our Top App Dev Interview with Jesse Squires. Huge thanks to Jesse for sharing his work at Instagram, his love for Swift and finally running a successful blog.
We hope you enjoyed this inspiring interview and if you’re thinking of starting your own blog or wanting to start talking at conferences to take Jesse’s advice to heart.
If you are an app developer with a hit app or game in the top 100 in the App store, we’d love to hear from you. Please drop us a line anytime. If you have a request for any particular developer you’d like to hear from, please post your suggestion below!
The post Instagram Dev, Swift Speaker & Swift Weekly Brief: A Top Dev Interview With Jesse Squires appeared first on Ray Wenderlich.
The Swift Algorithm Club is an open source project to implement popular algorithms and data structures in Swift.
We thought it would be useful to periodically give a status update with how things are going with the project.
I’d like to officially announce our newest member of the SAC team, Vincent Ngo!
Vincent Ngo is a writer, guitarist, and developer. After graduating from Virginia Tech, he continued to work with iOS development from an array of companies like Mindsense, IBM, and now Capital One. After work hours, he occasionally writes tutorials for raywenderlich.com.
When Vincent is not at work, he enjoys coding for fun, playing video games, enjoying time with family and friends, and golfing. With hard work and dedication, he hopes to one day become a golfer good enough to participate in small tournaments.
We’re excited to have Vincent on board, and he’ll be helping out with tutorials and repo management.
Our Swift 3 migration is coming to a close. So far, 52 of the 72 algorithms have been converted to Swift 3. Migration has generally been quite straightforward – it’s just the process of:
README.md
file reflects the updated playgroundWant to help out? It’s a great way to learn about algorithms and Swift 3 at the same time. If so, check out our Github issue and sign up!
The Swift Algorithm Club is always looking for new members. Whether you’re here to learn or here to contribute, we’re happy to have you around.
To learn more about the Swift Algorithm Club, check out our introductory article. We hope to see you at the club! :]
The post Swift Algorithm Club: November Digest appeared first on Ray Wenderlich.
“Art is anything you can do well. Anything you can do with Quality.”
—Robert M. Pirsig
AsyncDisplayKit is a UI framework that was originally born from Facebook’s Paper app. It came as an answer to one of the core questions the Paper team faced: how can you keep the main thread as clear as possible?
Nowadays, many apps have a user experience that relies heavily upon continuous gestures and physics based animations. At the very least, your UI is probably dependent on some form of scroll view.
These types of user interfaces depend entirely on the main thread and are extremely sensitive to main thread stalls. A clogged main thread means dropped frames and an unpleasant user experience.
Some of the big contributors to main thread work include:
-heightForRowAtIndexPath:
or calling -sizeThatFits
on a UILabel
as well as the exponential cost of AutoLayout‘s constraint solver.
UIImage
in an image view means the image data needs to be decoded first.
UIView
).When used correctly, AsyncDisplayKit allows you to perform all measurement, layout and rendering asynchronously by default. Without any extra optimization an app can experience roughly an order of magnitude reduction in the amount of work done on the main thread.
In addition to these performance wins, modern AsyncDisplayKit offers an impressive set of developer conveniences that allow implementing complex, sophisticated interfaces with a minimum of code.
In this two part AsyncDisplayKit 2.0 tutorial, you’ll learn all the essentials to build a useful and dynamic application with ASDK. In part one, you’ll learn some big picture ideas you can use when architecting an app. In part two, you’ll learn how to build your own node subclass as well as how to use ASDK’s powerful layout engine. In order to complete this tutorial you will need Xcode 7.3 and familiarity with Objective-C.
Disclaimer: ASDK is incompatible with both Interface Builder and AutoLayout, so you won’t be using them in this tutorial. Although ASDK fully supports Swift (a distinction from ComponentKit), many of its users are still writing Objective-C. At the moment, the majority of the top 100 free apps don’t include any Swift at all (at least 6 use ASDK). For these reasons, this series will focus on Objective-C. That being said, we’ve included a Swift version of the sample project in case you hate staples.
To begin, go ahead and download the starter project.
The project uses CocoaPods to pull in AsyncDisplayKit. So, in usual CocoaPods style, go ahead and open RainforestStarter.xcworkspace but NOT RainforestStarter.xcodeproj.
Note: A network connection is required to work through this tutorial.
Build and run to see an app consisting of one UITableView
containing a list of animals. If you look at the code in AnimalTableController
you’ll see that it’s a normal UITableViewController
class you’ve probably seen plenty of times.
Note: Make sure to run the code in this tutorial on a physical device instead of in the simulator.
Scroll through the animals and notice the number of frames that are being dropped. You don’t need to fire up Instruments to be able to see that this app needs some help in the performance department.
You can fix that, through the power of AsyncDisplayKit.
ASDisplayNode
is the core class of ASDK and is, at its heart, just an MVC “view” object in the same way as a UIView
or CALayer
. The best way to think about a node is by thinking about the relationship between UIViews
and CALayers
that you should already be familiar with.
Remember that everything onscreen in an iOS app is represented via a CALayer
object. UIViews
create and own a backing CALayer
to which they add touch handling and other functionality. UIViews
themselves are not a CALayer
subclass. Instead, they wrap around a layer object, extending its functionality.
This abstraction is extended in the case of ASDisplayNode
: you can think of them as wrapping a view, just like a view wraps a layer.
What nodes bring to the table over a regular view is the fact that they can be created and configured on background queues and are concurrently rendered by default.
Luckily, the API for dealing with nodes should be incredibly familiar to anyone who’s used UIViews
or CALayers
. All the view properties you would normally use are available on the equivalent node class. You can even access the underlying view or layer itself — just as you can access the .layer
of a UIView
.
While nodes themselves provide the possibility of vast performance improvements, the real magic happens when they’re used in conjunction with one of the four container classes.
These classes include:
UIViewController
subclass that allows you to provide the node you want to be managed.
UICollectionView
and UITableView
, a subclass of which is actually maintained under the hood.ASCollectionNode
which offers great swiping performance compared to UIKit
’s UIPageViewController
.
Fair enough, but the real magic comes from the ASRangeController
each of these classes uses to influence the behavior of the contained nodes. For now, just trust me and keep that in the back of your head for later.
The first thing you’ll do is to convert the current table view into a table node. Doing this is relatively straightforward.
First, navigate to AnimalTableController.m. Add the following line below the other imports in this class:
#import <AsyncDisplayKit/AsyncDisplayKit.h> |
This imports ASDK in order to use the framework.
Then, go ahead and replace the following property declaration of tableView
:
@property (strong, nonatomic) UITableView *tableView; |
with the following tableNode
:
@property (strong, nonatomic) ASTableNode *tableNode; |
This will cause a lot of code in this class to break, but do not panic!
Seriously, don’t worry. These errors and warnings will serve as your guide in the task of converting what you currently have into what you really want.
The errors in -viewDidLoad
are, of course, to do with the fact that the tableView
doesn’t exist anymore. I’m not going to make you go through and change all the instances of tableView
to tableNode (I mean, find and replace isn’t that hard so feel free to) but if you did you’d see that:
ASTableNode
to the property.
-registerClass:forCellReuseIdentifier:
.
At this point you should just replace -viewDidLoad
with the following:
- (void)viewDidLoad { [super viewDidLoad]; [self.view addSubnode:self.tableNode]; [self applyStyle]; } |
The interesting thing to note here is that you’re calling -addSubnode:
on a UIView
. This method has been added to all UIView
s via a category, and is exactly equivalent to:
[self.view addSubview:self.tableNode.view]; |
Next, fix -viewWillLayoutSubviews
by replacing that method definition with the following:
- (void)viewWillLayoutSubviews { [super viewWillLayoutSubviews]; self.tableNode.frame = self.view.bounds; } |
All this does is replace self.tableView
with self.tableNode
to set the table’s frame.
Next, find the -applyStyle
method and replace the implementation with the following:
- (void)applyStyle { self.view.backgroundColor = [UIColor blackColor]; self.tableNode.view.separatorStyle = UITableViewCellSeparatorStyleNone; } |
The line that sets the table’s separatorStyle
is the only line that changed. Notice how the table node’s view
property is accessed in order to set the table’s separatorStyle
. ASTableNode
does not expose all the properties of UITableView
, so you have to access the table node’s underlying UITableView
instance in order to change UITableView
specific properties.
Then, add the following line at the very beginning of -initWithAnimals:
_tableNode = [[ASTableNode alloc] initWithStyle:UITableViewStylePlain]; |
and add the following at the end, before the initializer’s return statement:
[self wireDelegation]; |
This initializes AnimalTableController
with a table node and calls -wireDelegation
to wire up the table node’s delegates.
Just like UITableView
, ASTableNode
uses a data source and delegate to get information about itself. Table node’s ASTableDataSource
and ASTableDelegate
protocols are very similar to UITableViewDataSource
and UITableViewDelegate
. As a matter of fact, they define some of the exact same methods such as -tableNode:numberOfRowsInSection:
. The two sets of protocols don’t match up perfectly because ASTableNode
behaves a bit differently than UITableView
.
Find -wireDelegation
and replace tableView
with tableNode
in the implementation:
- (void)wireDelegation { self.tableNode.dataSource = self; self.tableNode.delegate = self; } |
Now, you’ll be told that AnimalTableController
doesn’t actually conform to the correct protocol. Currently, AnimalTableController
conforms to to UITableViewDataSource
and UITableViewDelegate
. In the following sections you will conform to and implement each of these protocols so that the view controller’s table node can function.
Towards the top of AnimalTableController.m, find the following DataSource
category interface declaration:
@interface AnimalTableController (DataSource)<UITableViewDataSource> @end |
and replace UITableViewDataSource
with ASTableDataSource
:
@interface AnimalTableController (DataSource)<ASTableDataSource> @end |
Now that AnimalTableController
declares conformance to ASTableDataSource
, it’s time to make it so.
Navigate toward the bottom of AnimalTableController.m and find the implementation of the DataSource
category.
First, change the UITableViewDataSource
method -tableView:numberOfRowsInSection:
to the ASTableDataSource
version by replacing it with the following.
- (NSInteger)tableNode:(ASTableNode *)tableNode numberOfRowsInSection:(NSInteger)section { return self.animals.count; } |
Next, ASTableNode
s expect their cells to be returned in a different way than a UITableView
would. To accommodate the new paradigm replace -tableView:cellForRowAtIndexPath:
with the following method:
//1 - (ASCellNodeBlock)tableNode:(ASTableView *)tableView nodeBlockForRowAtIndexPath:(NSIndexPath *)indexPath { //2 RainforestCardInfo *animal = self.animals[indexPath.row]; //3 return ^{ //4 CardNode *cardNode = [[CardNode alloc] initWithAnimal:animal]; //You'll add something extra here later... return cardNode; }; } |
Let’s review this section by section:
ASCellNode
is the ASDK equivalent to a UITableViewCell
or a UICollectionViewCell
. The more important thing to notice is that this method returns an ASCellNodeBlock
. This is because an ASTableNode
maintains all of its cells internally and by giving it a block for each index path, it can concurrently initialize all of its cells when it’s ready.
indexPath
shouldn’t be used inside the block, in case the data changes before the block is run.
ASCellNode
.
CardNode
now instead of a CardCell
.
This brings me to an important point. As you may have gathered, there is no cell reuse when using ASDK. Alright, maybe I already basically said that twice, but it’s a good thing to keep in mind. Feel free to go to the top of the class and delete
static NSString *kCellReuseIdentifier = @"CellReuseIdentifier"; |
You won’t be needing it anymore.
Maybe take a second to mull that over. You never have to worry about -prepareForReuse
again…
Towards the top of AnimalTableController.m, find the following Delegate
category interface declaration:
@interface AnimalTableController (Delegate)<UITableViewDelegate> @end |
and replace UITableViewDelegate
with ASTableDelegate
:
@interface AnimalTableController (Delegate)<ASTableDelegate> @end |
Now that AnimalTableController
declares conformance to ASTableDelegate
, it’s time to handle the implementation. Navigate towards the bottom of AnimalTableController.m and find the implementation of this Delegate
category.
As I’m sure you’re aware, with a UITableView you usually need to, at least, provide an implementation of -tableView:heightForRowAtIndexPath:
. This is because, with UIKit
, the height of each cell is calculated and returned by the table’s delegate.
ASTableDelegate
lacks -tableView:heightForRowAtIndexPath:
. In ASDK, all ASCellNode
s are responsible for determining their own size. Instead of being providing a static height, you can optionally define a minimum and maximum size for your cells. In this case, you want each cell to at least be as tall as 2/3rds of the screen.
Don’t worry about this too much right now; it’s covered in detail in part two of this series.
For now, just replace -tableView:heightForRowAtIndexPath:
with:
- (ASSizeRange)tableView:(ASTableView *)tableNode constrainedSizeForRowAtIndexPath:(NSIndexPath *)indexPath { CGFloat width = [UIScreen mainScreen].bounds.size.width; CGSize min = CGSizeMake(width, ([UIScreen mainScreen].bounds.size.height/3) * 2); CGSize max = CGSizeMake(width, INFINITY); return ASSizeRangeMake(min, max); } |
After all your hard work, go ahead and build and run to see what you have.
That is one smooth table! Once you’ve composed yourself a little, get ready to make it even better.
In most apps, the server has more data points available than the number of cells you’d want to show in your average table. This means that darn near every app you work on will have some mechanism set up to load another batch of objects from the server as the user approaches the end of the current data set.
Many times, this is handled by manually observing the content offset in the scroll view delegate method -scrollViewDidScroll:
. With ASDK, there is a more declarative way of doing things. Instead, you can describe how many pages in advance you’d like to load new content.
The first thing you’ll do, is uncomment the helper methods that have been included. Go to the end of AnimalTableController.m and uncomment the two methods in the Helpers
category. You can think of -retrieveNextPageWithCompletion:
as your networking call, while -insertNewRowsInTableNode:
is a pretty standard method for adding new elements to a table.
Next, add the following line to -viewDidLoad:
.
self.tableNode.view.leadingScreensForBatching = 1.0; // overriding default of 2.0 |
Setting leadingScreensForBatching
to 1.0
means that you want new batches to be fetched whenever the user has scrolled to the point where only 1 screenful of content is left in the table before they would reach the end.
Next, add the following method to the Delegate
category implementation:
- (BOOL)shouldBatchFetchForTableNode:(ASTableNode *)tableNode { return YES; } |
This method is used to tell the table whether or not it should keep making requests for new batches after this one. If you know you’ve reached the end of your API’s data, return NO
and no more requests will be made.
Since you really do want this table to scroll forever, just return YES to ensure new batches will always be requested.
Next, also add:
- (void)tableNode:(ASTableNode *)tableNode willBeginBatchFetchWithContext:(ASBatchContext *)context { //1 [self retrieveNextPageWithCompletion:^(NSArray *animals) { //2 [self insertNewRowsInTableNode:animals]; //3 [context completeBatchFetching:YES]; }]; } |
This method is called when the user has neared the end of the table and the table has received a YES
from -shouldBatchFetchForTableNode:
.
Let’s review this section by section:
-completeBatchFetching:
with YES
when you’re done. New batch fetching requests won’t be made until this one has been completed.Build, run, and just start swiping. Don’t stop until you don’t care to see another bird. They are infinite.
Have you ever worked on an app where you decided to load content in advance in some kind of scroll view or page view controller? Maybe you were working on a full-screen image gallery and you decided you always wanted the next few images to be loaded and waiting so your users rarely saw a placeholder.
When you do work on a system like this, you soon realize there’s a lot to think about.
And this gets quite a lot more complex when you factor in multiple dimensions of content. Do you have a page view controller with a collection view inside of each of the view controllers? Now you need to think of how you’re going to dynamically load content in both directions… Also, go ahead and tune that for each device you’re supporting. K, thanks.
Remember how I told you to to keep that ASRangeController
thing on the back burner of your mind? Well move it to the front burner!
Within each of the container classes there is a concept of the interface state for each of the contained nodes. At any given time, a node can be in any combination of:
These ranges also work on the metric of “screenfuls” and can be easily tuned using the ASRangeTuningParameters
property.
For example, you’re using an ASNetworkImageNode
to display the image in each page of the gallery. Each one will request data from the network when it enters the Preload Range and decode the image it has retrieved when it enters the Display Range.
In general, you don’t have to think too hard about these ranges if you don’t want to. The built in components, such as ASNetworkImageNode
and ASTextNode
, take full advantage of them which means you will see huge benefits by default.
In general, the leading side of the range is larger than the trailing side. When the user changes their scroll direction, the sizes of the ranges reverse as well in order to favor the content the user is actually moving toward.
You’re probably wondering how exactly these ranges work right? I’m glad you asked.
Every node in the system has an interfaceState
property which is a “bitfield” (NS_OPTION) type ASInterfaceState
. As the ASCellNode
moves through a scroll view managed by an ASRangeController
, each subnode has its interfaceState
property updated accordingly. This means that even the deepest nodes in the tree can respond to interfaceState
changes.
Luckily, it’s rarely necessary to fiddle with the bits of a node’s interfaceState
directly. More often, you’ll just want to react to a node changing to or from a certain state. That’s where the interface state callbacks come in.
In order to see a node move through the various states, it is useful to give it a name. This way, you’ll be able to watch as each node loads its data, displays its content, comes on-screen and then does the whole thing in reverse as it leaves.
Go back to -tableNode:nodeBlockForRowAtIndexPath:
, and find the comment that says:
//You'll add something extra here later... |
Right below it, add the following line to give each cell a debugName
.
cardNode.debugName = [NSString stringWithFormat:@"cell %zd", indexPath.row]; |
Now you’ll be able to track the cells’ progression through the ranges.
Navigate to CardNode_InterfaceCallbacks.m. Here you’ll find six methods you can use to track a node’s progress through the various ranges. Uncomment them, and then build and run. Make sure your console in Xcode is visible and then scroll slowly. As you do, watch as the various cells react to their changing states.
Note: In most cases, the only ASInterfaceState change method you’ll care about is -didEnterVisibleState
or -didExitVisibleState
. That said, a lot of work is going on under the hood for you. To check out what you can do by integrating with the Preload and Display states, take a look at the code in ASNetworkImageNode
. All network image nodes will automatically fetch and decode their content, as well as free up memory, without you needing to lift a finger.
In the 2.0 release, the concept of intelligently preloading content in multiple directions was introduced. Say you have a vertically scrolling table view, and at some point a cell comes onscreen that contains a horizontal collection view.
Though this collection is now technically in the visible region, you wouldn’t want to load the entire collection up front. Instead, both scroll views have their own ASRangeController
complete with separately configurable range tuning parameters.
Now that you have completed AnimalTableController
, you’re able to use it as a page in an ASPagerNode
.
The view controller you’ll use to contain this pager is already in the project so the first thing you need to do is navigate to AppDelegate.m.
Find -installRootViewController
and replace:
AnimalTableController *vc = [[AnimalTableController alloc] initWithAnimals:[RainforestCardInfo allAnimals]]; |
with:
AnimalPagerController *vc = [[AnimalPagerController alloc] init]; |
Then, go into AnimalPagerController.m and add the following lines to the initializer right before the return statement. All you need to do is create a new pager and set its dataSource
to be this view controller.
_pagerNode = [[ASPagerNode alloc] init]; _pagerNode.dataSource = self; |
The pager node is actually a subclass of an ASCollectionNode
preconfigured to be used in the same way you’d use a UIPageViewController
. The nice thing about this is that the API is actually quite a bit simpler to think about than UIPageViewController
‘s.
The next thing you have to do is to implement the pager’s data source methods. Navigate to the ASPagerDataSource
category implementation at the bottom of this file.
First, tell the pager that its number of pages is equal to the number of animal arrays, in this case, three by replacing the existing -numberOfPagesInPagerNode:
.
- (NSInteger)numberOfPagesInPagerNode:(ASPagerNode *)pagerNode { return self.animals.count; } |
Then, you need to implement -pagerNode:nodeAtIndex:
, similar to the node block data source method you implemented for the ASTableNode
earlier.
- (ASCellNode *)pagerNode:(ASPagerNode *)pagerNode nodeAtIndex:(NSInteger)index { //1 CGSize pagerNodeSize = pagerNode.bounds.size; NSArray *animals = self.animals[index]; //2 ASCellNode *node = [[ASCellNode alloc] initWithViewControllerBlock:^{ return [[AnimalTableController alloc] initWithAnimals:animals]; } didLoadBlock:nil]; return node; } |
Let’s review this section by section:
-initWithViewControllerBlock:
initializer. All you need to do is return a block that returns the table node controller you fixed up earlier and the managed view will automatically be used as the view for each page. Pretty cool if you ask me. ;]
Once you’ve added this method you’ll have a fully functioning pager whose cells are generated from the tableNodeController
you created earlier. This comes fully stocked with two dimensional preloading based on the vertical and horizontal scrolling performed by the user!
To see the completed project for this AsyncDisplayKit 2.0 tutorial, download it here. If you’re wanting to see all this in Swift, we’ve got that too.
When you’re ready, move on to part 2 of this project to learn about the powerful new layout system introduced with AsyncDisplayKit 2.0.
If you’d rather do a little more research before moving on, you can check out AsyncDisplayKit’s home page and read through some of the documentation. Scott Goodson (the original author of AsyncDisplayKit) also has a few talks you may be interested in, listed in chronological order on the AsyncDisplayKit.org Resources page.
You may also be interested in the Building Paper event. Although none of this was open sourced at that time, and a lot has changed, it’s pretty interesting to see where it all started.
Lastly, as part of the AsyncDisplayKit community’s legendary reputation for welcoming newcomers, there is a public Slack channel where anyone is invited to come and ask questions!
Hopefully you enjoyed this tutorial, let us know if you have any questions or comments by joining the forum discussion below!
The post AsyncDisplayKit 2.0 Tutorial: Getting Started appeared first on Ray Wenderlich.
A major new update to Unity – version 5.5 – came out last week.
And good news – we’ve been hard at work updating our popular book Unity Games by Tutorials for Unity 5.5, and the new version is available today!
Here are some of the things we’ve updated in this version of the book:
It took several late nights of coding, testing, rewriting, and lots and lots of caffeine in various forms, but we wanted to get you this update as fast as we could. :]
It’s a huge achievement for us, and we’re happy to share it with you. Read on to see how to get your updated copy!
As an extra-special treat to celebrate this release, author Brian Moakley has built a Web GL version of Bobblehead Wars, the twin-stick shooter game you’ll create in Section 1.
Have a go at defending yourself against waves of marauding aliens, intent on snacking on your brave space marine. Arrow keys (or WASD keys) to move, and mouse to turn and shoot:
We show you how to build this game step-by-step in the book; along with a first-person shooter, a 2D platformer, and a tower defense game – with VR support!
If you’re looking to learn how to make cross-platform games with the game engine used to make City Skylines, Hearthstone, the Long Dark, and more – this is the book for you.
This free update is available today for all Unity Games by Tutorials PDF customers, as our way of saying “thanks” for supporting the book and the site.
We hope you enjoy this version of the book, fully updated for Unity 5.5. And a big thanks to the book team that managed to turn this around in an amazingly short time!
The post Unity Games by Tutorials Updated for Unity 5.5 appeared first on Ray Wenderlich.
Today I’m happy to announce my newly updated course on raywenderlich.com: iOS Concurrency with GCD & Operations!
Concurrency is a fancy way of saying “running more than one task at the same time”. Concurrency is used quite frequently on iOS devices so you can run tasks in the background (like downloading or processing data) while you keep your user interface responsive.
GCD (or Grand Central Dispatch) and Operations (formerly called NSOperation) are the APIs you use in iOS to manage concurrent tasks (as opposed to working with threads directly).
This is a course for intermediate iOS developers who are looking to master using GCD and Operation to run concurrent tasks in their apps. It’s particularly beneficial for folks who have used GCD and Operations a bit but want to take their knowledge to a deeper level.
It’s also fully up-to-date with Swift 3, Xcode 8, and iOS 10!
Here’s an overview of what’s inside:
Video 1: Introduction. You’ll get overview of what concurrency is, the difference between GCD and Operations and when to use each, and take a tour of some classic challenges with concurrency, such as race conditions, priority inversion, and deadlock.
Video 2: Terminology. You’ll learn the terms you’ll be using in the rest of the series, such as synch vs. asynchronous, serial queue vs. concurrent queue, and you’ll learn how to dispatch your first tasks to GCD queues in a Swift playground.
Video 3: Simple Use Cases. Next you’ll take a look at some simple GCD use cases, such as running a chain of tasks, a collection of independent but similar tasks, and how to make synchronous tasks asynchronous.
Video 4: Dispatch Groups. Learn the power of GCD dispatch groups, which allow you to respond to the completion of a collection of GCD tasks.
Video 5: Operations. Next you’ll go to a higher level of abstraction and use how to use Operation, is a class that allows you to wrap up a unit of work, into a package you can execute at some time in the future. Along the way, you’ll create a cool tilt-shift image filtering operation!
Video 6: OperationQueue. Learn how to use OperationQueue to gain more power and control over the scheduling and execution of Operations.
Video 7: AsyncOperation. Learn how to use an Operation to wrap an asynchronous function such as a network call into a reusable object.
Video 8: Dependencies. Learn how to create dependencies between different Operations, and how to pass data from one Operation to another.
Video 9: Cancelling Tasks. Learn how to cancel an Operation while it’s running.
Video 10: Operations in Practice. Pull together all the concurrency knowledge you’ve learned so far in this series to improve the scrolling performance of a table view in a realistic app.
Video 11: Concurrency Solutions. Learn how to create thread-safe objects to prevent inconsistent state, and how to avoid other concurrency problems.
Want to check out the course? You can watch the introduction for free!
The rest of the course is for raywenderlich.com subscribers only. Here’s how you can get access:
We hope you enjoy, and stay tuned for more new Swift 3 courses and updates to come! :]
The post Updated Course: iOS Concurrency with GCD & Operations appeared first on Ray Wenderlich.
The macOS team has over 20 free macOS tutorials, and the list continues to expand.
We’re looking to grow the team so that we can provide the macOS development community with top notch tutorials. This is also an opportunity that could lead to involvement in a new macOS book.
Joining our team is a great way to learn and improve – not to mention, getting paid for it!
If this sounds interesting, keep reading to find out what’s involved and how to apply.
Here are the top 5 reasons to join the macOS team:
Here are the requirements:
To apply, send me an e-mail. Be sure to include the following information:
If your application looks promising, we’ll send you a tryout to gauge your writing and/or editing skills.
If you pass the tryout, you’re in!
If this opportunity interests you, go on and send me an e-mail! I look forward to creating some great tutorials with you. :]
The post Open Call for Authors on the macOS Team appeared first on Ray Wenderlich.
Update Note: This tutorial has been updated to Swift 3.0 by Niv Yahel. The original tutorial was written by Erik Kerber.
Imagine you’re developing a racing game. You can drive a car, ride a motorcycle, or even fly a plane. A common approach to creating this type of application is by using object oriented design, encapsulating all of the logic inside of an object that gets inherited to all of those that share similarity.
This design approach works, but does come with some drawbacks. For example, if you add the ability to create machines that also require gas, birds that fly in the background, or anything else that may want to share game logic, there isn’t a good way to separate the functional components of vehicles into something reusable.
This scenario is where protocols really shine.
Swift has always let you specify interface guarantees on existing class
, struct
and enum
types using protocols. This lets you interact with them generically. Swift 2 introduced a way to extend protocols and provide default implementations. Finally, Swift 3 improves operator conformance and uses these improvements for the new numeric protocols in the standard library.
Protocol are extremely powerful and can transform the way you write code. In this tutorial, you’ll explore the ways you can create and use protocols, as well as use protocol-oriented programming patterns to make your code more extensible.
You’ll also see how the Swift team was able to use protocol extensions to improve the Swift standard library itself, and how it impacts the code you write.
Begin by creating a new playground. In Xcode, select File\New\Playground… and name the playground SwiftProtocols. You can select any platform, since all the code in this tutorial is platform-agnostic. Click Next to choose where you would like to save it, and finally click Create.
Once your new playground is open, add the following code to it:
protocol Bird { var name: String { get } var canFly: Bool { get } } protocol Flyable { var airspeedVelocity: Double { get } } |
This defines a simple protocol Bird
with properties name
and canFly
, as well as a Flyable
protocol which defines airspeedVelocity
.
In a pre-protocol world, you might have started with Flyable
as a base class and then relied on object inheritance to define Bird
as well as other things that fly, such as airplanes. Note that here, everything is starting out as a protocol! This allows you to encapsulate the functional concept in a way that doesn’t require a base class.
You’ll see how this makes the entire system more flexible when you start to define actual types next.
Add the following struct
definition to the bottom of the playground:
struct FlappyBird: Bird, Flyable { let name: String let flappyAmplitude: Double let flappyFrequency: Double let canFly = true var airspeedVelocity: Double { return 3 * flappyFrequency * flappyAmplitude } } |
This defines a new struct FlappyBird
, which conforms to both the Bird
and Flyable
protocols. Its airspeedVelocity
is calculated as a function of flappyFrequency
and flappyAmplitude
. Being flappy, it returns true
for canFly
. :]
Next, add the following two struct definitions to the bottom of the playground:
struct Penguin: Bird { let name: String let canFly = false } struct SwiftBird: Bird, Flyable { var name: String { return "Swift \(version)" } let version: Double let canFly = true // Swift is FASTER every version! var airspeedVelocity: Double { return version * 1000.0 } } |
A Penguin
is a Bird
, but cannot fly. A-ha — it’s a good thing you didn’t take the inheritance approach, and make all birds flyable after all! Using protocols allows you to define functional components and have any relevant object conform to them.
Already you can see some redundancies. Every type of Bird
has to declare whether it canFly
or not, even though there’s already a notion of Flyable
in your system.
With protocol extensions, you can define default behavior for a protocol. Add the following just below the Bird
protocol definition:
extension Bird { // Flyable birds can fly! var canFly: Bool { return self is Flyable } } |
This defines an extension on Bird
that sets the default behavior for canFly
to return true
whenever the type is also Flyable
. In other words, any Flyable
bird no longer needs to explicitly declare so!
Delete the let canFly = ...
from FlappyBird
, SwiftBird
and Penguin
struct declarations. You’ll see that the playground successfully builds since the protocol extension now handles that requirement for you.
Protocol extensions and default implementations may seem similar to using a base class or even abstract classes in other languages, but they offer a few key advantages in Swift:
In other words, protocol extensions provide the ability to define default behavior for value types and not just classes.
You’ve already seen this in action with a struct. Next, add the following enum definition to the end of the playground:
enum UnladenSwallow: Bird, Flyable { case african case european case unknown var name: String { switch self { case .african: return "African" case .european: return "European" case .unknown: return "What do you mean? African or European?" } } var airspeedVelocity: Double { switch self { case .african: return 10.0 case .european: return 9.9 case .unknown: fatalError("You are thrown from the bridge of death!") } } } |
As with any other value type, all you need to do is define the correct properties so UnladenSwallow
conforms to the two protocols. Because it conforms to both Bird
and Flyable
. It also gets the default implementation for canFly
!
Did you really think this tutorial involving airspeedVelocity
wouldn’t include a Monty Python reference? :]
Your UnladenSwallow
type automatically got an implementation for canFly
by virtue of conforming to the Bird
protocol. However, you really want UnladenSwallow.unknown
to return false
for canFly
. Is it possible to override the default implementation? Yes, it is. Add this to the end of your playground.
extension UnladenSwallow { var canFly: Bool { return self != .unknown } } |
Now only .african
and .european
will return true
for canFly
. Test it out by adding the following to the end of your playground:
UnladenSwallow.unknown.canFly // false UnladenSwallow.african.canFly // true Penguin(name: "King Penguin").canFly // false |
In this way, it is possible to override properties and methods much like you can with virtual methods in object oriented programming.
You can utilize protocols from the standard library and also define default behaviors.
Modify the Bird
protocol declaration to conform to the CustomStringConvertible
protocol:
protocol Bird: CustomStringConvertible { |
Conforming to CustomStringConvertible
means your type needs to have a description
property so it acts like a String. Does that mean you now have to add this property to every current and future Bird
type?
Of course, there’s an easier way with protocol extensions. Add the code underneath the Bird
definition:
extension CustomStringConvertible where Self: Bird { var description: String { return canFly ? "I can fly" : "Guess I’ll just sit here :[" } } |
This extension will make the canFly
property represent each Bird
type’s description
value.
To try it out, add the following to the bottom of the playground:
UnladenSwallow.african |
You should see “I can fly!”
appear in the assistant editor. But more notably, you just extended your own protocol!
You’ve seen how protocol extensions are a great way to customize and extend the capabilities. What may surprise you is how the Swift team was able to use protocols to improve the way the Swift standard library is written as well.
Add the following code to the end of your playground:
let numbers = [10,20,30,40,50,60] let slice = numbers[1...3] let reversedSlice = slice.reversed() let answer = reversedSlice.map { $0 * 10 } print(answer) |
This should look pretty straightforward, and you might even be able to guess the answer that is printed. What might be surprising are the types involved. slice
, for example, is not an Array
of integers but an ArraySlice<Int>
. This special wrapper type acts as a view into the original array and avoids costly memory allocations that can quickly add up. Similarly, reversedSlice
is actually a ReversedRandomAccessCollection<ArraySlice<Int>>
which is again just a wrapper type view into the original array.
Fortunately, the geniuses developing the standard library defined the map
method as an extension to the Sequence
protocol and all of the collection wrappers (of which there are dozens) to conform to this protocol. This makes it possible to call map on Array
just as easily as it is ReversedRandomAccessCollection
and not notice the difference. You will borrow this important design pattern shortly.
So far you defined several Bird
conforming types. Now add something totally different to the end of your playground.
class Motorcycle { init(name: String) { self.name = name speed = 200 } var name: String var speed: Double } |
This class that has nothing to do with birds or flying things you have defined so far. But you want to race motorcycles as well as penguins. Time to bring all of the pieces together.
It is time to unify all of these disparate types with a common protocol for racing. You can do this with out even going back and touching the original model definitions. The fancy term for this is retroactive modeling. Just add the following to your playground:
protocol Racer { var speed: Double { get } // speed is the only thing racers care about } extension FlappyBird: Racer { var speed: Double { return airspeedVelocity } } extension SwiftBird: Racer { var speed: Double { return airspeedVelocity } } extension Penguin: Racer { var speed: Double { return 42 // full waddle speed } } extension UnladenSwallow: Racer { var speed: Double { return canFly ? airspeedVelocity : 0 } } extension Motorcycle: Racer {} let racers: [Racer] = [UnladenSwallow.african, UnladenSwallow.european, UnladenSwallow.unknown, Penguin(name: "King Penguin"), SwiftBird(version: 3.0), FlappyBird(name: "Felipe", flappyAmplitude: 3.0, flappyFrequency: 20.0), Motorcycle(name: "Giacomo") ] |
In this code, you first define the protocol Racer
and then you make all of the different types conform. Some types, such as Motorcycle
conform trivially. Others, such as UnladenSwallow
need a bit more logic. In the end, you have a bunch of conforming Racer
types.
With all of the types conforming, you then create an array of racers.
Now it’s time to write a function that determines the top speed of the racers. Add this to the end of your playground:
func topSpeed(of racers: [Racer]) -> Double { return racers.max(by: { $0.speed < $1.speed })?.speed ?? 0 } topSpeed(of: racers) // 3000 |
This function uses the standard library max
to find the racer with the largest speed and return that. You return 0 if the user passes in an empty array in for racers
.
Looks like it’s Swift 3 FTW. As if it were ever in doubt! :]
There is a problem though. Suppose you want to find the top speed for a subset (slice) of racers
. Adding this to your playground you get an error:
topSpeed(of: racers[1...3]) // ERROR |
Swift complains it cannot subscript a value of type [Racer]
with an index of type CountableClosedRange
. Slicing returns one of those wrapper types.
The solution is to write your code against a common protocol instead of the concrete Array
. Add the following before the topSpeed(of:)
call.
func topSpeed<RacerType: Sequence>(of racers: RacerType) -> Double where RacerType.Iterator.Element == Racer { return racers.max(by: { $0.speed < $1.speed })?.speed ?? 0 } |
This might look a bit scary, so let’s break it down. RacerType
is the generic type for this function and it can be any type that conforms to the Swift standard library’s Sequence
protocol. The where
clause specifies that the element type of the sequence must conform to your Racer
protocol. All Sequence
types have an associated type named Iterator
that can loop through types of Element
. The actual method body is mostly the same as before.
This method works for any Sequence
type including array slices.
topSpeed(of: racers[1...3]) // 42 |
You can do even a little better. Borrowing from the standard library play book, you can extend Sequence
type itself so that topSpeed()
is readily discoverable. Add the following to the end of your playground:
extension Sequence where Iterator.Element == Racer { func topSpeed() -> Double { return self.max(by: { $0.speed < $1.speed })?.speed ?? 0 } } racers.topSpeed() // 3000 racers[1...3].topSpeed() // 42 |
Now you have a method that is easily discoverable but only applies (and autocompletes) when you are dealing with sequences of racers.
One Swift 3 improvement to protocols is how you create operator requirements.
Add the following to the bottom of the playground:
protocol Score { var value: Int { get } } struct RacingScore: Score { let value: Int } |
Having a Score
protocol means that you can write code that treats all scores the same way. However, by having different concrete types such as RacingScore
you are sure not to mix up these scores with style scores or cuteness scores. Thanks compiler!
You really want scores to be comparable so you can tell who has the high score. Before Swift 3, you needed to add global operator functions to conform to these protocols. Now you can define these static method that is part of the model. Do so now by replacing the definition of Score
and RacingScore
with the following:
protocol Score: Equatable, Comparable { var value: Int { get } } struct RacingScore: Score { let value: Int static func ==(lhs: RacingScore, rhs: RacingScore) -> Bool { return lhs.value == rhs.value } static func <(lhs: RacingScore, rhs: RacingScore) -> Bool { return lhs.value < rhs.value } } |
You just encapsulated all of the logic for RacingScore
in one place. Now you can compare scores, and, with the magic of protocol extension default implementations, even use operators such as greater-than-or-equal-to that you never explicitly defined.
RacingScore(value: 150) >= RacingScore(value: 130) // true |
You can download the complete playground with all the code in this tutorial
here.
You’ve seen the power of protocol-oriented programming by creating your own simple protocols and extending them using protocol extensions. With default implementations, you can give existing protocols common and automatic behavior, much like a base class but better since it can apply to structs and enums too.
In addition, protocol extensions can not only be used to extend your own protocols, but can extend and provide default behavior to protocols in the Swift standard library, Cocoa, Cocoa Touch, or any third party library.
To continue learning more about protocols, you should read the official Apple documentation.
You can view an excellent WWDC session on Protocol Oriented Programming on Apple’s developer portal for a more in-depth look into the theory behind it all.
The rationale for operator conformance can be found on the Swift evolution proposal. You might also want to learn more about Swift collection protocols and learn how to build your own.
Finally, as with any “new” programming paradigm, it is easy to get overly exuberant and use it for all the things. This interesting blog post by Chris Eidhof reminds us that we should beware of silver bullet solutions and using protocols everywhere “just because”.
Have any questions? Let us know in the forum discussion below!
The post Introducing Protocol-Oriented Programming in Swift 3 appeared first on Ray Wenderlich.
Learn how to validate data posted by users - like names or email addresses - using Vapor's built in validators.
The post Screencast: Server Side Swift with Vapor: Basic Validation appeared first on Ray Wenderlich.
In this video, you'll learn how to create methods in C# as well as learn about getters and setters.
The post Screencast: Beginning C# Part 19: Methods appeared first on Ray Wenderlich.
At RWDevCon, most of the time is spent on hands-on tutorials, where you code along with the instructor. This is because we believe the best way to learn a subject is through hands-on experience.
But we’ve found that after a long day’s work on tutorials, attendees are ready for a break!
That’s why in the afternoon, we switch gears to inspiration talks. These are 18-minute non-technical talks, designed to fill you with new ideas and energy.
We are looking for a few select people from the community to join us in this track and share some of their incredible experience and stories.
If you are a great speaker with a powerful message to share, please contact us with the following info:
Inspiration speakers get a free ticket, 4-nights at the conference hotel, and reimbursement for their flight.
Please send your application by next Friday, Dec 16. Please understand that although we wish we could invite everyone kind enough to volunteer for this, slots are limited.
We can’t wait to hear from you! :]
The post RWDevCon 2017: Call for Inspiration Speakers appeared first on Ray Wenderlich.