Discover how to find retain cycles and memory leaks using Xcode 8's new memory graph debugger.
The post iOS 10 Screencast: Memory Graph Debugger appeared first on Ray Wenderlich.
Discover how to find retain cycles and memory leaks using Xcode 8's new memory graph debugger.
The post iOS 10 Screencast: Memory Graph Debugger appeared first on Ray Wenderlich.
If you’ve never used Unity or if you are still really green, read this article: Introduction to Unity: Getting Started
Previous Video: Arrays
The post Screencast: Beginning C# Part 6: Section Review – Control Flow appeared first on Ray Wenderlich.
In this how to make a game like Monster Island tutorial, you will level up your knowledge of the SpriteKit physics system by creating a game named ZombieCat. Your goal as a zombie, is to defeat evil cats by throwing beakers of zombie goo at them—turning them into zombie cats. To do that, you’ll implement one of the major UI features of Monster Island: A large power meter showing the strength and direction of your throw.
Zombies AND cats—what could be better!
In case you’ve never played it, Monster Island is a fun physics-based puzzle game by Miniclip in which you defeat monsters by flinging bombs at them.
If you haven’t used Apple’s SpriteKit framework before, check out Sprite Kit Swift 2 Tutorial for Beginners. This tutorial assumes you’re familiar with actions, the physics system and the scene editor.
Let’s dive in!
This project uses Swift 3 and requires, at a minimum, Xcode beta 4. Once you have that, go ahead and download a copy of the starter project, and look through it.
Open GameScene.sks to see the basic elements of a physics puzzle level already set up.
The walls, cats and obstacles all have physics bodies defined for them, as well as their Category Mask values set in the Attributes Inspector.
At this point, all you can do in the game is watch the cats quietly plotting to take over the world. Scary, right? You need a way to fight back. That way, of course, is zombie goo. :]
In GameScene.swift, add these properties just before didMove(to:)
var pinBeakerToZombieArm: SKPhysicsJointFixed? var beakerReady = false |
Add a method to create the node for your beaker of goo.
func newProjectile () { let beaker = SKSpriteNode(imageNamed: "beaker") beaker.name = "beaker" beaker.zPosition = 5 beaker.position = CGPoint(x: 120, y: 625) let beakerBody = SKPhysicsBody(rectangleOf: CGSize(width: 40, height: 40)) beakerBody.mass = 1.0 beakerBody.categoryBitMask = PhysicsType.beaker beakerBody.collisionBitMask = PhysicsType.wall | PhysicsType.cat beaker.physicsBody = beakerBody addChild(beaker) if let armBody = childNode(withName: "player")?.childNode(withName: "arm")?.physicsBody { pinBeakerToZombieArm = SKPhysicsJointFixed.joint(withBodyA: armBody, bodyB: beakerBody, anchor: CGPoint.zero) physicsWorld.add(pinBeakerToZombieArm!) beakerReady = true } } |
Add the following line to didMove(to:)
:
newProjectile() |
This is a fairly standard example of creating a new SKSpriteNode
, setting its properties, adding a SKPhysicsBody
and adding it to the scene. Two properties you might not be familiar with are categoryBitMask
and collisionBitMask
. These mask values are how the physics system identifies which sprites interact with each other.
Each physics body has a categoryBitMask
property you set to one of the values defined in the PhysicsType
struct located at the top of GameScene.swift. Sprites will only collide with or bounce off objects where the categoryBitMask
matches the other object’s collisionBitMask
.
The beaker is attached to the zombie with a SKPhysicsJoint
. A fixed joint keeps two physics bodies in the same relative position. In this case, the beaker will move with the arm body, until the joint is removed.
Note: The physics simulation uses two types of body interactions, collision and contact.
Collision: In this type of interaction, the two sprites bounce off of each other like a ball off a wall. The physics simulation automatically takes care of the collision calculation, so you don’t have to deal with angles or differences in mass.
Contact: This interaction alerts you when two objects touch or overlap. The moving object will continue to move through the other object, unless you change its motion manually. You’d use contacts to be informed when an event happens, such as a ball crossing a goal line, without changing the object’s motion.
For more details about these interactions, or bit masks in general, the Making Contact section of How To Create a Breakout Game with Sprite Kit and Swift gives a great explanation.
Build and run to see your new beaker of zombie goo. You’re almost ready to attack those cats!
Now that you’ve got the beaker in the game, you need to be able to use it as a weapon.
To throw the beaker, all you have to do is remove the SKSKPhysicsJointFixed
and give it an impulse in the physics system. An impulse is like a kick, represented by a vector that has both an X and a Y component.
Add the following methods to GameScene.swift:
func tossBeaker(strength: CGVector) { if beakerReady == true { if let beaker = childNode(withName: "beaker") { if let arm = childNode(withName: "player")?.childNode(withName: "arm") { let toss = SKAction.run() { self.physicsWorld.remove(self.pinBeakerToZombieArm!) beaker.physicsBody?.applyImpulse(strength) beaker.physicsBody?.applyAngularImpulse(0.1125) self.beakerReady = false } let followTrough = SKAction.rotate(byAngle: -6*3.14, duration: 2.0) arm.run(SKAction.sequence([toss, followTrough])) } // explosion added later } } } override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { tossBeaker(strength: CGVector(dx: 1400, dy: 1150)) } |
When using the physics system, you change the motion of a sprite through its physics body, not the sprite itself. SKPhysicsBody
has two methods you could use to throw the beaker, applyForce
and applyImpulse
. Since you want the change to be instantaneous, use applyImpulse
. Usually, thrown objects spin while in the air, and applyAngularImpulse
simulates that. You can adjust the angular impulse to get the desired amount of spin, or even change its direction by using a negative number.
Don’t forget to remove the SKPhysicsJoint
, or the beaker will stay attached to the zombie hand.
Build and run the game. When you tap on the screen, the beaker should fly around. Notice how the beaker bounces off of the walls and cats.
But despite all that spinning and bouncing, there’s no explosion. For that, you need the explosion effect.
To display the explosion image, you’ll need a SKSpriteNode
. You may be wondering where the best place is to position the explosion. There are several answers, but one of the simplest is to add it as a child of the beaker.
Add the following to the end of newProjectile()
:
let cloud = SKSpriteNode(imageNamed: "regularExplosion00") cloud.name = "cloud" cloud.setScale(0) cloud.zPosition = 1 beaker.addChild(cloud) |
This creates a new sprite and adds it as a child node of the beaker, meaning the two will move and rotate together. You set the scale to zero to hide the sprite, since the explosion happens later. Setting the zPosition
ensures the cloud appears on top of the beaker instead of underneath.
To see the explosion, add the following to tossBeaker(strength:)
replacing the explosion added later
comment:
if let cloud = beaker.childNode(withName: "cloud") { // 1 let fuse = SKAction.wait(forDuration: 4.0) let expandCloud = SKAction.scale(to: 3.5, duration: 0.25) let contractCloud = SKAction.scale(to: 0, duration: 0.25) // 2 let removeBeaker = SKAction.run() { beaker.removeFromParent() } let boom = SKAction.sequence([fuse, expandCloud, contractCloud, removeBeaker]) // 3 let respawnBeakerDelay = SKAction.wait(forDuration: 1.0) let respawnBeaker = SKAction.run() { self.newProjectile() } let reload = SKAction.sequence([respawnBeakerDelay, respawnBeaker]) // 4 cloud.run(boom) { self.run(reload) } } |
Taking this step-by-step:
removeFromParent
action, but these actions run on the cloud node. You need to remove the beaker node instead, so you define a runBlock
action. Then you combine the actions in a sequence.cloud
node. While you could have the reload actions in the main sequence action, the completion block will come in handy later.Build and run to see how dangerous zombie goo can be.
You’ve got an explosion now, but it’s pretty basic. To really knock those kitties off their paws, animate the explosion during the expansion and contraction step. The starter project includes images regularExplosion00.png through regularExplosion08.png, and here you’ll add an action that cycles through them.
First, add a property to the top of GameScene.swift:
var explosionTextures = [SKTexture]() |
Now add the following to the end of didMove(to:)
:
for i in 0...8 { explosionTextures.append(SKTexture(imageNamed: "regularExplosion0\(i)")) } |
This builds the array of SKTexture
s for the animation.
Next, find section 2 of the tossBeaker(strength:)
method and replace the line let boom = SKAction.sequence...
with the following:
let animate = SKAction.animate(with: explosionTextures, timePerFrame: 0.056) let expandContractCloud = SKAction.sequence([expandCloud, contractCloud]) let animateCloud = SKAction.group([animate, expandContractCloud]) let boom = SKAction.sequence([fuse, animateCloud, removeBeaker]) |
You calculate the value for timePerFrame
by dividing the total duration for the expand and contract animation (0.5 s) by the number of frames (9).
Build and run to see your upgraded explosion.
When the beaker explodes, you’ll want to know which cat sprites are within range of the explosion. One way to do this is to create a separate SKPhysicsBody
for the explosion and use the contact system to be notified when the explosion touches a cat. However, since each SKSpriteNode
can only have one physics body attached to it, you’ll create an invisible node for the explosion radius and add it to the beaker as a child.
Add the following to the end of newProjectile()
:
let explosionRadius = SKSpriteNode(color: UIColor.clear, size: CGSize(width: 200, height: 200)) explosionRadius.name = "explosionRadius" let explosionRadiusBody = SKPhysicsBody(circleOfRadius: 200) explosionRadiusBody.mass = 0.01 explosionRadiusBody.pinned = true explosionRadiusBody.categoryBitMask = PhysicsType.explosionRadius explosionRadiusBody.collisionBitMask = PhysicsType.none explosionRadiusBody.contactTestBitMask = PhysicsType.cat explosionRadius.physicsBody = explosionRadiusBody beaker.addChild(explosionRadius) |
After you create the explosion physics body, you set its mass
property to 0.01 to minimize the amount of mass the explosionRadiusBody
will add to the beakerBody
.
Notice how explosionRadiusBody
has none
for its collisionBitMask
. You don’t want this extra “bubble” around the beaker to collide with anything, because then it would look like the beaker bounced before it hit the object. You do want the contactTestBitMask
set to cat
, so that the system will recognize those sprites overlapping as a contact.
Modify the if let cloud
statement in tossBeaker(strength:)
to look like:
if let cloud = beaker.childNode(withName: "cloud"), let explosionRadius = beaker.childNode(withName: "explosionRadius") { |
Next, add the following to section 2 in tossBeaker(strength:)
, just after the let animate...
line:
let greenColor = SKColor(red: 57.0/255.0, green: 250.0/255.0, blue: 146.0/255.0, alpha: 1.0) let turnGreen = SKAction.colorize(with: greenColor, colorBlendFactor: 0.7, duration: 0.3) let zombifyContactedCat = SKAction.run() { if let physicsBody = explosionRadius.physicsBody { for contactedBody in physicsBody.allContactedBodies() { if (physicsBody.contactTestBitMask & contactedBody.categoryBitMask) != 0 || (contactedBody.contactTestBitMask & physicsBody.categoryBitMask) != 0 { contactedBody.node?.run(turnGreen) contactedBody.categoryBitMask = PhysicsType.zombieCat } } } } |
This action finds all the physics bodies in contact with the explosionRadius
body and uses the colorize(with:colorBlendFactor:duration:)
action to turn the new zombie cat a putrid green color.
The if
statement with contactTestBitMask
and categoryBitMask
is there because the allContactedBodies()
method returns all the SKPhysicsBody
objects touching the given body, instead of only the ones that match the contactTestBitMask
. This statement filters out the extra physics bodies.
Change the expandContractCloud
action to this:
let expandContractCloud = SKAction.sequence([expandCloud, zombifyContactedCat, contractCloud]) |
Update the collisionBitMask
for the beaker in newProjectile()
to:
beakerBody.collisionBitMask = PhysicsType.wall | PhysicsType.cat | PhysicsType.zombieCat |
Without this change, the beaker would pass through the zombie cats—which would make sense if the zombie goo turned the cats into ghosts, but sadly, it does not.
Build and run. The beaker should explode next to the cat on the right, turning him into a zombie cat. Finally!
Now that the cats know what’s coming, it would be great to show how afraid they are of your beaker of goo. Since you already have a SKPhysicsBody
object to tell when the beaker is close to a cat, you can have the contact system notify you when this body touches a cat node. The system does this by calling delegate methods in your class. You’ll set that up next.
First, since you want to change the image texture for the cat sprites when contact starts, then revert it when it ends, it makes sense to store these textures in a property for easy access. Add these properties to the property section at the top of GameScene.swift:
let sleepyTexture = SKTexture(imageNamed: "cat_sleepy") let scaredTexture = SKTexture(imageNamed: "cat_awake") |
To keep your code nice and organized, add a class extension at the bottom of GameScene.swift to conform to the SKPhysicsContactDelegate
protocol:
// MARK: - SKPhysicsContactDelegate extension GameScene: SKPhysicsContactDelegate { func didBegin(_ contact: SKPhysicsContact) { if (contact.bodyA.categoryBitMask == PhysicsType.cat) { if let catNode = contact.bodyA.node as? SKSpriteNode { catNode.texture = scaredTexture } } if (contact.bodyB.categoryBitMask == PhysicsType.cat) { if let catNode = contact.bodyB.node as? SKSpriteNode { catNode.texture = scaredTexture } } } func didEnd(_ contact: SKPhysicsContact) { if (contact.bodyA.categoryBitMask == PhysicsType.cat) { if let catNode = contact.bodyA.node as? SKSpriteNode { catNode.texture = sleepyTexture } } if (contact.bodyB.categoryBitMask == PhysicsType.cat) { if let catNode = contact.bodyB.node as? SKSpriteNode { catNode.texture = sleepyTexture } } } } |
Both methods are almost identical, except that the first changes the texture to the afraid cat, and the second sets it back to the sleepy cat. When this method is called, it is passed a SKPhysicsContact
object, which contains the two bodies that generated the contact as bodyA
and bodyB
. One quirk of this system: there is no guarantee which body will be A and which will be B, so it’s helpful to test both. If the categoryBitMask
of that body matches the value for cat
, you can reassign the texture of that body.
Next, add the following line to the end of didMove(to:)
:
physicsWorld.contactDelegate = self |
This assigns the GameScene
class as the delegate for the physics contact system. The system will call didBegin(_:)
and didEnd(_:)
on the delegate each time the appropriate physics bodies touch or stop touching. The values of categoryBitMask
and contactTestBitMask
determine which bodies trigger these method calls.
You’ll need to switch from the afraid cat texture back to sleepy cat if the cat turns into a zombie (zombies aren’t afraid anymore, obviously). Update the zombifyContactedCat
action in tossBeaker(strength:)
to this:
let zombifyContactedCat = SKAction.run() { if let physicsBody = explosionRadius.physicsBody { for contactedBody in physicsBody.allContactedBodies() { if (physicsBody.contactTestBitMask & contactedBody.categoryBitMask) != 0 || (contactedBody.contactTestBitMask & physicsBody.categoryBitMask) != 0 { if let catNode = contactedBody.node as? SKSpriteNode { catNode.texture = self.sleepyTexture // set texture to sleepy cat } contactedBody.node?.run(turnGreen) contactedBody.categoryBitMask = PhysicsType.zombieCat } } } } |
It’s a simple addition, but having the enemies in a game react to their surroundings, especially their impending doom, can add a lot of fun and anticipation.
Build and run to see the effect.
The beaker throw is looking great, but you need to get that pesky remaining cat. It’s time to add the power meter so you can adjust the strength and angle of your throw.
In GameScene.sks, drag a Color Sprite from the object library into your scene for the base arrow. Set the name
, texture
, position
and anchor point
attributes to the following values:
Next, drag a second Color Sprite into the scene for the power indicator, and set its name
, parent
, texture
, position
, anchor point
, and zPosition
attributes:
The arrow tells you the direction of the thrown beaker, and the size of the green bar is the strength of the throw. Making the arrow the parent of the green bar means that the two will rotate together, and the anchor point
setting makes sure that the arrow rotates around its end, instead of the center of the sprite.
Build and run to see how it looks.
Tracking touches as you drag your finger across the screen doesn’t have to be complicated. Instead of calculating based on touch directly, you can add a UIGestureRecognizer
to do the heavy lifting.
In this case, a UIPanGestureRecognizer
is perfect, since it measures how far your finger has moved from the starting point in either direction.
Add these properties to GameScene.swift:
var previousThrowPower = 100.0 var previousThrowAngle = 0.0 var currentPower = 100.0 var currentAngle = 0.0 var powerMeterNode: SKSpriteNode? = nil var powerMeterFilledNode: SKSpriteNode? = nil |
Here you keep track of the current and previous values for power and angle, along with references to the power meter nodes so they can move.
The way UIPanGestureRecognizer
works is that you provide an update method for it to call. Add this to the end of GameScene.swift just after touchesBegan(_:with:)
:
func handlePan(recognizer:UIPanGestureRecognizer) { if recognizer.state == UIGestureRecognizerState.began { // do any initialization here } if recognizer.state == UIGestureRecognizerState.changed { // the position of the drag has moved let viewLocation = recognizer.translation(in: self.view) print("x: \(viewLocation.x) y: \(viewLocation.y)") } if recognizer.state == UIGestureRecognizerState.ended { // finish up tossBeaker(strength: CGVector(dx: 1600, dy: 1100)) } } |
Shortly after you start sliding your finger on the screen, the UIPanGestureRecognizer
recognizes this as a pan and calls your update method, with the UIPanGestureRecognizer
as a parameter. The state
of the recognizer tells you if the pan has just started, is moving, or has just ended, giving you a chance to respond appropriately in the update method.
Scroll up to didMove(to:)
and add the following before the end of the method:
powerMeterNode = childNode(withName: "powerMeter") as? SKSpriteNode powerMeterFilledNode = powerMeterNode?.childNode(withName: "powerMeterFilled") as? SKSpriteNode let panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePan)) view.addGestureRecognizer(panRecognizer) |
Here you get the power meter nodes from GameScene.sks, then add a UIPanGestureRecognizer
to the scene.
Finally, delete the touchesBegan(_:)
method.
Build and run the app. As you scroll your finger across the screen, look at Xcode’s console window, which will show you the current displacement, meaning the distance from your initial touch to your finger’s current position.
Take a look at the sign of the numbers in each direction. Notice that dragging your finger upwards makes the y values negative, not positive. This is because the UIPanGestureRecognizer
works in the UIKit coordinate system, where the origin is in the upper left, instead of the SpriteKit coordinate system, where the origin is the lower left.
The power meter isn’t moving, however, so you’ll tackle that next.
A meter that doesn’t move isn’t very helpful, so here you’ll update it to reflect a user’s touch.
For this game, the x component of the translation will change the power from 0% to 100%, while the y component will change the angle from 0° (horizontal) to 90° (vertical). To achieve this, add a method to do the calculations:
func updatePowerMeter(translation: CGPoint) { // 1 let changeInPower = translation.x let changeInAngle = translation.y // 2 let powerScale = 2.0 let angleScale = -150.0 // 3 var power = Float(previousThrowPower) + Float(changeInPower) / Float(powerScale) var angle = Float(previousThrowAngle) + Float(changeInAngle) / Float(angleScale) // 4 power = min(power, 100) power = max(power, 0) angle = min(angle, Float(M_PI_2)) angle = max(angle, 0) // 5 powerMeterFilledNode?.xScale = CGFloat(power/100.0) powerMeterNode?.zRotation = CGFloat(angle) // 6 currentPower = Double(power) currentAngle = Double(angle) } |
Here’s a summary of what’s happening:
Each time the UIPanGestureRecognizer
updates, you compute the new power from 0 to 100 and use the xScale
property to stretch the green bar accordingly. At the same time, you compute the updated angle and set the zRotation
on the entire meter to make it rotate. Since rotating an SKSpriteNode
also rotates its internal x and y axes, you can set the xScale
property on the power bar to scale it horizontally regardless of the orientation of the power arrow.
Next, find the section of handlePan(recognizer:)
where you check for UIGestureRecognizerState.changed
, and update it to call this new method:
if recognizer.state == UIGestureRecognizerState.changed { // the position of the drag has moved let translation = recognizer.translation(in: self.view) updatePowerMeter(translation: translation) } |
Build and run. Now, dragging your finger around the screen adjusts both the green power bar as well as the angle of the arrow.
The last thing to do is use the computed power and angle, instead of the hard coded values, to throw the beaker.
In the section of handlePan(recognizer:)
where you check for UIGestureRecognizerState.ended
, update the if
statement to look like:
if recognizer.state == UIGestureRecognizerState.ended { // finish up let maxPowerImpulse = 2500.0 let currentImpulse = maxPowerImpulse * currentPower/100.0 let strength = CGVector( dx: currentImpulse * cos(currentAngle), dy: currentImpulse * sin(currentAngle) ) tossBeaker(strength: strength) } |
Here you put together the strength
vector by calculating its x and y values.
If you’re not familiar with turning a vector into a x and y components, you make use of some functions from trigonometry.
You scale a maximum value down to currentImpulse
, which is r in the diagram. θ is the angle from the vector to the x-axis, which you have as currentAngle
.
Add these lines to tossBeaker(strength:)
at the end of section 1:
previousThrowPower = currentPower previousThrowAngle = currentAngle |
Build and run; the beaker will now use your custom values. See if you can get the last remaining cat!
You’re almost done! All that’s left is to track whether you convert all the cats to zombies before running out of beakers.
Your GameScene.sks file already has labels for the number of beakers and cats remaining, so you just need to add the code. Add the following to the property section of GameScene.swift:
var beakersLeft = 3 var catsRemaining = 2 |
Now add the following method:
func updateLabels() { if let beakerLabel = childNode(withName: "beakersLeftLabel") as? SKLabelNode { beakerLabel.text = "\(beakersLeft)" } if let catsLabel = childNode(withName: "catsRemainingLabel") as? SKLabelNode { catsLabel.text = "\(catsRemaining)" } } |
This method simply updates the labels onscreen to match the current values.
Add this method:
func checkEndGame() { if catsRemaining == 0 { print("you win") if let gameOverScene = GameOverScene(fileNamed: "GameOverScene") { gameOverScene.scaleMode = scaleMode gameOverScene.won = true view?.presentScene(gameOverScene) } return } if beakersLeft == 0 { print("you lose") if let gameOverScene = GameOverScene(fileNamed: "GameOverScene") { gameOverScene.scaleMode = scaleMode view?.presentScene(gameOverScene) } } } |
The game is over when you run out of beakers or if you convert all the cats to zombies. The files for GameOverScene.sks and GameOver.swift are included in the starter project.
Find the zombifyContactedCat
action inside the tossBeaker(strength:)
method and change it to:
let zombifyContactedCat = SKAction.run() { if let physicsBody = explosionRadius.physicsBody { for contactedBody in physicsBody.allContactedBodies() { if (physicsBody.contactTestBitMask & contactedBody.categoryBitMask) != 0 || (contactedBody.contactTestBitMask & physicsBody.categoryBitMask) != 0 { if let catNode = contactedBody.node as? SKSpriteNode { catNode.texture = self.sleepyTexture } contactedBody.node?.run(turnGreen) self.catsRemaining -= 1 contactedBody.categoryBitMask = PhysicsType.zombieCat } } } } |
The main change you made was subtracting 1 from the catsRemaining
property. You do this immediately after turning the cat green to keep track of the number of cats remaining.
Finally, update the completion block in section 4 of tossBeaker(strength:)
to:
cloud.run(boom) { self.beakersLeft -= 1 self.run(reload) self.updateLabels() self.checkEndGame() } |
This subtracts the thrown beaker from the internal count and updates the labels to the current values, then checks to see if you won or lost the game.
Build and run. As you play, you should see the labels displaying the number of cats and beakers remaining. The game also displays whether you won.
For one last bit of flair, add a particle emitter to the beaker. The starter project includes BeakerSmoke.sks, which looks like a toxic green cloud coming from the beaker, and BeakerSparkTrail.sks, which looks like a burning fuse to ignite the goo. You can add these to the beaker by adding the following lines to tossBeaker(strength:)
, between sections 1 and 2.
if let sparkNode = SKEmitterNode(fileNamed: "BeakerSparkTrail") { beaker.addChild(sparkNode) } if let smokeNode = SKEmitterNode(fileNamed: "BeakerSmoke") { smokeNode.targetNode = self beaker.addChild(smokeNode) } |
The targetNode
property changes which node the particles themselves are added to, in this case the scene node itself. If the smoke particles are added to the beaker
node, the smoke spins around as the beaker spins, which doesn’t look right.
Build and run, then admire your handiwork.
Voila! Those cats don’t stand a chance. ;]
Note: No cats were harmed in the implementation of this tutorial!
Download the completed project here.
In this tutorial, you used SpriteKit’s physics system to create a game similar to Monster Island, only better since it zombie-fies cats! You learned how to create and throw a projectile, animate a contact explosion and make sprites react to their surroundings. You also learned to incorporate a power meter to measure the user’s touch in order to throw the projectile.
There are tons of levels you can create using the tools learned in this tutorial. You can also explore ideas like obstacles that move and walls that can be destroyed. The options are endless!
For more about creating games with SpriteKit, check out 2D iOS & tvOS Games by Tutorials.
I hope you enjoyed this tutorial. If you have comments or questions, please join the forum discussion below.
Artwork by Vicki Wenderlich and kenney.nl.
The post How to Make a Game Like Monster Island Tutorial appeared first on Ray Wenderlich.
We are happy to announce that we are one of the proud sponsors of #Pragma Conference 2016, this October in Verona, Italy.
Last year’s conference was a hit and the line up for 2016 is looking quite promising!
This year, one of our long time tutorial team members, Ellen Shapiro, will be giving a talk. Ellen hasn’t chosen the title of her talk yet, but she promises it will be awesome!
Also, the organizer of #Pragma Conference, #pragma mark, has been kind enough to set up a 10% off discount for all raywenderlich.com readers. To get it, just order a ticket with the discount code PRAGMARAY10.
Also stay tuned for our official raywenderlich.com conference, RWDevCon, coming in March. Ticket sales open very soon *hint hint*. :]
The post raywenderlich.com at #Pragma Conference 2016 appeared first on Ray Wenderlich.
This year we ran our 2nd annual conference focused on high quality hands-on tutorials called RWDevCon.
The conference was a huge hit and got rave reviews, so we are running it for the 3rd time next March! This year we’re making a number of improvements, including:
This is just a quick heads-up that ticket sales for the conference will open up in 1 week, on Mon Aug 15 @ 9:00 AM EST.
And even better, the first 75 people who buy tickets will get $100 off.
For the past two years, the tickets have sold out very quickly, so if you’re interested in attending, be sure to snag your ticket while you still can.
If you’d like a reminder when tickets go on sale, sign up for our raywenderlich.com weekly newsletter. We hope to see you at the conference!
The post RWDevCon 2017: Ticket Sales Open in 1 Week! appeared first on Ray Wenderlich.
A lot of developers want to be able to share their app data via email, Messages, or AirDrop. Sharing is a convenient way for users to send data to each other or between devices – and it may even net you some new customers!
Luckily, since iOS6 Apple has provided the handy but much overlooked UIActivityViewController
class which provides a clean interface for sharing and manipulating data inside your app. You’re going to learn everything you need to know in this UIActivityViewController tutorial!
To setup sharing inside your app you just have to configure a few keys inside your applications Info.plist file and handle a few system callbacks inside your applications AppDelegate. Once setup, iOS can open your app with the URL pointing to the data to import or export.
Ready for some beer sharing fun? Read on.
Start by downloading the starter project for this tutorial, which is a modified version of the Beer Tracker app used in a previous tutorial. Build and run (Product \ Run or ⌘R) the project in Xcode; you’ll see the following:
Well, that’s no good, there are no beers to share. Lets get started so you can start sharing wonderful beers with everyone.
The first thing you need to do is set up your Info.plist to let iOS know your app can handle Beer Tracker Documents. To do this you need to register your app as being able to handle certain Uniform Type Identifiers, or UTIs, exporting any UTIs that are not already known by the system.
In summary, UTIs are unique identifiers that represent documents. There are UTIs already built-in to iOS for handling common document types such as public.jpeg or public.html.
You’re going to register your app to handle documents with the com.raywenderlich.BeerTracker.btkr UTI representing the description of a beer. You’ll tell iOS information about the UTI such as what file name extension it uses, what mime-type it’s encoded as when sharing and finally the file’s icon.
So let’s see it in action! Open Info.plist, and add the following entries under the Information Property List
key:
You can read up on what each of these value’s mean in Apple’s UTI guide, but here are the important things to note:
Document types
entry defines what UTIs your app supports – in your case, the com.raywenderlich.BeerTracker.btkr UTI, as an Owner/Editor.Document types
is also where you set the names of the icons iOS should use when displaying your file type. You’ll need to make sure you have an icon for each of the sizes listed in the plist.Exported Type UTIs
entry gives some information about com.raywenderlich.BeerTracker.btkr, since it isn’t a public UTI. Here you define files ending in .btkr, or files that have a mime type of application/beertracker can be handled by your app.Believe it or not, by setting these keys, you have informed iOS to start sending your app files that end with the .btkr extension. You can test this out by emailing yourself a copy of this sample beer file. Please make sure you unzip the file before emailing it to yourself otherwise both the file extension UTI and mime type will be wrong.
You can tap on the attachment and it will prompt you to open the beer in the Beer Tracker app. Selecting Beer Tracker will open the app, however it won’t load the data from the sample file because you haven’t implemented the code for that yet.
Now that you are a UTI wizard, lets sprinkle some magic.
UIActivityViewController
displayed after tapping the attached file in your email, you may need to edit the order of supported applications by scrolling to the end of the list, selecting More, and movings “Copy to Beer Tracker” to the top of the list.Before you can handle opening data from the file, you’ll need some code that can work with the file passed. Open Beer.swift, and replace the importDataFromURL(_:)
method with the following:
static func importDataFromURL(url: NSURL) { // 1 guard let dictionary = NSDictionary(contentsOfURL: url), beerInfo = dictionary as? [String: AnyObject], name = beerInfo[Keys.Name.rawValue] as? String, rating = beerInfo[Keys.Rating.rawValue] as? NSNumber else { return } // 2 let beer = Beer(name: name, note: beerInfo[Keys.Note.rawValue] as? String, rating: rating) // 3 if let base64 = beerInfo[Keys.ImagePath.rawValue] as? String, imageData = NSData(base64EncodedString: base64, options: .IgnoreUnknownCharacters), image = UIImage(data: imageData) { beer.saveImage(image) } // 4 BeerManager.sharedInstance.beers.append(beer) BeerManager.sharedInstance.saveBeers() // 5 do { try NSFileManager.defaultManager().removeItemAtURL(url) } catch { print("Failed to remove item from Inbox") } } |
Here’s a step-by-step explanation of the above code:
Beer
object with data from the URL.Next, when an external app wants to send your app a file, it does so via the application(_:openURL:options:)
method. Open AppDelegate.swift, and add the following code underneath the application(_:didFinishLaunchingWithOptions:)
method:
// MARK: - Handle File Sharing func application(app: UIApplication, openURL url: NSURL, options: [String : AnyObject]) -> Bool { // 1 guard url.pathExtension == "btkr" else { return false } // 2 Beer.importDataFromURL(url) // 3 guard let navigationController = window?.rootViewController as? UINavigationController, beerTableViewController = navigationController.viewControllers.first as? BeersTableViewController else { return true } // 4 beerTableViewController.tableView.reloadData() return true } |
Here’s a step-by-step explanation of the above:
static
method on the Beer object you added above to import the data into your app.UINavigationController
and that its first view controller is an instance of BeersTableViewController
. BeersTableViewController
‘s table view to show the newly imported beer, then return true to inform iOS that your app successfully processed the provided beer information.Build and run your app. If all works well you should be able to open the email attachment and see the beer imported into your app as shown below.
So far in this UIActivityViewController tutorial you’ve added functionality to your app that handles importing data from other apps, however what if you wish to share your app’s data? You’re in luck here, as Apple has made exporting data almost as nice as a lime infused cerveza on a hot beach – maybe.
Your app will need code to handle brewing exporting your favorite beers. Open Beer.swift and replace the existing exportToFileURL()
definition with the following:
func exportToFileURL() -> NSURL? { // 1 var contents: [String: AnyObject] = [Keys.Name.rawValue: name, Keys.Rating.rawValue: rating] // 2 if let image = beerImage() { let data = UIImageJPEGRepresentation(image, 1) contents[Keys.ImagePath.rawValue] = data?.base64EncodedStringWithOptions(.Encoding64CharacterLineLength) } // 3 if let note = note { contents[Keys.Note.rawValue] = note } // 4 guard let path = NSFileManager.defaultManager() .URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).first else { return nil } // 5 let saveFileURL = path.URLByAppendingPathComponent("/\(name).btkr") (contents as NSDictionary).writeToURL(saveFileURL, atomically: true) return saveFileURL } |
Here’s a step-by-step explanation of the above code:
Dictionary
to hold your basic beer information.Now that you can export Beer data to a file, you’re going to need an easy way to share it. Open BeerDetailViewController.swift, and replace the implementation of the share(_:)
method with the following:
@IBAction func share(sender: AnyObject) { guard let detailBeer = detailBeer, url = detailBeer.exportToFileURL() else { return } let activityViewController = UIActivityViewController( activityItems: ["Check out this beer I liked using Beer Tracker.", url], applicationActivities: nil) presentViewController(activityViewController, animated: true, completion: nil) } |
Here you’ve added a check to verify you have a detail beer, and can retrieve the URL from the exportToFileURL()
method. Next, you create an instance of a UIActivityViewController
passing in a message to be used, if possible and the URL to your app’s data file. UIActivityViewController
will do all of the heavy lifting for you. Since you defined all of the required information in your Info.plist file, it’ll know just what to do with it. Finally you present the UIActivityViewController
to the user.
UIActivityViewController
has been around since iOS6, however it is a very useful and sometimes under appreciated class. The message you pass to UIActivityViewController
will be used by applications such as Mail and Messages, adding this to the body of your message.Build and run your app, open a beer, and try to share. You’ll notice that you see a few options available to you as shown below:
Perhaps one the best options is the ability to AirDrop. If you have two devices – or better yet, a friend with the Beer Tracker app installed – you can test AirDropping beers!
You can download the finished project from this UIActivityViewController tutorial here.
Now that you know how iOS imports and exports app data, you’re ready to take this knowledge and start sharing your favorite beers or any other data of your choice.
In the meantime, if you have any questions or comments about this tutorial or Sharing Data in general, please join the forum discussion below!
The post UIActivityViewController Tutorial: Sharing Data appeared first on Ray Wenderlich.
Formatting measurements for every language and country is hard, so Foundation includes a new MeasurementFormatter class. See how easy it is to use in the screencast.
The post iOS 10 Screencast: Measurement Formatter appeared first on Ray Wenderlich.
Ever since the invention of the Mac, drag and drop has been a part of the user interface. The quintessential example is in Finder, where you can drag files around to organize things or drop them in the trash.
The fun doesn’t stop there.
You can drag your latest sunset panorama from Photos and drop it in Messages, or pull a file from Downloads on the dock and drop it right in an email. You get the point, right? It’s cool and an integral part of the macOS experience.
Drag and drop has come a long way from its beginnings and now you can drag almost anything anywhere. Try it and you might be pleasantly surprised by the actions and types supported by your favorite apps.
In this drag and drop tutorial for macOS, you’ll discover how to add support to your own apps, so users can get the full Mac experience with your app.
Along the way, you’ll learn how to:
NSView
subclassesThis project uses Swift 3 and requires, at a minimum, Xcode 8 beta 4. Download the starter project, open it in Xcode and build and run it.
Many kids enjoy playing with stickers and making cool collages with them, so you’re going to build an app that features this experience. You can drag images onto a surface and then you can kick things up a few notches by adding sparkles and unicorns to the view.
After all, who doesn’t like unicorns and sparkles? :]
To keep you focused on the objective — building out support for dragging and dropping — the starter project comes complete with the views you’ll require. All you need to do is learn about the mechanics of drag and drop.
There are three parts to the project window:
Take a look at the project.
You’ll edit four specific files as you work through this tutorial, and they’re in two places: Dragging Destinations and Dragging Sources:
In Dragging Destinations:
In Dragging Sources:
There are some other files in the Drawing and Other Stuff groups that provide helper methods and are essential to the project app, but you won’t need to give them any of your time. Go ahead and explore if you’d like to see how this thing is built!
Drag and drop involves a source and a destination.
You drag an item from a source, which needs to implement the NSDraggingSource
protocol. Then you drop it into a destination, which must implement the NSDraggingDestination
protocol in order to accept or reject the items received. NSPasteboard
is the class that facilitates the exchange of data.
The whole process is known as a dragging session:
When you drag something with your mouse, e.g., a file, the following happens:
That’s pretty much the gist of it. It’s a pretty simple concept!
First up is creating a dragging destination for receiving images from Finder or any other app.
A dragging destination is a view or window that accepts types of data from the dragging pasteboard. You create a dragging destination by adopting NSDraggingDestination
.
This diagram shows the anatomy of a dragging session from the point of view of a dragging destination.
There are a few steps involved in creating the destination:
Time to get down with some codeness!
Select DestinationView.swift in the project navigator and locate the following method:
func setup() { } |
Replace it with this:
var acceptableTypes: Set<String> { return [NSURLPboardType] } func setup() { register(forDraggedTypes: Array(acceptableTypes)) } |
This code defines a set with the supported types. In this case, URLs. Then, it calls register(forDraggedTypes:)
to accept drags that contain those types.
Add the following code into DestinationView
to analyze the dragging session data:
//1. let filteringOptions = [NSPasteboardURLReadingContentsConformToTypesKey:NSImage.imageTypes()] func shouldAllowDrag(_ draggingInfo: NSDraggingInfo) -> Bool { var canAccept = false //2. let pasteBoard = draggingInfo.draggingPasteboard() //3. if pasteBoard.canReadObject(forClasses: [NSURL.self], options: filteringOptions) { canAccept = true } return canAccept } |
You’ve done a few things in here:
NSDraggingInfo
is a protocol that declares methods to supply information about the dragging session. You don’t create them, nor do you store them between events. The system creates the necessary objects during the dragging session.
You can use this information to provide feedback to the dragging session when the app receives the image.
NSView
conforms to NSDraggingDestination
, so you need to override the draggingEntered(_:)
method by adding this code inside the DestinationView
class implementation:
//1. var isReceivingDrag = false { didSet { needsDisplay = true } } //2. override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation { let allow = shouldAllowDrag(sender) isReceivingDrag = allow return allow ? .copy : NSDragOperation() } |
This is what the code above does:
isReceivingDrag
to track when the dragging session is inside the view and has data that you want. It triggers a redraw on the view each time it is set.draggingEntered(_:)
, and decides if it accepts the drag operation.In section two, the method needs to return an NSDragOperation
. You have probably noticed how the mouse pointer changes depending on the keys you hold down or the destination of a drag.
For example, if you hold down Option during a Finder drag, the pointer gains a green + symbol to show you a file copy is about to happen. This value is how you control those pointer changes.
In this config, if the dragging pasteboard has an image then it returns .copy
to show the user that you’re about to copy the image. Otherwise it returns NSDragOperation()
if it doesn’t accept the dragged items.
What enters the view may also exit, so the app needs to react when a dragging session has exited your view without a drop. Add the following code:
override func draggingExited(_ sender: NSDraggingInfo?) { isReceivingDrag = false } |
You’ve overridden draggingExited(_:)
and set the isReceivingDrag
variable to false
.
You’re almost done with the first stretch of coding! Users love to see a visual cue when something is happening in the background, so the next thing you’ll add is a little drawing code to keep your user in the loop.
Still in DestinationView.swift, find draw(:_)
and replace it with this.
override func draw(_ dirtyRect: NSRect) { if isReceivingDrag { NSColor.selectedControlColor.set() let path = NSBezierPath(rect:bounds) path.lineWidth = Appearance.lineWidth path.stroke() } } |
This code draws a system-colored border when a valid drag enters the view. Aside from looking sharp, it makes your app consistent with the rest of the system by providing a visual when it accepts a dragged item.
Note: Want to know more about custom drawing? Check out our Core Graphics on macOS Tutorial.
Build and run then try dragging an image file from Finder to StickerDrag. If you don’t have an image handy, use sample.jpg inside the project folder.
You can see that the cursor picks up a + symbol when inside the view and that the view draws a border around it.
When you exit the view, the border and + disappears; absolutely nothing happens when you drag anything but an image file.
Now, on to the final step for this section: You have to accept the drag, process the data and let the dragging session know that this has occurred.
Append the DestinationView
class implementation with the following:
override func prepareForDragOperation(_ sender: NSDraggingInfo) -> Bool { let allow = shouldAllowDrag(sender) return allow } |
The system calls the above method when you release the mouse inside the view; it’s the last chance to reject or accept the drag. Returning false
will reject it, causing the drag image to slide back to its origination. Returning true
means the view accepts the image. When accepted, the system removes the drag image and invokes the next method in the protocol sequence: performDragOperation(_:)
.
Add this method to DestinationView
:
override func performDragOperation(_ draggingInfo: NSDraggingInfo) -> Bool { //1. isReceivingDrag = false let pasteBoard = draggingInfo.draggingPasteboard() //2. let point = convert(draggingInfo.draggingLocation(), from: nil) //3. if let urls = pasteBoard.readObjects(forClasses: [NSURL.self], options:filteringOptions) as? [URL], urls.count > 0 { delegate?.processImageURLs(urls, center: point) return true } return false } |
Here’s what you’re doing in there:
isReceivingDrag
flag to false
.true
— else you reject the drag operation returning false
.Note: Feeling extra heroic? If you were to make an animated drop sequence, performDragOperation(:_)
would be the best place to start the animation.
Congratulations! You’ve just finished the first section and have done all the work DestinationView
needs to receive a drag.
Next up you’ll use the data that DestinationView
provides in its delegate.
Open StickerBoardViewController.swift and introduce yourself to the class that is the delegate of DestinationView
.
To use it properly, you need to implement the DestinationViewDelegate
method that places the images on the target layer. Find processImage(_:center:)
and replace it with this.
func processImage(_ image: NSImage, center: NSPoint) { //1. invitationLabel.isHidden = true //2. let constrainedSize = image.aspectFitSizeForMaxDimension(Appearance.maxStickerDimension) //3. let subview = NSImageView(frame:NSRect(x: center.x - constrainedSize.width/2, y: center.y - constrainedSize.height/2, width: constrainedSize.width, height: constrainedSize.height)) subview.image = image targetLayer.addSubview(subview) //4. let maxrotation = CGFloat(arc4random_uniform(Appearance.maxRotation)) - Appearance.rotationOffset subview.frameCenterRotation = maxrotation } |
This code does the following tricks:
With all that in place, you’re ready to implement the method so it deals with the image URLs that get dragged into the view.
Replace processImageURLs(_:center:)
method with this:
func processImageURLs(_ urls: [URL], center: NSPoint) { for (index,url) in urls.enumerated() { //1. if let image = NSImage(contentsOf:url) { var newCenter = center //2. if index > 0 { newCenter = center.addRandomNoise(Appearance.randomNoise) } //3. processImage(image, center:newCenter) } } } |
What you’re doing here is:
Now build and run then drag an image file (or several) to the app window. Drop it!
Look at that board of images just waiting to be made fearlessly fanciful.
You’re at about the halfway point and have already explored how to make any view a dragging destination and how to compel it to accept a standard dragging type — in this case, an image URL.
You’ve played around with the receiving end, but how about the giving end?
In this section, you’ll learn how to supercharge your app with the ability to be the source by letting those unicorns and sparkles break free and bring glee to the users’ images in the right circumstances.
All dragging sources must conform to the NSDraggingSource
protocol. This MVP (most valuable player) takes the task of placing data (or a promise for that data) for one or more types on the dragging pasteboard. It also supplies a dragging image to represent the data.
When the image finally lands on its target, the destination unarchives the data from the pasteboard. Alternatively, the dragging source can fulfil the promise of providing the data.
You’ll need to supply the data of two different types: a standard Cocoa type (an image) and custom type that you create.
The dragging source will be ImageSourceView
— the class of the view that has the unicorn. Your objective is simple: get that unicorn onto your collage.
The class needs to adopt the necessary protocols NSDraggingSource
and NSPasteboardItemDataProvider
, so open ImageSourceView.swift and add the following extensions:
// MARK: - NSDraggingSource extension ImageSourceView: NSDraggingSource { //1. func draggingSession(_ session: NSDraggingSession, sourceOperationMaskFor context: NSDraggingContext) -> NSDragOperation { return .generic } } // MARK: - NSDraggingSource extension ImageSourceView: NSPasteboardItemDataProvider { //2. func pasteboard(_ pasteboard: NSPasteboard?, item: NSPasteboardItem, provideDataForType type: String) { //TODO: Return image data } } |
NSDraggingSource
. It tells the dragging session what sort of operation you’re attempting when the user drags from the view. In this case it’s a generic operation.NSPasteboardItemDataProvider
method. More on this soon — for now it’s just a stub.In a real world project, the best moment to initiate a dragging session depends on your UI.
With the project app, this particular view you’re working in exists for the sole purpose of dragging, so you’ll start the drag on mouseDown(with:)
.
In other cases, it may be appropriate to start in the mouseDragged(with:)
event.
Add this method inside the ImageSourceView
class implementation:
override func mouseDown(with theEvent: NSEvent) { //1. let pasteboardItem = NSPasteboardItem() pasteboardItem.setDataProvider(self, forTypes: [kUTTypeTIFF]) //2. let draggingItem = NSDraggingItem(pasteboardWriter: pasteboardItem) draggingItem.setDraggingFrame(self.bounds, contents:snaphot()) //3. beginDraggingSession(with: [draggingItem], event: theEvent, source: self) } |
Things get rolling when the system calls mouseDown(with:)
when you click on a view. The base implementation does nothing, eliminating the need to call super. The code in the implementation does all of this:
NSPasteboardItem
and sets this class as its data provider. A NSPasteboardItem
is the box that carries the info about the item being dragged. The NSPasteboardItemDataProvider
provides data upon request. In this case you’ll supply TIFF data, which is the standard way to carry images around in Cocoa.NSDraggingItem
and assigns the pasteboard item to it. A dragging item exists to provide the drag image and carry one pasteboard item, but you don’t keep a reference to the item because of its limited lifespan. If needed, the dragging session will recreate this object. snapshot()
is one of the helper methods mentioned earlier. It creates an NSImage
of an NSView
.Build and run. Try to drag the unicorn onto the top view.
An image of the view follows your mouse, but it slides back on mouse up because DestinationView
doesn’t accept TIFF data.
In order to accept this data, you need to:
setup()
to accept TIFF datashouldAllowDrag()
to accept the TIFF typeperformDragOperation(_:)
to take the image data from the pasteboardOpen DestinationView.swift.
Replace the following line:
var acceptableTypes: Set<String> { return [NSURLPboardType] } |
With this:
var nonURLTypes: Set<String> { return [String(kUTTypeTIFF)] } var acceptableTypes: Set<String> { return nonURLTypes.union([NSURLPboardType]) } |
You’ve just registered the TIFF type like you did for URLs and created a subset to use next.
Next, go to shouldAllowDrag(:_)
, and add find the return canAccept
method. Enter the following just above the return
statement:
else if let types = pasteBoard.types, nonURLTypes.intersection(types).count > 0 { canAccept = true } |
Here you’re checking if the nonURLTypes
set contains any of the types received from the pasteboard, and if that’s the case, accepts the drag operation. Since you added a TIFF type to that set, the view accepts TIFF data from the pasteboard.
Lastly, update performDragOperation(_:)
to unarchive the image data from the pasteboard. This bit is really easy.
Cocoa wants you to use pasteboards and provides an NSImage
initializer that takes NSPasteboard
as a parameter. You’ll find more of these convenience methods in Cocoa when you start exploring drag and drop more.
Locate performDragOperation(_:)
, and add the following code at the end, just above the return sentence return false
:
else if let image = NSImage(pasteboard: pasteBoard) { delegate?.processImage(image, center: point) return true } |
This extracts an image from the pasteboard and passes it to the delegate for processing.
Build and run, and then drag that unicorn onto the sticker view.
You’ll notice that now you get a green + on your cursor.
The destination view accepts the image data, but the image still slides back when you drop. Hmmm. What’s missing here?
You need to get the dragging source to supply the image data — in other words: fulfil its promise.
Open ImageSourceView.swift and replace the contents of pasteboard(_:item:provideDataForType:)
with this:
//1. if let pasteboard = pasteboard, type == String(kUTTypeTIFF), let image = NSImage(named:"unicorn") { //2. let finalImage = image.tintedImageWithColor(NSColor.randomColor()) //3. let tiffdata = finalImage.tiffRepresentation pasteboard.setData(tiffdata, forType:type) } |
In this method, the following things are happening:
kUTTypeTIFF
, you load an image named unicorn. Build and run, and drag the unicorn onto the sticker view. It’ll drop and place a colored unicorn on the view. Great!
So.many.unicorns!
Unicorns are pretty fabulous, but what good are they without magical sparkles? Strangely, there’s no standard Cocoa data type for sparkles. I bet you know what comes next. :]
Note: In the last section you supplied a standard data type. You can explore the types for standard data in the API reference.
In this section you’ll invent your own data type.
These are the tasks on your to-do list:
Open AppActionSourceView.swift. It’s mostly empty except for this important definition:
enum SparkleDrag { static let type = "com.razeware.StickerDrag.AppAction" static let action = "make sparkles" } |
This defines your custom dragging type and action identifier.
Dragging source types must be Uniform Type Identifiers. These are reverse-coded name paths that describe a data type.
For example, if you print out the value of kUTTypeTIFF
you’ll see that it is the string public.tiff.
To avoid a collision with an existing type, you can define the identifier like this: bundle identifier + AppAction. It is an arbitrary value, but you keep it under the private namespace of the application to minimize the risk of using an existing name.
If you attempt to construct a NSPasteboardItem
with a type that isn’t UTI, the operation will fail.
Now you need to make AppActionSourceView
adopt NSDraggingSource
. Open AppActionSourceView.swift and add the following extension:
// MARK: - NSDraggingSource extension AppActionSourceView: NSDraggingSource { func draggingSession(_ session: NSDraggingSession, sourceOperationMaskFor context: NSDraggingContext) -> NSDragOperation { switch(context) { case .outsideApplication: return NSDragOperation() case .withinApplication: return .generic } } } |
This code block differs from ImageSourceView
because you’ll place private data on the pasteboard that has no meaning outside the app. That’s why you’re using the context
parameter to return a NSDragOperation()
when the mouse is dragged outside your application.
You’re already familiar with the next step. You need to override the mouseDown(with:)
event to start a dragging session with a pasteboard item.
Add the following code into the AppActionSourceView
class implementation:
override func mouseDown(with theEvent: NSEvent) { let pasteboardItem = NSPasteboardItem() pasteboardItem.setString(SparkleDrag.action, forType: SparkleDrag.type) let draggingItem = NSDraggingItem(pasteboardWriter: pasteboardItem) draggingItem.setDraggingFrame(self.bounds, contents:snaphot()) beginDraggingSession(with: [draggingItem], event: theEvent, source: self) } |
What did you do in there?
You constructed a pasteboard item and placed the data directly inside it for your custom type. In this case, the data is a custom action identifier that the receiving view may use to make a decision.
You can see how this differs from ImageSourceView
in one way. Instead of deferring data generation to the point when the view accepts the drop with the NSPasteboardItemDataProvider
protocol, the dragged data goes directly to the pasteboard.
Why would you use the NSPasteboardItemDataProvider
protocol? Because you want things to move as fast as possible when you start the drag session in mouseDown(with:)
.
If the data you’re moving takes too long to construct on the pasteboard, it’ll jam up the main thread and frustrate users with a perceptible delay when they start dragging.
In this case, you place a small string on the pasteboard so that it can do it right away.
Next, you have to let the destination view accept this new type. By now, you already know how to do it.
Open DestinationView.swift and add SparkleDrag.type
to the registered types. Replace the following line:
var nonURLTypes: Set<String> { return [String(kUTTypeTIFF)] } |
With this:
var nonURLTypes: Set<String> { return [String(kUTTypeTIFF),SparkleDrag.type] } |
Now SparkleDrags are acceptable!
performDragOperation(:_)
needs a new else-if
clause, so add this code at the end of the method just before return false
:
else if let types = pasteBoard.types, types.contains(SparkleDrag.type), let action = pasteBoard.string(forType: SparkleDrag.type) { delegate?.processAction(action, center:point) return true } |
This addition extracts the string from the pasteboard. If it corresponds to your custom type, you pass the action back to the delegate.
You’re almost done, you just need to update StickerBoardViewController
to deal with the action instruction.
Open StickerBoardViewController.swift and replace processAction(_:center:)
with this:
func processAction(_ action: String, center: NSPoint) { //1. if action == SparkleDrag.action { invitationLabel.isHidden = true //2. if let image = NSImage(named:"star") { //3. for _ in 1..<Appearance.numStars { //A. let maxSize:CGFloat = Appearance.maxStarSize let sizeChange = CGFloat(arc4random_uniform(Appearance.randonStarSizeChange)) let finalSize = maxSize - sizeChange let newCenter = center.addRandomNoise(Appearance.randomNoiseStar) //B. let imageFrame = NSRect(x: newCenter.x, y: newCenter.y, width: finalSize , height: finalSize) let imageView = NSImageView(frame:imageFrame) //C. let newImage = image.tintedImageWithColor(NSColor.randomColor()) //D. imageView.image = newImage targetLayer.addSubview(imageView) } } } } |
The above code does the following:
NSImageView
and sets its frame.Build and run. Now you can drag from the sparkles view onto the sticker view to add a spray of stars to your view.
Congratulations, you created a custom drag and drop interface in your very own app!
You can use the Save Image To Desktop button to save your image as a JPG with the name StickerDrag. Maybe take it a step further and tweet it to the team @rwenderlich.
Here’s the source code for the the completed project.
This drag and drop tutorial for macOS covered the basics of the Cocoa drag and drop mechanism, including:
Now you have the knowledge and experience needed to support drag and drop in any macOS app.
There’s certainly more to learn.
You could study up on how to apply effects, such as changing the dragging image during the drag or implementing an animated drop transition, or working with promised files — Photos is one application that places promised data on the dragging pasteboard.
Another interesting topic is how to use drag and drop with NSTableView
and NSOutlineView
, which work in slightly different ways. Learn about it from the following resources:
If you have any questions or comments about this drag and drop tutorial for macOS, please join the discussion below! And remember, sometimes life is a dragging experience, but everything’s better with unicorns and sparkles. :]
The post Drag and Drop Tutorial for macOS appeared first on Ray Wenderlich.
iOS 10 includes a powerful speech recognition API. Discover how to transcribe audio recordings in this iOS 10 screencast.
The post iOS 10 Screencast: Audio File Speech Transcription 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 Lee Burrows.
Lee is a leading iOS technologist, well known for heading up the iOS team for the biggest retailer in the UK, Next PLC.
Next PLC is a British multinational clothing retailer based in the UK with over 700 stores. In addition, an online web presence with over 3 million active customers of which 500,000 actively use their iOS app per month.
Since we prepared this interview, Lee has moved on to a Senior iOS Developer position for Capital One, the eighth-largest bank holding company in the whole of the US.
Lee, can you give us a sense of how big the Next app and team is and how it’s structured?
I work in the eCommerce department. Here you will find 55 or so developers, just 4 of which are in our iOS team. All developers report directly to the product owner.
We also have our own dedicated QA team, who strive on breaking our implementation and are fast to point out when something isn’t quite up to scratch for production. A little up the office we have the UI & UX teams and technical architects.
It looks a little something like this:
Could you please describe the process by which your team works with the design team?
We are very closely coupled with the design team. It was always pushed upon us to be more “open desk” and the huge open office allowed this. We could often visit their desks and make suggestions.
Unfortunately, designs at Next were never approved by the higher ups until we went to release. At any stage they could and quite often did change. This ultimately had large issues with development timescales but something we learned to deal with…fast!
I often see this happen within very large companies. The design seems to be the forefront of ideas, with development the second thought. I think it is crucial for all skill sets to be involved throughout, from prototype right through to App Store delivery.
How much of the Next app is written in Swift? What were the challenges in transitioning the app from Objective-C to Swift in a large team environment?
Before Swift arrived, we made the iPad app in Objective-C. This is my proudest achievement to date: it includes a completely custom UI exhibiting UICollectionViews
used to their greatest potential and a UIPageViewController
that really went under the knife in Instruments.
The iPad app ended up being about 33,000 lines of Objective-C code. Later, we wrote the iPhone app in Swift, and it ended up being a little smaller in size. To make sure we re-used some core components between the two platforms, we created an internal private CocoaPod. It contains UI elements that were generic to both iPhone and iPad apps for reusability and of course all our API data access.
We are currently bridging Objective-C classes in our CocoaPod to our Swift code base. The transition over to Swift was a fun one, although initially I was against it! I was not overly comfortable with Swift as it was still in version 1 at that point, but after persuasion from some of the guys we took the plunge.
Debugging Swift back then was extremely painful, and it still is to this day when debugging Objective-C code in a Swift project. The po
command and I are now best of friends. :]
For other developers or teams, what advice would you give for a successful development life-cycle?
I would recommend you make sure your team fully understand your chosen source control system (GIT/SVN) and ensure they adhere to all the best practices. I have lost count how many times I tell developers to pull down as often as possible and push up just as often.
For us, communication is key. We encourage the use of our “Confessions” channel on Slack, a place where anyone can dump a snippet of code and allow time for other developers to offer an alternative solution.
For any developer looking to join a huge company, just remember that’s all you are: a developer. Unfortunately the chances of your fancy design and animation making the App Store release are probably small. In fact, only one of my fancy animations made it into the App Store and that was still scrutinized!
As a lead iOS developer, you are responsible for multiple skilled iOS developers, how do you manage those on a daily basis?
I generally have a 60/40 time split between getting my head down in code and managing the team. I use my favorite app, Todoist, to manage what development tasks I want to be completed and I use sticky notes attached to my monitor for analysis tasks and other ad-hoc duties other departments may need me for.
Being the Lead iOS Developer, it was all about being approachable. When you hold such a title, people will look at you like you’re Mr. StackOverflow and it’s wise to be approachable. After all, your suggestions around problems could well be the ones implemented.
It can be very frustrating managing a team when not many of our ideas get implemented. I remember I had to remove a developer’s force touch feature because it wasn’t in the requirements.
What’s your interview process while hiring new iOS developers? What sort of things do you look for in an ideal hire on your team?
We do most of our interviewing over the phone and sometimes face to face. Once we’re happy with a hire we get them to take an iOS quiz which generally gives us a good idea of their skillset. Here at Next, no interview is the same: we mix the process regularly to keep it fresh.
I love when developers talk about Swift, or anything new. It shows they keep up with the latest in the iOS development world and gives them chance to show off their passion for development.
If someone loves what they do they won’t tell you in those exact words, you’ll just know. If you can’t sense it what would be the point in hiring someone who didn’t love what they do? If you love what you do you’ll do it faster and probably better.
We have asked questions like:
I love very broad questions, since it allows a lot of mini questions to tail off their responses. I’ve had a lot of bad experiences with developers and SCM, I guess next time I would go more in depth about git and branching models.
Hiring contractors is usually a lot easier. We just have a quick telephone interview to assess their development history, a quick code pairing exercise, and that’s it – they’re in. Of course, we can always drop them with a little notice – I like to remind them of this, in a friendly way of course. :]
Do you have any final advice for readers looking to level up their iOS skills?
Keep on top of your development. iOS especially moves incredibly fast each year following WWDC. Swift is about to get kicked into version 3; it’s moving fast you just can’t sit still.
Watch the Apple videos, visit plenty of tutorial websites, fire up Xcode and get ahead of the game.
And that concludes our Top App Dev Interview with Lee Burrows. Huge thanks to Lee for sharing his experience with the iOS community.
We hope you enjoyed this interview and that you can take Lee’s advice when it comes to being a Lead iOS Developer or an iOS Developer within a large corporation. I think when working within a large corporation you’re a small cog in a giant wheel, everyone spinning to create the final product. We wish Lee all the best in his new position at Capital One.
If you are an app developer with a hit app or game in the top 100 in the App store or other significantly cool experience, 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 join the discussion in the forum below!
The post Lead iOS Developer at Next PLC: A Top Dev Interview With Lee Burrows appeared first on Ray Wenderlich.
Next March, we are running an iOS conference focused on high quality hands-on tutorials called RWDevCon 2017.
Today, the team and I are happy to announce that RWDevCon 2017 tickets are now available!
And good news – the first 75 people who buy tickets will get a $100 discount off the standard ticket price.
Keep reading to find out what makes RWDevCon special, and what’s in store this year!
RWDevCon is designed around 4 main principles:
1) Hands-On Tutorials
RWDevCon is unique in that it is focused on high quality hands-on tutorials. It has 3 simultaneous tracks of tutorials, leading to some challenging and fun choices on which to attend! :]
In each tutorial, you will follow along with a hands-on demo with the instructor:
After the demo, you’ll break out into a hands-on lab, where you’ll be challenged to try things out on your own – and the instructor will be right with you if you have any questions.
We really think this hands-on experience is the best way to learn, and this way you won’t just leave with notes and references – you’ll leave with actual new skills.
2) Team Coordination
Just like we do for books and tutorials on this site, RWDevCon is highly coordinated as a team. This lets us:
3) Inspiration
After a long days work on hands-on tutorials, you’ll be ready for a break.
That’s why at the end of the day, we switch to something completely different: inspiration talks.
These are short 18-minute non-technical talks with the goal of giving you a new idea, some battle-won advice, and leaving you excited and energized.
If you’re curious what these look like, check out our RWDevCon videos.
4) Friendship
We believe one of the best parts about going to a conference is the people, and there’s plenty of opportunities to meet new friends.
We’ll have an opening reception before the conference begins to get to meet each other, board games at lunch, and an awesome party on Friday night. We have some surprises up our sleeves this year too – you won’t want to miss it! :]
If you’re interested in getting a ticket, now’s the best time:
You can grab your ticket at the RWDevCon web site. We hope to see you there! :]
The post RWDevCon 2017: Tickets Now Available! appeared first on Ray Wenderlich.
TestFlight Beta Testing is an Apple product that makes it easy to invite users to test your iOS, watchOS and tvOS apps before you release them into the App Store. This TestFlight tutorial will walk you through integrating TestFlight into your own apps.
This is one of those rare tutorials where you don’t have to code — just follow through the steps in this tutorial and you’ll be up and running with TestFlight in no time! :]
Don’t have an app right now but still want to proceed with this tutorial? No problem! Download our beloved Flappy Felipe project and use that as you follow along. Make sure you change the bundle ID of the app to your own unique ID; for example, com.yourname.FlappyFelipe
.
This tutorial assumes that your app is set up for provisioning, and has an app ID created in both the developer portal and on iTunes Connect.
This setup is outside the scope of this tutorial, but you should be able to use automatic provisioning as described in Setting up Xcode to automatically manage your provisioning profiles, and create a record in iTunes by following the instructions in Create an iTunes Connect Record for TestFlight Beta Testing in the iTunes Connect Developer Guide.
Open up your project in Xcode, make sure you have a correct Bundle Identifier, that your Team ID is set, and that you’ve chosen the automatic Distribution Certificate Code Signing Identity:
Choose Product\Archive from the top toolbar:
Once Xcode finishes archiving your project, click the shiny blue Upload to App Store… button:
Now you need to choose your development team:
Finally, click Upload:
Wait for a few minutes as your build uploads. Grab a coffee, perhaps, or if you have a slow internet connection, go grab a bite. Or two. Or three. :]
Once you’re done, you should receive a success message like the following:
That’s all the work required for Xcode. Your beta build is now available on iTunes Connect, and that’s where you’ll be doing the rest of the work to set up TestFlight.
Your build is ready for testing, but who’s going to test it?
Apple defines two types of testers for Test Flight:
Before your external developers can test your app, you have to submit your app to be reviewed by Apple, just as you would with a normal App Store submission. These reviews tend to go faster than normal app reviews (although don’t count on it), and once it’s approved you can let external developers test your app. Internal testers, on the other hand, are instantaneously able to test new builds.
You’ll learn more about external testers later, but for now, let’s focus on internal testers.
To add an internal tester, head to the Users and Roles section in iTunes Connect:
On the Users and Roles screen, click the + button to add a new user:
Fill in your new user info and click Next:
You’ll need to use a unique email address for your new user. If you don’t have a second email account, you can usually append +whateveryouwant
to the first part of your email address, and the email will still get delivered to you. For example tom+mytestaccount@razeware.com
.
Now you need to assign roles for the user. In most cases, you’ll want to choose App Manager. You can read more about the privileges for each role and choose the appropriate one for your user.
Admin | App Manager | Developer | Marketer | Sales | |
---|---|---|---|---|---|
Can be an Internal Tester | ✓ | ✓ | ✓ | ✓ | Can Upload a Build | ✓ | ✓ | ✓ | Can Submit a Build for External Testing | ✓ | ✓ |
Once that’s done, click Next:
Choose the type of notifications you want your new testers to receive, then click Save:
Your user is now created, but as the message indicates, that user first needs to verify his or her email address before the account will show in iTunes Connect.
Creating a new internal beta tester is only the first part of the process. The remaining step is to invite this particular tester to test your latest build.
It’s time to enable your app for testing — so the tester actually has something to test! :]
To start beta testing of your app, go to the My Apps section on the iTunes Connect home page and click on your app:
Select the Activity tab and you’ll find your latest build. Make sure it is no longer marked as processing. If it is, go make another cup of coffee and come back later. :]
Next, click on the TestFlight tab, then Internal Testing in the left hand menu. Click Select Version to Test and choose the version you just uploaded. Finally, click Start Testing and, in the confirmation pop-up, Start Testing again.
All selected testers will now receive an email that lets them download and install this build from the TestFlight app. There are detailed instructions on how to do that in the last section of this tutorial.
That takes care of internal testers, but what about external testers?
That’s just as easy! First, go to the Test Information tab and fill in your Feedback Email, Marketing URL and Privacy Policy URL. You can choose to add a License Agreement at this stage as well if you wish, but it is not necessary.
Next, go to the External Testing tab, click the + button and select Add New Testers:
Add the email addresses of any external users you want to add. Once you’re finished, click Add to add these testers to your account. All external testers will count toward your 2000 external tester limit:
Click Save.
You now need to select a build (again) for your external testers, and put that build through the Beta App Review.
Click Add Build To Test, select your build, and then click Next.
Fill in all the fields. Remember – the more information you give Apple the easier it is for them to review your app! Finally, hit Submit.
Your app will be added to the review queue! :]
Once the app has passed Beta App Review you will receive an email with confirmation that your app can now be used by external testers.
Head back to the External Testing section for your app in iTunes Connect, select the build and hit Save. A dialog will pop up confirming that you are about to notify people. Click Start Testing.
Your external testers will then receive an invitation email similar to the one received by your internal testers as described above.
That shows the developer’s perspective of app testing, but what does it look like from the tester’s perspective?
As an internal tester, you need to link your Apple ID to iTunes Connect (external testers can skip to the TestFlight App section below). By now, you should have received an email from iTunes Connect that looks like this:
Click on activate your account and follow the supplied steps. Once your account is ready for testing, get your iOS device and go to the Settings app. Scroll down to iTunes & App Store:
Log in with the account you just verified a minute ago. If you’re already logged in with another account, log out first:
Go to the App Store, and search for the TestFlight app:
Download the TestFlight app and launch it.
Internal testers will automatically receive an email when new versions of the app are uploaded to iTunes Connect. External testers will receive a similar email after the app has been through Beta App Review and the build has been pushed out to external testers by an Admin or App Manager in iTunes Connect:
Open this email on your testing device, then tap Start Testing. This will launch TestFlight and show you the app you need to test. A tester must tap Start Testing on the device they’ll be testing on; otherwise the app won’t be available for download by the tester. Tap Accept, then Install, and wait for the app to download:
The app will be downloaded and appear on your home screen!
That was the hardest part of being a tester. From now on, whenever a new version of this app is available, you’ll see a notification from TestFlight. All you need to do is update your app and run the latest version.
In this TestFlight tutorial you learned how to upload your test build and invite internal and external testers to your app.
If you’re interested in knowing more about iTunes Connect in general, and beta testing in particular, read through Apple’s TestFlight Beta Testing Documentation. Apple’s Developer site also has a summary page for TestFlight, which includes links to all the relevant documentation as well as a video outlining the TestFlight process.
You can also check out iOS 8 by Tutorials; the final chapter What’s New with iTunes Connect showcases everything you need to know to manage your testing effort.
If you want to take your app deployment to the next level, take a look at our fastlane tutorial, which covers a set of tools that you can use to automate managing iTunes Connect and submitting apps.
I hope you enjoyed this TestFlight tutorial, and if you have any questions or comments, please join the forum discussion below!
The post TestFlight Tutorial: iOS Beta Testing appeared first on Ray Wenderlich.
For the past two years, we’ve been working hard creating a ton of high quality video tutorials.
In fact, we’ve been spending so much time making the content, that we didn’t have much time to focus on the website itself. But we admit it – a massive text list of courses isn’t very user-friendly. :]
Today, we have some good news. We’ve completely redesigned the video section of our site: videos.raywenderlich.com.
Whether you’re a subscriber or not, there’s lots in store for you in this launch, including a special discount and giveaway. Read on to see how you can enjoy our launch party!
The first thing you’ll notice about videos.raywenderlich.com is that it’s been completely redesigned:
The new design makes it easy to find the course or screencast you’re looking for:
In addition to the visual design, there are tons of new features to improve your learning experience.
First of all, you asked for it, and you got it. You can now play videos at 1.5x or 2x speed!
You can now easily see your progress working through a course:
It’s also easy to continue from where you left off watching videos last time:
Please check out the new design and let us know what you think! :]
Remember even if you’re not a subscriber, there’s still plenty for you to do over on the new site:
Or if you want to be more involved, check out the launch discount and giveaway below.
The entire team at Razeware has been working hard on this over the past few months, so we wanted to do something extra special to celebrate.
To celebrate the launch, we’re offering a special 50% discount off your first month – w00t!
If you’ve ever been on the fence about subscribing, now is the time. In addition to the 50% discount on your first month, you’ll enjoy all the usual subscriber benefits:
Already a subscriber? Enter our massive launch giveaway:
One lucky grand prize winner will get a 1-year raywenderlich.com all-access pass. This includes:
This represents over $1,000 in value – w00t!
But that’s not all. In addition to the grand prize, 10 lucky second prize winners will receive 1 free PDF book of their choice.
To enter the giveaway, simply comment on this post and answer the following question:
We will select random winners who answer this question by next Tuesday, Aug 23. Note you must be an active subscriber to be eligible for the giveaway.
Here’s how to check out the new site:
Remember – the launch discount and giveaway expires next Tuesday, Aug 23 – so take advantage of them while you still can.
The entire Razeware team and I hope you enjoy videos.raywenderlich.com – and thanks again to all of our subscribers for supporting what we do on this site and making this possible.
The post Introducing videos.raywenderlich.com – Discount and Giveaway! appeared first on Ray Wenderlich.
Xcode 8's view debugger has some improvements that make it easy to debug even code-based Auto Layout issues. Discover how in this screencast.
The post iOS 10 Screencast: Visual View Debugging appeared first on Ray Wenderlich.
Pattern matching is one of the most powerful features of any programming language, because it enables you to design rules that match values against each other. This gives you flexibility and simplifies your code.
Apple makes pattern matching available in Swift, and today you’ll explore Swift’s pattern matching techniques.
The tutorial covers the following patterns:
To show how useful pattern matching can be, in this tutorial you’ll adopt a unique perspective: that of the editor-in-chief at raywenderlich.com! You’ll use pattern matching to help you schedule and publish tutorials on the site.
Welcome aboard, temporary editor-in-chief! Your main duties today involve scheduling tutorials for publication on the website. Start by downloading the starter playground and open starter project.playground in Xcode.
The playground contains two things:
random_uniform(value:)
function, which returns a random number between zero and a certain value. You’ll use this to generate random days for the schedule.You don’t need to understand how all this works, but you should know the file’s structure, so go ahead and open tutorials.json from the playground’s Resources folder.
Each tutorial post you’ll be scheduling has two properties: title and scheduled day. Your team lead schedules the posts for you, assigning each tutorial a day value between 1 for Monday and 5 for Friday, or nil
if leaving the post unscheduled.
You want to publish only one tutorial per day over the course of the week, but when looking over the schedule, you see that your team lead has two tutorials scheduled for the same day. You’ll need to fix the problem. Plus, you want to sort the tutorials in a particular order. How can you do all of that?
If you guessed “Using patterns!” then you’re on the right track. :]
Let’s get to know the kinds of patterns you’ll be working with in this tutorial.
You’ll use all of these patterns in your quest to be the best editor-in-chief the site has ever seen!
First, you’ll create a tuple pattern to make an array of tutorials. In the playground, add this code at the end:
enum Day: Int { case monday, tuesday, wednesday, thursday, friday, saturday, sunday } |
This creates an enumeration for the days of the week. The underlying raw type is Int
, so the days are assigned raw values from 0 for Monday through 6 for Sunday.
Add the following code after the enumeration’s declaration:
class Tutorial { let title: String var day: Day? init(title: String, day: Day? = nil) { self.title = title self.day = day } } |
Here you define a tutorial type with two properties: the tutorial’s title and scheduled day. day
is an optional variable because it can be nil
for unscheduled tutorials.
Implement CustomStringConvertible
so you can easily print tutorials:
extension Tutorial: CustomStringConvertible { var description: String { var scheduled = ", not scheduled" if let day = day { scheduled = ", scheduled on \(day)" } return title + scheduled } } |
Now add an array to hold the tutorials:
var tutorials: [Tutorial] = [] |
Next, convert the array of dictionaries from the starter project to an array of tutorials by adding the following code at the end of the playground:
for dictionary in json { var currentTitle = "" var currentDay: Day? = nil for (key, value) in dictionary { // todo: extract the information from the dictionary } let currentTutorial = Tutorial(title: currentTitle, day: currentDay) tutorials.append(currentTutorial) } |
Here, you iterate over the json
array with the for-in
statement. For every dictionary in this array, you iterate over the key and value pairs in the dictionary by using a tuple with the for-in
statement. This is the tuple pattern in action.
You add each tutorial to the array, but it is currently empty—you are going to set the tutorial’s properties in the next section with the type-casting pattern.
To extract the tutorial information from the dictionary, you’ll use a type-casting pattern. Add this code inside the for (key, value) in dictionary
loop, replacing the placeholder comment:
// 1 switch (key, value) { // 2 case ("title", is String): currentTitle = value as! String // 3 case ("day", let dayString as String): if let dayInt = Int(dayString), let day = Day(rawValue: dayInt - 1) { currentDay = day } // 4 default: break } |
Here’s what’s going on, step by step:
Int
first and then into a day of the week with the Day
enumeration’s failable initializer init(rawValue:)
. You subtract 1 from the dayInt
variable because the enumeration’s raw values start at 0, while the days in tutorials.json start at 1.switch
statement should be exhaustive, so you add a default
case. Here you simply exit the switch with the break
statement.Add this line of code at the end of the playground to print the array’s content to the console:
print(tutorials) |
As you can see, each tutorial in the array has its corresponding name and scheduled day properly defined now. With everything set up, you’re ready to accomplish your task: schedule only one tutorial per day for the whole week.
You use the wildcard pattern to schedule the tutorials, but you need to unschedule them all first. Add this line of code at the end of the playground:
tutorials.forEach { $0.day = nil } |
This unschedules all tutorials in the array by setting their day to nil
. To schedule the tutorials, add this block of code at the end of the playground:
// 1 let days = (0...6).map { Day(rawValue: $0)! } // 2 let randomDays = days.sorted { _ in random_uniform(value: 2) == 0 } // 3 (0...6).forEach { tutorials[$0].day = randomDays[$0] } |
There’s a lot going on here, so let’s break it down:
random_uniform(value:)
function is used to randomly determine if an element should be sorted before or after the next element in the array. In the closure, you use an underscore to ignore the closure parameters, since you don’t need them here. Although there are technically more efficient and mathematically correct ways to randomly shuffle an array this shows the wildcard pattern in action!Add this line of code at the end of the playground to print the scheduled tutorials to the console:
print(tutorials) |
Success! You now have one tutorial scheduled for each day of the week, with no doubling up or gaps in the schedule. Great job!
The schedule has been conquered, but as editor-in-chief you also need to sort the tutorials. You’ll tackle this with optional patterns.
To sort the tutorials
array in ascending order—first the unscheduled tutorials by their title and then the scheduled ones by their day—add the following block of code at the end of the playground:
// 1 tutorials.sort { // 2 switch ($0.day, $1.day) { // 3 case (nil, nil): return $0.title.compare($1.title, options: .caseInsensitive) == .orderedAscending // 4 case (let firstDay?, let secondDay?): return firstDay.rawValue < secondDay.rawValue // 5 case (nil, let secondDay?): return true case (let firstDay?, nil): return false } } |
Here’s what’s going on, step-by-step:
tutorials
array with the array’s sort(_:)
method. The method’s argument is a trailing closure which defines the sorting order of any two given tutorials in the array. It returns true
if you sort the tutorials in ascending order, and false
otherwise.nil
, so you sort them in ascending order by their title using the array’s compare(_:options:)
method.Add this line of code at the end of the playground to print the sorted tutorials:
print(tutorials) |
There—now you’ve got those tutorials ordered just how you want them. You’re doing so well at this gig that you deserve a raise! Instead, however … you get more work to do.
Now let’s use the enumeration case pattern to determine the scheduled day’s name for each tutorial.
In the extension on Tutorial
, you used the enumeration case names from type Day
to build your custom string. Instead of remaining tied to these names, add a computed property name
to Day
by adding the following block of code at the end of the playground:
extension Day { var name: String { switch self { case .monday: return "Monday" case .tuesday: return "Tuesday" case .wednesday: return "Wednesday" case .thursday: return "Thursday" case .friday: return "Friday" case .saturday: return "Saturday" case .sunday: return "Sunday" } } } |
The switch
statement in this code matches the current value (self
) with the possible enumeration cases. This is the enumeration case pattern in action.
Quite impressive, right? Numbers are cool and all, but names are always more intuitive and so much easier to understand after all! :]
Next you’ll add a property to describe the tutorials’ scheduling order. You could use the enumeration case pattern again, as follows (don’t add this code to your playground!):
var order: String { switch self { case .monday: return "first" case .tuesday: return "second" case .wednesday: return "third" case .thursday: return "fourth" case .friday: return "fifth" case .saturday: return "sixth" case .sunday: return "seventh" } } |
But doing the same thing twice is for lesser editors-in-chief, right? ;] Instead, take a different approach and use the expression pattern. First you need to overload the pattern matching operator in order to change its default functionality and make it work for days as well. Add the following code at the end of the playground:
func ~=(lhs: Int, rhs: Day) -> Bool { return lhs == rhs.rawValue + 1 } |
This code allows you to match days to integers, in this case the numbers 1 through 7. You can use this overloaded operator to write your computed property in a different way.
Add the following code at the end of the playground:
extension Tutorial { var order: String { guard let day = day else { return "not scheduled" } switch day { case 1: return "first" case 2: return "second" case 3: return "third" case 4: return "fourth" case 5: return "fifth" case 6: return "sixth" case 7: return "seventh" default: fatalError("invalid day value") } } } |
Thanks to the overloaded pattern matching operator, the day
object can now be matched to integer expressions. This is the expression pattern in action.
Now that you’ve defined the day names and the tutorials’ order, you can print each tutorial’s status. Add the following block of code at the end of the playground:
for (index, tutorial) in tutorials.enumerated() { guard let day = tutorial.day else { print("\(index + 1). \(tutorial.title) is not scheduled this week.") continue } print("\(index + 1). \(tutorial.title) is scheduled on \(day.name). It's the \(tutorial.order) tutorial of the week.") } |
Notice the tuple in the for-in
statement? There’s the tuple pattern again!
Whew! That was a lot of work for your day as editor-in-chief, but you did a fantastic job—now you can relax and kick back by the pool.
Just kidding! An editor-in-chief’s job is never done. Back to work!
Here’s the final playground. For further experimentation, you can play around with the code in the IBM Swift Sandbox.
If you want to read more about pattern matching in Swift, check out Greg Heo’s Programming in a Swift Style video at RWDevCon 2016.
I hope you find a way to use pattern matching in your own projects. If you have any questions or comments, please join the forum discussion below! :]
The post Pattern Matching in Swift appeared first on Ray Wenderlich.
Join Mic, Jake, and Andrew as they discuss whether you can build a sustainable business developing macOS apps, and what tools are available to support you. The team then move on to looking at the changes to the App Store subscription model that were announced just before WWDC and what benefits they deliver to both consumers and developers.
[Subscribe in iTunes] [RSS Feed]
Hired is the platform for the best iOS developer jobs.
Candidates registered with Hired receive an average of 5 offers on the platform, all from a single application. Companies looking to hire include Facebook, Uber and Stripe.
With Hired, you get job offers and salary and/or equity before you interview, so you don’t have to waste your time interviewing for jobs you might not end up wanting, and it’s totally free to use!
Plus you will receive a $2000 bonus from Hired if you find a job through the platform, just for signing up using the show’s exclusive link.
Interested in sponsoring a podcast episode? We sell ads via Syndicate Ads, check it out!
We hope you enjoyed this episode of our podcast. Be sure to subscribe in iTunes to get notified when the next episode comes out.
We’d love to hear what you think about the podcast, and any suggestions on what you’d like to hear in future episodes. Feel free to drop a comment here, or email us anytime at podcast@raywenderlich.com.
The post Building macOS apps for profit, and iTC subscription changes – Podcast S06 E06 appeared first on Ray Wenderlich.
Learn how to use the new speech framework to do live speech recognition in this iOS 10 screencast.
The post iOS 10 Screencast: Live Speech Recognition appeared first on Ray Wenderlich.
Good news – the second early access release of iOS 10 by Tutorials is now available!
This release has 6/14 chapters ready:
let cycleRide = Measurement(value: 25, unit: UnitLength.kilometers) let swim = Measurement(value: 3, unit: UnitLength.nauticalMiles) let marathon = Measurement(value: 26, unit: UnitLength.miles) + Measurement(value: 385, unit: UnitLength.yards) |
This is the second of many early access releases for the book – stay tuned for some more early access releases soon!
As you probably know, in addition to making the book this year, we are also covering the content in screencast form! That way, you can choose how you prefer to learn: either written or video form.
To celebrate the second early access release of the book, we’re releasing another free iOS 10 screencast so you can see what they’re like.
This free screencast is on Measurements and Units, which covers the new classes in Foundation that make working with measurements much easier and less error prone. Enjoy!
Here’s how you can get your hands on the early access release of the book:
If you haven’t subscribed to raywenderlich.com yet, you should subscribe! You will get access to the book, the screencasts, and access to our entire video tutorial library.
Now’s a great time to subscribe because we’re currently running a special launch celebration for our new videos site. Subscribe for a monthly subscription by next Tuesday, Aug 23 to get 50% off your first month with the following coupon code: RW1MV
Thanks again to all raywenderlich.com subscribers – you are what makes this site possible. We hope you enjoy the iOS 10 book and screencasts – and stay tuned for more early access releases soon! :]
The post iOS 10 by Tutorials: First 6 Chapters Now Available! appeared first on Ray Wenderlich.
In this episode, you'll learn how to make choices based on your data through the use of conditionals. Otherwise known as if statements.
The post Screencast: Beginning C# Part 8: Conditionals appeared first on Ray Wenderlich.
If you’ve ever tried a programming challenge, then you know they are fun and addicting.
But they’re more than just fun – they also help you become a better developer.
Over the past few years, I’ve done hundreds of programming challenges. I’ve noticed the regular practice has made me a smarter, faster, and stronger developer.
In this article, I’ll explain how you can do the same:
Get ready to get challenged!
To understand why programming challenges work, first you need to understand muscle memory.
Think back to when you were first learning how to drive. At first, it required your complete focus. You had to think about how fast you were going, worry when you were parking or merging into traffic, and so on. But now, you can drive while talking with someone and even singing your favorite song!
At first, performing an unfamiliar action takes focus. But if you practice enough, you can preform the action almost without thinking.
That’s muscle memory: knowing how to perform an action without conscious effort. And you can develop muscle memory for programming tasks too!
Think back to when you first started programming on a new platform. It took a lot of effort at first, right? Often you needed to look up method names, read documentation, and so on.
But after a while, you developed muscle memory, and are now able to write code off the top of your head.
Consider a few other common programming tasks:
When you first try these things, it can often be time consuming or confusing. But if you practice it enough, you will build a muscle memory with the solution.
At this point, you won’t need to think about how to solve the problem anymore. it will just become a normal routine when writing code.
This explains why programming challenges work. They give you opportunities to build muscle memory, via practice on common development challenges.
That way, the next time you need to solve a similar challenge, you’ll have a solution ready! This helps you be efficient and allows you to focus on larger problems, rather than the small details.
Ready to start building some muscle memories? Luckily, there are some great sites out there offering programming challenges.
Here’s what I consider the best. I recommend you pick one that sounds interesting and give it a shot!
HackerRank, formerly known as Interview Street, is a platform offering coding challenges, usually in the form of algorithms to implement. I love its slogan: “Your most productive distraction“.
On HackerRank, you’ll find:
Big O notation: What is it? It measures the complexity of an algorithm, and it’s used to figure out the expected processing time depending from the problem size.
It usually makes sense when the algorithm performs one or more loops, where the number of iterations is in strict relation with the input size.
For more details, check out our Collection Data Structures in Swift article.
Employers use time-limited challenges on HackerRank to evaluate potential candidates’ programming skills and problem-solving abilities with any of its available languages.
Note that employers can choose to reduce the list of available languages — I presume that a company looking for an Android developer wouldn’t be interested in a candidate who excels in Swift development. Well, at least not today. Tomorrow, who knows? :]
I recently participated in a screening process after getting an invite from Amazon — yes, that Amazon. :] The coding experience was pretty good and the available time was reasonable; I finished it in 1:58, just two minutes shy of the two-hour time limit.
Although the environment includes a web-based editor where you can write code and test, I always prefer a local, desktop-class IDE. In this case, I opted for CodeRunner, a coding editor that supports several languages and capable of compiling and running directly from the IDE.
To some extent, it’s similar to Swift playgrounds. The list of available languages and environments is pretty long — 45 in total — which includes new, old, OO, functional, imperative, shell, DB, and so forth. Just to name a few:
Among all the services reviewed in this article, MathFights is the only one where you don’t code. Actually, it’s not even about programming. As its name suggests, it’s about math challenges that are usually served as 1:1 fights.
Why mathematics? Well, this article is about keeping your brain sharp, and solving math problems does that. Practicing mathematics gives your brain elasticity.
Moreover, you use math daily as a developer, so being able to do calculations quickly can help in your daily life.
How it Works
You start a fight with a click of a button, then you’re matched with a random opponent and the fight begins!
You and your opponent are presented with one question at a time along with a list of potential answers. Your challenge is to determine the correct answer in the shortest possible time, staying within the predefined time limit.
Each question has a winner and loser; the winner is whomever chooses the right answer in the least amount of time. For each round, you and your opponent get a score. At the end of the fight, the player with the highest total score wins.
Pro tip: Keep some paper, a pencil and maybe a calculator nearby because you will have to calculate.
Math problems come in different shades of complexity, so MathFights uses divisions to group questions by difficulty. Division 1 hosts the simplest problems and is where you start. After a certain number of wins, you’re promoted to the next division. Look out: you’ll get a downgrade if you lose too many fights.
You can also create public or private tournaments, which is very handy when you and some friends want to engage in some friendly, intelligent competition.
Lastly, there’s a practice section where you can just solve problems without the pressure of a real opponent. It’s just you against yourself and a clock.
In my personal opinion, MathFights is undeniably addictive and remarkably useful. I used to spend a lot of time on it. One day I realized that it was becoming a distraction, so I decided to stop. This was after reaching division 7. Incidentally, I don’t know that I could have progressed much further. Staying there without getting a downgrade was pretty hard.
TopCoder was the one of the first (if not the first) service to offer coding challenges.
Whereas similar services offer explicit recruitment services to customers, or don’t have a business model at all, Topcoder has a few models that are probably unique in this area.
You, as a developer, can solve challenges, but can also participate in events that are somewhere between a paid gig and a contest. In fact, companies can create an event if they need fresh ideas for development projects, such as:
Candidates compete to win a payment from the company. As you might expect, there’s a lot of competition, and usually only one winner per project. On the bright side, it’s absolutely an excellent way to test your capabilities on a real, tangible project.
The list of companies you might potentially work for are respectable names, such as:
There are also recurring tournaments, single-round matches and other contests, some of which allow or require teams. If you opt for a team contest, you collaborate with others in a hackathon-like format to solve a problem.
There’s a lot more to TopCoder than can reasonably be discussed here — covering it all would monopolize this article. Have a look, explore, then feel free to talk about what you find in our forums!
CodinGame is about coding games, but it’s not a freeform blank sheet of paper where you have to invent and draw your game.
Don’t let the presence of the word “Game” fool you into thinking this is a casual site — it’s not. It is, however, a unique way to stimulate your brain. In coding game you’re given:
Your task is to write the AI. Pretty awesome and interesting, eh?
The site provides all the graphics, animations, effects, scenography, etc. CodinGame is a bit like being on a movie set where you’re the director; you write your script to control your actors, and then you watch them execute that script.
The list of languages is pretty long — 25 in total. For you Apple Developers, there’s Objective-C and Swift.
How does it work? You have a browser-based IDE, and it’s a full featured build that includes a:
The editor comes prepopulated with some skeleton code and a clear indication of where to write yours. Once you’re done coding you start running test cases.
When running a test case, the simulator actually shows the game with the AI-controlled actor following the orders you gave with your code. By the end of the simulation you know whether the test passed or not. If not, most likely your actor was killed. :]
CodinGame provides you with a rare opportunity to watch your code come to life — and it’s as amusing as it is stimulating. If you like games and coding, then this is the place to be.
Following an established pattern for coding challenge platforms, CodinGame also offers services that are designed for companies that want to hire candidates.
Now tell me: when you’re looking for your next job, which of the following two would you choose to show your expertise?
The choice is clear. Killer AI bots are always the better choice for showing off your skills. :]
As soon as you land to the CodeFights, you immediately notice the appealing superhero avatars. Disappointingly, you can’t use them for your own profile — they’re just there for looks.
However, the site is not about cool avatars; it’s about coding and debugging.
Once you’ve registered, you’ll get an invite to a 1:1 fight with a bot. It’s pretty easy — any developer with 1 day of experience should be able to win the fight.
Each challenge consists of a three-round fight, and there are three types of challenges you need to solve:
Each round comes with a description of the problem to solve.
One downside of this otherwise useful service is the restricted list of languages. There’s also no Swift or Objective-C, so iOS and macOS developers may feel some disappointment.
On the upside, you have its browser-based editor that sports autocompletion and smart navigation — you can use CTRL + left or right arrow to move to the previous/next word, even with camel case identifiers.
Besides battling bots, what else can you do on CodeFights?
CodeEval seems like a front end for a recruitment business. While that’s not entirely a negative thing, it is the feeling I got after registering and accessing to the dashboard. But that’s just my subjective take.
Once in the dashboard, I spent some time wondering what to do next. Unlike the other services reviewed here, there’s no guidance and no “check this” nor “do that” kind of UI. You have to explore on your own.
The number of available languages is 26. You won’t find Swift, but Objective-C made the cut. Besides the dashboard, which looks very nice, the rest of the experience is a little spartan.
The editor is suitable. It has auto-complete, but the comment font color is a little difficult to read. Copying and pasting into an external editor is highly recommended.
At CodeEval, there is only one type of challenge: you versus yourself. There are three lists of challenges that are divided by difficulty.
Another potentially useful section is the “Jobs & Offers” page. Here you can find job offers, some of which require you to solve a problem in order to apply — most likely, it’ll require coding. The rest of the posts are more traditional, static job ads.
There are two things that I really love about Advent of Code:
Rather than having an IDE, a list of available languages, complexity, Big O notation, and so forth, Advent of Code just asks you to provide an answer to a question.
How you find the solution does not matter. Nobody cares what language your work in or how efficient your algorithm is. Advent of Code offers complete freedom of choice.
Challenges are fun and fulfilling but not the only way to keep your brain sharp. There are other ways to stimulate your brain! Here are some of the best:
I’d like to wrap up with a few tips and tricks.
As you may have noticed, many of these programming challenge sites are competitive.
As humans, we’re naturally competitive. We compete on almost everything:
Competition often drives us to try harder than we would otherwise. This can be great when you’re trying to improve your coding skills.
But there are a few gotchas about competition I’d like to point out.
Competition is Not for Bragging
Competition is a tool to improve yourself, what you’re working on, and the people around you. It is not a way to prove you’re superior.
Having this attitude can cause significant problems in the workplace, as Sarah Mei notes:
So remember to stay focused on self-improvement, not superiority. As Swift team member Gemma Barlow said, “…friendly rivalry is always good as long as you’re striving to create a better product for your users”.
Competition has a Time and Place
Another thing to note is competition isn’t always the proper solution.
Unity team member Barbara Reichart said that she prefers the following:
Finally, Gemma points out that competition isn’t for everyone. “It’s good to acknowledge that not all personality types thrive in a competitive environment, and there are other ways to keep your brain sharp.”
So remember – competition is a tool, and deploy it only when appropriate!
These programming challenge sites can often be addicting, as I know from experience!
But be sure not to overdo it – your brain can only take in so much at a time. It’s better to be consistent and space it out.
Gemma put it well: “I would prefer to be getting some sunshine and resting my brain rather than continuing to stress it”.
That’s a good piece of advice. Don’t expect too much from your brain after you use it all day long. Would you expect your legs to run all day or your arms to do pushups nonstop?
Give it a break and let it relax, from time to time. Just don’t abuse it.
I hope that I’ve convinced you to give programming challenges a try!
Note there are more great places for programming challenges beyond what I’ve covered here. This article focused on those that I’ve used so that I could speak from experience.
I asked our readers what other sites they recommend, and they gave the following list:
Remember, although traditional learning and a challenging workplace are valuable tools, they’re not enough.
To be the best developer you can be, you want to build your muscle memory. You should look outside your comfort zone and work with unfamiliar tools and problems. That’s what programming challenges are all about!
I recommend you take a site mentioned in this article and give it a shot! Whatever you choose, what’s most important is that you do it regularly.
That’s it for now. If you have any questions, comments or suggestions, please join the discussion below!
raywenderlich.com tutorial team members:
raywenderlich.com readers:
I may not have quoted everybody, but trust me when I say that their input helped me a lot shaping and giving this article a direction!
The post How To Be a Better Developer with Programming Challenges appeared first on Ray Wenderlich.