Learn some of the basics of Core data including modeling your data, adding objects, and fetching data.
The post Video Tutorial: Beginning Core Data Part 1: Getting Started appeared first on Ray Wenderlich.
Learn some of the basics of Core data including modeling your data, adding objects, and fetching data.
The post Video Tutorial: Beginning Core Data Part 1: Getting Started appeared first on Ray Wenderlich.
I recently came across Howard Perlman from Stained Glass Logos, who specializes in making beautiful stained glass logos for technologies we know and love, like Ruby on Rails, WordPress, or Linux:
I thought these looked amazing, so I asked Howard if he’d be willing to make something special for the readers of raywenderlich.com. He then made this amazing Swift logo:
Looks amazing, eh? I know I’d love one of these on my patio!
Howard has kindly offered to donate this to a lucky reader of raywenderlich.com, to help spread the word about his stained glass logo business.
To enter to win this unique prize, simply leave a comment on this post answering the following question:
In 24 hours, we’ll choose a random lucky winner. To be eligible to win, you must live in the United States. Also, raywenderlich.com team members are not eligible to win – sorry guys and gals! :]
If you have a logo you’d like in stained glass (whether Swift or otherwise), be sure to contact Howard – he takes custom orders. We hope you enjoy the Swift stained glass logo, and thanks for reading raywenderlich.com! :]
The post Swift Stained Glass Logo Giveaway! appeared first on Ray Wenderlich.
Even though Swift is only a year old, it’s already one of the most popular languages. Its syntax looks very easy — so easy that when it was announced JavaScript developers felt like the image to the right.
In reality, Swift, is a complex language. It embraces both object oriented and functional approaches, and it’s still evolving with each new release.
There’s a lot to Swift – but how can you test how much you’ve learned? In this article, the raywenderlich.com Tutorial Team and I have put together a list of sample Swift interview questions.
You can use these questions to interview candidates to test their Swift knowledge, or test your own! And if you don’t know the answer, don’t worry: each question has a solution so you can learn.
The questions are split into two sections:
Also, each section is split into three levels:
If you want to try answering these questions, I suggest you keep a Playground open to play with the code attached to the question before answering. Answers were tested against Xcode 7.0 beta 6.
Ready? Buckle up, it’s time to go!
Note: Special thanks to raywenderlich.com Tutorial Team members Warren Burton, Greg Heo, Mikael Konutgan, Tim Mitra, Luke Parham, Rui Peres, and Ray Wenderlich who helped me come up with some of these questions, and test them for difficulty level.
Hello there, padowan. I’ll start you off with the basics.
Question #1 – Swift 1.0 or later
What’s a better way to write this for
loop with ranges?
for var i = 0; i < 5; i++ { print("Hello!") } |
Solution Inside: Answer | SelectShow> | |||
---|---|---|---|---|
Swift implements two range operators, the closed operator and the half-open operator. The first includes all the values in a range. For example, the following includes all the integers from 0 to 4:
The half open operator doesn’t include the last element. The following produces the same 0 to 4 result:
|
Question #2 – Swift 1.0 or later
Consider the following:
struct Tutorial { var difficulty: Int = 1 } var tutorial1 = Tutorial() var tutorial2 = tutorial1 tutorial2.difficulty = 2 |
What’s the value of tutorial1.difficulty
and tutorial2.difficulty
? Would this be any different if Tutorial
was a class? Why?
Solution Inside: Answer | SelectShow> | |
---|---|---|
Structures in Swift are value types, and they are copied by value rather than reference. The following line creates a copy of
From this line on, any change to If
|
Question #3 – Swift 1.0 or later
view1
is declared with var
, and view2
is declared with let
. What’s the difference here, and will the last line compile?
import UIKit var view1 = UIView() view1.alpha = 0.5 let view2 = UIView() view2.alpha = 0.5 // Will this line compile? |
Solution Inside: Answer | SelectShow> | ||
---|---|---|---|
However,
|
Question #4 – Swift 1.0 or later
This code sorts an array of names alphabetically and looks complicated. Simplify it and the closure as much as you can.
let animals = ["fish", "cat", "chicken", "dog"] let sortedAnimals = animals.sort { (one: String, two: String) -> Bool in return one < two } |
Solution Inside: Answer | SelectShow> | ||||||
---|---|---|---|---|---|---|---|
The first simplification is related to the parameters. The type inference system can calculate the type of the parameters in the closure, so you can get rid of them:
The return type can also be inferred, so drop it:
The
In single statement closures, the
This is simpler already, but don’t stop now! For strings, there’s a comparison function defined as follows:
This neat little function makes your code as easy as:
Notice that each step of this progression compiles and outputs the same result, and you made a one-character closure!
|
Question #5 – Swift 1.0 or later
This code creates two classes, Address
and Person
, and it creates two instances to represent Ray and Brian.
class Address { var fullAddress: String var city: String init(fullAddress: String, city: String) { self.fullAddress = fullAddress self.city = city } } class Person { var name: String var address: Address init(name: String, address: Address) { self.name = name self.address = address } } var headquarters = Address(fullAddress: "123 Tutorial Street", city: "Appletown") var ray = Person(name: "Ray", address: headquarters) var brian = Person(name: "Brian", address: headquarters) |
Suppose Brian moves to the new building across the street, so you update his record like this:
brian.address.fullAddress = "148 Tutorial Street" |
What’s going on here? What’s wrong with this?
Solution Inside: Answer | SelectShow> |
---|---|
Ray also moved to the new building!
|
Now to step up the difficulty and the trickiness. Are you ready?
Question #1 – Swift 2.0 or later
Consider the following:
var optional1: String? = nil var optional2: String? = .None |
What’s the difference between nil
and .None
? How do the optional1
and optional2
variables differ?
Solution Inside: Answer | SelectShow> | ||
---|---|---|---|
There is no difference. In fact, this statement outputs true:
Remember that under the hood an optional is an enumeration:
|
Question #2 – Swift 1.0 or later
Here’s a model of a thermometer as a class and a struct:
public class ThermometerClass { private(set) var temperature: Double = 0.0 public func registerTemperature(temperature: Double) { self.temperature = temperature } } let thermometerClass = ThermometerClass() thermometerClass.registerTemperature(56.0) public struct ThermometerStruct { private(set) var temperature: Double = 0.0 public mutating func registerTemperature(temperature: Double) { self.temperature = temperature } } let thermometerStruct = ThermometerStruct() thermometerStruct.registerTemperature(56.0) |
This code fails to compile. Where? Why?
Tip: Read it carefully and think about it a bit before testing it in a Playground.
Solution Inside: Answer | SelectShow> |
---|---|
The compiler will complain about the last line. The In structures, methods that change the internal state must be marked as
|
Question #3 – Swift 1.0 or later
What will this code print out and why?
var thing = "cars" let closure = { [thing] in print("I love \(thing)") } thing = "airplanes" closure() |
Solution Inside: Answer | SelectShow> | |
---|---|---|
It’ll print If you omit the capture list in the closure, then the compiler uses a reference instead of a copy. In this case, any change to the variable is reflected when the closure is invoked, as in the following code:
|
Question #4 – Swift 2.0 or later
Here’s a global function that counts the number of unique values in an array:
func countUniques<T: Comparable>(array: Array<T>) -> Int { let sorted = array.sort(<) let initial: (T?, Int) = (.None, 0) let reduced = sorted.reduce(initial) { ($1, $0.0 == $1 ? $0.1 : $0.1 + 1) } return reduced.1 } |
It uses <
and ==
operators, so it restricts T
to types that implement, the Comparable
protocol.
You call it like this:
countUniques([1, 2, 3, 3]) // result is 3 |
Rewrite this function as an extension method on Array
so that you can write something like this:
[1, 2, 3, 3].countUniques() // should print 3 |
Solution Inside: Answer | SelectShow> | ||
---|---|---|---|
In Swift 2.0, generic types can be extended with conditions by enforcing type constraints. If the generic type doesn't satisfy the constraint, the extension is neither visible nor accessible. So the global
Notice that the new method is available only when the generic
|
Question #5 - Swift 2.0 or later
Here's a function to calculate divisions given to two (optional) doubles. There are three preconditions to verify before performing the actual division:
nil
valuenil
valuefunc divide(dividend: Double?, by divisor: Double?) -> Double? { if dividend == .None { return .None } if divisor == .None { return .None } if divisor == 0 { return .None } return dividend! / divisor! } |
This code works as expected but has two issues:
guard
statementImprove this function using the guard
statement and avoid the usage of forced unwrapping.
Solution Inside: Answer | SelectShow> | ||||
---|---|---|---|---|---|
The new
It can also be used for optional binding, making the unwrapped variable accessible after the
So the
Notice the absence of the implicitly unwrapped operators on the last line, because both One more thing. You can group
There are now only two guard statements, because you specify the non-zero condition using the
|
Oh, hi there. You're now at the midpoint and I see you're going strong. Shall we see how you fare with some advanced questions?
Question #1 - Swift 1.0 or later
Consider the following structure that models a thermometer:
public struct Thermometer { public var temperature: Double public init(temperature: Double) { self.temperature = temperature } } |
To create an instance, you can obviously use this code:
var t: Thermometer = Thermometer(temperature:56.8) |
But it would be nicer to initialize it this way:
var thermometer: Thermometer = 56.8 |
Can you? How? Hint: it has to do with convertibles, but not convertibles like Camaros and Mustangs :)
Solution Inside: Answer | SelectShow> | ||
---|---|---|---|
Swift defines the following protocols that enable a type to be initialized with literal values by using the assignment operator:
Adopting the corresponding protocol and providing a public initializer allows literal initialization of a specific type. In the case of the
And now you can create an instance by using a simple float.
|
Question #2 - Swift 1.0 or later
Swift has a set of predefined operators that perform different types of operations, such as arithmetic or logic. It also allows creating custom operators, either unary or binary.
Define and implement a custom ^^
power operator with the following specifications:
Int
s as parametersSolution Inside: Answer | SelectShow> | ||
---|---|---|---|
Create a new custom operator in two steps: declaration and implementation. The declaration uses the In this case, the operator is
The implementation is as follows:
Note that it doesn't take overflows into account; if the operation produces a result that
|
Question #3 - Swift 1.0 or later
Can you define an enumeration with raw values like this? Why?
enum Edges : (Double, Double) { case TopLeft = (0.0, 0.0) case TopRight = (1.0, 0.0) case BottomLeft = (0.0, 1.0) case BottomRight = (1.0, 1.0) } |
Solution Inside: Answer | SelectShow> |
---|---|
No, you cannot. A raw value type must:
In the code above, the raw type is a tuple and is not compatible -- even if its individual values are.
|
Question #4 - Swift 2.0 or later
Consider the following code that defines Pizza
as a struct and Pizzeria
as a protocol, with an extension that includes a default implementation for the method makeMargherita()
:
struct Pizza { let ingredients: [String] } protocol Pizzeria { func makePizza(ingredients: [String]) -> Pizza func makeMargherita() -> Pizza } extension Pizzeria { func makeMargherita() -> Pizza { return makePizza(["tomato", "mozzarella"]) } } |
You'll now define the restaurant Lombardi’s
as follows:
struct Lombardis: Pizzeria { func makePizza(ingredients: [String]) -> Pizza { return Pizza(ingredients: ingredients) } func makeMargherita() -> Pizza { return makePizza(["tomato", "basil", "mozzarella"]) } } |
The following code creates two instances of Lombardi's. Which of the two will make a margherita with basil?
let lombardis1: Pizzeria = Lombardis() let lombardis2: Lombardis = Lombardis() lombardis1.makeMargherita() lombardis2.makeMargherita() |
Solution Inside: Answer | SelectShow> | |
---|---|---|
They both do. The What if the protocol doesn't declare the
In this case, only
|
Question #5 - Swift 2.0 or later
The following code has a compile time error. Can you spot where and why it happens?
struct Kitten { } func showKitten(kitten: Kitten?) { guard let k = kitten else { print("There is no kitten") } print(k) } |
Hint: There are three ways to fix it.
Solution Inside: Answer | SelectShow> | |||
---|---|---|---|---|
The
Here's a version that throws an exception.
Finally, here's an implementation calling
|
You're good, but you can't claim jedi status yet. Anybody can figure out the code, but how do you do with more open-ended questions of theory and practice?
To answer some of them you still might need to play with the code in a Playground.
Question #1 - Swift 1.0 or later
What is an optional and what problem do optionals solve?
Solution Inside: Answer | SelectShow> |
---|---|
An optional is used to let a variable of any type represent the lack of value. In Objective-C, the absence of value is available in reference types only, and it uses the Swift extends the lack of value concept to both reference and value types with optionals. An optional variable can hold either a value or
|
Question #2 - Swift 1.0 or later
When should you use a structure, and when should you use a class?
Solution Inside: Answer | SelectShow> |
---|---|
There's an ongoing debate about whether using classes over structures is a good or bad practice. Functional programming tends to favor value types, whereas object-oriented programming prefers classes. In Swift, classes and structures differ by a number of features. You can sum up the differences as follows:
There's no universal rule that determines what's best to use. The general recommendation is to use the most minimal tool required to accomplish your goal, but a good rule of thumb is to use structures unless you need inheritance or reference semantics. For more details, check out this detailed post on the matter. Note on performance: At runtime, structures are more performant than classes because method invocations are statically bound, whereas method invocation for classes is dynamically resolved at runtime. That's another good reason to use structures instead of classes when possible.
|
Question #3 - Swift 1.0 or later
What are generics and what problem do they solve?
Solution Inside: Answer | SelectShow> | ||||
---|---|---|---|---|---|
Generics are used to make algorithms safely work with types. In Swift, generics can be used both in functions and data types, e.g. in classes, structures or enumerations. Generics solve the problem of code duplication. A common case is when you have a method that takes a type of parameter and you have to duplicate it just to accommodate a parameter of another type. For example, in the following code the second function is a "clone" of the first one -- it just accepts strings instead of integers.
An Objective-C developer might think to resolve with an
This code works as intended, but it's unsafe at compile time. It allows comparing a string with an integer, like this:
The application doesn't crash, but allowing the comparison of a string to an integer is probably not the intended result. By adopting generics, you can conflate the two functions into one and keep type safety at the same time. Here's the implementation:
Since you're testing equality in this case, you restrict the parameters to any type that implements the
|
Question #4 - Swift 1.0 or later
There are a few cases when you can't avoid using implicitly unwrapped optionals. When? Why?
Solution Inside: Answer | SelectShow> |
---|---|
The most common reasons to use implicitly unwrapped optionals are:
Pro tip: Don't use implicitly unwrapped optionals unless you must. Using them improperly increases the chance of runtime crashes. In some cases, a crash might be the intended behavior, but there are better ways to achieve the same result, for example, by using
|
Question #5 - Swift 1.0 or later
What are the various ways to unwrap an optional? How do they rate in terms of safety?
Hint: There are six ways.
Solution Inside: Answer | SelectShow> |
---|---|
|
Time to rachet up the challenge here. Seems you're doing just fine so far, but let's see if you make it through these questions.
Question #1 - Swift 1.0 or later
Is Swift an object-oriented language or a functional language?
Solution Inside: Answer | SelectShow> |
---|---|
Swift is a hybrid language that supports both paradigms. It implements the three fundamental principles of OOP:
As for Swift being a functional language, there are different but equivalent ways to define it. One of the more common is on Wikipedia: "…a programming paradigm [...] that treats computation as the evaluation of mathematical functions and avoids changing-state and mutable data." It's hard to argue that Swift is a full-fledged functional language, but it has the basics.
|
Question #2 - Swift 1.0 or later
Which of the following features are included in Swift?
Solution Inside: Answer | SelectShow> |
---|---|
|
Question #3 - Swift 1.0 or later
In Objective-C, a constant can be declared like this:
const int number = 0; |
Here is the Swift counterpart:
let number = 0 |
Is there any difference between them? If yes, can you explain how they differ?
Solution Inside: Answer | SelectShow> |
---|---|
A An immutable created via
|
Question #4 - Swift 1.0 or later
To declare a static property or function you use the static
modifier on value types. Here's an example for a structure:
struct Sun { static func illuminate() {} } |
For classes, it's possible to use either the static
or the class
modifier. They achieve the same goal, but in reality they're different. Can you explain how they differ?
Solution Inside: Answer | SelectShow> | |
---|---|---|
When applied to classes, For example, in this code the compiler will complain when you try to override
|
Question #5 - Swift 1.0 or later
Can you add a stored property by using an extension? Explain.
Solution Inside: Answer | SelectShow> |
---|---|
No, it's not possible. An extension can be used to add new behavior to an existing type, but not to alter the type itself or its interface. If you add a stored property, you'd need extra memory to store the new value. An extension cannot manage such a task.
|
Oh boy, you're a clever one, aren't you? It's time to step it up another notch.
Question #1 - Swift 1.2
In Swift 1.2, can you explain the problem with declaring an enumeration with generic types? Take for example an Either
enumeration with two generic types T
and V
, with T
used as the associated value type for a Left
case and V
for a Right
case:
enum Either<T, V> { case Left(T) case Right(V) } |
Pro tip: Inspect this case in an Xcode project, not in a Playground. Also notice that this question is related to Swift 1.2 so you'll need Xcode 6.4.
Solution Inside: Answer | SelectShow> | ||
---|---|---|---|
Compilation fails with the following (cryptic) error message:
The problem is that the memory size of The most used workaround is to box the generic type into a reference type, conventionally called
This problem affects only Swift 1.0 or later, but it has been solved in 2.0.
|
Question #2 - Swift 1.0 or later
Are closures value or reference types?
Solution Inside: Answer | SelectShow> |
---|---|
Closures are reference types. If a closure is assigned to a variable and the variable is copied into another variable, a reference to the same closure and its capture list is also copied.
|
Question #3 - Swift 1.0 or later
The UInt
type is used to store unsigned integers. It implements the following initializer for converting from a signed integer:
init(_ value: Int) |
However, the following code generates a compile time error exception if you provide a negative value:
let myNegative = UInt(-1) |
Knowing that a negative number is internally represented, using two's complement as a positive number, how can you convert an Int
negative number into an UInt
while keeping its memory representation?
Question #4 - Swift 1.0 or later
Can you describe a situation where you might get a circular reference in Swift, and how you'd solve it?
Solution Inside: Answer | SelectShow> |
---|---|
A circular reference happens when two instances hold a strong reference to each other, causing a memory leak because none of two instances will ever be deallocated. The reason is that an instance cannot be deallocated as long as there's a strong reference to it, but each instance keeps the other alive because of its strong reference. You'd solve the problem by breaking the strong circular reference by replacing one of the strong references with a
|
Question #5 - Swift 2.0 or later
Swift 2.0 features a new keyword to make recursive enumerations. Here is an example of such an enumeration with a Node
case that takes two associated value types, T
and List
:
enum List<T> { case Node(T, List<T>) } |
What's that keyword?
Solution Inside: Answer | SelectShow> | |
---|---|---|
It's the
|
Congrats on making it to the end, and don't feel bad if you didn't actually know all the answers!
Some of these questions are pretty complicated and Swift is a rich, expressive language. There's a lot to learn. Moreover, Apple keeps improving it with new features, so even the best of us may not know it all.
To get to know Swift or build upon what you already know, be sure to check out our in-depth, tutorial-rich book, Swift by Tutorials, or sign up for our hands-on tutorial conference RWDevCon!
Of course, the ultimate resource for all aspects of Swift is the official The Swift Programming Language by Apple.
At the end of the day, using a language is the best way to learn it. Just play with it in a Playground or adopt it in a real project. Swift works (almost) seamlessly with Objective-C, so building on an existing project you already know is an excellent way to learn the ins and outs.
Thanks for visiting and working through these questions! Feel free to chime in below with your questions, problems and discoveries. I wouldn't mind if you wanted to pose some of your own challenges too. We can all learn from each other. See you in the forums!
The post Swift Interview Questions and Answers appeared first on Ray Wenderlich.
Update note: This tutorial was updated for iOS 8 and Swift by Ray Fix. Original tutorial was by site editor-in-chief Ray Wenderlich.
One of the great things about being an iOS devleoper is that there are a variety of models you can use to make money off of your apps, including paid, free with ads, and in-app purchases.
In-app purchases are a particularly compelling option, for several reasons:
You can use In-App Purchases with varying business models. For example, the Ray Wenderlich app Wild Fables comes with three stories included with more available as in-app purchases. Battle Map 2 is an example of a paid app with optional extra content as in-app purchases.
In this tutorial, you’ll learn how to use in-app purchases to unlock local content embedded in your app.
This tutorial assumes that you are familiar with basic Swift and iOS programming concepts. If these concepts are new to you, check out some of the other tutorials on this site.
For this tutorial, you’re going to make a little app called In App Rage. The app allows users to buy rage comics, sometimes called “F7U12”. Readers of this site will undoubtedly recognize the genre. They’re basically funny little comics where someone goes through a common and frustrating situation, resulting in a wild rage or other humorous expression.
Before you can start coding, you’ll need to create a placeholder app in the iOS Developer Center and iTunes Connect.
First, log into the iOS Developer Center. Select Identifiers under iOS Apps and then select the App IDs tab. Click the + button and complete the form like the following:
You must change the bundle identifier to have your own unique prefix. A common practice is to use your domain name in reverse. If all fails use a made-up one based on your name or something else unique.
Notice that In-App Purchase (and GameKit) are enabled by default. When you’re done, click Continue and then Submit. Viola – you have a new App ID! Now you’ll use it to create a new app in iTunes Connect.
Log onto iTunes Connect, click My Apps then + to add a new iOS App. If you’re prompted to choose an app type, select New iOS App (obviously). Then complete the form as shown below:
If you are quick in getting to this step, you might notice that the Bunde ID is not showing up in the dropdown list. Apparently, this takes time to propagate. Take this opportunity to obey your Apple Watch, stand up, and walk around the block. Refresh the page when you get back and hopefully it will be there.
Also you’ll have to tweak the app Name, because app names need to be unique across the App Store. I’ve added an entry for this one. Maybe replace the RAF with your own initials.
The reason you just created a placeholder app is that before you can code in-app purchases, you have to set them up in iTunes Connect. Now that you have a placeholder app, just click In-App Purchases, as shown below:
Then click Create New in the upper left corner:
You will get to a screen that lets you select the type of In-App Purchase you want to add. Note that there are are two types of frequently-used In-App Purchases:
For In App Rage, you are going to be selling comics. Once the user purchases them, they should always have them, so choose Non-Consumable.
We’ll talk more about how to allow the user to restore the non-consumable content they purchased on other devices later.
There is no such requirement for consumables though – consumables can be for just the device the user bought them on. If you want consumables to be cross-device, you’d have to implement that yourself with iCloud or some other technology.
Next, you will be taken to a page to enter some information about your in-app purchase. Fill in the fields according to the screenshot below:
Let’s cover what each of these fields means:
After you’ve set that up, scroll down to the Language section and click Add Language. Fill out the form that pops up with the following information:
This information will be returned to you when you query the App Store later on for the in-app purchases that are available. Prices will be in the correct currency for the part of the world you are selling in and you can also enable/disable these purchases on the fly. Don’t worry about the descriptions – you won’t be using them in this tutorial, so you can just use the Display Name for those.
You will also notice that there are fields for review Notes and Screenshots. While ultimately you will need to provide these for Apple’s review process, you do not need them for testing in the sandbox.
In order for the sample app to work without changes, your Product ID should take the form “YYYYY.XXXXX” where YYYYY is your unique name (mine was org.rayfix.inapprage) and XXXXX is the name of the image to be displayed. The names are: nightlyrage
, girlfriendofdrummer
, iphonerage
, and updog
.
You can now click Save. Great you just created your first In-App purchase. Now, repeat the process three more times for the remaining purchases. When you’re done, your purchases should look like this:
You might notice that this process takes a while. I could imagine it gets annoying if you have a ton of in-app purchases in your app. Luckily you’re not in that situation, If you are in your app, draw me a rage comic :]
Download the starter project, unzip it and open in Xcode. Open MasterViewController.swift. This class imports StoreKit
and displays a table view of available in-app purchases. Purchases are stored in an array of SKProduct
objects. Each row (if not purchased) has a “Buy” button that lets you purchase the product. An NSNumberFormatter
is used to show the localized price. Once purchased, you can view the comic for that purchase using the detail view. Finally, a “Restore” button lets you restore all previous purchases.
You will notice that MasterViewController.swift is using an object called RageProducts.store of type IAPHelper
to do the heavy lifting. However, this class is currently stubbed out. If you build and run the app you will not run at this point.
For anything to work, you need to match up the bundle identifier and product identifiers in your app to the same ones you entered in iTunes Connect.
Select your project target in Project Navigator and then the General tab Change the value of Bundle Identifier to match. I used “org.rayfix.inappragedemo” but yours will be different.
Next change the product identifiers to match what you entered. Open RageProducts.swift and notice the list of four in-app purchases.
You probably only need to change the Prefix
constant to match what you used in the previous sections. (It is marked with the TODO comment.)
The RageProduct.store
is an instance of IAPHelper
. This object interacts with the StoreKit API to list and perform purchases. Open IAPHelper.swift and notice that is not yet implemented. You will now do so.
The first thing you need to do is get a list of in-app purchases from Apple’s server. Add the following private properties to IAPHelper
class.
/// MARK: - Private Properties // Used to keep track of the possible products and which ones have been purchased. private let productIdentifiers: Set<ProductIdentifier> private var purchasedProductIdentifiers = Set<ProductIdentifier>() // Used by SKProductsRequestDelegate private var productsRequest: SKProductsRequest? private var completionHandler: RequestProductsCompletionHandler? |
You will use these properties to perform your requests and keep track of what purchases have already been made. When you add this code you will immediately see a compiler error in init(productIdentifiers:)
. This is because the initialization rules of Swift dictate that you must initialize all class properties before calling super.init()
. Fix that by adding the following to init(productIdentifiers:)
:
self.productIdentifiers = productIdentifiers |
An IAPHelper
is created by passing in the set of product identifiers supported. This is how RageProducts
creates its store
instance. Next, replace the implementation of requestProductsWithCompletionHandler(_:)
/// Gets the list of SKProducts from the Apple server calls the handler with the list of products. public func requestProductsWithCompletionHandler(handler: RequestProductsCompletionHandler) { completionHandler = handler productsRequest = SKProductsRequest(productIdentifiers: productIdentifiers) productsRequest?.delegate = self productsRequest?.start() } |
This code saves away the user’s completion handler so that it can be executed later. It then creates a request and fires the request off to Apple. Since IAPHelper
does not yet conform to the SKProductsRequestDelegate
protocol, you’ll see another compiler error. Fix that by adding the following IAPHelper
extension at the end of the file:
// MARK: - SKProductsRequestDelegate extension IAPHelper: SKProductsRequestDelegate { public func productsRequest(request: SKProductsRequest!, didReceiveResponse response: SKProductsResponse!) { println("Loaded list of products...") let products = response.products as! [SKProduct] completionHandler?(success: true, products: products) clearRequest() // debug printing for p in products { println("Found product: \(p.productIdentifier) \(p.localizedTitle) \(p.price.floatValue)") } } public func request(request: SKRequest!, didFailWithError error: NSError!) { println("Failed to load list of products.") println("Error: \(error)") clearRequest() } private func clearRequest() { productsRequest = nil completionHandler = nil } } |
This extension is used to get a list of products, their titles, descriptions and prices from Apple`s server by implementing the two methods required by the SKProductsRequestDelegate
protocol.
productsRequest(_:didReceiveResponse:) is called when the list is succesfully retrieved. It receives an array of SKProduct
objects and passes them to the previously saved completion handler. The handler reloads the table with new data. If a problem occurs, productsRequest(_:didFailWithError:)
is called. In either case, when the request finishes you clear the request and completion handler with clearRequest()
.
Build and run now. You should now see a list of products in the table view. This part should work even in the simulator without a sandbox account.
Didn’t work? If this didn’t work for you, there are a number of things to check (this list courtesy of itsme.manish and abgtan from the forums:
Tried all that and still stuck? Try the old forum thread or this thread’s comments for discussion with other readers.
You want to be able to determine which items are already purchased. To do this you will use the purchasedProductIdentifiers
property you added. If a product identifier is contained in this set, the user has purchased the item. The method for checking this is straightforward. Find the isProductPurchased(_:)
function and replace the implementation with the following:
return purchasedProductIdentifiers.contains(productIdentifier) |
Every time your app starts you don’t want to have to go to Apple’s server to find out if a particular purchase has been made. It is a good idea to save this information locally. You will use NSUserDefaults
to save purchasedProductIdentifiers.
Add the following before the call to super
in your init(productIdentifiers:)
method:
for productIdentifier in productIdentifiers { let purchased = NSUserDefaults.standardUserDefaults().boolForKey(productIdentifier) if purchased { purchasedProductIdentifiers.insert(productIdentifier) println("Previously purchased: \(productIdentifier)") } else { println("Not purchased: \(productIdentifier)") } } |
For each of your product identifiers, you check to see if the value is stored in NSUserDefaults
and if it is, you insert it into the set. Later, you’ll also add an identifier to the set after a purchase is made.
That’s great, but you need to be able to make purchases. How do you do that? That is what you will implement next. Still in IAPHelper.swift, replace the purchaseProduct(_:)
implementation with the following:
/// Initiates purchase of a product. public func purchaseProduct(product: SKProduct) { println("Buying \(product.productIdentifier)...") let payment = SKPayment(product: product) SKPaymentQueue.defaultQueue().addPayment(payment) } |
This creates a payment object using a SKProduct
(which you got from the server) to add to a payment queue. There’s a singleton SKPaymentQueue
object called defaultQueue()
. Boom! Money in the bank!
How do you know if the payment went through? For that, you need your IAPHelper to observer transactions happening on the SKPaymentQueue
. Go back to your init(productIdentifiers:)
method and add the following line to the end of the function, right after super.init()
.
SKPaymentQueue.defaultQueue().addTransactionObserver(self) |
This results in a compiler error because IAPHelper
needs to conform to the SKPaymentTransactionObserver
protocol. If you think of the compiler as a helpful todo list generator, this is the next item on the list!
Go to the end of the file and add the following extension and methods:
extension IAPHelper: SKPaymentTransactionObserver { /// This is a function called by the payment queue, not to be called directly. /// For each transaction act accordingly, save in the purchased cache, issue notifications, /// mark the transaction as complete. public func paymentQueue(queue: SKPaymentQueue!, updatedTransactions transactions: [AnyObject]!) { for transaction in transactions as! [SKPaymentTransaction] { switch (transaction.transactionState) { case .Purchased: completeTransaction(transaction) break case .Failed: failedTransaction(transaction) break case .Restored: restoreTransaction(transaction) break case .Deferred: break case .Purchasing: break } } } private func completeTransaction(transaction: SKPaymentTransaction) { println("completeTransaction...") provideContentForProductIdentifier(transaction.payment.productIdentifier) SKPaymentQueue.defaultQueue().finishTransaction(transaction) } private func restoreTransaction(transaction: SKPaymentTransaction) { let productIdentifier = transaction.originalTransaction.payment.productIdentifier println("restoreTransaction... \(productIdentifier)") provideContentForProductIdentifier(productIdentifier) SKPaymentQueue.defaultQueue().finishTransaction(transaction) } // Helper: Saves the fact that the product has been purchased and posts a notification. private func provideContentForProductIdentifier(productIdentifier: String) { purchasedProductIdentifiers.insert(productIdentifier) NSUserDefaults.standardUserDefaults().setBool(true, forKey: productIdentifier) NSUserDefaults.standardUserDefaults().synchronize() NSNotificationCenter.defaultCenter().postNotificationName(IAPHelperProductPurchasedNotification, object: productIdentifier) } private func failedTransaction(transaction: SKPaymentTransaction) { println("failedTransaction...") if transaction.error.code != SKErrorPaymentCancelled { println("Transaction error: \(transaction.error.localizedDescription)") } SKPaymentQueue.defaultQueue().finishTransaction(transaction) } } |
That is a lot of code! So let’s go through it in detail. paymentQueue(_:updatedTransactions:)
is the only method actually required by the protocol. It gets called when one or more transactions’ states change. This method goes through an array of updated transactions and looks at their state. Based on that state it calls other methods defined here: completeTransaction(_:)
, restoreTransaction(_:)
or failedTransaction(_:)
.
If the transaction was completed or restored, it adds to the set of purchases and saves the identifier in NSUserDefaults
. It also posts a notification with that transaction so that any interested object in the app can listen for it to do things like update the user interface. Finally, in both the case of success or failure, it marks the transaction as finished.
If the user deletes and re-installs the app, or if they install it on another device, they need to be able to recover their purchases. In fact, Apple may reject your app if you do not implement the ability to restore non-consumable purchases.
You are already listening for when purchases have been restored. But you need to write the method that initiates it. Find the restoreCompletedTransactions()
method and add the following to it:
SKPaymentQueue.defaultQueue().restoreCompletedTransactions() |
That was almost too easy! You’ve already set the transaction observer and implemented the method to handle restoring transactions in the previous step.
While you’re running your app in Xcode, you’re not making transactions against the real in-app purchase servers – you’re running against sandbox servers.
This means you can buy things without fear of getting charged, etc. But you need to set up a test account, and also make sure you’re logged out of the store with your real account if you’re testing on device.
To make accounts, log onto iTunes Connect and click Users and Roles. Click Sandbox User then follow the buttons to create a test user.
Then go to your iPhone and make sure you’re logged out of your current account. To do this, go to the Settings app and tap iTunes & App Store. Tap your iCloud account name, and then select Sign Out.
Finally, go ahead and build and run your app and attempt to purchase a rage comic. Enter your test user account information and if all goes well, it should purchase with a happy check mark next to it. You can tap on it and see the comic! The list of purchases should look like this:
Some devices and accounts may not permit in-app purchase. This can happen, for example, if parental controls are set to disallow it. Apple requires that you handle this situation gracefully; not doing so will likely result in an app rejection.
Open IAPHelper.swift and add the following method to the class:
public class func canMakePayments() -> Bool { return SKPaymentQueue.canMakePayments() } |
When canMakePayments()
is false, your master view controller should display cells differently. For example, don’t show a “Buy” button, and simply say “Not Available” instead of listing the price.
To do this, open MasterViewController.swift and update the implementation of tableView(_:cellForRowAtIndexPath)
as follows:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! UITableViewCell let product = products[indexPath.row] cell.textLabel?.text = product.localizedTitle if RageProducts.store.isProductPurchased(product.productIdentifier) { cell.accessoryType = .Checkmark cell.accessoryView = nil cell.detailTextLabel?.text = "" } else if IAPHelper.canMakePayments() { priceFormatter.locale = product.priceLocale cell.detailTextLabel?.text = priceFormatter.stringFromNumber(product.price) var button = UIButton(frame: CGRect(x: 0, y: 0, width: 72, height: 37)) button.setTitleColor(view.tintColor, forState: .Normal) button.setTitle("Buy", forState: .Normal) button.tag = indexPath.row button.addTarget(self, action: "buyButtonTapped:", forControlEvents: .TouchUpInside) cell.accessoryType = .None cell.accessoryView = button } else { cell.accessoryType = .None cell.accessoryView = nil cell.detailTextLabel?.text = "Not Available" } return cell } |
This implementation will make the display more appropriate in the case where payments cannot be made with the device.
And there you have it – an app with in-app purchase!
Here is In App Rage Final.zip with all of the code you’ve developed above. Feel free to re-use the in-app purchase helper class.
One shortcoming of the sample app is that it doesn’t indicate to the user when it is communicating with Apple. A possible improvement would be to display a spinner or HUD control at appropriate times. This UI enhancement, however, is beyond the scope of this tutorial.
In-app purchases can be an important part of your business model – use them wisely and be sure to follow the guidelines about restoring purchases and failing gracefully, and you’ll be well on your way to success!
If any of you have questions or comments about this tutorial please join the forum discussion below!
And to end things off with a laugh, here’s a great iOS app rage comic made by Jayant C Varma from the original Objective-C version of this tutorial! :]
The post In-App Purchases Tutorial: Getting Started appeared first on Ray Wenderlich.
Learn how to use predicates to filter your data and sort descriptors to order your data in a sensible way.
The post Video Tutorial: Beginning Core Data Part 4: Predicates & Sorting appeared first on Ray Wenderlich.
In the grand finale of Season 4, Mic and Jake talk about Core Text, current projects, season highlights, and much more.
[Subscribe in iTunes] [RSS Feed]
Interested in sponsoring a podcast episode? We sell ads via Syndicate Ads, check it out!
Contact Us
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 next season. Feel free to drop a comment here, or email us anytime at podcast@raywenderlich.com.
The post Season Finale with Mic and Jake – Podcast S04 E10 appeared first on Ray Wenderlich.
Next March, we are running an iOS conference focused on high quality hands-on tutorials called RWDevCon 2016.
One of the unique things about RWDevCon is it’s coordinated as a team.
That means we can do some cool things, like let you decide the content of the conference. Here’s how it works:
There’s no other conference like this – RWDevCon is truly a conference where you decide what’s inside.
We’ll be sending out a call for suggestions tomorrow, so if you’d like to be a part of the decision making process, grab your ticket today.
Also, I just wanted to give you a heads up that the current $100 off early bird discount will be ending in 1 week; another reason to grab your ticket now.
We can’t wait to see what you choose this year! :]
The post RWDevCon 2016: Choose Your Topics! appeared first on Ray Wenderlich.
Although skeuomorphism in iOS apps is a thing of the past, that doesn’t mean you’re limited to the stock appearance of controls in your iOS app.
While you can develop your own controls and app stylings from scratch, Apple recommends that you use standard UIKit controls and take advantage of the various customization techniques in iOS. This is because UIKit controls are highly efficient, and your customizations to the controls should be mostly future-proof.
In this UIAppearance tutorial, you’ll use some basic UI customization techniques to customize a plain Pet Finder app and make it stand out from the pack! :]
Download the starter project for this tutorial here. The app has many of the standard UIKit controls and looks extremely vanilla.
Open the project and have a look around to get a feel for its structure. Build and run and you’ll see the main UI elements of Pet Finder:
There’s a navigation bar and a tab bar. The main screen shows a list of pets; tap a pet to see some details about it. As well there’s a search screen and — aha! A screen that allows you to select a theme for your app. That sounds like a pretty good place to start!
Many apps don’t allow users to select a theme, and it’s not always advisable to ship an app with a theme selector. If you have little control over the content your app displays then you may quickly find yourself in a position where one of your themes clashes with the content other’s have generated or shared. However, you might want to test different themes during development to see which ones work best for your app, or you might A/B test your app with your beta users to see which style is the most popular.
In this UIAppearance tutorial, you’ll create a number of themes for your app that you can try out to see which one is most aesthetically pleasing.
Select File\New\File… and choose iOS\Source\Swift File. Click Next and type Theme as the name of the file. Finally click Next, followed by Create. Xcode automatically opens your new file, which contains just a single line of code.
Delete the single line and replace it with the following:
import UIKit enum Theme { case Default, Dark, Graphical var mainColor: UIColor { switch self { case .Default: return UIColor(red: 87.0/255.0, green: 188.0/255.0, blue: 95.0/255.0, alpha: 1.0) case .Dark: return UIColor(red: 242.0/255.0, green: 101.0/255.0, blue: 34.0/255.0, alpha: 1.0) case .Graphical: return UIColor(red: 10.0/255.0, green: 10.0/255.0, blue: 10.0/255.0, alpha: 1.0) } } } |
This adds an enum with the different themes for your app. For now, all themes only have a mainColor
that’s specific to that particular theme.
Next, add the following struct:
struct ThemeManager { } |
This will let you use a theme in the app. It’s still empty, but that will change soon enough!
Next, add the following line right above the enum
declaration:
let SelectedThemeKey = "SelectedTheme" |
Now, add the following method to ThemeManager
:
static func currentTheme() -> Theme { if let storedTheme = NSUserDefaults.standardUserDefaults().valueForKey(SelectedThemeKey)?.integerValue { return Theme(rawValue: storedTheme)! } else { return .Default } } |
Nothing overly complex here: this is the main method you’ll use to style the app. It uses NSUserDefaults
to persist the current theme, and uses it every time you launch your app.
To test that this works, open AppDelegate.swift and add the following line to application(_:didFinishLaunchingWithOptions)
:
println(ThemeManager.currentTheme().mainColor) |
Build and run. You should the following printed to the console:
UIDeviceRGBColorSpace 0.94902 0.396078 0.133333 1 |
At this point, you have three themes and can manage them through ThemeManager
. Now it’s time to go use them in your app.
Back in Theme.swift, add the following method to ThemeManager
:
static func applyTheme(theme: Theme) { // 1 NSUserDefaults.standardUserDefaults().setValue(theme.rawValue, forKey: SelectedThemeKey) NSUserDefaults.standardUserDefaults().synchronize() // 2 let sharedApplication = UIApplication.sharedApplication() sharedApplication.delegate?.window??.tintColor = theme.mainColor } |
Here’s a quick run-through of the above code:
NSUserDefaults
.tintColor
property of your application’s window. You’ll learn more about tintColor
in just a moment.Now the only thing you need to do is to call this method. There’s no better place than in AppDelegate.swift.
Replace the println()
statement you added earlier with the following:
let theme = ThemeManager.currentTheme() ThemeManager.applyTheme(theme) |
Build and run. You’ll see that your new app looks decidedly more green:
Navigate through the app; there are green accents everywhere! But you didn’t change any of your controllers or views. What is this black — er, green — magic?! :]
Since iOS 7, UIView
has exposed the tintColor
property, which is often used to define the primary color indicating selection and interactivity states for interface elements throughout an app.
When you specify a tint for a view, the tint is automatically propagated to all subviews in that view’s view hierarchy. Because UIWindow
inherits from UIView
, you can specify a tint color for the entire app by setting the window’s tintColor
, which is exactly what you did in applyTheme()
above.
Click on the Gear icon in the top left corner of your app; a table view with a segmented control slides up, but when you select a different theme and tap Apply, nothing changes. Time to fix that.
Open SettingsTableViewController.swift and add these lines to applyTheme()
, just above dismiss()
:
if let selectedTheme = Theme(rawValue: themeSelector.selectedSegmentIndex) { ThemeManager.applyTheme(selectedTheme) } |
Here you call the method you added to ThemeManager
, which sets the selected theme’s mainColor
on the tintColor
property of the root UIWindow
instance.
Next, add the following line to the bottom of viewDidLoad()
to select the theme persisted to NSUserDefaults
when the view controller is first loaded:
themeSelector.selectedSegmentIndex = ThemeManager.currentTheme().rawValue |
Build and run. Tap the settings button, select Dark, and then tap Apply. The tint in your app will change from green to orange right before your eyes:
Eagle-eyed readers likely noticed these colors were defined in mainColor()
, in ThemeType
.
But wait, you selected Dark, and this doesn’t look dark. To get this effect working, you’ll have to customize a few more things.
Open Theme.swift and add the following two methods to Theme
:
var barStyle: UIBarStyle { switch self { case .Default, .Graphical: return .Default case .Dark: return .Black } } var navigationBackgroundImage: UIImage? { return self == .Graphical ? UIImage(named: "navBackground") : nil } |
These methods simply return an appropriate bar style and background image for the navigation bar for each theme.
Next, add the following lines to the bottom of applyTheme()
:
UINavigationBar.appearance().barStyle = theme.barStyle UINavigationBar.appearance().setBackgroundImage(theme.navigationBackgroundImage, forBarMetrics: .Default) |
Okay — why does this work here, and not earlier when you set barStyle
on your UINavigationBar
instance?
UIKit has an informal protocol called UIAppearance
that most of its controls conform to. When you call appearance()
on UIKit classes— not instances —it returns a UIAppearance
proxy. When you change the properties of this proxy, all the instances of that class automatically get the same value. This is very convenient as you don’t have to manually style each control after it’s been instantiated.
Build and run. Select the Dark theme and the navigation bar should now be much darker:
This looks a little better, but you still have some work to do.
Next, you’ll customize the back indicator. iOS uses a chevron by default, but you can code up something far more exciting! :]
This change applies to all themes, so you only need to add the following lines to applyTheme()
in Themes.swift:
UINavigationBar.appearance().backIndicatorImage = UIImage(named: "backArrow") UINavigationBar.appearance().backIndicatorTransitionMaskImage = UIImage(named: "backArrowMask") |
Here you’re simply setting the image and transition mask image to be used as the back indicator.
Build and run. Tap one of the pets and you should see the new back indicator:
Open Images.xcassets and find the backArrow image in the Navigation group. The image is all black, but in your app it takes on the tint color of your window and it just works.
But how can iOS just change the bar button item’s image color, and why doesn’t it do that everywhere?
As it turns out, images in iOS have three rendering modes:
Head back to the app, tap one of the pets and tap Adopt. Watch the animation of the back indicator in the navigation bar carefully. Can you see the problem?
When the Back text transitions to the left, it overlaps the indicator and looks pretty bad:
To fix this, you’ll have to change the transition mask image.
Update the line where you set backIndicatorTransitionMaskImage
in applyTheme()
, in Themes.swift, to the following:
UINavigationBar.appearance().backIndicatorTransitionMaskImage = UIImage(named: "backArrow") |
Build and run. Once again tap one of the pets and then tap Adopt. This time the transition looks much better:
The text is no longer cut off and looks like it goes underneath the indicator. So, what’s happening here?
While iOS uses all the non-transparent pixels of the back indicator image to draw the indicator, it does something entirely different with the transition mask image: it masks the indicator with the non-transparent pixels of the transition mask image so that when the text moves to the left, the indicator is only visible in the those areas.
In the original implementation, you provided an image that covered the entire surface of the back indicator so the text remained visible through the transition. But now you’re using the indicator image itself as the mask, but the text disappeared at the far right edge of the mask, not under the indicator proper.
Look at the indicator image and the “fixed” version of the mask in your image assets catalog; you’ll see they they line up perfectly with each other:
The black shape is your back indicator and the red shape is your mask. You want the text to only be visible when it’s passing under the red area and hidden everywhere else.
Change the last line of applyTheme()
once again, this time to use the updated mask:
UINavigationBar.appearance().backIndicatorTransitionMaskImage = UIImage(named: "backArrowMaskFixed") |
Build and run. For the last time, tap one of the pets and then tap Adopt. You’ll see that the text now disappears under the image, just as you anticipated it would:
Now that your navigation bar is pixel perfect, it’s time to give the tab bar some much-needed love.
Still in Theme.swift, add the following properties to Theme
:
var tabBarBackgroundImage: UIImage? { return self == .Graphical ? UIImage(named: "tabBarBackground") : nil } var backgroundColor: UIColor { switch self { case .Default, .Graphical: return UIColor(white: 0.9, alpha: 1.0) case .Dark: return UIColor(white: 0.8, alpha: 1.0) } } var secondaryColor: UIColor { switch self { case .Default: return UIColor(red: 242.0/255.0, green: 101.0/255.0, blue: 34.0/255.0, alpha: 1.0) case .Dark: return UIColor(red: 34.0/255.0, green: 128.0/255.0, blue: 66.0/255.0, alpha: 1.0) case .Graphical: return UIColor(red: 140.0/255.0, green: 50.0/255.0, blue: 48.0/255.0, alpha: 1.0) } } |
These properties provide appropriate tab bar background images, background colors, and secondary colors for each theme.
To apply these styles, add the following lines to applyTheme()
.
UITabBar.appearance().barStyle = theme.barStyle UITabBar.appearance().backgroundImage = theme.tabBarBackgroundImage let tabIndicator = UIImage(named: "tabBarSelectionIndicator")?.imageWithRenderingMode(.AlwaysTemplate) let tabResizableIndicator = tabIndicator?.resizableImageWithCapInsets( UIEdgeInsets(top: 0, left: 2.0, bottom: 0, right: 2.0)) UITabBar.appearance().selectionIndicatorImage = tabResizableIndicator |
Setting the barStyle
and backgroundImage
should be familiar by now; it’s done in exactly the same you did for UINavigationBar
previously.
In the final three lines of code above, you retrieve an indicator image from the asset catalog and set its rendering mode to .AlwaysTemplate
. This is an example of one context where iOS doesn’t automatically use the template rendering mode.
Finally, you create a resizable image and set it as the tab bar’s selectionIndicatorImage
.
Build and run. You’ll see your newly themed tab bar:
The dark theme is starting to look more, well, dark! :]
See the line below the selected tab? That’s your indicator image. Although it’s only 6 points high and 49 points wide, iOS stretches this to the full width of the tab at run time.
The next section covers resizeable images and how they work.
One element that hasn’t changed yet is the segmented control that shows the currently selected theme. Time to bring that control into the wonderful world of theming.
Add the following code to the bottom of applyTheme()
in Theme.swift:
let controlBackground = UIImage(named: "controlBackground")? .imageWithRenderingMode(.AlwaysTemplate) .resizableImageWithCapInsets(UIEdgeInsets(top: 3, left: 3, bottom: 3, right: 3)) let controlSelectedBackground = UIImage(named: "controlSelectedBackground")? .imageWithRenderingMode(.AlwaysTemplate) .resizableImageWithCapInsets(UIEdgeInsets(top: 3, left: 3, bottom: 3, right: 3)) UISegmentedControl.appearance().setBackgroundImage(controlBackground, forState: .Normal, barMetrics: .Default) UISegmentedControl.appearance().setBackgroundImage(controlSelectedBackground, forState: .Selected, barMetrics: .Default) |
To understand the code above, first take a look at the controlBackground image in your asset catalog. The image may be tiny, but iOS knows exactly how to use it to draw the borders of your UISegmentedControl
, as it’s been pre-sliced and is resizable.
What does sliced mean? Take a look at the following magnified model:
There are four 3×3 squares, one in each corner. These squares are left untouched when resizing the image, but the gray pixels get stretched horizontally and vertically as required.
In your image, all the pixels are black and assume the tint color of the control. You instruct iOS how to stretch the image using UIEdgeInsets()
and passed 3
for the top, left, bottom and right parameters since your corners are 3×3.
Build and run. Tap the Gear icon in the top left and you’ll see that the UISegmentedControl
now reflects your new styling:
The rounded corners are gone and have been replaced by your 3×3 square corners.
Now that you’ve tinted and styled your segmented control, all that’s left is to tint the remaining controls.
Close the settings screen in the app, and tap the magnifier in the top right corner; you’ll see another segmented control which also has your customizations, along with a UIStepper
, UISlider
, and UISwitch
that still need to be themed.
Grab your brush and drop cloths — you’re going painting! :]
To change the colors of the stepper, add the following lines to applyTheme()
in Theme.swift:
UIStepper.appearance().setBackgroundImage(controlBackground, forState: .Normal) UIStepper.appearance().setBackgroundImage(controlBackground, forState: .Disabled) UIStepper.appearance().setBackgroundImage(controlBackground, forState: .Highlighted) UIStepper.appearance().setDecrementImage(UIImage(named: "fewerPaws"), forState: .Normal) UIStepper.appearance().setIncrementImage(UIImage(named: "morePaws"), forState: .Normal) |
You’ve used the same resizable image as you did for UISegmentedControl
; the only difference here is that UIStepper
segments become disabled when they reach their minimum or maximum values, so you needed to specify an image for this case as well. To keep things simple, you re-use the same image.
This not only changes the color of the stepper, but you also get some nice image buttons instead of the boring + and – symbols.
Build and run. Open Search to see how the stepper has changed:
UISlider
and UISwitch
need some theme lovin’ too.
Add the following code to applyTheme()
:
UISlider.appearance().setThumbImage(UIImage(named: "sliderThumb"), forState: .Normal) UISlider.appearance().setMaximumTrackImage(UIImage(named: "maximumTrack")? .resizableImageWithCapInsets(UIEdgeInsets(top: 0, left: 0.0, bottom: 0, right: 6.0)), forState: .Normal) UISlider.appearance().setMinimumTrackImage(UIImage(named: "minimumTrack")? .imageWithRenderingMode(.AlwaysTemplate) .resizableImageWithCapInsets(UIEdgeInsets(top: 0, left: 6.0, bottom: 0, right: 0)), forState: .Normal) UISwitch.appearance().onTintColor = theme.mainColor.colorWithAlphaComponent(0.3) UISwitch.appearance().thumbTintColor = theme.mainColor |
UISlider
has three main customization points: the slider’s thumb, the minimum track and the maximum track.
The thumb uses an image from your assets catalog, while the maximum track uses a resizable image in original rendering mode so it stays black regardless of the theme. The minimum track also uses a resizable image, but you’ve used the template rendering mode so that it inherits the tint of the template.
You’ve modified UISwitch
by setting thumbTintColor
to the main color and onTintColor
as a slightly lighter version of the main color t bump up the contrast between the two.
Build and run. Tap Search and your slider and switch should appear as follows:
As you saw with UISegmentedControl
, the appearance proxy customizes all instances of a class. But sometimes you don’t want a global appearance for a control — in these cases, you can customize just a single instance of a control.
Open SearchTableViewController.swift and add the following lines to viewDidLoad()
:
speciesSelector.setImage(UIImage(named: "dog"), forSegmentAtIndex: 0) speciesSelector.setImage(UIImage(named: "cat"), forSegmentAtIndex: 1) |
Here you’re simply setting the image for each segment in the species selector.
Build and run. Open Search and you’ll see the segmented species selector looks like this:
iOS inverted the colors on the selected segment’s image without any work on your part; this is because images are automatically rendered in Template mode.
What about selectively changing the typeface on your controls? That’s easy as well.
Open PetTableViewController.swift and add the following two lines to the bottom of viewWillAppear()
:
view.backgroundColor = ThemeManager.currentTheme().backgroundColor tableView.separatorColor = ThemeManager.currentTheme().secondaryColor |
Next, add the following line to the end of tableView(_:cellForRowAtIndexPath:)
just before you return from the method:
cell.textLabel!.font = UIFont(name: "Zapfino", size: 14.0) |
This simply changes font on the label that represents the pet’s name.
Build and run. Compare the before and after:
The image below shows the before and after results of the Search screen; I think you’ll agree that the new version is much less vanilla and much more interesting that the original:
You can download the finished project with all the tweaks from this tutorial here.
In addition to the tweaks you’ve already made, in Objective-C you can specify certain customizations to be applied to controls only when they’re contained in other controls of a specific class. For example, you can apply customizations to UITextField
, but only when contained in a UINavigationBar
.
Unfortunately, you cannot use this kind of customization in Swift, yet. The good news is that iOS 9 will add this functionality, and I’ll update this tutorial accordingly once it’s released.
I hope you enjoyed this UIAppearance tutorial and learned how easy it can be to tweak your UI. If you have any comments or questions about this tutorial, please join the forum discussion below!
The post UIAppearance Tutorial: Getting Started appeared first on Ray Wenderlich.
Learn how to edit and delete objects in Core Data and how to handle relationships when deleting objects.
The post Video Tutorial: Beginning Core Data Part 5: Editing & Deleting Data appeared first on Ray Wenderlich.
Review the Core Data basics you were introduced to in this beginning video tutorial series and get ready for our upcoming intermediate series.
The post Video Tutorial: Beginning Core Data Part 6: Conclusion appeared first on Ray Wenderlich.
A record-breaking 390 attendees — including a full slate of 55 speakers — recently descended upon Denver, Colorado to take part in the annual 360 iDev conference.
360iDev was broken into three tracks — design, code and business — and had something to offer to anyone involved in the business of developing apps. With 61 sessions to choose from, it was easy to find find a mix of talks tailored to your interests.
In this year’s conference, the talks tended to focus on three overarching themes:
In this article, I’ll share my own thoughts of the conference along with those of the 11 raywenderlich.com team members who were at the conference, and share a selection of quality presentations from 360iDev 2015 below, grouped by theme. Let’s dive in!
“Steve Jobs’ educational device is now a casino.” – Josh Michaels
The conference opened with a keynote by Josh Michaels, a rare successful independent developer. It seems that lately our iOS and Mac indie developer heroes have been faltering. Josh reminded the gathered developers that the current playing field has changed.
He asked audience members to stand up if they’d contributed to shipping an app; nearly the entire room stood. He then asked those who are making money from apps to remain standing. Only around five out of the 390 attendees remained standing.
Josh went on to explain that the venture-capital backed apps had deflated prices and inflated app building costs. As many indies have complained, the upgrade revenue never arrived. Apple created a store offering free upgrades, with no reward to developers. As he showed a screenshot of the big budget & top selling apps he stated, “Steve Jobs’ educational device is now a casino.”
Josh went on to explains that this situation exists because the app market has reached maturity. Unsurprisingly, this is how capitalism works, and for indies it’s better to accept that easy early days are over. He then presented his 2015 guide to being an indie developer:
Being indie is a lifestyle choice, and Josh advised developers that remaining exclusive to Apple will be a losing battle in the long run. He urged developers to focus on their work, diversify their interests and their income sources. He notes that “Think Different” is about pushing against the dominant paradigm; Apple, being the dominant paradigm, is no longer the way to think different.
Next raywenderlich.com team member Marin Todorov took us on a journey though his own trials and tribulations as an indie developer.
He started by tracing the history of the “Indiepocalypse” – from its origins in a collection of doom and gloom articles published by iOS-Goodies.com to the current state where app sales continue to decline, and large companies dominate the market.
Marin went on to describe his struggles developing a keyboard extension with his partner. Their app Doodle Doodle suffered from a seemingly ever-changing set of rules arbitrarily declared by Apple. Eventually the app shipped, but like many keyboard extensions, suffers from an obtuse and lengthy installation process.
Being limited by the lack of in-app purchases or advertising options, the app could only be purchased outright. Most iOS customers expect apps to be free, which limits an indie keyboard extension app’s success. The final slap in the face is an alert view that states giving the app the full access it needs logs keyboard use and exposes credit card data to the developer.
Marin tells of an enlightening Coursera course he viewed: Developing Innovative Ideas for New Companies. The course draws parallels between the telegraph industry and developing for the App Store. In the early years of communications, messages were sent by coach and it was possible for anyone with sufficient skills to develop a telegraph business. Eventually, the market grew until the telephone was invented — at which point the market died. The app market began in 2008; it’s reached its mid-point in 2015 and if you extrapoalate the data then it should die off by 2017.
Marin read from a transcript of Steve Jobs’ introduction of the App Store. Apple would create the App Store, collect the funds, distribute the apps and App Store Search — Marin pauses here — would also provide the market. Apple didn’t promise that you would get rich. Marin’s advice, similar to others, is be realistic about success, make a high quality app, team up, launch into a niche, contribute to the community, write books and share your code.
“If you can take a nap without asking permission: you’re an indie!” – Charles Perry
Charles Perry, founder of MetaKite Software and host of the Release Notes podcast, provided some advice for those contemplating going indie. There’s lots of money to be made on the App Store — but only for a few developers. Why would you want to go indie? The answer is: “freedom”!
At past conferences, Charles has given a few talks about making a living selling apps on the iOS App Store, Mac App Store and through self-managed sales of OS X software. His podcast also focuses on the business of being indie.
His guide in brief suggests that a developer should set a profit target, start saving, conceive of a product, execute the product, build momentum and then take the leap into the indie lifestyle. In response to Josh Michael’s keynote where he claims indies have lost, Charle counters that they didn’t lose, “…they failed to adjust.” As mentioned in other indie development talks, Charles reiterated we are no longer in an early app market. Indie developers need to adjust to the mature market and build a mature business.
raywenderlich.com team member Greg Heo presented one of the most popular talks to a standing-room only crowd. Was it about striking it rich on the app store, or how to run a mobile iOS consulting firm from a beach in Bora Bora? Nope – it was about making the switch to developing in Swift and covered the differences between Swift and Objective-C.
Greg started the talk by polling the audience and found the vast majority had not written more than one hundred lines of Swift code. Drawing on analogies from Objective-C and C++, Greg attempted to lift the veil on Swift. He patiently explained the aspects of types and type safety by exploring how Optional types play an important role, and used the analogy of unwrapping or unboxing to reveal a puppy. The point, he says, is an Optional is more like the box than the puppy, which the box may or may not contain.
He went on to demonstrate how Swift and Objective-C can work together and demonstrated some of the ways that Swift works with Objective-C. He also covered some of the newer features of Objective-C that work with Swift. The talk helped clarify where the limits of Objective-C lie.
raywenderlich.com team member Rene Cacheaux’s talk was based on the experiences of his development team as they transitioned a large project from Objective-C to Swift. He began by stating he doesn’t dislike Objective-C, but because Apple went all in he’s since adopted the language. Swift is more concise and makes for safer, less crash-prone projects.
Using the Lord of the Rings as a metaphor, he tells of his fellowship’s journey. The poor framework support was the Mines of Moria, the project scheme bugs the Black Gates of Mordor. Gollum was the enterprise coding troubles that chased them on their journey and shipping an all-Swift app was analogous to destroying the Ring at Mount Doom.
The benefits of Swift are found in working with one file, without the need for .h or .m files or extensions. The downsides are that Swift doesn’t support macros and there was initial incompatibility with third-party libraries. The massive changes between Swift 1.0 and 1.2 posed many challenges, as did the limited ability to port Swift elements back to Objective-C.
Réné said his team decided to approach the project in a vertical fashion:
They began by converting one view controller, then moved downwards through child classes, then through persistence, networking and other utilities. Once the first vertical slice was converted, they moved on the the next vertical slice. Eventually, his team arrived at a complete rewrite — and Sauron was defeated! :]
Functional programming gained a lot of attention with the introduction of Swift, and was mentioned in some of the more popular sessions at WWDC 2015. In his talk, Chris Woodward noted that wrapping your head around functional programming while making the switch to Swift can be incredibly challenging for object-oriented developers.
Many apps consist of complex and tangled logic, with thousands and thousands of lines of code. Managing this requires a complex mental model resulting in code that’s hard to read, reason about, extend, fix and test. Chris contended that humans have a limited capacity to store things. By grouping things together, our own mental storage capacity can go up and reduce the human bottleneck. So how do you avoid complex code? Add structure, avoid extra dependencies and adopt a functional style.
Chris stated this state of coding nirvana could be reached through the use of short functions. He laid out a few functional rules stating that functions should not share mutable state or cause side effects. Chris also discussed filtering using predicates, map and reduce functions. The main takeaways of the talk was that code should be readable and a functional style could be used in any language that supports anonymous functions.
Six hands-on workshops preceded the conference, covering debugging, prototyping, core motion, HealthKit and animations; the format is quite similar to that of the hands-on tutorials at RWDevCon.
One of the highlights was the Universal Layout workshop, covered by raywenderlich.com team member Sam Davies. Sam presented a three-and-a-half hour review of layout in iOS, size classes, Adaptive Layout and stack views.
He began with a quick refresher on Auto Layout, and offered some tips to clarify some idiosyncrasies of using Auto Layout in Interface Builder. For example, when Control-dragging to create constraints, the direction of the drag changes the contextual menu that pops up. Also, bullets that appear in the contextual menu indicate which constraints already exist. He also suggested updating the frames in Interface Builder so the design time frames satisfy the constraints.
The introduction of multitasking in iOS 9 lets multiple apps appear on the screen at the same time. Adaptive Layout and size classes, which once appeared arbitrary, are now extremely important in the multitasking paradigm. If you already use Auto Layout, you’re ahead of the multitasking game.
Sam suggested that you handle layout as follows:
Rotation methods should be avoided in the future; a combination of Auto Layout and Adaptive Layout will handle device rotation, since it’s really the size of the view that’s changing. In the end you avoid the “angry” red errors and the “less angry” orange warnings.
New in iOS 9, stack views now simplify the process of creating layouts, and Sam gave a great overview and demo of how they work as well. After seeing Sam give this talk, I have to say Stack Views are my new favorite thing!
Sam’s Auto Layout Workshop on GitHub
In another standing-room only talk, Jack Cox covered many tips and tricks for working with the often-challenging world of Auto Layout. He covered table views and scroll views, which by their variable nature create challenges for Auto Layout. Like Sam’s workshop, Jack covered stack views and the issues that surround them. For each issue, he provided a value on the “Auto Layout pain scale” with scroll views and table views providing moderate pain, while stack views present the least amount of pain. Debugging Auto Layout, he said, provides the worst kind of pain. :]
Jack provided a walkthrough debugging session dealing with the root causes of Auto Layout debugging pain: conflicts and ambiguity. Jack demonstrated one interesting way to reveal the workings under the hood: while breaking down the Visual Formatting Language, he explained how to convert the console output of po [[UIWindow keyWindow] _autolayoutTrace]
into a formula that makes sense.
Jack’s Auto Layout Problem’s on GitHub
Justin Williams’ talk on Auto Layout began by covering the new iOS 9 layout anchors and the default 8-pt values set by Apple. He switched to the code and demonstrated how to modify the layout anchors through increaseMargin()
or decreaseMargin()
actions. Expounding further on the new hotness of layout anchors, he once again used code to create a simple form with its view elements centered neatly in a minimum number of lines.
His third coding demonstration covered the new Layout Guides, which can simplify complex layout scenarios in a minimal amount of code. He closed out the session by showcasing some Auto Layout debugging tips. Similar to others who spoke on Auto Layout, he suggested using the identifier property on constraints, which you can print out to the console and use to identify the element’s constraints in the output. You can then output a view’s constraints by overriding viewDidLayoutSubviews()
like this:
override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() print("self.view.constraints = \(self.view.constraints)") } |
Justin’s Mastering Auto Layout on GitHub
The conference was more than just talks – there was some fun & games too!
The second annual “Stump the Experts Speakers 360” picks up where the WWDC favorite “Stump the Experts” leaves off. A rag-tag collection of “experts” takes on the gathered audience in a game-show style battle of inane Apple trivia. The hosts present questions to challenge the audience, who in turn write trivia questions on 3×5 index cards.
The event is rife with comedic moments, and most often useless trivia, with points awarded to each side. Prizes consist of extremely valuable 5 1/4″ inch floppies that may have been overwritten, old eWorld and Newton stickers and bits of a tree stump from Tom’s own backyard. This session is a true highlight, and I look forward to many more years of Stump the Speakers 360 or whatever they choose to call it.
Full disclosure: I did manage to correctly answer a question in both the first and second “Stump”! :]
Each year I’ve attended 360|iDev there’s been an all-nighter dev jam, where bleary-eyed developers show off their work first thing in the morning to the collected masses. Three years ago, Sprite Kit had just been released, so there were lots of games produced based on the new framework as well as Unity and Cocos2D.
This year’s game jam featured WatchKit app and several games, one of them built by three developers — including raywenderlich.com team member Ryan Poolos — who recently took the indie plunge. The game dev jam and accompanying board game night provided a great way to socialize and collaborate with other developers from around the world.
There are a few more other interesting thoughts that I thought you might like to hear about.
Stephan Huda, a principal engineer at Shopkeep, covered the ins and outs of paying for services with credit cards, near field communication (NFC), ApplePay and loyalty programs. Delving into the mechanisms of each, he began by explaining what information is stored in a credit card’s magnetic strip. He then presented an overview of the transaction process, from the merchant terminal, through a payment processor, to the card issuer. The transaction travels from the issuer to the acquirer who sends the approval back to the merchant.
He then described how NFC payments are processed using cryptography and a “secure element” chip in your device. While your device’s chip is powered by a nearby NFC reader, the secure element combines your account, a secure key and a unique transaction key. The transaction follows a similar process to the credit card, but with the extra encoded information related to the active transaction.
The details behind the payment process was covered lightly “with lots of hard waving” in Session 701 at WWDC 2015, but the exact process remains undisclosed by Apple. Stephan speculated that a transaction contains a series of merchant identifiers along with encrypted information in a manner that may be similar to NFC.
I attended the talk of raywenderlich.com team member Ellen Shapiro mostly out of curiosity, having employed localization in several apps myself. To my surprise and delight, Ellen’s talk illuminated many aspects of localization that I either wasn’t aware or hadn’t considered. She began with the statistics that 66% of Apple’s revenue comes from outside of the United States — 41% from China alone.
Localization, or as Ellen prefers, “internationalization”, is more complicated than it appears. Ellen presented a complete set of considerations and tips when adapting your app for a broader audience. Using NSLocalizedString
along with a key and comment is the first step most developers are aware of. Running a genstrings script on a Swift or Objective-C file will generate a “.strings” file which can then be sent out to create translation files and placed into relative lprog folders. Ellen pointed out that the Base.lprog is the fallback location for missing translations.
Storyboards can also support localization using a strings file that bears the storyboard’s name. Ellen noted that numeric values are often displayed and handled differently in other languages and developers need to be aware of the different date formats and timezones used internationally. Developers should be mindful that iOS 9 will automatically handle right-to-left language orientations. Also new in iOS 9 is NSPersonNameComponentsFormatter
, which deals with the various ways first names, family names, initials and titles display across languages. All in all, Ellen’s talk was good overview if you’re planning to sell your apps in a worldwide market.
Jeff Biggus presented a well-attended talk on super computing which contained a fascinating overview of the chips that work alongside and separate from the CPU.
Jeff gave a brief history of the chips beginning with the CUDA chip in 2007. In 2008, Apple introduced OpenCL, which permitted the same code to be run on any hardware that supported the OpenCL framework. From there, the market started to fragment. AMD brought out the Mantle API in 2013 for standard CPUs with embedded GPUs. Apple followed this with Metal for Mac and iOS; the Vulkan project began to use AMD’s code and Google decided to go with Vulkan. Jeff’s hope is that Apple also adopts project Vulkan in future.
Jeff brought along a demo project that displayed a Mandelbrot image on the Mac. Using the CPU, zooming in on the Mandelbrot was a sluggish operation. Jeff then demonstrated various combinations of settings in the app and changes to the processing used. Finally, he landed on pure Metal, which uses the GPUs; the Mandelbrot scaled up smoothly at hundreds of processes per second.
In the middle of his very complex talk of maths, chips and technology, Jeff displayed a very welcome image of a puppy! It was a refreshing break in a technical but otherwise great talk on the subject.
Jeff’s Code Examples on GitHub
I can’t recommend 360iDev highly enough; it’s a great experience for any developer, designer or anyone involved in app production.
The hosts John Wilker, Nicole Wilker, and Tom Ortega make the conference feel like home, and the collective masses are super friendly. No matter what obstacles might come up, I feel I cannot afford to miss this conference. Every year I’ve attended I come away re-energized, enlightened and ready to take on the next year’s work.
Ray’s said a number of times that 360iDev is one of his favorite iOS conferences — and I’d have to agree. If you’re looking for more hands-on tutorials, check out RWDevCon which runs March 11th-12th, 2016 in Washington D.C.; it and 360iDev are both at the top of my own personal list of conferences.
Let us know what you think! Did you attend? Will you attend next year? Will you step up and submit a talk of your own? Please join the forum discussion below!
The post 360iDev 2015 Conference Highlights appeared first on Ray Wenderlich.
View next video: Core Data Stack
The post Video Tutorial: Intermediate Core Data: Series Introduction appeared first on Ray Wenderlich.
Learn about the classes that make up Core Data and assemble these objects together into a reusable class.
The post Video Tutorial: Intermediate Core Data Part 1: Core Data Stack appeared first on Ray Wenderlich.
Tower defense games are incredibly popular, and no wonder — few things are more satisfying than watching your defense obliterate evil invaders! In this two-part tutorial, you build a tower defense game with Unity!
You’ll learn how to…
At the end, you’ll have a framework for this genre that you can expand upon!
Note: You need to know Unity basics, like how to add game assets and components, understand prefabs and know some basic C#. To learn those things I recommend completing the Unity tutorials by Chris LaPollo.
I’m using the OS X version of Unity, but this tutorial works on Windows too.
In this tutorial, you build a tower defense game, where enemies — little bugs — crawl towards a cookie that belongs to you and your minions, which are of course monsters! You can place and upgrade monsters at strategic points for a bit of gold.
The player must kill the bugs before they feast on your cookie. Each wave of enemies is successively harder to defeat. The game ends when you survive all waves (Victory!) or when five enemies reach the cookie. (Defeat!).
Here’s a screenshot of the finished game:
If you don’t already have Unity 5 or newer, download it from Unity’s website.
Also, download this starter project, unzip and open the TowerDefense-Part1-Starter project in Unity.
The starter project includes art and sound assets, along with prebuilt animations and a few helpful scripts. The scripts aren’t directly related to tower defense games, so they won’t be explained here. However, if you’d like to learn more about creating Unity 2D animations, check out this Unity 2D tutorial.
The project also contains prefabs you’ll later expand upon to create characters. Finally, the project includes a scene with its background and user interface set up.
Open GameScene, found in the folder Scenes, and set your Game view’s aspect ratio to 4:3 to ensure the labels line up properly with the background. You should see the following in the Game view:
Credits:
Starter project – check!
Assets – check!
The first step towards world domination… ehm, I mean your tower defense game…is done!
Monsters can only post up at spots marked with an x.
To add these to the scene, drag and drop Images\Objects\Openspot from the Project Browser into the Scene view. For now, position doesn’t matter.
With Openspot selected in the Hierarchy, click Add Component in the Inspector and select Physics 2D\Box Collider 2D. Unity displays the box collider with a green line in the Scene view. You’ll use this collider to detect mouse clicks on that spot.
Following the same steps, add an Audio\Audio Source component to Openspot. Set the Audio Source’s AudioClip to tower_place, which you can find in the Audio folder, and deactivate Play On Awake.
You need to create 11 more spots. While it’s tempting to repeat all those steps, Unity has a great solution for that: Prefabs!
Drag and drop Openspot from the Hierarchy into the Prefabs folder in the Project Browser. Its name then turns blue in the Hierarchy to show that it’s connected to a prefab. Like this:
Now that you have a prefab, you can create as many copies as you need. Just drag and drop Openspot from the Prefabs folder in the Project Browser into the Scene view. Do this 11 times to make a total of 12 Openspot objects in the scene.
Now use the Inspector to set the positions of these 12 Openspot objects to the following coordinates:
When you’re done, your scene should look like this.
To make placing easier, the project’s Prefab folder contains a Monster prefab.
At this point, it consists of an empty game object with three different sprites and their shooting animations as their children.
Each sprite represents the monster at a different power level. The prefab also contains an Audio Source component, which you’ll trigger to play a sound whenever the monster shoots a laser.
You’ll now create a script that can place a Monster on an Openspot.
In the Project Browser, select Openspot in the Prefabs folder. In the Inspector, click Add Component, then choose New Script and name it PlaceMonster. Select C Sharp as the Language and click Create and Add. Because you added the script to the Openspot prefab all Openspots in your scene now also have the script attached. Neat!
Double click on the script to open it in MonoDevelop. Then add these two variables:
public GameObject monsterPrefab; private GameObject monster; |
You’ll instantiate a copy of the object stored in monsterPrefab
to create a monster, and store it in monster
so you can manipulate it during the game.
Add the following method to allow only one monster per location:
private bool canPlaceMonster() { return monster == null; } |
In canPlaceMonster()
you check whether the monster
variable is still null
. If so, it means there is currently no monster here and it’s okay to place one.
Now add the following code to actually place a monster when the player clicks this GameObject:
//1 void OnMouseUp () { //2 if (canPlaceMonster ()) { //3 monster = (GameObject) Instantiate(monsterPrefab, transform.position, Quaternion.identity); //4 AudioSource audioSource = gameObject.GetComponent<AudioSource>(); audioSource.PlayOneShot(audioSource.clip); // TODO: Deduct gold } } |
This code places a monster on mouse click or tap. So how does this work?
OnMouseUp
when a player taps a GameObject’s physics collider.canPlaceMonster()
returns true
.Instantiate
, a method that creates an instance of a given prefab with the specified position and rotation. In this case, you copy monsterPrefab
, give it the current GameObject’s position and no rotation, cast the result to a GameObject
and store it in monster
.PlayOneShot
to play the sound effect attached to the object’s AudioSource
component.Now your PlaceMonster
script can place a new monster, but you still have to specify the prefab.
Save the file and switch back to Unity.
To assign the monsterPrefab variable, first select Openspot in the Prefabs folder in the project browser.
In the Inspector, click on the circle to the right of the PlaceMonster (Script) component’s Monster Prefab field, and select Monster from the dialog that appears.
That’s it. Run the scene and build monsters on various x spots with a click or tap.
In the image below, you see how your monsters look increasingly horrifying at higher levels.
A script acts as the basis for implementing a leveling system for the monsters. It tracks how powerful the monster should be on each level, and of course, the current level of a monster.
Add this script now.
Select Prefabs/Monster in the Project Browser. Add a new C# script named MonsterData. Open the script in MonoDevelop and add the following code above the MonsterData
class.
[System.Serializable] public class MonsterLevel { public int cost; public GameObject visualization; } |
This creates MonsterLevel
. It groups the cost (in gold, which you’ll support later) and the visual representation for a specific monster level.
You add [System.Serializable]
at the top to make instances of the class editable from the inspector. This allows you to quickly change all values in the Level class — even while the game is running. It’s incredibly useful for balancing your game.
In this case, you’ll store predefined MonsterLevel
in a List<T>
.
Why not simply use MonsterLevel[]
? Well, you’ll need the index of a particular MonsterLevel
object several times. While it’s not difficult to write code for that, you’ll use IndexOf()
, which implements the functionality for Lists
. No need to reinvent the wheel this time. :]
At the top of MonsterData.cs, add the following using
statement:
using System.Collections.Generic; |
This gives you access to generic data structures, so you can use the List<T>
class in your script.
Note: Generics are a powerful part of C#. They allow you to define type-safe data structures without committing to a type. This is practical for container classes like lists and sets. To learn more about generics, have a look at Introduction to C# Generics.
Now add the following variable to MonsterData
to store a list of MonsterLevel
:
public List<MonsterLevel> levels; |
Using generics, you ensure the levels
List
can only ever contain MonsterLevel
objects.
Save the file and switch to Unity to configure each stage.
Select Prefabs/Monster in the Project Browser. In the Inspector, you can now see a Levels field in the MonsterData (Script) component. Set its size to 3.
Next, set the cost for each level to the following values:
Now assign the visualization field values.
Expand Prefabs/Monster in the project browser so that you can see its children. Drag and drop the child Monster0 to Element 0‘s visualization field.
Repeat to assign Monster1 to Element 1 and Monster2 to Element 2. See the following GIF that demonstrates this process:
When you select the Prefabs/Monster, the prefab should look like this:
Switch back to MonsterData.cs in MonoDevelop, and add another variable to MonsterData
.
private MonsterLevel currentLevel; |
In the private variable currentLevel
you’ll store the… wait for it … current level of the monster. I bet you did not see that one coming :]
Now set currentLevel
and make it accessible to other scripts. Add the following to MonsterData
, along with instance variable declarations:
//1 public MonsterLevel CurrentLevel { //2 get { return currentLevel; } //3 set { currentLevel = value; int currentLevelIndex = levels.IndexOf(currentLevel); GameObject levelVisualization = levels[currentLevelIndex].visualization; for (int i = 0; i < levels.Count; i++) { if (levelVisualization != null) { if (i == currentLevelIndex) { levels[i].visualization.SetActive(true); } else { levels[i].visualization.SetActive(false); } } } } } |
Quite a bit of C# there, eh? Take it all it turn:
currentLevel
. With a property defined, you can call just like any other variable: either as CurrentLevel
(from inside the class) or as monster.CurrentLevel
(from outside it). You can define custom behavior in a property’s getter or setter method, and by supplying only a getter, a setter or both, you can control whether a property is read-only, write-only or read/write.currentLevel
.currentLevel
. Next you get the index of the current level. Finally you iterate over all the levels and set the visualization to active or inactive, depending on the currentLevelIndex
. This is great because it means that whenever someone sets currentLevel
, the sprite updates automatically. Properties sure do come handy!Add the following implementation of OnEnable
:
void OnEnable() { CurrentLevel = levels[0]; } |
This sets CurrentLevel
upon placement, making sure that it shows only the correct sprite.
Note: It’s important to initialize the property in OnEnable
instead of OnStart
, because you call the order methods when prefabs are instantiated.
OnEnable
will be called immediately when you create the prefab (if the prefab was saved in an enabled state), but OnStart
isn’t called until after the object starts running as part of the scene.
You’ll need to check this data before you place a monster, so you initialize it in OnEnable
.
Save the file and switch to Unity. Run the project and place monsters; now they display the correct and lowest level sprites.
Switch back to MonoDevelop and add the following method to MonsterData
:
public MonsterLevel getNextLevel() { int currentLevelIndex = levels.IndexOf (currentLevel); int maxLevelIndex = levels.Count - 1; if (currentLevelIndex < maxLevelIndex) { return levels[currentLevelIndex+1]; } else { return null; } } |
In getNextLevel
you get the index of currentLevel
and the index of the highest level provided the monster did not reach the maximal level to return the next level. Otherwise, return null
.
You can use this method to figure out whether upgrading the monster is possible.
Add the following method to increase a monster’s level:
public void increaseLevel() { int currentLevelIndex = levels.IndexOf(currentLevel); if (currentLevelIndex < levels.Count - 1) { CurrentLevel = levels[currentLevelIndex + 1]; } } |
Here you get the index of the current level, and then you make sure it’s not the maximum level by checking if it’s smaller than levels.Count - 1
. If so, set CurrentLevel
to the next level.
Save the file and then switch to PlaceMonster.cs in MonoDevelop and add this new method:
private bool canUpgradeMonster() { if (monster != null) { MonsterData monsterData = monster.GetComponent<MonsterData> (); MonsterLevel nextLevel = monsterData.getNextLevel(); if (nextLevel != null) { return true; } } return false; } |
First check whether there is a monster that you can upgrade by checking the monster
variable for null
. If this is the case, you get the current level of the monster from its MonsterData
.
Then you test whether a higher level is available, which is when getNextLevel()
doesn’t return null
. If up-leveling is possible, you return true
, otherwise, you return false
.
To enable the upgrade option, add an else if
branch to OnMouseUp
:
if (canPlaceMonster ()) { // Your code here stays the same as before } else if (canUpgradeMonster()) { monster.GetComponent<MonsterData>().increaseLevel(); AudioSource audioSource = gameObject.GetComponent<AudioSource>(); audioSource.PlayOneShot(audioSource.clip); // TODO: Deduct gold } |
Check whether an upgrade is possible with canUpgradeMonster()
. If yes, you access the MonsterData
component with GetComponent()
and call increaseLevel()
, which increases the level of the monster. Lastly, you trigger the monster’s AudioSource.
Save the file and switch back to Unity. Run the game, place and upgrade as many monsters as you like…for now.
Right now it’s possible to build and upgrade all the monsters immediately, but where’s the challenge in that?
Let’s drill down into the issue of the gold. The problem with keeping track of it is that you need to share information between different game objects.
The following image shows all the objects that want a piece of the action.
You’ll use a shared object that’s accessible to other objects to store this data.
Right-click in the Hierarchy and select Create Empty. Name the new game object GameManager.
Add a C# script named GameManagerBehavior to GameManager, then open the new script in MonoDevelop. You’ll display the player’s total gold in a label, so add the following line to the top of the file:
using UnityEngine.UI; |
This lets you access UI-specific classes like Text
, which the project uses for the labels. Now add the following variable to the class:
public Text goldLabel; |
This will store a reference to the Text
component used to display how much gold the player owns.
Now that GameManager
knows about the label, how can you ensure the amount of gold stored in your variable and the amount displayed on the label are in sync? You’ll create a property.
Add the following code to GameManagerBehavior
:
private int gold; public int Gold { get { return gold; } set { gold = value; goldLabel.GetComponent<Text>().text = "GOLD: " + gold; } } |
Seem familiar? It’s similar to the CurrentLevel
you defined in Monster
. At first, you create a private variable, gold
, to store the current gold total. Then you define a property named Gold
— creative, right? — and implement a getter and setter.
The getter simply returns the value of gold
. The setter is more interesting. In addition to setting the variable’s value, it also sets the text
field on goldLabel
to display the new amount of gold.
How generous do you feel? Add the following line to Start()
to give the player 1000 gold, or less if you feel miserly:
Gold = 1000; |
Save the file and switch to Unity.
In the Hierarchy, select GameManager. In the Inspector, click on the circle to the right of Gold Label. In the Select Text dialog, select the Scene tab and select GoldLabel.
Run the scene and the label displays Gold: 1000.
Open PlaceMonster.cs in MonoDevelop, and add the following instance variable:
private GameManagerBehavior gameManager; |
You’ll use gameManager
to access the GameManagerBehavior
component of the scene’s GameManager. To assign it, add the following to Start()
:
gameManager = GameObject.Find("GameManager").GetComponent<GameManagerBehavior>(); |
You get the GameObject named GameManager using GameObject.Find()
, which returns the first game object it finds with the given name. Then, retrieve its GameManagerBehavior
component and store it for later.
Note: You could have accomplished this by setting the field in Unity’s editor, or by adding a static method to GameManager
that returns a singleton instance from which you could get the GameManagerBehavior
.
However, there’s a dark horse method in the block above: Find
, which is slower at runtime but convenient and ok to use sparingly.
You don’t yet deduct gold, so add this line twice inside OnMouseUp()
, replacing each of the comments that read // TODO: Deduct gold
:
gameManager.Gold -= monster.GetComponent<MonsterData>().CurrentLevel.cost; |
Save the file and switch to Unity, upgrade some monsters and watch the Gold readout update. Now you deduct gold, but players can build monsters as long as there is space; they just get into debt.
Infinite credit? Awesome! But you can’t allow this. Monsters should only be placed when the player has enough gold.
Switch to PlaceMonster.cs in MonoDevelop, and replace the contents of canPlaceMonster()
with the following:
int cost = monsterPrefab.GetComponent<MonsterData> ().levels[0].cost; return monster == null && gameManager.Gold >= cost; |
Retrieve the cost for placing the monster from levels
in its MonsterData
. You then check that monster
is not null
and that gameManager.Gold
is bigger than the cost.
Challenge: Add the check for whether a player has enough gold in canUpgradeMonster()
by yourself.
Solution Inside | SelectShow> | ||
---|---|---|---|
Replace this line:
with this one:
This checks if the player has more Gold than the cost of the upgrade. |
Save and run the scene in Unity. Go ahead, just try to place unlimited monsters!
Time to “pave the road” for your enemies. Enemies appear at the first waypoint, move towards the next and repeat until they reach your cookie.
You’ll get the enemies marching by:
Right-click in the Hierarchy and select Create Empty to make a new empty game object. Name it Road, and make sure it’s at position (0, 0, 0).
Now, right-click on Road in the hierarchy and create another empty game object as a child of Road. Name it Waypoint0 and set its position to (-12, 2, 0) — this is where enemies start their assault.
Create five more waypoints the same way with the following names and positions:
The following screenshot highlights the waypoint locations and the resulting path.
Now to make some enemies to follow the road. The Prefabs folder contains an Enemy prefab. Its position is (-20, 0, 0), so new instances will spawn off screen.
Otherwise, it’s set up much like the Monster prefab, with an AudioSource
and a child Sprite
, and it’s a sprite so you can rotate it later without rotating the forthcoming health bar.
Add a new C# script named MoveEnemy to the Prefabs\Enemy prefab. Open the script in MonoDevelop, and add the following variables:
[HideInInspector] public GameObject[] waypoints; private int currentWaypoint = 0; private float lastWaypointSwitchTime; public float speed = 1.0f; |
waypoints
stores a copy of the waypoints in an array, while [HideIninspector]
above waypoints
ensures you cannot accidentally change the field in the inspector, but you can still access it from other scripts.
currentWaypoint
tracks which waypoint the enemy is currently walking away from, and lastWaypointSwitchTime
stores the time when the enemy passed over it. Finally, you store the enemy’s speed
.
Add this line in Start()
:
lastWaypointSwitchTime = Time.time; |
This initializes lastWaypointSwitchTime
to the current time.
To make the enemy move along the path, add the following code to Update()
:
// 1 Vector3 startPosition = waypoints [currentWaypoint].transform.position; Vector3 endPosition = waypoints [currentWaypoint + 1].transform.position; // 2 float pathLength = Vector3.Distance (startPosition, endPosition); float totalTimeForPath = pathLength / speed; float currentTimeOnPath = Time.time - lastWaypointSwitchTime; gameObject.transform.position = Vector3.Lerp (startPosition, endPosition, currentTimeOnPath / totalTimeForPath); // 3 if (gameObject.transform.position.Equals(endPosition)) { if (currentWaypoint < waypoints.Length - 2) { // 3.a currentWaypoint++; lastWaypointSwitchTime = Time.time; // TODO: Rotate into move direction } else { // 3.b Destroy(gameObject); AudioSource audioSource = gameObject.GetComponent<AudioSource>(); AudioSource.PlayClipAtPoint(audioSource.clip, transform.position); // TODO: deduct health } } |
Step by step:
Vector3.Lerp
, you interpolate the current position of the enemy between the segment’s start and end positions.endPosition
. If yes, handle these two possible scenarios:currentWaypoint
and update lastWaypointSwitchTime
. Later, you’ll add code to rotate the enemy so it points in the direction it’s moving, too.health
, too.Save the file and switch to Unity.
In its current state, the enemies don’t know the order of the waypoints.
Select Road in the Hierarchy, and add a new C# script named SpawnEnemy. Then open it in MonoDevelop, and add the following variable:
public GameObject[] waypoints; |
You’ll use waypoints
to store references to the waypoint in the scene in the proper order.
Save the file and switch to Unity. Select Road in the Hierarchy and set the Size of the Waypoints array to 6.
Drag each of Road’s children into the fields, putting Waypoint0 into Element 0, Waypoint1 into Element 1, and so on.
Now you have an array that contains neatly ordered waypoints so there’s a path – note that they never retreat; they will die trying to get a sugar fix.
Head to SpawnEnemy in MonoDevelop, and add the following variable:
public GameObject testEnemyPrefab; |
This keeps a reference to the Enemy prefab in testEnemyPrefab
.
To create an enemy when the script starts, add the following code to Start()
:
Instantiate(testEnemyPrefab).GetComponent<MoveEnemy>().waypoints = waypoints; |
This instantiates a new copy of the prefab stored in testEnemy
and assigns it waypoints to follow.
Save the file and switch to Unity. Select Road in the Hierarchy and set its Test Enemy to the Enemy prefab.
Run the project to see the enemy follow the road.
Did you notice they aren’t always looking where they’re going? Funny! But you’re trying to be a professional here, yes? Continue with part two to learn how to get them to put their best faces forward.
You’ve gotten a lot done and are well on your way to having your very own tower defense game.
Players can build monsters, but not an unlimited amount, and there’s an enemy running towards your cookie. Players have gold and can also upgrade monsters.
Download the result here.
Use the forums below to share your questions, notes, learnings, lightbulb moments and feedback. I look forward to talking with you!
The post How to Create a Tower Defense Game in Unity – Part 1 appeared first on Ray Wenderlich.
Your challenge is to add an image to your device record. See the Challenge PDF for full details.
Other resources:
View previous video: Core Data Stack
The post Video Tutorial: Intermediate Core Data Part 2: Advanced Attributes appeared first on Ray Wenderlich.
Welcome to part two of How to Create a Tower Defense Game in Unity. You’re making a tower defense game in Unity, and at the end of part one, you could place and upgrade monsters. You also had one enemy attack the cookie.
However, the enemy had no idea which way to face! Also, it was a poor excuse for an attack. In this part, you’ll add enemy waves and arm your monsters so they can defend your precious cookie.
In Unity, open your completed project from the first part of this tutorial series, or if you’re just joining in now, download the starter project and open TowerDefense-Part2-Starter.
Open GameScene from the Scenes folder.
At the end of the last tutorial, the enemy followed the road, but appeared to have no idea which way to face.
Open MoveEnemy.cs in MonoDevelop, and add the following method to fix this.
private void RotateIntoMoveDirection() { //1 Vector3 newStartPosition = waypoints [currentWaypoint].transform.position; Vector3 newEndPosition = waypoints [currentWaypoint + 1].transform.position; Vector3 newDirection = (newEndPosition - newStartPosition); //2 float x = newDirection.x; float y = newDirection.y; float rotationAngle = Mathf.Atan2 (y, x) * 180 / Mathf.PI; //3 GameObject sprite = (GameObject) gameObject.transform.FindChild("Sprite").gameObject; sprite.transform.rotation = Quaternion.AngleAxis(rotationAngle, Vector3.forward); } |
RotateIntoMoveDirection
rotates the enemy so that it always looks forward, like so:
Mathf.Atan2
to determine the angle toward which newDirection
points, in radians, assuming zero points to the right. Multiplying the result by 180 / Mathf.PI
converts the angle to degrees.rotationAngle
degrees along the z-axis. Note that you rotate the child instead of the parent so the health bar — you’ll add it soon — remains horizontal.In Update()
, replace the comment // TODO: Rotate into move direction
with the following call to RotateIntoMoveDirection
:
RotateIntoMoveDirection(); |
Save the file and switch to Unity. Run the scene; now your monster knows where he’s going.
One single enemy? Hardly impressive. Let the hordes come. And like in every tower defense game, hordes will come in waves!
Before you set the hordes into motion, you need to let the player know about the coming onslaught. Also, why not display the current wave’s number at the top of the screen?
Several GameObjects need wave information, so you’ll add it to the GameManagerBehavior component on GameManager.
Open GameManagerBehavior.cs in MonoDevelop and add these two variables:
public Text waveLabel; public GameObject[] nextWaveLabels; |
The waveLabel
stores a reference to the wave readout at the top right corner of the screen. nextWaveLabels
stores the two GameObjects that when combined, create an animation you’ll show at the start of a new wave, as shown below:
Save the file and switch to Unity. Select GameManager in the Hierarchy. Click on the small circle to the right of Wave Label, and in the Select Text dialog, select WaveLabel in the Scene tab.
Now set the Size of Next Wave Labels to 2. Then assign Element 0 to NextWaveBottomLabel and Element 1 to NextWaveTopLabel the same way as you set Wave Label.
If the player has lost the game, he shouldn’t see the next wave message. To handle this, switch back to GameManagerBehavior.cs in MonoDevelop and add another variable:
public bool gameOver = false; |
In gameOver
you’ll store whether the player has lost the game.
Once again, you’ll use a property to keep the game’s elements in sync with the current wave. Add the following code to GameManagerBehavior
:
private int wave; public int Wave { get { return wave; } set { wave = value; if (!gameOver) { for (int i = 0; i < nextWaveLabels.Length; i++) { nextWaveLabels[i].GetComponent<Animator>().SetTrigger("nextWave"); } } waveLabel.text = "WAVE: " + (wave + 1); } } |
Creating the private variable, property and getter should be second nature by now. But again, the setter is a bit trickier.
You update wave
with the new value
.
Then you check that the game is not over. If so, you iterate over all labels in nextWaveLabels — those labels have an Animator component. To trigger the animation on the Animator you set the trigger nextWave.
Lastly, you set waveLabel
‘s text
to the value of wave + 1
. Why the +1
? – Normal human beings do not start counting at zero. Weird, I know :]
In Start()
, set the value of this property:
Wave = 0; |
You start counting at Wave
number 0.
Save the file, then run the scene in Unity. The Wave readout properly starts at 1.
It sounds obvious, but you need to be able to create more enemies to unleash the hordes — right now you can’t do that. Furthermore, you shouldn’t spawn the next wave once the current wave is obliterated — at least for now.
So, the games must be able to recognize whether there are enemies in the scene, and Tags are a good way to identify game objects.
Select the Enemy prefab in the Project Browser. At the top of the Inspector, click on the Tag dropdown and select Add Tag.
Create a Tag named Enemy.
Select the Enemy prefab. In the Inspector, set its Tag to Enemy.
Now you need to define a wave of enemies. Open SpawnEnemy.cs in MonoDevelop, and add the following class implementation before SpawnEnemy
:
[System.Serializable] public class Wave { public GameObject enemyPrefab; public float spawnInterval = 2; public int maxEnemies = 20; } |
Wave holds an enemyPrefab
, the basis for instantiating all enemies in that wave, a spawnInterval
, the time between enemies in the wave in seconds and the maxEnemies
, which is the quantity of enemies spawning in that wave.
This class is Serializable, which means you can change the values in the Inspector.
Add the following variables to the SpawnEnemy
class:
public Wave[] waves; public int timeBetweenWaves = 5; private GameManagerBehavior gameManager; private float lastSpawnTime; private int enemiesSpawned = 0; |
This sets up some variables for spawning that are quite similar to how you moved the enemies along waypoints.
You’ll define the game’s various waves in waves
, and track the number of enemies spawned and when you spawned them in enemiesSpawned
and lastSpawnTime
, respectively.
Players need breaks after all that killing, so set timeBetweenWaves
to 5 seconds
Replace the contents of Start()
with the following code.
lastSpawnTime = Time.time; gameManager = GameObject.Find("GameManager").GetComponent<GameManagerBehavior>(); |
Here you set lastSpawnTime
to the current time, which will be when the script starts as soon as the scene loads. Then you retrieve the GameManagerBehavior
in the familiar way.
Add this to Update()
:
// 1 int currentWave = gameManager.Wave; if (currentWave < waves.Length) { // 2 float timeInterval = Time.time - lastSpawnTime; float spawnInterval = waves[currentWave].spawnInterval; if (((enemiesSpawned == 0 && timeInterval > timeBetweenWaves) || timeInterval > spawnInterval) && enemiesSpawned < waves[currentWave].maxEnemies) { // 3 lastSpawnTime = Time.time; GameObject newEnemy = (GameObject) Instantiate(waves[currentWave].enemyPrefab); newEnemy.GetComponent<MoveEnemy>().waypoints = waypoints; enemiesSpawned++; } // 4 if (enemiesSpawned == waves[currentWave].maxEnemies && GameObject.FindGameObjectWithTag("Enemy") == null) { gameManager.Wave++; gameManager.Gold = Mathf.RoundToInt(gameManager.Gold * 1.1f); enemiesSpawned = 0; lastSpawnTime = Time.time; } // 5 } else { gameManager.gameOver = true; GameObject gameOverText = GameObject.FindGameObjectWithTag ("GameWon"); gameOverText.GetComponent<Animator>().SetBool("gameOver", true); } |
Go through this code step by step:
timeInterval
is bigger than timeBetweenWaves
. Otherwise, you check whether timeInterval
is bigger than this wave’s spawnInterval
. In either case, you make sure you haven’t spawned all the enemies for this wave.enemyPrefab
. You also increase the enemiesSpawned
count.Save the file and switch to Unity. Select Road in the Hierarchy. In the Inspector, set the Size of Waves to 4.
For now, set Enemy Prefab to Enemy for all four elements. Set the Spawn Interval and Max Enemies fields as follows:
The final setup should look like the screenshot below.
Of course, you can play around with those settings to increase or decrease the carnage.
Run the game. Ah-ha! The bugs are marching toward your cookie!
No tower defense game is complete with only one type of enemy. Luckily, the Prefabs folder contains another option, Enemy2.
Select Prefabs\Enemy2 in Inspector and add the MoveEnemy script to it. Set its Speed to 3 and its Tag to Enemy. You can now use this speedy bug to keep the player on his toes!
Even though hordes of bugs storm towards the cookie, the player takes no damage. But no more. The player should take a hit when he lets the enemy encroach.
Open GameManagerBehavior.cs in MonoDevelop, and add the following two variables:
public Text healthLabel; public GameObject[] healthIndicator; |
You’ll use healthLabel
to access the player’s health readout, and healthIndicator
to access the five little green cookie-crunching monsters — they simply represent player health in a more fun way than a standard health label.
Next, add a property to maintain the player’s health in GameManagerBehavior
:
private int health; public int Health { get { return health; } set { // 1 if (value < health) { Camera.main.GetComponent<CameraShake>().Shake(); } // 2 health = value; healthLabel.text = "HEALTH: " + health; // 3 if (health <= 0 && !gameOver) { gameOver = true; GameObject gameOverText = GameObject.FindGameObjectWithTag ("GameOver"); gameOverText.GetComponent<Animator>().SetBool("gameOver", true); } // 4 for (int i = 0; i < healthIndicator.Length; i++) { if (i < Health) { healthIndicator[i].SetActive(true); } else { healthIndicator[i].SetActive(false); } } } } |
This manages the player’s health. Once again, the bulk of the code is in the setter:
CameraShake
component to create a nice shake effect. This script is included with the project and not covered here.gameOver
to true
and trigger the GameOver
animation.Initialize Health
in Start()
:
Health = 5; |
You set Health
to 5
when the scene starts playing.
With this property in place, you can now update the player’s health whenever a bug reaches the cookie. Save this file and then switch to MoveEnemy.cs, still in MonoDevelop.
To update the player’s health, find the comment in Update()
that reads // TODO: deduct health
and replace it with this code:
GameManagerBehavior gameManager = GameObject.Find("GameManager").GetComponent<GameManagerBehavior>(); gameManager.Health -= 1; |
This gets the GameManagerBehavior
and subtracts one from its Health
.
Save the file and switch to Unity.
Select GameManager in the Hierarchy and set its Health Label to HealthLabel.
Expand Cookie in the Hierarchy and drag and drop its five HealthIndicator children into GameManager’s Health Indicator array — the health indicators are the tiny green monsters happily eating their cookie.
Play the scene and wait for the bugs to reach the cookie. Do nothing until you lose.
Monsters in place? Check. Enemies advancing? Check — and they look mean! Time to mow those suckers down!
This requires several things:
You’ll use two images to implement the health bar, one for a dark background and a slightly smaller green bar you’ll scale to match the enemy’s health.
Drag Prefabs\Enemy into the scene from the Project Browser.
Then drag Images\Objects\HealthBarBackground onto Enemy in the Hierarchy to add it as a child.
In the Inspector, set the Position for HealthBarBackground to (0, 1, -4).
Next, select Images\Objects\HealthBar in the Project Browser and ensure its Pivot is set to Left. Then, add it as a child of Enemy in the Hierarchy and set its Position to (-0.63, 1, -5). Set its X Scale to 125.
Add a new C# script named HealthBar to the HealthBar game object. Later, you’ll edit it to adjust length of the health bar.
With Enemy selected in the Hierarchy, make sure it’s position is (20, 0, 0).
Click on Apply at the top of the Inspector to save all your changes as part of the prefab. Finally, delete Enemy from the Hierarchy.
Now, repeat those steps to add the health bar to Prefabs\Enemy2.
Open HealthBar.cs in MonoDevelop, and add the following variables:
public float maxHealth = 100; public float currentHealth = 100; private float originalScale; |
maxHealth
stores the enemy’s maximal health points, and currentHealth
tracks how much health remains. Lastly, originalScale
remembers the health bar’s original size.
Store the object’s originalScale
in Start()
:
originalScale = gameObject.transform.localScale.x; |
You save the localScale
‘s x
value.
Set the health bar’s scale by adding the following to Update()
:
Vector3 tmpScale = gameObject.transform.localScale; tmpScale.x = currentHealth / maxHealth * originalScale; gameObject.transform.localScale = tmpScale; |
You copy localScale
to a temporary variable because you cannot adjust only its x value. Then, calculate a new x scale based on the bug’s current health, and set the temporary variable back on localScale
.
Save the file and run the game in Unity. You’ll see health bars above the enemies.
While the game runs, expand one of the Enemy(Clone) objects in the Hierarchy and select its HealthBar child. Change its Current Health value and check for that health bar to change.
Now the monsters need to know which enemies to target. You have a bit of prework to do on the Monster and the Enemy before you implement.
Select Prefabs\Monster in the Project Browser and add a Circle Collider 2D component to it in the Inspector.
Set the collider’s Radius to 2.5 — this sets the monsters’ firing range.
Check Is Trigger so that objects pass through the area rather than bump into it.
Finally, at the top of the Inspector, set Monster’s Layer to Ignore Raycast. Click Yes, change children in the dialog. If you don’t ignore raycasts, the collider reacts to click events. That is a problem because the Monsters block events meant for the Openspots below them.
To allow detection of an enemy in the trigger area, you need to add a collider and rigid body to it, because Unity only sends trigger events if one of the colliders has a rigid body attached.
In the Project Browser, select Prefabs\Enemy. Add a Rigid Body 2D component with Is Kinematic checked. This means the body shouldn’t be affected by physics.
Add a Circle Collider 2D with a Radius of 1. Repeat those steps for Prefabs\Enemy 2
The triggers are now set up, so monsters detect when an enemy is in range.
You need to prepare one more thing: A script that notifies monsters when an enemy is destroyed so they don’t cause an exception by continuing to fire.
Create a new C# script named EnemyDestructionDelegate and add it to both the Enemy and Enemy2 prefabs.
Open EnemyDestructionDelegate.cs in MonoDevelop, and add the following delegate declaration:
public delegate void EnemyDelegate (GameObject enemy); public EnemyDelegate enemyDelegate; |
Here you create a delegate
, which is a container for a function that can be passed around like a variable.
Note: Use delegates when you want one game object to actively notify other game objects of changes. Learn more about delegates from the Unity documentation.
Add the following method:
void OnDestroy () { if (enemyDelegate != null) { enemyDelegate (gameObject); } } |
Upon destruction of a game object, Unity calls this method automatically, and it checks whether the delegate is not null
. In that case, you call it with the gameObject
as a parameter. This lets all listeners that are registered as delegates know the enemy was destroyed.
Save the file and go back to Unity.
And now the monsters can detect enemies in range. Add a new C# script to the Monster prefab and name it ShootEnemies.
Open ShootEnemies.cs in MonoDevelop, and add the following using
statement to get access to Generics
.
using System.Collections.Generic; |
Add a variable to keep track of all enemies within range:
public List<GameObject> enemiesInRange; |
In enemiesInRange
, you’ll store all enemies that are in range.
Initialize the field in Start()
.
enemiesInRange = new List<GameObject>(); |
In the beginning, there are no enemies in range, so you create an empty list.
Fill the enemiesInRange
list! Add this code to the script:
// 1 void OnEnemyDestroy (GameObject enemy) { enemiesInRange.Remove (enemy); } void OnTriggerEnter2D (Collider2D other) { // 2 if (other.gameObject.tag.Equals("Enemy")) { enemiesInRange.Add(other.gameObject); EnemyDestructionDelegate del = other.gameObject.GetComponent<EnemyDestructionDelegate>(); del.enemyDelegate += OnEnemyDestroy; } } // 3 void OnTriggerExit2D (Collider2D other) { if (other.gameObject.tag.Equals("Enemy")) { enemiesInRange.Remove(other.gameObject); EnemyDestructionDelegate del = other.gameObject.GetComponent<EnemyDestructionDelegate>(); del.enemyDelegate -= OnEnemyDestroy; } } |
OnEnemyDestroy
, you remove the enemy from enemiesInRange
. When an enemy walks on the trigger around your monster OnTriggerEnter2D
is called.enemiesInRange
and add OnEnemyDestroy
to the EnemyDestructionDelegate
. This makes sure that OnEnemyDestroy
is called when the enemy is destroyed. You don’t want monsters to waste ammo on dead enemies now — do you? OnTriggerExit2D
you remove the enemy from the list and unregister your delegate. Now you know which enemies are in range. Save the file and then run the game in Unity. To test whether it works, place a monster, select it and watch the changes to the enemiesInRange
list in the Inspector.
Now monsters know which enemy is in range. But what do they do when there are multiple in-range enemies?
They attack the one closest to the cookie, of course!
Open MoveEnemy.cs in MonoDevelop, and add this new method to calculates this:
public float distanceToGoal() { float distance = 0; distance += Vector3.Distance( gameObject.transform.position, waypoints [currentWaypoint + 1].transform.position); for (int i = currentWaypoint + 1; i < waypoints.Length - 1; i++) { Vector3 startPosition = waypoints [i].transform.position; Vector3 endPosition = waypoints [i + 1].transform.position; distance += Vector3.Distance(startPosition, endPosition); } return distance; } |
This code calculates the length of road not yet traveled by the enemy. It does so using Distance
, which calculates the difference between two Vector3
instances.
You’ll use this method later to figure out which target to attack. However, your monsters are unarmed and helpless, so fix that first.
Save the file and go back to Unity to begin setting up your bullets.
Drag and drop Images/Objects/Bullet1 from the Project Browser into the scene. Set z position to -2 — x and y positions don’t matter because you set them each time you instantiate a new bullet at run time.
Add a new C# script named BulletBehavior, and add the following variables to it in MonoDevelop:
public float speed = 10; public int damage; public GameObject target; public Vector3 startPosition; public Vector3 targetPosition; private float distance; private float startTime; private GameManagerBehavior gameManager; |
speed
determines how quickly bullets fly; damage
is self-explanatory.
The target
, startPosition
, and targetPosition
determine the bullet’s direction.
distance
and startTime
track the bullet’s current position. gameManager
rewards players when they crush an enemy.
Assign values to these variables in Start()
:
startTime = Time.time; distance = Vector3.Distance (startPosition, targetPosition); GameObject gm = GameObject.Find("GameManager"); gameManager = gm.GetComponent<GameManagerBehavior>(); |
You set startTime
to the current time and calculate the distance between the start and target positions. You also get the GameManagerBehavior
as usual.
Add the following code to Update()
to controls the bullet movement:
// 1 float timeInterval = Time.time - startTime; gameObject.transform.position = Vector3.Lerp(startPosition, targetPosition, timeInterval * speed / distance); // 2 if (gameObject.transform.position.Equals(targetPosition)) { if (target != null) { // 3 Transform healthBarTransform = target.transform.FindChild("HealthBar"); HealthBar healthBar = healthBarTransform.gameObject.GetComponent<HealthBar>(); healthBar.currentHealth -= Mathf.Max(damage, 0); // 4 if (healthBar.currentHealth <= 0) { Destroy(target); AudioSource audioSource = target.GetComponent<AudioSource>(); AudioSource.PlayClipAtPoint(audioSource.clip, transform.position); gameManager.Gold += 50; } } Destroy(gameObject); } |
Vector3.Lerp
to interpolate between start and end positions.targetPosition
, you verify that target
still exists.HealthBar
component and reduce its health by the bullet’s damage
.Save the file and return to Unity.
Wouldn’t it be cool if your monster shot bigger bullets at higher levels? – Yes, yes, it would! Fortunately, this is easy to implement.
Drag and drop the Bullet1 game object from the Hierarchy to the Project tab to create a prefab of the bullet. Remove the original object from the scene — you don’t need it anymore.
Duplicate the Bullet1 prefab twice. Name the copies Bullet2 and Bullet3.
Select Bullet2. In the Inspector, set the Sprite Renderer component’s Sprite field to Images/Objects/Bullet2. This makes Bullet2 look a bit bigger than Bullet1.
Repeat that procedure to set the Bullet3 prefab’s sprite to Images/Objects/Bullet3.
Next, set how much damage the bullets deliver in Bullet Behavior.
Select the Bullet1 prefab in the Project tab. In Inspector you can see the Bullet Behavior (Script), and there you set the Damage to 10 for Bullet1, 15 for Bullet2, and 20 for Bullet3 — or whatever makes you happy there.
Note: I set the values so that at higher levels, the cost per damage is higher. This counteracts the fact that the upgrade allows the player to improve the monsters in the best spots.
Assign different bullets to different monster levels so stronger monsters shred enemies faster.
Open MonsterData.cs in MonoDevelop, and add these variables to MonsterLevel
:
public GameObject bullet; public float fireRate; |
These will set the bullet prefab and fire rate for each monster level. Save the file and head back to Unity to finish setting up your monsters.
Select the Monster prefab in the Project Browser. In the Inspector, expand Levels in the Monster Data (Script) component. Set Fire Rate to 1 for each of the elements. Then set Bullet for Elements 0, 1 and 2 to Bullet1, Bullet2 and Bullet3, respectively.
Your monster levels should be configured as shown below:
Bullets to kill your enemies? – Check! Open fire!
Open the ShootEnemies.cs in MonoDevelop, and add some variables:
private float lastShotTime; private MonsterData monsterData; |
As their names suggest, these variables keep track of when this monster last fired, as well the MonsterData
structure that includes information about this monster’s bullet type, fire rate, etc.
Assign values to those fields in Start()
:
lastShotTime = Time.time; monsterData = gameObject.GetComponentInChildren<MonsterData> (); |
Here you set lastShotTime
to the current time and get access to this object’s MonsterData
component.
Add the following method to implement shooting:
void Shoot(Collider2D target) { GameObject bulletPrefab = monsterData.CurrentLevel.bullet; // 1 Vector3 startPosition = gameObject.transform.position; Vector3 targetPosition = target.transform.position; startPosition.z = bulletPrefab.transform.position.z; targetPosition.z = bulletPrefab.transform.position.z; // 2 GameObject newBullet = (GameObject)Instantiate (bulletPrefab); newBullet.transform.position = startPosition; BulletBehavior bulletComp = newBullet.GetComponent<BulletBehavior>(); bulletComp.target = target.gameObject; bulletComp.startPosition = startPosition; bulletComp.targetPosition = targetPosition; // 3 Animator animator = monsterData.CurrentLevel.visualization.GetComponent<Animator> (); animator.SetTrigger ("fireShot"); AudioSource audioSource = gameObject.GetComponent<AudioSource>(); audioSource.PlayOneShot(audioSource.clip); } |
bulletPrefab
. Earlier, you set the bullet prefab’s z position value to make sure the bullet appears behind the monster firing it, but in front of the enemies.bulletPrefab
for MonsterLevel
. Assign the startPosition
and targetPosition
of the bullet.Time to wire everything together. Determine the target and make your monster watch it.
Still in ShootEnemies.cs, add this code to Update()
:
GameObject target = null; // 1 float minimalEnemyDistance = float.MaxValue; foreach (GameObject enemy in enemiesInRange) { float distanceToGoal = enemy.GetComponent<MoveEnemy>().distanceToGoal(); if (distanceToGoal < minimalEnemyDistance) { target = enemy; minimalEnemyDistance = distanceToGoal; } } // 2 if (target != null) { if (Time.time - lastShotTime > monsterData.CurrentLevel.fireRate) { Shoot(target.GetComponent<Collider2D>()); lastShotTime = Time.time; } // 3 Vector3 direction = gameObject.transform.position - target.transform.position; gameObject.transform.rotation = Quaternion.AngleAxis( Mathf.Atan2 (direction.y, direction.x) * 180 / Mathf.PI, new Vector3 (0, 0, 1)); } |
Go through this code step by step.
minimalEnemyDistance
. Iterate over all enemies in range and make an enemy the new target if its distance to the cookie is smaller than the current minimum.Shoot
if the time passed is greater than the fire rate of your monster and set lastShotTime
to the current time.Save the file andplay the game in Unity. Your monsters vigorously defend your cookie. You’re totally, completely DONE!
You can download the finished project here.
Wow, so you really did a lot between both tutorials and you have a cool game to show for it.
Here are a few ideas to build on what you’ve done:
Each of these ideas requires minimal changes and can make your game addictive. If you created a new game from this tutorial, we’d love to play it — so share the link and your brags in the comments.
You can find interesting thoughts on making a hit tower defense game in this interview.
Thank you for taking the time to work through these tutorials. I look forward to seeing your awesome concepts and killing lots of monsters.
The post How to Create a Tower Defense Game in Unity – Part 2 appeared first on Ray Wenderlich.
This is just a quick note to remind you that the early bird discount for RWDevCon 2016 ends in just a few hours!
As explained in the previous post, RWDevCon is something really special, due to its focus on hands-on tutorials, team coordination, inspiration, and friendship.
If you’ve been thinking of attending, now’s the time! You can register here:
The team and I look forward to seeing you in DC! :]
The post RWDevCon 2016: Last Chance for Early Bird Discount! appeared first on Ray Wenderlich.
Update note: This tutorial was updated for iOS 8 and Swift by Andy Pereira. Original post by Abdul Azeem with fixes and clarifications made by Joseph Neuman.
Recording videos (and playing around with them programmatically) is one of the coolest things you can do with your phone, but not nearly enough apps make use of it. To do this requires the AV Foundation framework that has been a part of OS X since Lion (10.7), and Apple added it to iOS 4 in 2010.
AV Foundation has grown considerably since then, with well over 100 classes now. This tutorial covers media playback and some light editing to get you started with AV Foundation. In particular, you’ll learn how to:
If you run the code in this tutorial on the simulator, you’ll have no way to capture video. Plus, you’ll need to figure out a way to add videos to the media library manually. In other words, you really need to test this code on a device! To do that you’ll need to be a registered Apple developer .
Are you ready? Lights, cameras, action!
Download the starter project. This project contains a storyboard and several view controllers with the UI for a simple video playback and recording app.
The main screen contains the three buttons below that segue to other view controllers.
Build and run the project, and test out the buttons; only the three buttons on the initial scene do anything, but you will change that soon!
The “Play Video” button on the main screen segues to PlayVideoController
. In this section of the tutorial, you’ll add the code to select a video file and play it.
Start by opening PlayVideoViewController.swift, and add the following import statements at the top of the file:
import MediaPlayer import MobileCoreServices |
Importing MediaPlayer
gives you access to the MediaPlayer
object that plays the selected video. MobileCoreServices
contains predefined constants such as kUTTypeMovie
, which you’ll need to refer to when selecting videos.
Next, scroll down to the end of the file and add the following class extensions. You need to add these to the very bottom of the file, outside the curly braces of the class declaration:
// MARK: - UIImagePickerControllerDelegate extension PlayVideoViewController: UIImagePickerControllerDelegate { } // MARK: - UINavigationControllerDelegate extension PlayVideoViewController: UINavigationControllerDelegate { } |
These extensions set up the PlayVideoViewController
to adopt the UIImagePickerControllerDelegate
and UINavigationControllerDelegate
protocols. You’ll be using the system-provided UIImagePickerController
to allow the user to to browse videos in the photo library, and that class communicates back to your app through these delegate protocols. Although the class is named “image picker”, rest assured it works with videos too!
Next, head back to the main class definition and add the implementation for the following helper method to open the image picker:
func startMediaBrowserFromViewController(viewController: UIViewController, usingDelegate delegate: protocol<UINavigationControllerDelegate, UIImagePickerControllerDelegate>) -> Bool { // 1 if UIImagePickerController.isSourceTypeAvailable(.SavedPhotosAlbum) == false { return false } // 2 var mediaUI = UIImagePickerController() mediaUI.sourceType = .SavedPhotosAlbum mediaUI.mediaTypes = [kUTTypeMovie as NSString] mediaUI.allowsEditing = true mediaUI.delegate = delegate // 3 presentViewController(mediaUI, animated: true, completion: nil) return true } |
In the above code, you do the following:
.SavedPhotosAlbum
source is available on the device. Other sources are the camera itself and the photo library. This check is essential whenever you use a UIImagePickerController
to pick media. If you don’t do it, you might try to pick media from a non-existent media library, resulting in crashes or other unexpected issues.UIImagePickerController
object and set its source and media type. Notice the mediaTypes
array only includes kUTTypeMovie
, which will filter the results to only include video.UIImagePickerController
modally.Now add the following code to playVideo(_:)
:
startMediaBrowserFromViewController(self, usingDelegate: self) |
The above code ensures that tapping the “Play Video” button will open the UIImagePickerController
, allowing the user to select a video file from the media library.
Now you’re ready to give your project another whirl! Build and run. When you tap the “Select and Play Video” button on the first screen, and then tap the “Play Video” button on the second screen, you should see your videos presented similar to the following screenshot.
Once you see the list of videos, select one. You’ll be taken to another screen that shows the video in detail, along with buttons to cancel, play and “choose”. If you tap the play button the video will play. However, if you tap the choose button, the app just returns to the Play Video screen! This is because you haven’t implemented any delegate methods to handle choosing a video from the picker.
Back in Xcode, scroll down to the UIImagePickerControllerDelegate
class extension and add the following delegate method implementation:
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [NSObject : AnyObject]) { // 1 let mediaType = info[UIImagePickerControllerMediaType] as! NSString // 2 dismissViewControllerAnimated(true) { // 3 if mediaType == kUTTypeMovie { let moviePlayer = MPMoviePlayerViewController(contentURL: info[UIImagePickerControllerMediaURL] as! NSURL) self.presentMoviePlayerViewControllerAnimated(moviePlayer) } } } |
Here’s what’s going on in this method:
MPMoviePlayerViewController
to play it.Build and run. Press the “Select and Play Video” button, then the “Play Video” button, and finally choose a video from the list. You should be able to see the video playing in the media player.
Now that you have video playback working, it’s time to record a video using the device’s camera and save it to the media library.
Open RecordVideoViewController.swift, and add the following import:
import MobileCoreServices |
You’ll also need to adopt the same protocols as PlayVideoViewController
, by adding the following to the end of the file:
extension RecordVideoViewController: UIImagePickerControllerDelegate { } extension RecordVideoViewController: UINavigationControllerDelegate { } |
Add the following code to RecordVideoViewController
:
func startCameraFromViewController(viewController: UIViewController, withDelegate delegate: protocol<UIImagePickerControllerDelegate, UINavigationControllerDelegate>) -> Bool { if UIImagePickerController.isSourceTypeAvailable(.Camera) == false { return false } var cameraController = UIImagePickerController() cameraController.sourceType = .Camera cameraController.mediaTypes = [kUTTypeMovie as NSString] cameraController.allowsEditing = false cameraController.delegate = delegate presentViewController(cameraController, animated: true, completion: nil) return true } |
This method follows the same logic is in PlayVideoViewController
, but it accesses the .Camera
instead to record video.
Now add the following to record(_:)
:
startCameraFromViewController(self, withDelegate: self) |
You are again in familiar territory. The code simply calls startCameraControllerFromViewController(_:usingDelegate:)
when you tap the “Record Video” button.
Build and run to see what you’ve got so far.
Go to the Record screen and press the “Record Video” button. Instead of the Photo Gallery, the camera UI opens. Start recording a video by tapping the red record button at the bottom of the screen, and tap it again when you’re done recording.
When you get to the next screen, you can opt to use the recorded video or re-take the video. If you select “Use,” you’ll notice that nothing happens – that’s because, you guessed it, you haven’t implemented an appropriate callback method. You need the callback method to save the recorded video to the media library.
Add the following method to the UIImagePickerControllerDelegate
class extension:
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [NSObject : AnyObject]) { let mediaType = info[UIImagePickerControllerMediaType] as! NSString dismissViewControllerAnimated(true, completion: nil) // Handle a movie capture if mediaType == kUTTypeMovie { let path = (info[UIImagePickerControllerMediaURL] as! NSURL).path if UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(path) { UISaveVideoAtPathToSavedPhotosAlbum(path, self, "video:didFinishSavingWithError:contextInfo:", nil) } } } |
As before, the delegate method gives you a URL pointing to the video. You verify that the app can save the file to the device’s photo album, and if so, save it.
UISaveVideoAtPathToSavedPhotosAlbum
is the function provided by the SDK to save videos to the Photos Album. As parameters, you pass the path to the video you want to save as well as a target and action to call back, which will inform you of the status of the save operation.
Add the implementation of the callback to the main class definition next:
func video(videoPath: NSString, didFinishSavingWithError error: NSError?, contextInfo info: AnyObject) { var title = "Success" var message = "Video was saved" if let saveError = error { title = "Error" message = "Video failed to save" } let alert = UIAlertController(title: title, message: message, preferredStyle: .Alert) alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Cancel, handler: nil)) presentViewController(alert, animated: true, completion: nil) } |
The callback method simply displays an alert to the user, telling them whether saving the video file was successful or not based on the error status.
Build and run. Record a video and select “Use” when you stop recording. If the “Video was saved” alert pops up, you just successfully saved your video to the photo library!
Now that you can play videos and record videos, it’s time to take the next step and try some light video editing.
The final piece of functionality for the app is to do a little editing. The user will select two videos and a song from the music library, and the app will combine the two videos and mix in the music.
The project already has a starter implementation in MergeVideoViewController.swift. The code here is similar to the code you wrote to play a video. The big difference is when merging, the user needs to select two videos. That part is already set up, so the user can make two selections that will be stored in firstAsset
and secondAsset
.
The next step is to add the functionality to select the audio file.
The UIImagePickerController
only provides functionality to select video and images from the media library. To select audio files from your music library, you will use the MPMediaPickerController
. It works essentially the same as UIImagePickerController
, but instead of images and video, it accesses audio files in the media library.
Open MergeVideoViewController.swift and add the following code to loadAudio(_:):
let mediaPickerController = MPMediaPickerController(mediaTypes: .Any) mediaPickerController.delegate = self mediaPickerController.prompt = "Select Audio" presentViewController(mediaPickerController, animated: true, completion: nil) |
The above code creates a new MPMediaPickerController
instance and displays it as a modal view controller.
Build and run. Now when you tap the “Load Audio” button, you can access the audio library on your device. Of course, you’ll need some audio files on your device. Otherwise, the list will be empty. The songs will also have to be physically present on the device, so make sure you’re not trying to load a song from the cloud.
If you select a song from the list, you’ll notice that nothing happens. That’s right, MPMediaPickerController
needs delegate methods! Find the MPMediaPickerControllerDelegate
class extension at the bottom of the file and add the following two methods to it:
func mediaPicker(mediaPicker: MPMediaPickerController!, didPickMediaItems mediaItemCollection: MPMediaItemCollection!) { let selectedSongs = mediaItemCollection.items if selectedSongs.count > 0 { let song = selectedSongs[0] as! MPMediaItem if let url = song.valueForProperty(MPMediaItemPropertyAssetURL) as? NSURL { audioAsset = (AVAsset.assetWithURL(url) as! AVAsset) dismissViewControllerAnimated(true, completion: nil) let alert = UIAlertController(title: "Asset Loaded", message: "Audio Loaded", preferredStyle: .Alert) alert.addAction(UIAlertAction(title: "OK", style: .Cancel, handler:nil)) presentViewController(alert, animated: true, completion: nil) } else { dismissViewControllerAnimated(true, completion: nil) let alert = UIAlertController(title: "Asset Not Available", message: "Audio Not Loaded", preferredStyle: .Alert) alert.addAction(UIAlertAction(title: "OK", style: .Cancel, handler:nil)) presentViewController(alert, animated: true, completion: nil) } } else { dismissViewControllerAnimated(true, completion: nil) } } func mediaPickerDidCancel(mediaPicker: MPMediaPickerController!) { dismissViewControllerAnimated(true, completion: nil) } |
The code is very similar to the delegate methods for UIImagePickerController
. You set the audio asset based on the media item selected via the MPMediaPickerController
after ensuring it’s a valid media item.
Build and run, and go to the Merge Videos screen. Select an audio file and if there are no errors, you should see the “Audio Loaded” message.
You now have all your assets loading correctly. It’s time to merge the various media files into one file.
But before you get into that code, you have to do a little bit of set up.
The code to merge your assets will require a completion handler to export the final video to the photos album.
Add the code below to MergeVideoViewController
.
func exportDidFinish(session: AVAssetExportSession) { if session.status == AVAssetExportSessionStatus.Completed { let outputURL = session.outputURL let library = ALAssetsLibrary() if library.videoAtPathIsCompatibleWithSavedPhotosAlbum(outputURL) { library.writeVideoAtPathToSavedPhotosAlbum(outputURL, completionBlock: { (assetURL:NSURL!, error:NSError!) -> Void in var title = "" var message = "" if error != nil { title = "Error" message = "Failed to save video" } else { title = "Success" message = "Video saved" } let alert = UIAlertController(title: title, message: message, preferredStyle: .Alert) alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Cancel, handler: nil)) self.presentViewController(alert, animated: true, completion: nil) }) } } activityMonitor.stopAnimating() firstAsset = nil secondAsset = nil audioAsset = nil } |
Once the export completes successfully, the above code saves the newly exported video to the photo album. You could just display the output video in an AssetBrowser
, but it’s easier to copy the output video to the photo album so you can see the final output.
Now, add the following code to merge(_:)
:
if let firstAsset = firstAsset, secondAsset = secondAsset { activityMonitor.startAnimating() // 1 - Create AVMutableComposition object. This object will hold your AVMutableCompositionTrack instances. var mixComposition = AVMutableComposition() // 2 - Video track let videoTrack = mixComposition.addMutableTrackWithMediaType(AVMediaTypeVideo, preferredTrackID: Int32(kCMPersistentTrackID_Invalid)) videoTrack.insertTimeRange(CMTimeRangeMake(kCMTimeZero, firstAsset.duration), ofTrack: firstAsset.tracksWithMediaType(AVMediaTypeVideo)[0] as! AVAssetTrack, atTime: kCMTimeZero, error: nil) videoTrack.insertTimeRange(CMTimeRangeMake(kCMTimeZero, secondAsset.duration), ofTrack: secondAsset.tracksWithMediaType(AVMediaTypeVideo)[0] as! AVAssetTrack, atTime: firstAsset.duration, error: nil) // 3 - Audio track if let loadedAudioAsset = audioAsset { let audioTrack = mixComposition.addMutableTrackWithMediaType(AVMediaTypeAudio, preferredTrackID: 0) audioTrack.insertTimeRange(CMTimeRangeMake(kCMTimeZero, CMTimeAdd(firstAsset.duration, secondAsset.duration)), ofTrack: loadedAudioAsset.tracksWithMediaType(AVMediaTypeAudio)[0] as! AVAssetTrack, atTime: kCMTimeZero, error: nil) } // 4 - Get path let documentDirectory = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as! String var dateFormatter = NSDateFormatter() dateFormatter.dateStyle = .LongStyle dateFormatter.timeStyle = .ShortStyle let date = dateFormatter.stringFromDate(NSDate()) let savePath = documentDirectory.stringByAppendingPathComponent("mergeVideo-\(date).mov") let url = NSURL(fileURLWithPath: savePath) // 5 - Create Exporter let exporter = AVAssetExportSession(asset: mixComposition, presetName: AVAssetExportPresetHighestQuality) exporter.outputURL = url exporter.outputFileType = AVFileTypeQuickTimeMovie exporter.shouldOptimizeForNetworkUse = true // 6 - Perform the Export exporter.exportAsynchronouslyWithCompletionHandler() { dispatch_async(dispatch_get_main_queue(), { () -> Void in self.exportDidFinish(exporter) }) } } |
Here’s a step-by-step breakdown of the above code:
AVMutableComposition
object to hold your video and audio tracks and transform effects.AVMutableCompositionTrack
for the video and add it to your AVMutableComposition
object. Then you insert your two videos to the newly created AVMutableCompositionTrack
.
Note that insertTimeRange(_:ofTrack:atTime:error:)
allows you to insert a part of a video into your main composition instead of the whole video. This way, you can trim the video to a time range of your choosing.
In this instance, you want to insert the whole video, so you create a time range from kCMTimeZero
to your video asset duration. The atTime
parameter allows you to place your video/audio track wherever you want it in your composition. Notice how the code inserts firstAsset
at time zero, and it inserts secondAsset
at the end of the first video. This tutorial assumes you want your video assets one after the other. But you can also overlap the assets by playing with the time ranges.
For working with time ranges, you use CMTime
structs. CMTime
structs are non-opaque mutable structs representing times, where the time could be a timestamp or a duration.
AVAssetExportSession
object that transcodes the contents of an AVAsset
source object to create an output of the form described by a specified export preset.presetName
), and the output file type (outputFileType
), you start the export running by invoking exportAsynchronouslyWithCompletionHandler()
. Because the code performs the export asynchronously, this method returns immediately. The code calls the completion handler you supply to exportAsynchronouslyWithCompletionHandler()
whether the export fails, completes, or the user canceled. Upon completion, the exporter’s status property indicates whether the export has completed successfully. If it has failed, the value of the exporter’s error property supplies additional information about the reason for the failure.An AVComposition
instance combines media data from multiple file-based sources. At its top level, an AVComposition
is a collection of tracks, each presenting media of a specific type such as audio or video. An instance of AVCompositionTrack
represents a single track.
Similarly, AVMutableComposition
and AVMutableCompositionTrack
also present a higher-level interface for constructing compositions. These objects offer insertion, removal, and scaling operations that you’ve seen already and will come up again.
Go ahead, build and run your project!
Select two videos and an audio files and merge the selected files. If the merge was successful, you should see a “Video Saved” message. At this point, your new video should be present in the photo album.
Go to the photo album, or browse using your the “Select and Play Video” screen within the app. You’ll might notice that although the app merged the videos, there are some orientation issues. Portrait video is in landscape mode, and sometimes videos are turned upside down.
This is due to the default AVAsset
orientation. All movie and image files recorded using the default iPhone camera application have the video frame set to landscape, and so the iPhone saves the media in landscape mode.
AVAsset
has a preferredTransform
property that contains the media orientation information, and it applies this to a media file whenever you view it using the Photos app or QuickTime. In the code above, you haven’t applied a transform to your AVAsset
objects, hence the orientation issue.
You can correct this easily by applying the necessary transforms to your AVAsset
objects. But as your two video files can have different orientations, you’ll need to use two separate AVMutableCompositionTrack
instances instead of one as you originally did.
Before you can do this, add the first of two helper methods to MergeVideoViewController
:
func orientationFromTransform(transform: CGAffineTransform) -> (orientation: UIImageOrientation, isPortrait: Bool) { var assetOrientation = UIImageOrientation.Up var isPortrait = false if transform.a == 0 && transform.b == 1.0 && transform.c == -1.0 && transform.d == 0 { assetOrientation = .Right isPortrait = true } else if transform.a == 0 && transform.b == -1.0 && transform.c == 1.0 && transform.d == 0 { assetOrientation = .Left isPortrait = true } else if transform.a == 1.0 && transform.b == 0 && transform.c == 0 && transform.d == 1.0 { assetOrientation = .Up } else if transform.a == -1.0 && transform.b == 0 && transform.c == 0 && transform.d == -1.0 { assetOrientation = .Down } return (assetOrientation, isPortrait) } |
This code will construct an affine transform to get the output video looking correct based on the input video’s orientation.
Next, add one more helper method to the class:
func videoCompositionInstructionForTrack(track: AVCompositionTrack, asset: AVAsset) -> AVMutableVideoCompositionLayerInstruction { // 1 let instruction = AVMutableVideoCompositionLayerInstruction(assetTrack: track) // 2 let assetTrack = asset.tracksWithMediaType(AVMediaTypeVideo)[0] as! AVAssetTrack // 3 var transform = assetTrack.preferredTransform let assetInfo = orientationFromTransform(transform) var scaleToFitRatio = UIScreen.mainScreen().bounds.width / assetTrack.naturalSize.width if assetInfo.isPortrait { // 4 scaleToFitRatio = UIScreen.mainScreen().bounds.width / assetTrack.naturalSize.height let scaleFactor = CGAffineTransformMakeScale(scaleToFitRatio, scaleToFitRatio) instruction.setTransform(CGAffineTransformConcat(assetTrack.preferredTransform, scaleFactor), atTime: kCMTimeZero) } else { // 5 let scaleFactor = CGAffineTransformMakeScale(scaleToFitRatio, scaleToFitRatio) var concat = CGAffineTransformConcat(CGAffineTransformConcat(assetTrack.preferredTransform, scaleFactor), CGAffineTransformMakeTranslation(0, UIScreen.mainScreen().bounds.width / 2)) if assetInfo.orientation == .Down { let fixUpsideDown = CGAffineTransformMakeRotation(CGFloat(M_PI)) let windowBounds = UIScreen.mainScreen().bounds let yFix = assetTrack.naturalSize.height + windowBounds.height let centerFix = CGAffineTransformMakeTranslation(assetTrack.naturalSize.width, yFix) concat = CGAffineTransformConcat(CGAffineTransformConcat(fixUpsideDown, centerFix), scaleFactor) } instruction.setTransform(concat, atTime: kCMTimeZero) } return instruction } |
This method takes a track and asset, and returns a AVMutableVideoCompositionLayerInstruction
which wraps the affine transform needed to get the video right side up. Here’s what’s going on, step-by-step:
AVMutableVideoCompositionLayerInstruction
and associate it with your firstTrack.AVAssetTrack
object from your AVAsset
. An AVAssetTrack
object provides the track-level inspection interface for all assets. You need this object in order to access the preferredTransform
and dimensions of the asset..Down
will handle this case.With the helper methods set up, find merge(_:)
and replace section #2 with the following so that you have two AVMutableCompositionTrack
instances instead of one:
// 2 - Create two video tracks var firstTrack = mixComposition.addMutableTrackWithMediaType(AVMediaTypeVideo, preferredTrackID: Int32(kCMPersistentTrackID_Invalid)) firstTrack.insertTimeRange(CMTimeRangeMake(kCMTimeZero, firstAsset.duration), ofTrack: firstAsset.tracksWithMediaType(AVMediaTypeVideo)[0] as! AVAssetTrack, atTime: kCMTimeZero, error: nil) var secondTrack = mixComposition.addMutableTrackWithMediaType(AVMediaTypeVideo, preferredTrackID: Int32(kCMPersistentTrackID_Invalid)) secondTrack.insertTimeRange(CMTimeRangeMake(kCMTimeZero, secondAsset.duration), ofTrack: secondAsset.tracksWithMediaType(AVMediaTypeVideo)[0] as! AVAssetTrack, atTime: firstAsset.duration, error: nil) // 2.1 var mainInstruction = AVMutableVideoCompositionInstruction() mainInstruction.timeRange = CMTimeRangeMake(kCMTimeZero, CMTimeAdd(firstAsset.duration, secondAsset.duration)) // 2.2 let firstInstruction = videoCompositionInstructionForTrack(firstTrack, asset: firstAsset) firstInstruction.setOpacity(0.0, atTime: firstAsset.duration) let secondInstruction = videoCompositionInstructionForTrack(secondTrack, asset: secondAsset) // 2.3 mainInstruction.layerInstructions = [firstInstruction, secondInstruction] let mainComposition = AVMutableVideoComposition() mainComposition.instructions = [mainInstruction] mainComposition.frameDuration = CMTimeMake(1, 30) mainComposition.renderSize = CGSize(width: UIScreen.mainScreen().bounds.width, height: UIScreen.mainScreen().bounds.height) |
First, you set up two separate AVMutableCompositionTrack
instances. That means you need to apply an AVMutableVideoCompositionLayerInstruction
to each track in order to fix the orientation separately.
2.1: First, you set up mainInstruction
to wrap the entire set of instructions. Note that the total time here is the sum of the first asset’s duration and the second asset’s duration.
2.2: Next, you set up the two instructions – one for each asset – using the helper method you defined earlier. The instruction for the first video needs one extra addition – you set its opacity to 0 at the end so it becomes invisible when the second video starts.
2.3: Now that you have your AVMutableVideoCompositionLayerInstruction
instances for the first and second tracks, you just add them to the main AVMutableVideoCompositionInstruction
object. Next, you add your mainInstruction
object to the instructions property of an instance of AVMutableVideoComposition
. You also set the frame rate for the composition to 30 frames/second.
Now that you’ve got an AVMutableVideoComposition
object configured, all you need to do is assign it your exporter. Insert the following code at the end of of section #5 (just before exportAsynchronouslyWithCompletionHandler():
:
exporter.videoComposition = mainComposition |
Whew – that’s it!
Build and run your project. If you create a new video by combining two videos (and optionally an audio file), you will see that the orientation issues disappear when you play back the new merged video.
Here is the final project with all of the code from this tutorial.
If you followed along, you should now have a good understanding of how to play video, record video, and merge multiple videos and audio in your apps.
AV Foundation gives you a lot of flexibility when playing around with videos. You can also apply any kind of CGAffineTransform to merge, scale, or position videos.
If you haven’t already done so, I would recommend that you have a look at the WWDC videos on AV Foundation, such as WWDC 2013 session 612 Advanced Editing with AV Foundation, and session 606 Moving to AV Kit and AV Foundation (this is about the introduction of AV Kit to OS X). Also, WWDC 2014 session 503 Mastering Modern Media Playback where Apple introduced AV Kit in iOS 8. Also, be sure to check out the Apple AV Foundation Framework programming guide.
I hope this tutorial has been useful to get you started with video manipulation in iOS. If you have any questions, comments, or suggestions for improvement, please join the forum discussion below!
The post How to Play, Record, and Merge Videos in iOS and Swift appeared first on Ray Wenderlich.
In one of the final chapters of “Steve Jobs” by Walter Isaacson — Steve was quoted saying “It will have the simplest user interface you could imagine. I finally cracked it.” He was, of course, referring to Apple TV.
It wasn’t long after that Steve unfortunately passed away, and since then many of us have been eagerly anticipating this product. Yesterday on September 9th, 2015 — more than four years later — we were finally presented with what may possibly be Steve’s vision, or perhaps just the beginning of it.
Yesterday’s announcement was super-exciting for us as iOS developers, because Apple announced that the new Apple TV will have an App Store that we can develop apps for – leveraging much of our existing iOS knowledge and opening many new opportunities.
I and the rest of the Tutorial Team have been digging into the tvOS SDK and are already hard at work preparing some tutorials on tvOS (stay tuned!), but to tide you over in the meantime, I wanted to share my initial impressions of tvOS from an iOS developer’s perspective.
Let’s dig in!
The original Apple TV was a set-top box that provides users with a simplistic user interface designed primarily for navigating and viewing video content. The new Apple TV announced yesterday expands this to full-blown apps for things like shopping and even games.
Let’s took a look at the critical info:
By this point, it should be obvious that developing apps for the new Apple TV is a thing, and it’s a great thing!
When developing for Apple TV you will be developing for tvOS — the name coined by Apple as the device’s operating system. tvOS is built upon iOS and lends many of the same frameworks that you may already be familiar with.
In order to develop for tvOS you will need to download Xcode 7.1 from the developer center. This is a beta version of Xcode that includes support for tvOS, and a tvOS Simulator. You can install the Xcode 7.1 beta alongside Xcode 7 GM if needed.
Apple has provided two methods of developing apps on tvOS:
When you develop an Apple TV app, it appears to be a separate target (which implies that tvOS apps would be a separate purchase for customers). However, in the keynote Apple said there would be support for “universal purchases” where customers could purchase an iOS and tvOS app for a single price. We’re not quite sure how that works yet – maybe some support in iTunes Connect to link the two targets is coming?
As mentioned, the first method of making apps is via TVML, TVJS, and TVMLKit. If these abbreviations sound foreign to you, don’t fret because they should. Here’s what they are:
If you’re a native iOS developer the thought of these might make you wince a little bit. But keep an open mind, there is some great power to all of this.
Here’s a very typical use-case for apps on Apple TV. Consider the following: you have content on a server and you want to display that content to users. Your content is organized in a predictable manner and navigating it should be intuitive and familiar. You want your tvOS app to feel at home with other apps. You’re not interested in pushing the envelope on cutting edge user experiences and designs.
If this sounds like you and your app’s requirements, you may want to consider TVMLKit. Apple has put a lot of work into providing reusable templates that will be familiar to the user, there are in fact 18 of them as of this writing. Many of them allow you to create stunning interfaces that feel right at home on the user’s television. You can browse the templates in the Apple TV Markup Language Reference which I would highly recommend.
I also recommend running the TVML Catalog sample app to view each template in action in the tvOS simulator. Be sure to check out the README.md
file as you’ll be required to start a local web server to host the content for the app to access. Don’t worry, it’s a single terminal command.
There is truly a ton of content to cover on this topic and it is very much worth your while to consider TVMLKit as an option for building your app. At this point in time I would recommend it to be the first point of consideration for apps that primarily provide menus of content. However, if you’re aiming to provide a fully immersive experience in your app, where your users will be spending more time interacting with your interface than passively watching or listening to content, then it is time to move on to developing a fully custom app.
When you need to get down to the nuts and bolts and provide a one-of-a-kind experience you will need to brush off your iOS skills and work your UIKit-fu.
Many of the iOS frameworks you know and love like UIKit, Core Graphics, SpriteKit, CloudKit, and more are available on tvOS – you can see the whole list of what is (and isn’t) included here. If you are an existing iOS developer, you should feel very empowered by this list!
Writing a tvOS app can be done using both Swift, Objective-C and C (I have not seen anything to say C++ is off limits either).
Although there’s definitely a lot of new material to learn about making custom apps for tvOS, you should feel right at home as an iOS developer.
One concept that will be new to iOS developers is the user input method. Users will not be touching your UI with their fingers or noses (you know you’ve done it or at least thought about it!). But rather, they will be using the provided remote control or even a game controller.
tvOS introduces a system known as the focus engine. The main concept is that there is always one — and only one — item that is in focus at a given time. Users navigate your UI by using swipe gestures or a directional pad/control to move up, down, left or right.
The focus engine automatically decides which view to focus based on user input; you don’t have to do anything. For example, you just arrange your views in your Storyboard, and if one of the views is focused and the user swipes right, the focus engine will automatically find the closest view to the left of the selected view and focus it for you.
As a developer, there are some new APIs related to the focus engine you’ll want to be aware of. There are APIs for your app to get notified when the focused item updates, to programmatically trigger a focus update, and much more. For more info, check out the Supporting Focus Within Your App section of the App Programming Guide for tvOS.
Although tvOS development relies primarily on iOS frameworks you may already be familiar with, there are a few new frameworks to be aware of, such as the new TVServices
framework.
The TVServices
framework is primarily used to describe content in your app so that the system can display it on the “Top Shelf”. When a user places your app in the top row of their home screen your app is considered one of the most important apps that the user has installed.
This is an excellent opportunity to provide extra value to your users by giving them shortcuts to content they may be interested in. For a game, it may be game saves so they can jump right back into the action. For a social media app, you may have trending content that appears on the top shelf. And a photo sharing app may show off recently shared pictures by friends and family.
Check out the TVServices Framework Reference for more information on how to implement this feature. It is very important that you take full advantage of this opportunity.
One thing that may have struck you as a little odd in the Keynote was the presenter’s emphasis on parallax effects happening on images and app icons. This is a really neat visual effect, but what’s so important about it?
As you play around with the tvOS simulator you’ll begin to understand why it’s so important: to move the focus item, you need to swipe a fair bit to the left or right. But if you swipe a little bit (but not far enough) Apple will begin to rotate the focus item, in order to give you a visual cue that you are doing something (but need to continue swiping to change the focus). It’s subtle but powerful.
Apple considers the parallax effect a key component of tvOS design. It is required of your App’s icon and highly encouraged for other media such as movie poster art.
Thankfully, Apple has outlined exactly how to create these assets in the Creating Parallax Artwork section of the App Programming Guide for tvOS, and has even provided a neat previewer app.
Everyone who buys the new Apple TV will receive a powerful new remote control. The previous generation was very simple with a directional pad and couple other navigational buttons. The new Apple TV remote has some exciting new features, specifically:
You can use gesture recognizers you know and love to detect swipes and taps, and there are also new pressesBegan()
, pressesEnded()
, pressesChanged()
, and pressesCancelled()
APIs to detect when various buttons on the control are selected.
The remote communicates using Bluetooth, which leaves the door wide open for Bluetooth game controllers (especially since the default remote isn’t particularly well suited to games IMHO). Apple has already announced that the Nimbus Steelseries Controller will be available for the new Apple TV. Check out the Working with Game Controllers section to learn more.
We’re willing to bet that games will be really hot on tvOS – Apple seems to be aiming squarely at Nintendo’s casual games market.
tvOS has strong game support. SpriteKit and SceneKit both work on tvOS, and in the keynote the Crossy Roads developer showed a tvOS port of their game, which is made in Unity, which implies Unity support may be coming in the future as well.
Many SceneKit games port almost seamlessly to the Apple TV. For example, Ray ported Zombie Conga from iOS Games by Tutorials to the Apple TV in less than 10 minutes, without even having to change any code:
This ease of porting games is sure to boost Apple TV’s initial games catalog!
Perhaps two of the most puzzling aspects of tvOS are the limitations imposed on local storage and app size.
Starting with local storage… there is none! If your app needs to persist any user data you must do so using iCloud, CloudKit, or your own back-end service. Any data that you store on device is not guaranteed to be available the next time your app is opened. Furthermore if you want to synchronize data across devices you will need to store it somewhere else.
This is important to keep in mind as you design the architecture of your tvOS apps. Here are some rules of thumb:
Another limitation is that your app size cannot exceed 200MB.
But before you flip your table, think back to WWDC 2015 and the “On-Demand Resources” APIs that were introduced – it’s almost as if Apple planned this! :]
These APIs provide the ability to reduce the initial installation size of your app by downloading resources on demand. Developers can tag assets in Xcode and the App Store will automatically split tagged content into downloadable bundles upon submission. When the user needs the tagged resources you can request them to be downloaded to the device. You would ideally anticipate the content needs and download them before the user needs them so they do not notice the load times.
A simple example of this approach is a game that has 10 levels. You may opt to include only the first 2 levels when the app is installed. Once the user finishes level 1 you send a request to download the assets required for level 3 so that when the user finishes level 2, level 3 is ready to go. You’d then continue to do this as the user progresses through the game.
While this may seem annoying and painful as a developer, you can think of the user benefit. Rather than waiting for your 1GB game to download, they can perhaps download 100MB and start playing right away. A possible unfortunate outcome is that the user has a poor Internet connection and prefers to queue up downloads overnight. Downloading the next 900MB of your game as they play through may result in a terrible experience. Unfortunately as tvOS developers our hands are tied in this scenario.
There is a lot that can and will be done on tvOS! So prepare your couch; if you don’t have a designated “groove”, you may before year’s end :]
And remember: this is only the beginning. iOS didn’t even have cut, copy, and paste until iOS 3.0!
I believe that this platform can truly reinvent the way we use our TVs. Aside from the obvious gaming and content viewing use-cases, there are many areas for developers to innovate.
Here are some resources to learn more about tvOS:
What do you think of the Apple TV and tvOS? Please join us in the forum discussion below!
The post tvOS SDK: An iOS Developer’s Initial Impressions appeared first on Ray Wenderlich.
Learn how to create your own mapping model which Core Data will use to perform the migration from an older version of your data model to the latest version.
The post Video Tutorial: Intermediate Core Data Part 3: Mapping Models appeared first on Ray Wenderlich.