Learn how to implement zooming inside a scroll view.
Video Tutorial: Scroll View School Part 2: Zooming is a post from: Ray Wenderlich
The post Video Tutorial: Scroll View School Part 2: Zooming appeared first on Ray Wenderlich.
Learn how to implement zooming inside a scroll view.
Video Tutorial: Scroll View School Part 2: Zooming is a post from: Ray Wenderlich
The post Video Tutorial: Scroll View School Part 2: Zooming appeared first on Ray Wenderlich.
There are a ton of different ways you can monetize your app, and the most straightforward way is to make it paid. Unfortunately, a large number of users are not willing to purchase an app without trying it first.
One way to solve this problem on the App Store is to offer two different versions of your app:
In this tutorial, you will learn how to create both a paid and lite version of an iPhone app in a single Xcode project, using Xcode target settings.
Specifically, you will create an app focused on iOS interview questions, where the lite version has ads and the paid version does not.
Note: An alternate way to accomplish this is to have a single app that is free, which includes an in-app purchase to hide ads or unlock extra features or content. This was done in the popular app Letterpress.
Which option is best for you depends on your particular app. If you decide to go down the in-app purchase route, we have a tutorial for that.
The bad approach to creating both a paid and lite version of an app is to have multiple projects:
That’s nice and simple, and it will work, but pretty soon you’re going to run into some big headaches. You now have two completely separate projects to update – so if you find a bug, you need to update it in both places! That’s a hassle, it’s error prone, and it’s not good programming practice.
So how can you change your approach to managing two versions that will avoid this issue?
The better approach is to have just a single project, with multiple targets instead – one for each version of your app. So what are targets, and how do they help us achieve this goal?
Apple describes targets as follows:
“A target specifies a product to build and contains the instructions for building the product from a set of files in a project or workspace. A target defines a single product; it organizes the inputs into the build system—the source files and instructions for processing those source files—required to build that product. Projects can contain one or more targets, each of which produces one product.”
In plain english, this means that targets define products that each have their own configurations and source files within a single project. This sounds like a perfect solution for creating a free and paid version of an app while avoiding multiple projects and duplicate code!
Each target has its own plist that defines values like the bundle identifier, version number and build number which allow you to build unique products within a project. The target also has settings accessible from the project editor in XCode, including Preprocessor Macros found under Build Settings:
For this tutorial, you’ll use Preprocessor Macros to identify the product version in your code, thus enabling logical decisions based on the version. You’ll learn how to configure the Plist and Build Settings (most importantly the Preprocessor Macros) to enable product version differences under the same codebase. You’ll also learn how to easily swap assets used by different targets – in this case icons – to further customize product versions.
With that knowledge, your new plan for multiple versions will look something like this:
To get you started, I’ve created a simple application that will serve as a cree version of an app focused on iOS interview questions. You will enhance it by adding a paid version.
Download the starter project, unzip it and open it in XCode. Run your project and you’ll see a welcome screen that has some static text and a button:
Once the user presses the Test my skills! button, the quiz will start:
Play a little bit and see how well you do. Once you hit the question limit, you’ll see an alert with your score:
I was able to answer all 5 questions easily, how about you?
Now that you’ve tried the app, let’s take a tour of the code.
IQViewController is the quiz view controller and contains all of the custom code in this simple project. Open up IQViewController.m and take a look at setupQuiz:
- (void)setupQuiz { self.navigationItem.title = @"Free Version"; self.maxAllowedQuestions = 5; [self setupAds]; NSString* plistPath = [[NSBundle mainBundle] pathForResource:@"Questions" ofType:@"plist"]; self.questions = [NSArray arrayWithContentsOfFile:plistPath]; self.currentQuestionIndex = 0; [self showNextQuestion]; } |
This is called by viewDidLoad
, and configures the UI as well as loading the questions from a plist into an array called questions
. Note that along with kicking off iAds, it sets things like the controller title and number of allowed questions. These would all be different in the paid version of the app, so you’ll be seeing this again later in the tutorial.
Since it’s driving much of the app, take a moment to look at Questions.plist:
This is the plist that was stored in the questions
array. Each question is a dictionary with the following keys:
Head back to IQViewController.m and look at showNextQuestion:
- (void)showNextQuestion { //1 - handle last question if (self.currentQuestionIndex + 1 > self.maxAllowedQuestions) { [[[UIAlertView alloc] initWithTitle:@"Game Over" message:[NSString stringWithFormat:@"Your score is %ld",(long)self.score] delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil] show]; return; } //2 - update view for next question NSDictionary *questionDetail = self.questions[self.currentQuestionIndex]; self.questionLabel.text = questionDetail[@"question"]; for (int buttonCount = 1; buttonCount <= 4; buttonCount++) { UIButton *answerButton = (UIButton *)[self.view viewWithTag:buttonCount]; [answerButton setTitle:questionDetail[[NSString stringWithFormat:@"answer%d",buttonCount]] forState:UIControlStateNormal]; } } |
This method is called each time a new question needs to be displayed, and it does a couple of things:
maxAllowedQuestions
, this displays a UIAlertView with your score.questions
array and then uses it to set the label with the question as well as configuring the buttons with the four possible answers. Note that the answer buttons are referenced via their tag (set in Main.storyboard), and are numbered 1 – 4 to match the question numbers.Finally, take a look at answerButtonPressed:
- (IBAction)answerButtonPressed:(UIButton *)sender { int correctAnswer = [(NSNumber *)self.questions[self.currentQuestionIndex][@"correctAnswer"] intValue]; if (sender.tag == correctAnswer) { self.score++; self.scoreLabel.text = [NSString stringWithFormat:@"%ld",(long)self.score]; } self.currentQuestionIndex++; [self showNextQuestion]; } |
Once a user presses an answer button, this checks if the answer is correct, adds the score accordingly, and then loads the next question.
Now that you’re familiar with the project and the functionality of the free version, it’s time to implement the paid version using the same project and codebase. The key to accomplishing this is the creation of a second target. This will enable two important things:
Creating a new target is easiest if you duplicate an existing target and make some modifications. This saves a lot of time versus configuring a target from scratch. Start by selecting your project from the navigator:
Now right click your target InterviewQuestionsFree, and choose duplicate:
Choose Duplicate Only on the popup:
Note: The reason this dialog exists is to provide you with an easy way to create both iPhone and iPad versions of an app within a single project, using two different targets. If this is what you wanted to do, you could select “Duplicate and Transition to iPad”.
However, before you go down this route you should consider making your app universal rather than making separate iPhone and iPad versions of your apps – IMHO it’s a nicer experience for users because they don’t have to buy your app twice. For more information on creating a universal app, check out this tutorial.
You want to be able to easily identify this new target as the paid app version target – so you have some renaming to do! First, select the new target, hit return, and rename it to InterviewQuestionsFull:
Next there’s a naming issue to fix. If you look up at the top part of XCode where you usually choose the device you want to run your app on, you’ll find something called a scheme just to the left of that:
You’ll use the scheme in just a bit to select with target you want to build. Currently you have InterviewQuestionsFree selected, but if you expand the popup, you’ll find a terrible thing:
The other scheme is called InterviewQuestionsFree copy. You’ll want to rename that. Press on the current InterviewQuestionsFree to present the popup, then select manage schemes.
Click on InterviewQuestionsFree copy, hit return to make it editable then rename it to InterviewQuestionsFull. Hit enter once more and then press OK.
Everything looks neat now. I can sleep soundly tonight!
You might have noticed that after you created the new target for the full version, a new plist was added to your project:
This plist has a problem. Its name isn’t consistent with our target name anymore!
Rename it to InterviewQuestionsFull.plist and move it just under the InterviewQuestionsFree.plist by dragging it in the navigator.
Now open your newly renamed InterviewQuestionsFull.plist. Update the Bundle display name – which is the app’s name that you see under it’s icon on the Springboard – to Interview:
You’ve made a small mistake. What was it? Select your project from the navigator then select your InterviewQuestionsFull target:
Your target was pointing to the old plist name, and now that you renamed it, the target has no plist! You need to link it back to the target. Press on Choose Info.plist File and choose InterviewQuestionsFull.plist from the popup:
For the final piece of renaming fun, hop over to the Build Settings for this target. In the Packaging section, update Product Name to be InterviewQuestionsFull. Although you’ve already set a display name in the plist, the product name will appear in the bundle identifier, and you should update it for consistency.
It’s time to finally run the app, and test this new target. To identify which product (free or paid) you want to run, you need to select the appropriate scheme followed by a choice of your favorite device. In this case, select the InterviewQuestionsFull scheme so that you can try our new target.
Build and run. You’ll see that the new target is running, and as you might expect it looks identical to the free target you duplicated:
Now press the home button on your sim (or select Hardware->Home from the sim menu). You’ll notice you now have two installs – one with the Bundle display name you chose for Full (Interview), and the other with the default set for Free (Interview Free).This is because each target has its own Bundle Identifier, making it a unique app.
This means your second target is working properly, and you’re now building two distinct apps out of this project.
Right now the apps are exactly the same, so let’s start adding some differentiation. You’ll start by giving the paid version its own icon and splash screen that don’t say “Free” on them.
Go to your project’s folder in the Finder and navigate to Interview Questions Starter/InterviewQuestions/InterviewQuestions/Images/Full. You’ll find assets for the paid version that haven’t yet been added to the project.
Drag them to your XCode project and place them in the paid folder that you’ll find in Supporting Files/Images, but don’t click finish yet!
Pay attention to this tricky part:
The bottom half of the popup has a section labeled Add to targets that you may have ignored when adding resources to a single target project. Now it’s time to start paying attention. These added images will only be used in the paid target, so check the InterviewQuestionsFull target and uncheck the InterviewQuestionsFree target before hitting Finish.
If you go back to your navigator, you’ll notice you now have 3 files in your paid folder that appear with the same names in the Free folder. Go ahead and select the InterviewQuestionsFull scheme, then build and run. While the project still runs, you’ll see XCode throwing some warnings if you look at the Issue Navigator:
XCode is pointing out that you’re trying to copy the same file into your product bundle twice – something it cannot do because the bundle is a flat structure. This happened because you copied the paid images to the InterviewQuestionsFull target while the Free versions of the files were already there from when you duplicated the Free target.
Time to fix it! In the Project Explorer, select the 3 images under the Free folder, and then direct your attention to the File Inspector tab of the Utility Pane on the right:
Looking closer at the File Inspector, you’ll see a list of checkboxes under Target Membership:
Uncheck the InterviewQuestionsFull target for these three resources, as you only want them associated with the Free target:
Build with the InterviewQuestionsFull scheme again and you’ll get a successful build without any warnings about duplicate files.
Even more exciting is that you can now see unique splash and icon images for the two targets. Take a look at your icons:
Now only your free product has the word “free” in it’s icon, and you’ve successfully learned how to tie unique assets to different targets within a project.
That was easy, but what about changes that require code modification?
Now that you have your two targets on the same codebase, you need to make some changes to enable paid features on the paid version. For example, you need the maximum number of allowed questions to be set to 10 in the paid version, rather than the 5 used for Free.
Running on a single codebase, you’re going to need some programatic way to determine which product (target) is currently running so you can make decisions like this appropriately.
This is where a Preprocessor Macro comes into play. Macros are the small fragments of code that you’re used to seeing in #define statements. You’ve probably used them before for something like defining a custom RGB color:
#define GREEN [UIColor colorWithRed:126.0/255.0 green:176.0/255.0 blue:122.0/255.0 alpha:1.0] |
The key here is that you can define a preprocessor macro in the configuration of a target. This way, you can define a flag that identifies the product version and make decisions in your code based on that. To set this up, start off by selecting your project form the navigator:
Select your InterviewQuestionsFree target and then go to the Build Settings tab.
Filter on the word “macro” and you’ll find a place to enter preprocessor macros:
You’ll see the Debug build already has a macro called DEBUG=1
here. You want to add something similar to both the Debug and Release profiles to signify this target is tied to the Free product. Double click the values for these, then hit the ‘+’ button and enter a macro called FREE=1:
Once complete, your macros for the InterviewQuestionsFree target should look like this:
Now you can use this macro in your code to differentiate between your two targets!
Test this out by changing the navigation item title according to different targets. Open IQViewController.m and replace the following line in setupQuiz:
self.navigationItem.title = @"Free Version"; |
with this:
#ifdef FREE self.navigationItem.title = @"Free Version"; #else self.navigationItem.title = @"Paid Version"; #endif |
The #ifdef simply confirms if there is a macro called FREE
defined. Because you defined this on the free version only, this allows you to handle the two versions differently.
Run your app using the IntervewQuestionsFull scheme, and click Test my skills. You’ll see that the navigation item title has changed to “Full Version”.
If you go back and run with the InterviewQuestionsFree scheme, you’ll see it’s still displaying “Free Version” as you’d expect.
You can use this same technique to make other changes between the two versions. Let’s make the maximum allowed questions to 5 for the Free target and 10 for Full, and remove ads for the full version.
To do this, still in setupQuiz
, find the following lines:
#ifdef FREE self.navigationItem.title = @"Free Version"; #else self.navigationItem.title = @"Paid Version"; #endif self.maxAllowedQuestions = 5; [self setupAds]; |
Modify this code to look like the following:
#ifdef FREE self.navigationItem.title = @"Free Version"; self.maxAllowedQuestions = 5; [self setupAds]; #else self.navigationItem.title = @"Full Version"; self.maxAllowedQuestions = 10; #endif |
Run your application with each schema to see the differences. You’ll now be able to see all 10 questions for the full version, and rejoice – no more ads!
As a bonus, let’s add a new feature to the full version: the ability to allow the users to skip a question.
To do this, add some code to set up this new button only in the full version by pasting the below inside the #else
block in setupQuiz
:
self.skipQuestionButton = [UIButton buttonWithType:UIButtonTypeRoundedRect]; [self.skipQuestionButton setFrame:CGRectMake(20, 80, 100, 44)]; [self.skipQuestionButton setTitle:@"Skip Question" forState:UIControlStateNormal]; [self.skipQuestionButton addTarget:self action:@selector(skipButtonPressed) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:self.skipQuestionButton]; |
Your else statement will now look like this:
#else self.navigationItem.title = @"Full Version"; self.maxAllowedQuestions = 10; self.skipQuestionButton = [UIButton buttonWithType:UIButtonTypeRoundedRect]; [self.skipQuestionButton setFrame:CGRectMake(20, 80, 100, 44)]; [self.skipQuestionButton setTitle:@"Skip Question" forState:UIControlStateNormal]; [self.skipQuestionButton addTarget:self action:@selector(skipButtonPressed) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:self.skipQuestionButton]; #endif |
Nothing fancy here. The skip button was created and set to trigger skipButtonPressed
when pressed. Now move to the stubbed out skipButtonPressed and replace it with the following:
- (void)skipButtonPressed { //1 self.score++; self.scoreLabel.text = [NSString stringWithFormat:@"%ld",(long)self.score]; //2 self.currentQuestionIndex++; [self showNextQuestion]; //3 self.skipQuestionButton.hidden = YES; } |
This does the following.
1. Gives the user a point and refreshes the displayed score.
2. Display the next question.
3. Hide the skip button, as it’s only allowed once per quiz.
Run your app and test out the Skip Button functionality.
At this point, you’ve got two uniquely functioning versions of the app. Test them well by running different schemes to ensure that all is going great.
Here is the example project that you developed in this tutorial.
Congratulations – you now have a project that uses two targets with the same code-base, so now if you hit a bug that impacts both versions of your app, you only have to make that fix in one place. So once again, the day is saved. Thanks to the power rangers… oh wait… thanks to targets!
Targets are one of the features that are usually ignored by developers. With this new knowledge in hand, you’ll be able to create powerful products.
For example, you might create a template news app with different targets for each of your clients. Each target could have its own colors, resources, and minor changes in the code. This way you’ll be able to build a totally new app in just few clicks. Sell that to different clients and you’ll become rich in no time… just don’t forget my commission $$$! :]
I hope you enjoyed this tutorial, and if you have any questions or comments please join the forum discussion below!
How to Create Both a Paid and Lite Version of an iPhone App is a post from: Ray Wenderlich
The post How to Create Both a Paid and Lite Version of an iPhone App appeared first on Ray Wenderlich.
Learn how to center content in scroll views.
Video Tutorial: Scroll View School Part 3: Centering Content is a post from: Ray Wenderlich
The post Video Tutorial: Scroll View School Part 3: Centering Content appeared first on Ray Wenderlich.
Learn how scroll views work with auto layout.
Video Tutorial: Scroll View School Part 4: Auto Layout is a post from: Ray Wenderlich
The post Video Tutorial: Scroll View School Part 4: Auto Layout appeared first on Ray Wenderlich.
iOS 7 introduced official support for hardware game controllers. This is great because some games are particularly well suited for hardware game controllers – imagine Mario with a touch interface!
It’s also great because now that it’s official, you can support a variety of game controllers just by implementing a single API.
On top of that, Apple has provided prospective controller manufacturers with specific requirements in order to be compatible with iOS. Apple isn’t just letting just any yahoo make a hardware controller for iOS!
In this tutorial, you will add hardware game controller support into a simple platformer game like Mario, made with Sprite Kit.
This tutorial has the following prerequisites:
Grab your game pad and let’s get started!
There are exactly three game controller types supported by the Game Controller framework, as specified in Game Controller Programming Guide:
1) A standard form-fitting controller
The form-fitting controller encases the iPhone within the controller, connecting via the lightning connector.
At the time of writing this article, there is only one of these types of controllers available – Logitech’s Powershell controller.
This type of controller supports the “standard” controller profile, which means it has the following inputs:
2) An extended form-fitting controller
The second type of controller is also form-fitting, but it contains some extra buttons so is considered “extended.”
At the time of writing this article, there is only one of these types of controllers available – MOGA’s ACE Power controller.
Note that an extended controller like this one contains some additional buttons:
3) An extended wireless controller
The final controller type is the external/bluetooth controller, such as Stratus’s SteelSeries controller.
This supports the extended gamepad (I believe that external controllers are all required to support the extended profile, that’s my impression from the WWDC videos in any case).
The external controllers connect via bluetooth and must first be paired to the device in order work. Once paired, any time the controller is powered on it will connect via bluetooth to the same device.
The full collection
Here’s an image of my current hardware collection:
In this image:
Common characteristics
Beyond specifying the controller types, Apple specifies certain characteristics that all controllers must satisfy. These are intended to provide a consistent, high quality experience when using a hardware controller with an iOS device.
If you would like to have a complete picture of the benefits of Apple’s requirements, I suggest watching the video on hardware controllers from WWDC 2013. But, let me just mention a few things:
Apple has a few requirements that you must satisfy in order to submit an app that supports a hardware controller. The first, and most important, is that you must not require a hardware controller. Your game must be playable with standard touch/tilt controls.
This means that you have to put thought into how you are going to implement your control scheme. An Extended Gamepad has a total of 13 controls, good luck fitting all of those controls on a touch screen!
Because of these issues, many games provide completely different controls schemes on a hardware controller versus the touch screen.
For example, LEGO Star Wars uses the hardware controller to move around the player in 3D space, but if you use the touch screen, you simply touch on the screen where you want the player to go, and he will move to that spot.
You will have to be creative to make a game that takes advantage of the hardware controller, but still plays well with touch screen controls. You don’t want to design for the hardware and then add touch screen controls as an after thought. Realistically, most of your users will be playing your game via the touch screen, so you want both experiences to yield praise and good reviews.
As I mentioned earlier, in this tutorial you will be adding game controller support to the Super Koalio game from our Sprite Kit platformer game tutorial.
In the original tutorial, Super Koalio has a very simple control scheme. Tapping on the left side of the screen walks forward (forward only) and tapping on the right side of the screen jumps.
However, I have created a modified version of the starter project that contains a more complete set of controls and on screen buttons and joystick.
Download the starter project, build and run, and try out the new control scheme.
The new control scheme uses an analog stick (provided on github by the sneaky narwal – you can get that code here), and two buttons:
Note that the control layer is a separate class called HUDNode. The hardware controller will communicate with the HUD.
First, make sure your controller is on and paired to your device, following the instructions that came with your controller.
Next, you’ll add some code to connect your game with the controller. Start by opening HUDNode.m and importing the GameController framework:
@import GameController; |
Then, add a new GCController property to your private interface:
@property (nonatomic, strong) GCController *controller; |
Now you can add the code that interacts with the hardware. Add the following method to HUDNode.m:
- (void)toggleHardwareController:(BOOL)useHardware { //1 if (useHardware) { //2 self.alpha = 0.0; //3 self.controller = [GCController controllers][0]; //4 if (self.controller.gamepad) { //5 __weak typeof(self) weakself = self; [self.controller.gamepad.buttonA setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) { weakself.aPushed = pressed; }]; [self.controller.gamepad.buttonX setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) { weakself.shouldDash = pressed; }]; //Add controller pause handler here } } else { //7 self.alpha = 1.0; self.controller = nil; } } |
YES
will initiate and set up the methods to handle the input from the controller. It will also hide the on screen HUD. Passing NO
will reveal the on screen HUD and reset the controller.controllers
class method that returns an array of all the connected hardware. In this app you will only ever communicate with the first controller in the array. You can connect up to four controllers to a single iOS device.
There are two profiles. The gamepad profile and the extended gamepad profile. The extended gamepad profile has all the controls that the gamepad profile has plus a bunch more. In this app you are going to support both profiles by adding code that responds to both the dpad and the left analog stick. But, for now you are just adding support for the A and B buttons.
.gamepad
property or the .extendedGamepad
property. If the property is not supported, accessing the property will return nil
.aPushed
(or shouldDash
) property to the value of pressed
.gamePad
(or extendedGamePad
) object.
There are two ways to access the game controller object for input. First, you can query the state of these properties and read the state of the various buttons and controls. Alternatively, you can set the changeHandler
which executes a block every time the state changes. For the buttons, you use change handlers.
I’ve mapped the A button on the controller to the A button in the HUD and the X button on the controller to the B button in the HUD. This is the most comfortable way to use both dash and jump on the physical control.
NO
, you reset the alpha property to 1.0 (revealing the HUD) and set the controller
property to nil
.Now that you have that method in place, it’s time to connect to a controller. First, you will handle the case when a controller is already connected when the app starts. Make sure your bluetooth enables controller is paired with your device or plug your lightning connected controller in (when it’s time to test again).
Find the comment ‘//Add hardware controller code here’ in initWithSize and add this code:
if ([[GCController controllers] count]) { [self toggleHardwareController:YES]; } |
This will check for any existing controllers and if it finds one or more, it will run the method you just added.
Note: If you have a controller that connects to the lightning cable, testing gets tricky because you have to disconnect your Xcode connected cable to plug your controller in. So, from now on, testing will be several steps:
You won’t be able to get logs in that case either, so you may need to create a logging SKLabelNode or do something else to debug issues. You won’t be relying on any specific NSLog statements in this tutorial, but if you get stuck or need to debug something, you won’t be able to do that with a lightning connected controller. If you have a bluetooth controller this will be easier.
Go ahead and connect your controller and run the app. The HUD should be gone and you should be able to dash and jump using the buttons.
The next step is to connect the thumb stick to the controller. You already have a custom getter method that accesses the state of the on screen thumbstick, so you’ll be adding a little more code and reading (instead of using a change handler) the hardware left thumbstick input instead.
Change the xJoystickVelocity
method to the following:
- (float)xJoystickVelocity { //1 if (self.controller.extendedGamepad) { return self.controller.extendedGamepad.leftThumbstick.xAxis.value; //2 } else if (self.controller.gamepad) { return self.controller.gamepad.dpad.xAxis.value; } //3 return self.joystick.velocity.x / 60.0; } |
Go ahead and build and run now. You should now have full control over your Koala.
Not bad for just a few lines of code, eh? In my opinion this also makes the game much more fun than the on-screen controls as well.
The next step is to make your app respond to controller being connected and disconnected during gameplay. This is done with a system notification. First add a couple properties to keep track of the observers and remove them when the class is deallocated. Add this to HUDNode.m, @interface
section:
@property (nonatomic, strong) id connectObserver; @property (nonatomic, strong) id disconnectObserver; |
Next, add the following code in the initWithSize method of the HUD at the ‘//Add observers here’ comment:
__weak typeof(self) weakself = self; self.connectObserver = [[NSNotificationCenter defaultCenter] addObserverForName:GCControllerDidConnectNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { if ([[GCController controllers] count] == 1) { [weakself toggleHardwareController:YES]; } }]; self.disconnectObserver = [[NSNotificationCenter defaultCenter] addObserverForName:GCControllerDidDisconnectNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { if (![[GCController controllers] count]) { [weakself toggleHardwareController:NO]; } }]; |
The GCControllerDidConnectNotification and GCControllerDidDisconnectNotification notifications are fired when a new controller is connected or disconnected. If a new controller is connected and the count of controllers is 1, you can conclude that there were no connected controllers before the notification fired and you call your routine that hides the HUD and sets up the controller. When the disconnect notification is fired, you check to see whether there are any controllers left, and if there are none, you call that method to reveal the HUD and remove the GCController.
Pretty straight forward. Finally, when using the block methods for NSNotificationCenter you need to remove those observers in the dealloc
method or they will cause a leak (they’ll retain the HUDNode object).
- (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self.connectObserver]; [[NSNotificationCenter defaultCenter] removeObserver:self.disconnectObserver]; } |
Build and run now. You should be able to start the app without a controller, then watch the HUD disappear when you connect and reappear when you disconnect your controller. This is much easier if you have a bluetooth connected controller, but will work in either case.
The last thing you must do in order to support gamepads – and this is a requirement from Apple – is add support for the pause button.
I’m going to use NSNotificationCenter to communicate between the HUDNode and the SKView/SKScene. A delegate protocol would work as well, but this kind of event seems more suited to a notification to me. Add the following line of code to HUDNode.h before the @interface
line:
extern NSString * const kGameTogglePauseNotification; |
Then add this line before @interface in HUDNode.m:
NSString * const kGameTogglePauseNotification = @"GameTogglePauseNotification"; |
This is just the NSString name of the notification. Using const
like this just makes it easier not to make a mistake typing (and copying/pasting) the string into multiple places. Then switch to the ViewController.m and add a new property to keep track of the observer for removal:
@property (nonatomic, strong) id pauseToggleObserver; |
You need to #import
the HUDNode.h file to get access to the notification NSString const that you just created:
#import "HUDNode.h" |
Then, add this to the end of viewDidAppear:
__weak typeof(self) weakself = self; self.pauseToggleObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kGameTogglePauseNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { [weakself togglePause]; }]; |
This just creates the new NSNotification observer that will fire when the notification is fired. Next, create a dealloc
method to remove that observer:
- (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self.pauseToggleObserver]; } |
Now, in that same file, add the pause method:
- (void)togglePause { SKView *view = (SKView *)self.view; view.paused = (view.paused) ? NO : YES; if (!view.paused) { self.pauseView.hidden = YES; } else { self.pauseView.hidden = NO; } } |
I’ve added a hidden UIImageView with a pause image to the storyboard already. It’s called pauseView
.
I would prefer to use an SKLabelNode or SKSpriteNode to add a pause label or button the the scene, but once you set self.view.paused = YES
, nothing renders in the SKView after that. So you never see the new node or label that you’ve added. There are ways around this issue, but to keep it simple I just used UIKit.
Now, you need to add the code that sends the notification. In HUDNode, in toggleHardwareController
, there’s a comment line ‘//Add controller pause handler here’, replace that comment with this code:
[self.controller setControllerPausedHandler:^(GCController *controller) { [[NSNotificationCenter defaultCenter] postNotificationName:kGameTogglePauseNotification object:nil]; }]; |
Build and run now. Press pause button. If everything is in place, you should see something like this (and the game should be paused):
The Game Controller framework has one more capability that you are going to explore. You can serialize (convert to NSData to be saved in a plist or sent over the network) the state of the controller.
This ability can be used in different ways. For example, you could use this feature to send the controller state across the network to another player, or you can save the entire history of inputs to a file. In this tutorial, you’ll be recording and playing back the sequences of input you use to progress through the level.
The first step is to add a boolean to indicate to the HUD class that it is in snapshot recording mode. Add the following in the @interface
section of HUDNode.h:
@property (nonatomic, assign) BOOL shouldRecordSnapshots; |
Now, add a new NSMutableArray that will contain the snapshots to HUDNode.m @interface
:
@property (nonatomic, strong) NSMutableArray *snapShots; |
The next step is to designate a file that the snapshots can be saved to. Open HUDNode.m and add the following two methods (end of the file):
- (NSURL *)snapShotDataPath { //1 NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *filePath = paths[0]; filePath = [filePath stringByAppendingPathComponent:@"snapshotData.plist"]; //2 return [NSURL fileURLWithPath:filePath]; } - (void)saveSnapshotsToDisk:(NSArray *)snapShots { //3 if (![snapShots count]) return; //4 if (![snapShots writeToURL:[self snapShotDataPath] atomically:YES]) { NSLog(@"Couldn't save snapshots array to file"); } } |
writeToURL
on the snapshots array (a method that saved an array to disk in plist form). This method returns a BOOL
indicating whether the operation was successful or not. If it fails, you want to let yourself know so you can do further investigation.Now, you are ready to write the code that creates the snapshots and adds them to the array. You need to generate one snapshot per frame. You want to find a place in your code that is called once per frame, every frame. The touch methods aren’t going to work, because they are called when the touches change, so there would be many frames where no touch methods would fire.
I chose to use the xJoystickVelocity
method in HUDNode.m. Every frame, the player’s update method calls this method (once per frame) to get the state of the joystick. Change the following block of code:
//1 if (self.controller.extendedGamepad) { return self.controller.extendedGamepad.leftThumbstick.xAxis.value; //2 } else if (self.controller.gamepad) { return self.controller.gamepad.dpad.xAxis.value; } |
To this:
if (self.controller.extendedGamepad) { //1 if (self.shouldRecordSnapshots) { //2 NSData *snapShot = [[self.controller.extendedGamepad saveSnapshot] snapshotData]; //3 NSDictionary *snapshotDict = @{@"type": @"extended", @"data":snapShot}; //4 [self.snapShots addObject:snapshotDict]; } return self.controller.extendedGamepad.leftThumbstick.xAxis.value; } else if (self.controller.gamepad) { //5 if (self.shouldRecordSnapshots) { NSData *snapShot = [[self.controller.gamepad saveSnapshot] snapshotData]; NSDictionary *snapshotDict = @{@"type": @"gamepad", @"data":snapShot}; [self.snapShots addObject:snapshotDict]; } return self.controller.gamepad.dpad.xAxis.value; } |
shouldRecordSnapshot
boolean is YES
.saveSnapshot
on the gamepad profile object. In this case the extendedGamepad
profile. The GCExtendedGamepadSnapshot that’s created by calling saveSnapshot
is an object that you can query the snapshot buttons the same way to do the controller profile object. More on that in a bit. Once you have the snapshot, you need to convert it to NSData so it can be saved to a plist. You do that by calling snapshotData
.You need a method that starts the recording process. Add this to HUDNode.m (at the end):
- (BOOL)recordSnapshots { if (!self.controller || self.shouldRecordSnapshots) return NO; self.shouldRecordSnapshots = YES; self.snapShots = [NSMutableArray array]; return YES; } |
Here, you are checking that there’s a controller connected. You don’t want to enable snapshot recording without a controller. Also, if you are already in recording mode, you don’t want to enable it again, or you’ll erase all the snapshots you’ve collected up to that point.
Then you set shouldRecordSnapshots
to YES
and create a new array for the snapshots object.
You are returning a BOOL
from this method. This is a way to tell if the recording mode successfully started.
I’m not going to be creating UI to start/stop the recording mode or the playback mode. I’ll have you do all that in code. In a real game, you’d want buttons or a settings pane to enable these options. Returning a boolean from this method makes it easier to change the state of that UI (like turning a recording button to a YES
state).
Now, add this line to the very end of HUDNode.m, initWithSize:
[self recordSnapshots]; |
That is all you need to do in order to retrieve snapshots and store then in an array. However, you still need to call the method that saves the snapshots to a plist on the disk. For simplicity, you’re going to be calling that when the player wins the game (if he dies before reaching the end of the level, that run won’t be saved).
Find this line in GameLevelScene.m, gameOver:
gameText = @"You Won!"; |
Add these lines immediately after it:
if (self.hud.shouldRecordSnapshots) { [self.hud saveSnapshots]; } |
There’s one last thing you must do, create the saveSnapshots
method. You already have a saveSnapshotsToDisk
method, but that one must be called internally (to have access to the private snapshots array).
Add this method declaration to HUDNode.h:
- (void)saveSnapshots; |
Now, add this method to the end of HUDNode.m:
- (void)saveSnapshots { [self saveSnapshotsToDisk:self.snapShots]; } |
That’s it. You can now build and run. Make sure when you start the game, the controller is already connected, or the recordSnapshots
method with return without enabling the function. Play through the level and make sure you win!
When you are done, you’ll have a new plist inside the app’s bundle on the device. In order to validate that this worked correctly, you’ll need to use a program that allows you to browse all the contents of your iPhone, not just the pictures. I use iExplorer. Navigate to the app, find the Documents folder, and you should have a snapshotsData.plist file that looks like this:
If you don’t have iExplorer or a program that can navigate the device’s file system, you can still proceed. The next build and run step will validate whether or not you’ve got the recording part working right.
The next step is to play back those snapshots.
You’ll need some new instance variables, a boolean to indicate whether you are in replay mode and a index number of the current snapshot in the array. Add these two properties to HUDNode.m, @interface section:
@property (nonatomic, assign) NSUInteger currentSnapshotIndex; @property (nonatomic, assign) BOOL shouldReplaySnapshot; |
Next, set add the replaySnapshots
method:
- (BOOL)replaySnapshots { //1 self.snapShots = [NSArray arrayWithContentsOfURL:[self snapShotDataPath]]; //2 if (!self.snapShots || ![self.snapShots count]) return NO; //3 self.shouldReplaySnapshot = YES; //4 return YES; } |
arrayWithContentsOfURL
returns nil
. You check for that next, or if there is a valid plist that’s initialized and it’s empty, then you return NO
and you do not set the game into replay mode.shouldReplaySnapshot
to YES
.YES
indicating that you are now in replay mode.The next step is to convert the objects in the snapshots array back into GCGamepadSnapshot or GCExtendedGamepadSnapshot objects. Create a convenience method that parses the dictionary, initializes the right object of the two depending on the value in the “type” key, and returns whichever is correct.
Add this method to HUDNode.m:
- (id)currentGamepadFromSnapshot { //1 NSDictionary *currentSnapshotData = self.snapShots[self.currentSnapshotIndex]; //2 id snapshot = nil; //3 if ([currentSnapshotData[@"type"] isEqualToString:@"gamepad"]) { //4 snapshot = [[GCGamepadSnapshot alloc] initWithSnapshotData:currentSnapshotData[@"data"]]; } else { //5 snapshot = [[GCExtendedGamepadSnapshot alloc] initWithSnapshotData:currentSnapshotData[@"data"]]; } return snapshot; } |
currentSnapshotIndex
. You’ll be incrementing that index in another place to ensure that it’s only incremented once per frame.initWithSnapshotData
method.This method is just a way to compartmentalize code and make it easier to write the several bits that query the snapshot, using this common method in multiple places.
The next step is to change the way the inputs are queried if shouldReplaySnapshot
is YES
. First, modify xJoystickVelocity
. Add this block of code to the beginning of that method (before all the existing code):
//1 if (self.shouldReplaySnapshot) { //2 id currentSnapshot = [self currentGamepadFromSnapshot]; //3 self.currentSnapshotIndex++; //4 if ([currentSnapshot isKindOfClass:[GCGamepadSnapshot class]]) { //5 GCGamepadSnapshot *gamepadSnapshot = (GCGamepadSnapshot *)currentSnapshot; //6 return gamepadSnapshot.dpad.xAxis.value; } else { //7 GCExtendedGamepadSnapshot *extendedGamepadSnapshot = (GCExtendedGamepadSnapshot *)currentSnapshot; return extendedGamepadSnapshot.leftThumbstick.xAxis.value; } } |
shouldReplaySnapshot
is YES
. If it isn’t then the rest of the existing code runs as though you hadn’t added this new block.currentSnapshotIndex
variable. You know that this method, xJoystickVelocity
, will be called exactly once per frame. So, it is the right place to increment the index of the snapshot to reliably get a new snapshot each frame.currentSnapshot
object to see if it’s a GCGamepadSnapshot class using isKindOfClass
. isKindOfClass
is an NSObject method that you can use to determine which class type an object is at runtime.GCGamepadSnapshot
, you cast that variable to that type so you can access its properties without a compiler error.xAxis
value from the dpad
control.leftThumbstick
control instead of the dpad
.The only thing left to handle are the buttons. You may wonder how to do this, because currently, the player’s update method is just querying the state of the two booleans you created earlier, aPushed
and shouldDash
. There’s actually a really easy way to handle this, create a custom property accessor.
Add the following custom accessor method for shouldDash:
- (BOOL)shouldDash { //1 if (self.shouldReplaySnapshot) { //2 id snapshot = [self currentGamepadFromSnapshot]; //3 return [[snapshot valueForKeyPath:@"buttonX.pressed"] boolValue]; } //4 return _shouldDash; } |
shouldReplaySnapshot
is YES
.The approach I’m using is Key Value coding. KVC is a way of accessing an object’s properties with an NSString matching the property’s name. KVC has a couple important methods, valueForKey
and valueForKeyPath
. If you need to access a property of a property, like you do here, use the valueForKeyPath
. This means that you don’t have to know whether the object is a GCExtendedGamepadSnapshot or a GCGamepadSnapshot, because they both contain the same keys for buttons, you can ask for the value of that same key path from both objects.
KVC returns an NSNumber representation of the BOOL instead of the BOOL value. So, if you were to return this without calling boolValue
, you get a pointer to an NSNumber which would always evaluate to YES
. That would give you a bunch of erroneous values, essentially it would interpret the snapshot as you pressing both buttons continuously.
One word on Key Value Coding. It’s a very useful tool, and there are times when it can save you a lot of code. But, if you can use either dot property notation or Key Value Coding, normally it’s better to use dot property access instead. You get better compile time checking. If you misspell a key or ask for a key that doesn’t exist, with KVC your code will compile then crash at runtime. That wouldn’t happen with dot property notation. I’m using it in this case to bring it to the attention of those who’ve never used it, and because it saves me several lines of code. I’m able to avoid the branching and casting calls on the different types of snapshot objects. It happens to work because the property names on the different objects are the same.
shouldReplaySnapshot
mode, then you just return the value of the properties backing instance variable, _shouldDash
.Go ahead and add the getter for aPushed. It follows identical logic to shouldDash:
- (BOOL)aPushed { if (self.shouldReplaySnapshot) { id snapshot = [self currentGamepadFromSnapshot]; return [[snapshot valueForKeyPath:@"buttonA.pressed"] boolValue]; } return _aPushed; } |
That’s it, your recording and replaying code should now all be in place. However, you’ll need to do some careful actions to test it. First, make sure you beat a game as mentioned in the previous section so it saves a valid snapshots plist file.
Then, find this line:
[self recordSnapshots]; |
Remove that line and replace it with:
[self replaySnapshots]; |
If you’ve done everything correctly, you will see your player performing the same motions, almost as if by magic, as you directed using your controller.
Note: Note that this is a simplistic method of recording screenshots, and suffers from timing variations between the frame rate at which the recording took place versus the frame rate at which the playback takes place.
Due to these variations, the simulation might not be exactly as you expect when you play back the recording. In a real app, you’d want to use a more advanced algorithm that takes into effect timing, and perhaps has periodic checkpoints of player state.
One last minor thing. You can control the state of the LED lights on the controller. These lights are there to indicate which player is controlled by that controller.
In toggleHardwareController, right after this line:
self.controller = [GCController controllers][0]; |
Add this line:
self.controller.playerIndex = 0; |
This will set the value on the controller for the playerIndex
to 0. This does two things, it should set the LED indicator on the controller (on the SteelSeries it didn’t work consistently for me, but it worked fine on the MOGA).
Secondarily, it will remember the playerIndex
for that controller. So, if you disconnect that controller, then reconnect it again later, that hardware will still identify itself to the GameController framework as playerIndex
0. If you want to clear the playerIndex
(and I recommend that you do when you are finished with a controller), you set the playerIndex
value to GCControllerPlayerIndexUnset. This constant is the default value when a controller is connected.
Build and run, and check out the LED!
You can download the final project here.
Now that you understand hardware controllers, you should add support into your games! There are more than a hundred games now that support hardware controllers, with hopefully many more to come.
To learn more about hardware controllers, check out Apple’s Game Controller Programming Guide.
I hope you enjoyed this tutorial, and if you have any questions or comments, please join the forum discussion below!
iOS 7 Game Controller Tutorial is a post from: Ray Wenderlich
The post iOS 7 Game Controller Tutorial appeared first on Ray Wenderlich.
Learn the difference between content insets and content offsets and how to use them in your app.
Video Tutorial: Scroll View School Part 6: Content Insets and Content Offsets is a post from: Ray Wenderlich
The post Video Tutorial: Scroll View School Part 6: Content Insets and Content Offsets appeared first on Ray Wenderlich.
Learn how to make paging scroll views.
Video Tutorial: Scroll View School Part 7: Paging Scroll Views is a post from: Ray Wenderlich
The post Video Tutorial: Scroll View School Part 7: Paging Scroll Views appeared first on Ray Wenderlich.
When Xcode 5 and iOS 7 were announced, a small addition was mentioned that most people might have missed: HeaderDoc.
HeaderDoc is a command line tool that you can use to automatically generate nice HTML documentation for your code – as long as you structure your comments in a particular way.
In addition, Xcode parses HeaderDoc-style comments behind the scenes to automatically present your documentation in quick look panels.
In this HeaderDoc tutorial, you will learn how to:
Let’s get documenting!
Download the starter project for this tutorial, and build and run. It should look like this:
This is a simple little test project that includes two main helper classes:
Right now, these files are barely documented. Let’s see how you can use HeaderDoc to make creating documentation for these classes super easy.
HeaderDoc is a tool that you can either run from the command-line, or is automatically run by Xcode. Basically it scans your files for comments made in a particular style.
There are three styles of comments that HeaderDoc scans for:
Option 1:
/// Your documentation comment will go here |
Option 2:
/**
* Your documentation comment will go here
*/ |
Option 3:
/*!
* Your documentation comment will go here
*/ |
Note that these are similar to “normal” comments, except that there’s an extra / in option 1, and an extra character in the first line of options 2 and 3 (* and !, respectively).
Note: In options two and three, there is an additional asterisk on each line in-between the required opening and closing lines. This is purely aesthetic, and is not required.
All three syntaxes will produce the same result, if done in Xcode.
Since we could start a small war over which way is the “best” way to document your code, we’ll stick with the following rules for the purposes of this tutorial:
Once HeaderDoc finds a comment in one of the above styles, it will search that comment for tags for more information. You will use tags to mark-up your code.
A tag starts with the @ symbol and a keyword, followed by a space, and a string that contains a description relative to that keyword (such as @param foo). There are two levels of tags:
An example of a top-level tag is @typedef, which you use to indicate typedefs for things like enums, structs and function pointers.
HeaderDoc is pretty good at adding Top-Level tags automatically via the context, so they are generally optional. In this tutorial, you’ll focus mostly on Second-Level tags.
Let’s take a look at some helpful Second-Level tags:
This is by no means all of the tags, and you can find all of the available tags in the HeaderDoc User Guide.
For the sake of this tutorial, all comments will be placed in the header files, just to keep the implementation files clean.
Let’s start by documenting a few properties.
Back in Xcode, in the DocumentationExamples project, open ViewController.h. You’ll see two properties that are lacking some documentation. Just above the declaration for the car
property, add a comment block so that it looks like this:
/*! * @brief The ViewController class' car object. */ @property (nonatomic) Car *car; |
Now, build your project. When it finishes, hold the option key, and click on the car
variable name. You should see a popover with your comment.
Troubleshooting: If your popover didn’t contain your comment, you may not have waited long enough for the project to build. If it has finished, try restarting Xcode, just to make sure.
Since that was so easy, try adding a documentation comment to the other property in ViewController
on your own. It should say something about how this is a title that is supposed to be funny.
Solution Inside: Solution | SelectShow> | |
---|---|---|
|
Before you complete the remainder of your documentation, check out another way to view documentation in Xcode. Open the Utilities panel’s Quick Help Inspector. Now click on the “car” variable name. In the Quick Help Inspector, you should see your comment appear:
Now, there are two more classes that need to be documented: MathAPI
, and Car
.
MathAPI
includes a method that needs to be commented, so open MathAPI.h.
Examine the method addNumber:toNumber:
. It returns a value and takes two parameters. So you’ll need to add a @description
tag, two @param
tags and a @return
tag. Go ahead and create a comment that looks like the following:
/*! * @discussion A really simple way to calculate the sum of two numbers. * @param firstNumber An NSInteger to be used in the summation of two numbers * @param secondNumber The second half of the equation. * @return The sum of the two numbers passed in. */ + (NSInteger)addNumber:(NSInteger)firstNumber toNumber:(NSInteger)secondNumber; |
Now, Build the project again then option-click on part of the method name to see your handiwork.
Troubleshooting: Many symbols are option-clickable in the Xcode text editor. Make sure you click on the thing you want to get help on. In the above example, you’ll need to click on either “addNumber:” or “toNumber:” to see the correct help.
Now, unbeknownst to you, I’ve created a pretty terrible implementation of this method. It will only really work with non-negative numbers. To inform the user of this method of that, you can add a little more information to your comment. In this case, let’s add a @warning tag. Add the following text just above the @return tag:
* @warning Please make note that this method is only good for adding non-negative numbers. |
Now, build your project again, and option-click on the method name when it finishes. You’ll see a nice warning to the user.
That was really fast, no? But what if you could make that process faster?
There’s something that’s been mentioned before on the site, and on the the raywenderlich.com podcast: code snippets.
Code snippets are one of the unsung heroes of Xcode. A snippet is a block of code that you can save, to a snippet library, and reuse over and over again. Snippets can even contain placeholders for you to know what you need to fill in. What’s that you say? You want a snippet for documenting your code? Fabulous idea.
In MathAPI.h, copy and paste in the following, just before the comment you already have:
/*!
* @discussion <#description#>
* @param <#param description#>
* @return <#return description#>
*/ |
Notice that when you copied or wrote out the code, the parts that had “<# #>” around them became tokens that you could tab between. It’s exactly the same thing when you use autocomplete for your code.
The next part may be a little tricky. You’ll need to select the Code Snippet Library in the Utilities panel, highlight your comment block, and drag the text to the Code Snippet Library section (by dragging from one of the placeholder text areas like <#description#>):
A popover will appear that lets you edit some information about the snippet and create an autocompletion shortcut. It will even let you edit the code. Fill out the information as you see it below:
If you ever need to change the code or autocomplete shortcut, you can always go back and do it later. Feel free to change it however you’d like, or make new ones. To edit the snippet, click on it in the Code Snippet Library, then click the Edit button.
To see your new snippet in action, delete the comments you added for addNumber:toNumber:
, place your cursor to the left of the “+”, type doccomment
, and hit enter. Your snippet text will appear.
Tab through the three tokens filling out each one to complete your documentation as follows:
/*!
* @discussion A really simple way to calculate the sum of two numbers.
* @param firstNumber An NSInteger to be used in the summation of two numbers.
* @param secondNumber The second half of the equation.
* @warning Please make note that this method is only good for adding non-negative numbers.
* @return The sum of the two numbers passed in.
*/ |
Note you’ll need to still manually add a second @param
tag and the @warning
tag, but this snippet still saves a ton of time.
You’re doing great so far. See, this documentation stuff isn’t so bad after all!
Open Car.h. You’ll see there are a few more things here than the previous class. There’s an NS_ENUM
, typedef enum, a block, multiple properties, and a void method. Don’t freak out – it’s still really easy to document a class with more than one or two things in it. :]
Remember the @typdef
tag? This Top-Level tag is a little special. It can be used to document things like enum
typedefs and struct
typedefs. But depending on what you are documenting, it should only contain Second-Level tags that match the type being defined.
For enum
, you’ll want to include the @constant
tag for each constant you have. (For struct
, you would include @field
tags.)
Find the enum OldCarType
. It has two constants, and these should always be really old cars. Above the typedef declaration, replace the existing comment so that it looks like this:
/*! * @typedef OldCarType * @brief A list of older car types. * @constant OldCarTypeModelT A cool old car. * @constant OldCarTypeModelA A sophisticated old car. */ typedef enum { OldCarTypeModelT, OldCarTypeModelA } OldCarType; |
Build, and option+click on OldCarType
. You’ll notice your popover has the information from your @brief
tag. Now, option+click on OldCarTypeModelA
. Did you see your comment? Queue the sad music. :[
But fear not, you can still get your information where you need it - we've got the trusty triple forward slash in our tool belt. Add the comments to the enum
like you see here:
typedef enum { /// A cool, old car. OldCarTypeModelT, /// A sophisticated older car. OldCarTypeModelA } OldCarType; |
When you build, and option+click, you'll now see your comment.
Since there's an NS_ENUM
in the class, go head try documenting it on your own. The constants are already documented, it just needs to be commented overall.
Solution Inside: Solution | SelectShow> | |
---|---|---|
|
Note: Since this enum is created through a macro, sadly Xcode doesn’t actually give you the same documentation features as a traditional typedef enum, even though NS_ENUM
is the recommended way to make enums. Perhaps one day this will change, but for now, that’s the way it is.
Now take a minute to document the carType
property. Add the following line above it’s declaration, so it looks like this:
/// Indicates the kind of car as enumerated in the "CarType" NS_ENUM @property (nonatomic, assign) CarType carType; |
Build again and then view your shorthand documentation by option-clicking the carType
variable name.
Moving on, there’s also a typedef block in Car.h. Commenting a block is no more difficult than anything you’ve done so far. Add the text below so it looks like this:
/*! * @brief A block that makes the car drive. * @param distance The distance is equal to a distance driven when the block is ready to execute. It could be miles, or kilometers, but not both. Just pick one and stick with it. ;] */ typedef void(^driveCompletion)(CGFloat distance); |
Notice that it’s not too different from anything else. It has:
@brief
tag, giving a very simple explanation about the block.@param
tag, to let you know that you’ll need to pass something as a parameter when you call the blockSometimes, it’s nice for a programmer to have an example of how to use a method.
To try this out, you’re going to document the Car
class’ driveCarWithCompletion:
method. This method takes a block as a parameter, and it might be a good idea to show a programmer how to use it – since blocks seem to be a little hard for new developers.
For this, you can use a @code
tag. Add the following in Car.h above the driveCarWithCompletion:
declaration:
/*!
* @brief The car will drive, and then execute the drive block
* @param completion A driveCompletion block
* @code [car driveCarWithCompletion:^(CGFloat distance){
NSLog(@"Distance driven %f", distance);
}];
*/ |
Build, and option+click the method name. You should see a nicely formatted code block in the popover. Beauty comes in many forms, readers.
Now that you’ve learned how to comment, wouldn’t it be nice if Xcode would check your work, much the same way it checks if your code is typed correctly? Good news! Clang has a flag (“CLANG_WARN_DOCUMENTATION_COMMENTS”) that you can set to make Xcode check your HeaderDoc style comments.
In Xcode, open the DocumentationExamples Project Settings, click the Build Settings button at the top, search for Documentation Comments, and set the value to YES.
To see it in action, open to MathAPI.h, and change your first @param tag’s parameter name to thirdNumber, instead of firstNumber, then Build.
Notice that a warning was generated and even gave you an auto-fix option to change the variable to the correct name. While it won’t catch everything, it can be very useful to double check yourself.
While using HeaderDoc style comments to document your code is useful for yourself and others, there are a few other special types of comments Xcode supports. These are really useful for you, or others that work in your code.
Open Car.m and in driveCarWithCompletion:
, paste the following three comment lines above the call to completion
:
// FIXME: This is broken // !!!: Holy cow, it should be checked! // ???: Perhaps check if the block is not nil first? |
you have added three different types of comments here:
Not only are these types of comments useful to see as you go through code, Xcode recognizes them, and will show them in the Jump Bar at the top of the editor. Select the method menu from the Jump Bar, shown here:
Once you do, you’ll see the three comments you entered, bolded, with the text of your comment:
With all of this, you’ve learned enough to document the rest of this project.
Take some time to practice on the other properties and methods included in the project, and try adding a few things yourself. See what happens when you change things around in your comment blocks, or if you leave a tag out. It’s important to know how your formatting will impact your documentation.
All of this documentation magic is done with a tool called HeaderDoc. It’s already installed on your computer if you have Xcode installed. Not only does it parse your comments, making those cool popovers and Quick Help panels, it can also create HTML, XML and man pages containing your documentation.
You’ll focus on creating HTML files in this section. But if you want to learn more about all of the options for creating stand-alone documentation with HeaderDoc, check out Appple’s HeaderDoc User Guide.
Open Terminal by going to /Applications/Utitlities/Terminal. Once you do, navigate to the DocumentationExamples project folder. You can do this by typing in the following:
cd /path/to/your/folder |
Make sure to enter the path to the top level folder that contains the Xcode project file. (“DocumentationExamples.xcodeproj”)
Now, create a set of HTML documentation files by typing in the following in Terminal:
headerdoc2html -o ~/Desktop/documentation DocumentationExamples/ |
You’ll see a lot of output in the Terminal. Once it finishes, go to your Desktop, and you’ll see a new directory named documentation. Open it up, and navigate to Car_h, and open index.html. Awesome – you have pretty docs!
So what just happened? Well, you ran the headerdoc2html script with 2 options:
-o ~/Desktop/documentation
– This told the script to output its results to the documentation on your desktop.DocumentationExamples/
– This told the script to only parse files in the DocumentationExamples/ folder inside the project folder. (There are other folders in the project directory that do not contain code files.)Troubleshooting: You may find that with the latest versions of headerdoc2html and Google Chrome, that opening index.html does not correctly show the Table of Contents toc.html in the left sidebar. However, opening index.html in Safari should show both the documentation content and the Table of Contents correctly.
Also, you may have noticed that the documentation for the carType
property you added earlier did not appear. It looks like the latest version of headerdoc2html does not correctly parse /// styled comments. For now, you might want to stick the /*! style.
That’s all really cool, but it gets better. Instead of having to navigate down into the output directory you want, HeaderDoc can create a master table of contents page for you. Back in Terminal, navigate to your new documentation directory by entering the following:
cd ~/Desktop/documentation |
Now, to create the table of contents, enter the following:
gatherheaderdoc . |
gatherheaderdoc will now search your documentation directory, indicated by the period (representing the current Terminal directory) in the command. In Finder, go back to your documentation directory. You’ll notice there’s a new file named masterTOC.html. Open this file in Safari. You’ll see a page that contains links to all of your documented properties, methods, enums and blocks.
Now, you can host all of this HTML content on a web server, and give access to your documentation to anyone!
Last up in our roundup of documentation tips is VVDocumenter-Xcode, a third party Xcode plugin that will make documenting even easier than using the Code Snippet you created earlier.
To get started, download the plugin from Github.
All you need to do is open the project, and build. As it builds, it will automatically install the plugin for you in your ~/Library/Application Support/Developer/Shared/Xcode/Plug-ins directory.
Once it finishes, restart Xcode, and open your DocumentationExamples project if it is not already open. In MathAPI.h delete the entire comment block for addNumber:toNumber:. Now, type the following above the method declaration:
/// |
Once you do, VVDocumenter-Xcode will automatically create a comment block with all of the necessary @param tags, with autocomplete tokens between all necessary fields.
Convenient, eh?
Now, go to Car.h and delete all of your comments for the CarType NS_ENUM
, even the comments for each constant. Above the NS_ENUM
declaration, type:
/// |
This time, it created the discussion section above the enum, and even put the necessary comments above each constant!
VVDocumenter-Xcode can make your life so much easier. If you want to customize VVDocumenter-Xcode, in Xcode, go to Window>VVDocumenter.
Here, you can change the autocomplete keyword, the comment style, and more. Feel free to customize it however you’d like. I’ve found it saves a ton of time.
You can download the finished project from this HeaderDoc tutorial here.
As a challenge, go through some of your own code, and create documentation for yourself. Try using the code snippet you made, and VVDocumentor. See what fits your style, and how you can work it in to your workflow.
Also, be sure to check out Appple’s HeaderDoc User Guide for more information now that you know the basics.
If you have any questions or comments on this tutorial, please join the discussion below!
Documenting in Xcode with HeaderDoc Tutorial is a post from: Ray Wenderlich
The post Documenting in Xcode with HeaderDoc Tutorial appeared first on Ray Wenderlich.
Learn how to use UIPageViewController to scroll between content.
Video Tutorial: Scroll View School Part 8: UIPageViewController is a post from: Ray Wenderlich
The post Video Tutorial: Scroll View School Part 8: UIPageViewController appeared first on Ray Wenderlich.
Learn how to implement sidebar navigation in your apps using scroll views.
Video Tutorial: Scroll View School Part 9: Sidebar Navigation is a post from: Ray Wenderlich
The post Video Tutorial: Scroll View School Part 9: Sidebar Navigation appeared first on Ray Wenderlich.
May has arrived and you guys really let the apps fly this month. There were 62 submissions this month!
It was a battle to narrow them down this month. After you finish trying the few I found time to review, you’ve got to give the Honorable Mentions a look as well. There were tons of great games and apps I didn’t have time to cover here in detail.
Highlights this month include:
What are you waiting for? Dive into over 60 apps from your fellow readers!
Rotatetris is a tetris style game with a big twist.
As balls are falling, its up to you to rotate your device to control where they land. Match three colors to eliminate them and clear some space.
With three different game modes, GameCenter leaderboards, and powerups to keep it interesting, this gyro-tastic game will keep you moving.
Quiver is a note taker’s dream. Quiver is a Mac app that lets you combine text and code snippets into notes.
Notes can be organized by tags and then grouped into notebooks. You entire note is editable inline anytime and your code snippets can be syntax highlighted in the language of your choice. Quiver supports super fast searching full text searching to find your notes fast.
And best of all, Quiver stores all your notes in plain JSON so you have full control of your content.
Rolling Zimro is a game that will have your head spinning!
Zimro is wanted in the galaxy for his unique knowledge of growing wisdom weeds. He’s running for his life from UFOs and aliens trying to capture him.
Rolling Zimro has beautiful low poly graphics across several spinning worlds. Run as far as you can collecting powerups to boost your GameCenter highscore and save Zimro.
This is an awesome game for your kids. I Love Vegie Classic uses the front facing camera and face detection to create an awesome experience for kids.
The app tracks your mouth while vegetables fall from the sky. You’ve got to move your iPad or your mouth to make sure the vegetables end up in the right place.
Its a supremely fun and unique game that takes advantage of the iPad’s mobility. Everyone should give this game a try.
Sbapix is an awesome app to express your creativity.
Spapix uses a simple pixel grid and color wheel to let you design masterpieces of your very own. Its simple interface and bright colors make for a very relaxing artistic experience.
Spapix offers up any size pixel grid, color theory education, sharing, and a colorful way to use your iPhone.
Jetpack-Jones is a side scroller that takes us back to the basics.
Jetpack-Jones takes simple controls for thrusting and blasting and sends you on a lunar adventure. Aliens, asteroids, and more stand between you collecting stars while you bounce along the moon’s surface.
Jetpack-Jones takes you back to a pixelated past of simple fun.
If you have kids who refuse to sleep, Siesta Testa is the app you’ve been waiting for.
Siesta Testa is a tool to track your kids batteries using special scanning technology. Show your kids how low their batterie are so they can understand the importance of getting a good nights rest early.
I love the idea of this app. Its a cute app well executed to help kids get to bed on time.
Robots absolutely hate bugs, incase you didn’t know.
Robots hate Bugs is a reflex game with three lanes of pain. Bugs crawl down to you and you launch Robots to defend yourself. But don’t launch too many Robots or you’ll blow up everything.
The game offers a unique experience that combines spam tapping with calculated control. Its difficult to master but easy to play.
There are tons of visual creation applications these days and its getting hard to keep up with all their hotkeys.
Just intime GFX Hotkeys comes along as an excellent reference tool to keep track of all your hotkeys. GFX Hotkeys packs over 80 applications full hotkey sets.
With all those different applications its important to have it clearly organized to find what you need quickly. GFX hotkeys delivers with color coded applications and favorites to track your most used applications.
Rampaging Dragons are attacking and you don’t want to be on the receiving end of a fireball!
Dodge hurly balls of fire with simple side scrolling controls. Watch out for the dragons themselves, they have something extra sinister for you. Between dodging fireballs find time to tap the dragons to hold back their fire.
Compete against friends’ highscores in GameCenter, but don’t get burned! ;]
Every month I get flooded with more apps than I can handle. Its not a popularity contest or even a favorite picking contest. I thoroughly enjoy all of your apps but there is only so much time in the day. Active forum members get bumped to the top of the list, but otherwise just do your best and I’ll do mine to review as many apps as I can!
Splatty Bug
Archange
iMathGenius
EPICYCLE
Tiny Hop
Drawings
KnockToCall
The Holy Hand Grenade
Shifty – Calendar Assist
NotPong
Bodytrack.it
TraductoPro
SuYeeQu
Yoink
ScreenFloat
Briefly
Transloader
Do You Know Your USA?
2048 Slider
Flip Blip
Snapster
Permeate
BugStar
Word Match – The Language Game
Zippy Finger
Obstruction
Auto Pin
Car Pal
Front Flash:Selfie Camera
Jumping Josh
ChargedUp!
Flappy Duck – Fly Home
Eco Recorder
TapTrap
Von Neumann
I Love Fruit Classic
Bricks
Do Not Collide
iMathGenius
Math Trick
Young June Jung
Bombs and Sombreros
Who let the bird out ?
Space-Rocket
Iggy Bubble
Scored – A Better Sports App
Face Yoga Bear
Onboard Camera
Word Connectz
Taskiary
Another month, another group of excellent apps. We love seeing what our readers build each month.
If you’ve never made an app, we’ve got you covered! Check out our free tutorials to become an iOS star. What are you waiting for – I want to see your app next month!
If you’ve already made the next great app, let me know about it. Submit here!
Readers’ App Reviews – May 2014 is a post from: Ray Wenderlich
The post Readers’ App Reviews – May 2014 appeared first on Ray Wenderlich.
Learn some basics of pull to refresh, specifically how to make a place holder view that is shaded a different color based on how far down the user pulls it.
Video Tutorial: Scroll View School Part 10: Pull to Refresh (Part 1) is a post from: Ray Wenderlich
The post Video Tutorial: Scroll View School Part 10: Pull to Refresh (Part 1) appeared first on Ray Wenderlich.
Welcome to cooking with Chef Charlie!
Tonight’s menu doesn’t include smoked trout; but it does include three recipes for working with NSURLSession
.
In this cookbook-style tutorial, you will learn how to download data, download an Image, and post data all using the NSURLSession
suite of classes. Bon Apetit!
NSURLSession
Downloading data from a web service is a very common task. NSURLSession
makes this very easy. Check out the following code snippet
// 1 NSString *dataUrl = @"YOUR_DATA_URL"; NSURL *url = [NSURL URLWithString:dataUrl]; // 2 NSURLSessionDataTask *downloadTask = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { // 4: Handle response here }]; // 3 [downloadTask resume]; |
NSURLSessionDataTask
using the NSURLSession shared session. The data task is the object that does the work for you.NSURLSession
start in a suspended state. Start the task here.NSURLSession
also makes it incredibly easy to download Images.
//1 NSURL *url = [NSURL URLWithString: @"http://upload.wikimedia.org/wikipedia/commons/7/7f/Williams_River-27527.jpg"]; // 2 NSURLSessionDownloadTask *downloadPhotoTask = [[NSURLSession sharedSession] downloadTaskWithURL:url completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) { // 3 UIImage *downloadedImage = [UIImage imageWithData: [NSData dataWithContentsOfURL:location]]; }]; // 4 [downloadPhotoTask resume]; |
NSURLSession
. This time create a download task.NSURLSessionDownloadTask
downloads the file to a temporary location on the iOS device. In the completionHandler, you can save this file permanently. Check out the sample app code at the end of the tutorial to see how to save the image to your Photo Album.NSURLSession
:]You should now have a good grasp on how to download data. If you need to post data to a web service, NSURLSession
can handle that too.
Until now, you have been using the convenience method [NSURLSession sharedSession]
to create NSURLSessionTasks
on a pre-configured session. This time, you will create a configurable NSURLSession
using an NSURLSessionConfiguration
object, so that you can set any attributes you may need (such as HTTP header attributes).
// 1 NSURL *url = [NSURL URLWithString:@"YOUR_WEBSERVICE_URL"]; NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; NSURLSession *session = [NSURLSession sessionWithConfiguration:config]; // 2 NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url]; request.HTTPMethod = @"POST"; // 3 NSDictionary *dictionary = @{@"key1": @"value1"}; NSError *error = nil; NSData *data = [NSJSONSerialization dataWithJSONObject:dictionary options:kNilOptions error:&error]; if (!error) { // 4 NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest:request fromData:data completionHandler:^(NSData *data,NSURLResponse *response,NSError *error) { // Handle response here }]; // 5 [uploadTask resume]; } |
NSURLSessionConfiguration
and then create a session with that configuration. This allows you to create a session with specific attributes set on it. For example, your web service might require you to set an API key in the HTTP Header (see the sample project.) Note that the configuration
property of an NSURLSession is read-only so any configuration needs to be done before creating the session.NSURLSessionTask
subclasses can be created using either an NSURL or an NSMutableURLRequest
. Here, create an NSMutableURLRequest
so that you can specifically set the request to be a POST.NSURLSessionUploadTask
using the JSON data you created in the previous step. This will make an HTTP POST with the body set as JSON data.Well, we didn’t have any smoked trout to cook up this time. But we do have a sample app where you can track your own fishing adventures.
You can download the TroutTrip source code here. This simple application will show you all three of the above recipes by doing the following:
Note: You will need to provide your own API keys for forecast.io and parse.com. There are #warning
pragmas in the code to show you where to enter your API keys. And, there are some helpful URLs there to follow to get more information on the two services.
You have just scratched the surface of what’s available with NSURLSession. For further information and examples check out these other great resources:
Cookbook: Using NSURLSession is a post from: Ray Wenderlich
The post Cookbook: Using NSURLSession appeared first on Ray Wenderlich.
Learn how to add a parallax scrolling effect inside the pull to refresh control.
Video Tutorial: Scroll View School Part 11: Pull to Refresh (Part 2) is a post from: Ray Wenderlich
The post Video Tutorial: Scroll View School Part 11: Pull to Refresh (Part 2) appeared first on Ray Wenderlich.
Learn the final steps for pull to refresh: Locking and Refreshing
Video Tutorial: Scroll View School Part 12: Pull to Refresh (Part 3) is a post from: Ray Wenderlich
The post Video Tutorial: Scroll View School Part 12: Pull to Refresh (Part 3) appeared first on Ray Wenderlich.
We recently had a call for nominees for the best animations introduced by apps in iOS 7.
Well, after tons of great nominations and careful consideration, we have selected our choices for the Top 5 iOS 7 Animations from the past year! The quality of apps nominated for this article were outstanding, making the final decision incredibly difficult.
Because the apps were such great quality, I would like to thank judges Kirill Muzykov, Ricardo Rendon Cepeda, Rocir Santiago, and Tammy Coron who volunteered to collectively decide these top five winners. It was no easy task!
Also, a big thank you to the readers who nominated animations in the first article. Without you all, this article would not have been possible.
Without further ado, here are the Top 5 iOS 7 Animations!
Flight is a utility app that informs you of a variety of information regarding your flights. The developers did a great job at packaging this information in an app that is useful and looks great too.
In this animation, information regarding the flight, such as the type of plane and the duration, protrudes from the bottom of the screen, pushing the main flight label up to the top of the screen. Any other labels fade out, making way for the new information to be presented.
As seen in the first article, the planes gracefully fly in and out of the presented views. Also, if you look closely at views like the flight number and the terminal, the information changes with a great cube-like animation. As the transition begins, the views fold back to show the new information.
Overall, the animations in this app are very high quality and greatly improve the user experience.
Taasky is a To-Do list that uses animations extensively to engage the user.
In this animation, the four list icons pop in like bubbles. Coming in one after another with the currently selected choice highlighted, this presentation looks great, clearly delivers the content to the user, and makes the app fun to use.
The folding effects in Taasky are also very impressive. When a new task is added, or the navigation bar is revealed, these new views are presented like they are unfolding from a cube. In addition to the 3-D effect, take a look at the top left bar button item while the navigation bar is unfolded. The rotating icon adds clarity to the user interface and adds a nice touch to top off the cube effect.
City Guides, by National Geographic, is an app for tourists in some of the world’s most famous cities, including New York City, London, Paris, and Rome. It informs the user about places to see, currency conversions, facts about the city and its history, and much more.
The city-picker in this app is a customized collection view. The items rotate 3-dimensionally around the center on their entrance and exit. The effect acts like a carousel rotating around you— notice the items appearing to grow as they exit the screen, like they are actually getting closer.
Here is another very interesting animation. As views go offscreen, you can see the numbers decrease rapidly to 0, and as the views come back to the screen, they return to their original values. This dynamic behavior really looks great and leaves a strong impression on the user. It fits perfectly into the user interface in a non-disruptive way.
Thanks filipzamorsky for pointing out this great app!
Flickr, built by Yahoo’s fantastic iOS development team, is a great example of the company’s efforts to become more relevant on mobile platforms.
While many apps have pull-to-refresh controls, Flickr’s animation looks great and changes according to the current user’s profile. When the animation begins, the profile picture rotates with the other sphere, and when the loading is finished, the picture returns to the spotlight. The rotating spheres are eye candy while you are waiting for your content to load.
Lots of apps have tutorials/introductions when they first start, but Flickr’s is the most impressive that I have seen, especially because of the great 3-D effects. Of these effects, the camera stands out the most to me.
Paper claims our title as the Best iOS 7 Animations app. The developers at Facebook clearly put a lot of time and effort into making this app look great – even developing their own open source animation API!
The sections at the top have a fun free-floating animation. When a new section is dragged up to the top row, the other sections make room for the new item while continuing to “float.” The bottom row also has a similar animation to City Guides where the items rotate around a point below the screen.
This paper-folding animation is the most recognizable aspect of Paper. The article folds and unfolds according to how far your finger pans on the screen, and locks into place once you have pulled far enough in either direction. While Paper is not the first to use this animation on iOS, the developers put a lot of effort into perfecting this great paper-like effect.
We have received several requests for more tutorials covering animations, especially since the Call for Nominees was announced. Because of this, we are going to cover how you can implement similar animations to the ones listed above.
Starting today, you can vote on one animation from the apps listed above to be featured in a future tutorial. Instead of more generic tools and techniques, the tutorial will focus specifically on the animations from the app.
What are you waiting for? Vote now!
Note: There is a poll embedded within this post, please visit the site to participate in this post's poll.If you want to see more interesting animations like these, I recommend looking at capptivate.co. This site has been putting together a very respectable compilation of iOS animations for quite some time.
Next time you are planning to release your app, make sure to think about your user experience. Is there anything in your app that stands out or leaves a good impression? If not, try adding some animation just to spice things up. A little animation can go a long way.
Animation allows these apps to really engage the user and create great experiences. Maybe next time it will be your app that really catches our eyes!
Top 5 iOS 7 Animations is a post from: Ray Wenderlich
The post Top 5 iOS 7 Animations appeared first on Ray Wenderlich.
Learn how to take scroll views and integrate them within your Sprite Kit games for some neat scrolling and zooming behavior.
Video Tutorial: Scroll View School Part 13: Sprite Kit Integration is a post from: Ray Wenderlich
The post Video Tutorial: Scroll View School Part 13: Sprite Kit Integration appeared first on Ray Wenderlich.
Are you super excited about WWDC next week? I know we are!
Well, there’s one more thing to be excited about too: AltConf! AltConf is a free, community-driven iOS conference running next week, right across the street from Moscone West. It’s split into two parts:
Myself and several members of the team are going to be participating in AltConf this year, and have some fun things ready for you. Keep reading to find out what’s in store!
First, on Monday 6/2 from 1PM-4PM (after the WWDC keynote) we’ll be offering something special at the AltConf labs in Jillian’s: 1-on-1 Tutorials!
Drop in and pick from our menu of 1-on-1 tutorials:
We’ll give you a live walkthrough of your chosen subject, with a cool demo and supporting material, doing their best to tailor the content to your interests and skill level. It should be a blast!
Several of us from the team will also be giving talks at AltConf. I’ll be giving a talk where I start with a game loosely based on a tutorial by Toby Stephens:
And then show you how to add “juice” to turn it into a game like this:
Special note for subscribers: I will be making a video tutorial series on this soon too :]
Also, upcoming book author Saul Mora will be giving a talk on “Are we doing what we should be with our time?”, and Code Team member Orta Therox will be giving a talk on “Project Eigen Post Mortem.”
You can see the full AltConf talk schedule here.
AltConf is shaping up to be an amazing conference – whether you got a WWDC ticket or not. We can’t wait to see you there!
raywenderlich.com at AltConf! is a post from: Ray Wenderlich
The post raywenderlich.com at AltConf! appeared first on Ray Wenderlich.
Lean how to make a scrolling level selector in Sprite Kit.
Video Tutorial: Scroll View School Part 14: Sprite Kit Level Selector is a post from: Ray Wenderlich
The post Video Tutorial: Scroll View School Part 14: Sprite Kit Level Selector appeared first on Ray Wenderlich.
This is a reminder that today immediately following the WWDC keynote, we have a special live tech talk for you.
It’s titled WWDC Keynote – Podcasters React!
Our podcasters will discuss all of the highlights of the WWDC keynote and give their reactions. We’ll also have live Q&A to hear your questions and reactions as well. This should be a blast!
We hope to see some of you at the tech talk, and we hope you enjoy!
Reminder: WWDC Keynote – Podcasters React Live Today! is a post from: Ray Wenderlich
The post Reminder: WWDC Keynote – Podcasters React Live Today! appeared first on Ray Wenderlich.